text:
/*
 * Copyright (c) 2016 Juniper Networks, Inc. All rights reserved.
 */

#include "base/os.h"
#include "testing/gunit.h"

#include <base/logging.h>
#include <io/event_manager.h>
#include <tbb/task.h>
#include <base/task.h>

#include <cmn/agent_cmn.h>

#include "cfg/cfg_init.h"
#include "oper/operdb_init.h"
#include "controller/controller_init.h"
#include "pkt/pkt_init.h"
#include "services/services_init.h"
#include "vrouter/ksync/ksync_init.h"
#include "oper/interface_common.h"
#include "oper/nexthop.h"
#include "route/route.h"
#include "oper/vrf.h"
#include "oper/mpls.h"
#include "oper/vm.h"
#include "oper/vn.h"
#include "filter/acl.h"
#include "test_cmn_util.h"
#include "vr_types.h"
#include <controller/controller_export.h>
#include <ksync/ksync_sock_user.h>
#include <boost/assign/list_of.hpp>
#include "pkt/test/test_flow_util.h"
#include "uve/test/test_uve_util.h"

#define vm1_ip "11.1.1.1"
#define vm2_ip "11.1.1.2"

void RouterIdDepInit(Agent *agent) {
}

typedef enum {
    INGRESS = 0,
    EGRESS = 1,
    BIDIRECTION = 2
} AclDirection;

static string AddAclXmlString(const char *node_name, const char *name, int id,
                              int proto, const char *action) {
    char buff[10240];
    sprintf(buff,
    "<?xml version=\"1.0\"?>\n"
    "<config>\n"
    "   <update>\n"
    "       <node type=\"%s\">\n"
    "           <name>%s</name>\n"
    "           <id-perms>\n"
    "               <permissions>\n"
    "                   <owner></owner>\n"
    "                   <owner_access>0</owner_access>\n"
    "                   <group></group>\n"
    "                   <group_access>0</group_access>\n"
    "                   <other_access>0</other_access>\n"
    "               </permissions>\n"
    "               <uuid>\n"
    "                   <uuid-mslong>0</uuid-mslong>\n"
    "                   <uuid-lslong>%d</uuid-lslong>\n"
    "               </uuid>\n"
    "           </id-perms>\n"
    "           <access-control-list-entries>\n"
    "                <acl-rule>\n"
    "                    <match-condition>\n"
    "                        <src-address>\n"
    "                            <virtual-network> any </virtual-network>\n"
    "                        </src-address>\n"
    "                        <protocol>%d</protocol>\n"
    "                        <src-port>\n"
    "                            <start-port> 0 </start-port>\n"
    "                            <end-port> 10000 </end-port>\n"
    "                        </src-port>\n"
    "                        <dst-address>\n"
    "                            <virtual-network> any </virtual-network>\n"
    "                        </dst-address>\n"
    "                        <dst-port>\n"
    "                            <start-port> 0 </start-port>\n"
    "                            <end-port> 10000 </end-port>\n"
    "                        </dst-port>\n"
    "                    </match-condition>\n"
    "                    <action-list>\n"
    "                        <simple-action>\n"
    "                            %s\n"
    "                        </simple-action>\n"
    "                    </action-list>\n"
    "                </acl-rule>\n"
    "                <acl-rule>\n"
    "                    <match-condition>\n"
    "                        <src-address>\n"
    "                            <virtual-network> any </virtual-network>\n"
    "                        </src-address>\n"
    "                        <protocol>any</protocol>\n"
    "                        <src-port>\n"
    "                            <start-port> 0 </start-port>\n"
    "                            <end-port> 60000 </end-port>\n"
    "                        </src-port>\n"
    "                        <dst-address>\n"
    "                            <virtual-network> any </virtual-network>\n"
    "                        </dst-address>\n"
    "                        <dst-port>\n"
    "                            <start-port> 0 </start-port>\n"
    "                            <end-port> 60000 </end-port>\n"
    "                        </dst-port>\n"
    "                    </match-condition>\n"
    "                    <action-list>\n"
    "                        <simple-action>\n"
    "                            deny\n"
    "                        </simple-action>\n"
    "                    </action-list>\n"
    "                </acl-rule>\n"
    "           </access-control-list-entries>\n"
    "       </node>\n"
    "   </update>\n"
    "</config>\n", node_name, name, id, proto, action);
    string s(buff);
    return s;
}

static void AddAclEntry(const char *name, int id, int proto,
                        const char *action, AclDirection direction) {
    char acl_name[1024];
    uint16_t max_len = sizeof(acl_name) - 1;
    strncpy(acl_name, name, max_len);
    if (direction == EGRESS) {
        strncat(acl_name, "egress-access-control-list", max_len);
    } else {
        strncat(acl_name, "ingress-access-control-list", max_len);
    }
    std::string s = AddAclXmlString("access-control-list", acl_name, id, proto,
                                    action);
    pugi::xml_document xdoc_;
    pugi::xml_parse_result result = xdoc_.load(s.c_str());
    EXPECT_TRUE(result);
    Agent::GetInstance()->ifmap_parser()->ConfigParse(xdoc_.first_child(), 0);
    client->WaitForIdle();
}

static void AddSgEntry(const char *sg_name, const char *name, int id,
                       int proto, const char *action, AclDirection direction) {

    AddSg(sg_name, 1);
    char acl_name[1024];
    uint16_t max_len = sizeof(acl_name) - 1;
    strncpy(acl_name, name, max_len);
    switch (direction) {
        case INGRESS:
            AddAclEntry(name, id, proto, action, direction);
            strncat(acl_name, "ingress-access-control-list", max_len);
            AddLink("security-group", sg_name, "access-control-list", acl_name);
            break;
        case EGRESS:
            AddAclEntry(name, id, proto, action, direction);
            strncat(acl_name, "egress-access-control-list", max_len);
            AddLink("security-group", sg_name, "access-control-list", acl_name);
            break;
        case BIDIRECTION:
            AddAclEntry(name, id, proto, action, EGRESS);
            strncat(acl_name, "egress-access-control-list", max_len);
            AddLink("security-group", sg_name, "access-control-list", acl_name);

            strncpy(acl_name, name, max_len);
            strncat(acl_name, "ingress-access-control-list", max_len);
            AddAclEntry(name, id+1, proto, action, INGRESS);
            AddLink("security-group", sg_name, "access-control-list", acl_name);
            break;
    }
}

class PolicyTest : public ::testing::Test {
public:
    PolicyTest() : util_() {
        agent_ = Agent::GetInstance();
        flow_proto_ = agent_->pkt()->get_flow_proto();
    }

    void SetPolicyDisabledStatus(struct PortInfo *input, bool status) {
        ostringstream str;

        str << "<virtual-machine-interface-disable-policy>";
        if (status) {
            str << "true";
        } else {
            str << "false";
        }
        str << "</virtual-machine-interface-disable-policy>";

        AddNode("virtual-machine-interface", input[0].name, input[0].intf_id,
                str.str().c_str());
    }

    TestUveUtil util_;
    Agent *agent_;
    FlowProto *flow_proto_;
};

TEST_F(PolicyTest, IntfPolicyDisable_Vn) {
    struct PortInfo input[] = {
        {"vnet1", 1, "1.1.1.1", "00:00:00:00:00:01", 1, 1},
    };
    IpamInfo ipam_info[] = {
        {"1.1.1.0", 24, "1.1.1.10"},
    };

    AddIPAM("vn1", ipam_info, 1);
    client->WaitForIdle();
    CreateVmportEnv(input, 1, 1);
    client->WaitForIdle();
    EXPECT_TRUE(VmPortFind(1));

    //Verify that interface has policy enabled
    VmInterface *intf = static_cast<VmInterface *>(VmPortGet(1));
    EXPECT_TRUE(intf->policy_enabled());

    uint32_t prev_label = intf->label();
    //Verify that interface's MPLS label points to policy-enabled NH
    const NextHop *nh = MplsToNextHop(prev_label);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's L2 MPLS label points to policy-enabled NH
    nh = MplsToNextHop(intf->l2_label());
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's route points to policy-enabled NH
    nh = RouteToNextHop("vrf1", intf->primary_ip_addr(), 32);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's L2 route points to policy-enabled NH
    MacAddress mac(intf->vm_mac());
    nh = L2RouteToNextHop("vrf1", mac);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's flow_key_nh points to policy-enabled NH
    nh = intf->flow_key_nh();
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's mdata route points to policy-enabled NH
    nh = RouteToNextHop(agent_->fabric_vrf_name(), intf->mdata_ip_addr(), 32);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Configure disable-policy as true on interface
    //---------------------------------------------
    SetPolicyDisabledStatus(input, true);
    client->WaitForIdle();

    //Verify that policy is disabled on interface
    EXPECT_FALSE(intf->policy_enabled());

    //Verify that interface's MPLS label has changed
    EXPECT_TRUE (prev_label != intf->label());
    prev_label = intf->label();

    //Verify that interface's MPLS label points to policy-disabled NH
    nh = MplsToNextHop(prev_label);
    EXPECT_TRUE(nh != NULL);
    EXPECT_FALSE(nh->PolicyEnabled());

    //Verify that interface's L2 MPLS label points to policy-disabled NH
    nh = MplsToNextHop(intf->l2_label());
    EXPECT_TRUE(nh != NULL);
    EXPECT_FALSE(nh->PolicyEnabled());

    //Verify that interface's route points to policy-disabled NH
    nh = RouteToNextHop("vrf1", intf->primary_ip_addr(), 32);
    EXPECT_TRUE(nh != NULL);
    EXPECT_FALSE(nh->PolicyEnabled());

    //Verify that interface's L2 route points to policy-disabled NH
    nh = L2RouteToNextHop("vrf1", mac);
    EXPECT_TRUE(nh != NULL);
    EXPECT_FALSE(nh->PolicyEnabled());

    //Verify that interface's flow_key_nh still points to policy-enabled NH
    nh = intf->flow_key_nh();
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's mdata route still points to policy-enabled NH
    nh = RouteToNextHop(agent_->fabric_vrf_name(), intf->mdata_ip_addr(), 32);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Configure disable-policy as false on interface
    //---------------------------------------------
    SetPolicyDisabledStatus(input, false);
    client->WaitForIdle();

    //Verify that policy is enabled
    EXPECT_TRUE(intf->policy_enabled());

    //Verify that interface's MPLS label has changed
    EXPECT_TRUE (prev_label != intf->label());

    //Verify that interface's MPLS label points to policy-enabled NH
    nh = MplsToNextHop(intf->label());
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's L2 MPLS label points to policy-enabled NH
    nh = MplsToNextHop(intf->l2_label());
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's route points to policy-enabled NH
    nh = RouteToNextHop("vrf1", intf->primary_ip_addr(), 32);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's L2 route points to policy-enabled NH
    nh = L2RouteToNextHop("vrf1", mac);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's flow_key_nh points to policy-enabled NH
    nh = intf->flow_key_nh();
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's mdata route points to policy-enabled NH
    nh = RouteToNextHop(agent_->fabric_vrf_name(), intf->mdata_ip_addr(), 32);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    DeleteVmportEnv(input, 1, true, 1);
    client->WaitForIdle();
    DelIPAM("vn1");
    client->WaitForIdle();
    EXPECT_FALSE(VmPortFind(1));
    client->Reset();
}

TEST_F(PolicyTest, IntfPolicyDisable_Fip) {
    struct PortInfo input[] = {
        {"vnet1", 1, "1.1.1.1", "00:00:00:01:01:01", 1, 1}
    };
    IpamInfo ipam_info[] = {
        {"1.1.1.0", 24, "1.1.1.10"},
    };

    AddIPAM("vn1", ipam_info, 1);
    client->WaitForIdle();

    //Add VN
    util_.VnAdd(input[0].vn_id);
    // Nova Port add message
    util_.NovaPortAdd(&input[0]);
    // Config Port add
    util_.ConfigPortAdd(&input[0]);
    //Verify that the port is inactive
    EXPECT_TRUE(VmPortInactive(input, 0));

    //Add necessary objects and links to make vm-intf active
    util_.VmAdd(input[0].vm_id);
    util_.VrfAdd(input[0].vn_id);
    AddLink("virtual-network", "vn1", "routing-instance", "vrf1");
    client->WaitForIdle();
    AddLink("virtual-network", "vn1", "virtual-machine-interface", "vnet1");
    client->WaitForIdle();
    AddLink("virtual-machine", "vm1", "virtual-machine-interface", "vnet1");
    client->WaitForIdle();
    AddVmPortVrf("vnet1", "", 0);
    client->WaitForIdle();
    AddInstanceIp("instance0", input[0].vm_id, input[0].addr);
    AddLink("virtual-machine-interface", input[0].name,
            "instance-ip", "instance0");
    client->WaitForIdle(3);
    AddLink("virtual-machine-interface-routing-instance", "vnet1",
            "routing-instance", "vrf1");
    client->WaitForIdle(3);
    AddLink("virtual-machine-interface-routing-instance", "vnet1",
            "virtual-machine-interface", "vnet1");
    client->WaitForIdle(3);
    EXPECT_TRUE(VmPortActive(input, 0));
    const VmInterface *intf = VmInterfaceGet(input[0].intf_id);
    EXPECT_TRUE(intf != NULL);
    EXPECT_TRUE(intf->policy_enabled());

    //Create a VN for floating-ip
    client->Reset();
    AddVn("default-project:vn2", 2);
    client->WaitForIdle();
    WAIT_FOR(1000, 100, (client->vn_notify_ >= 1));
    AddVrf("default-project:vn2:vn2");
    client->WaitForIdle();
    EXPECT_TRUE(client->VrfNotifyWait(1));
    EXPECT_TRUE(VrfFind("default-project:vn2:vn2"));
    AddLink("virtual-network", "default-project:vn2", "routing-instance",
            "default-project:vn2:vn2");
    client->WaitForIdle();

    // Configure Floating-IP
    AddFloatingIpPool("fip-pool1", 1);
    AddFloatingIp("fip1", 1, "71.1.1.100");
    AddLink("floating-ip", "fip1", "floating-ip-pool", "fip-pool1");
    AddLink("floating-ip-pool", "fip-pool1", "virtual-network",
            "default-project:vn2");
    client->WaitForIdle();
    AddLink("virtual-machine-interface", "vnet1", "floating-ip", "fip1");
    client->WaitForIdle();
    WAIT_FOR(1000, 500, ((VmPortFloatingIpCount(1, 1) == true)));
    EXPECT_TRUE(intf->policy_enabled());

    uint32_t prev_label = intf->label();
    //Verify that interface's MPLS label points to policy-enabled NH
    const NextHop *nh = MplsToNextHop(prev_label);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's L2 MPLS label points to policy-enabled NH
    nh = MplsToNextHop(intf->l2_label());
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's route points to policy-enabled NH
    nh = RouteToNextHop("vrf1", intf->primary_ip_addr(), 32);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's L2 route points to policy-enabled NH
    MacAddress mac(intf->vm_mac());
    nh = L2RouteToNextHop("vrf1", mac);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's flow_key_nh points to policy-enabled NH
    nh = intf->flow_key_nh();
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's mdata route points to policy-enabled NH
    nh = RouteToNextHop(agent_->fabric_vrf_name(), intf->mdata_ip_addr(), 32);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Configure disable-policy as true on interface
    //---------------------------------------------
    SetPolicyDisabledStatus(input, true);
    client->WaitForIdle();

    //Verify that policy is disabled on interface
    EXPECT_FALSE(intf->policy_enabled());

    //Verify that interface's MPLS label has changed
    EXPECT_TRUE (prev_label != intf->label());
    prev_label = intf->label();

    //Verify that interface's MPLS label points to policy-disabled NH
    nh = MplsToNextHop(prev_label);
    EXPECT_TRUE(nh != NULL);
    EXPECT_FALSE(nh->PolicyEnabled());

    //Verify that interface's L2 MPLS label points to policy-disabled NH
    nh = MplsToNextHop(intf->l2_label());
    EXPECT_TRUE(nh != NULL);
    EXPECT_FALSE(nh->PolicyEnabled());

    //Verify that interface's route points to policy-disabled NH
    nh = RouteToNextHop("vrf1", intf->primary_ip_addr(), 32);
    EXPECT_TRUE(nh != NULL);
    EXPECT_FALSE(nh->PolicyEnabled());

    //Verify that interface's L2 route points to policy-disabled NH
    nh = L2RouteToNextHop("vrf1", mac);
    EXPECT_TRUE(nh != NULL);
    EXPECT_FALSE(nh->PolicyEnabled());

    //Verify that interface's flow_key_nh still points to policy-enabled NH
    nh = intf->flow_key_nh();
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's mdata route still points to policy-enabled NH
    nh = RouteToNextHop(agent_->fabric_vrf_name(), intf->mdata_ip_addr(), 32);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Configure disable-policy as false on interface
    //---------------------------------------------
    SetPolicyDisabledStatus(input, false);
    client->WaitForIdle();

    //Verify that policy is enabled
    EXPECT_TRUE(intf->policy_enabled());

    //Verify that interface's MPLS label has changed
    EXPECT_TRUE (prev_label != intf->label());

    //Verify that interface's MPLS label points to policy-enabled NH
    nh = MplsToNextHop(intf->label());
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's L2 MPLS label points to policy-enabled NH
    nh = MplsToNextHop(intf->l2_label());
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's route points to policy-enabled NH
    nh = RouteToNextHop("vrf1", intf->primary_ip_addr(), 32);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's L2 route points to policy-enabled NH
    nh = L2RouteToNextHop("vrf1", mac);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's flow_key_nh points to policy-enabled NH
    nh = intf->flow_key_nh();
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Verify that interface's mdata route points to policy-enabled NH
    nh = RouteToNextHop(agent_->fabric_vrf_name(), intf->mdata_ip_addr(), 32);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    //Delete the floating-IP
    DelLink("floating-ip", "fip1", "floating-ip-pool", "fip-pool1");
    DelLink("virtual-machine-interface", "vnet1", "floating-ip", "fip1");
    DelFloatingIp("fip1");
    client->WaitForIdle();

    //cleanup
    DelLink("floating-ip-pool", "fip-pool1", "virtual-network",
            "default-project:vn2");
    DelLink("floating-ip-pool", "fip-pool1", "virtual-network", "vn1");
    DelFloatingIpPool("fip-pool1");
    DelLink("virtual-machine-interface-routing-instance", "vnet1",
            "routing-instance", "vrf1");
    DelLink("virtual-machine-interface-routing-instance", "vnet1",
            "virtual-machine-interface", "vnet1");
    DelLink("virtual-machine", "vm1", "virtual-machine-interface", "vnet1");
    DelLink("virtual-network", "vn1", "virtual-machine-interface", "vnet1");
    client->WaitForIdle();
    DelLink("virtual-network", "default-project:vn2", "routing-instance",
            "default-project:vn2:vn2");
    client->WaitForIdle(3);
    DelLink("virtual-machine-interface", input[0].name,
            "instance-ip", "instance0");
    DelLink("virtual-network", "vn1", "routing-instance", "vrf1");
    client->WaitForIdle(3);

    DelNode("virtual-network", "default-project:vn2");
    DelVrf("default-project:vn2:vn2");
    client->WaitForIdle(3);
    DelNode("virtual-machine-interface-routing-instance", "vnet1");
    client->WaitForIdle(3);
    DelNode("virtual-machine", "vm1");
    DelNode("routing-instance", "vrf1");
    client->WaitForIdle(3);
    DelNode("virtual-machine-interface", "vnet1");
    DelInstanceIp("instance0");
    client->WaitForIdle(3);
    IntfCfgDel(input, 0);
    util_.VnDelete(input[0].vn_id);
    client->WaitForIdle(3);
    WAIT_FOR(1000, 500, (VnGet(input[0].vn_id) == NULL));
    DelIPAM("vn1");
    client->WaitForIdle();

    //clear counters at the end of test case
    client->Reset();
}

TEST_F(PolicyTest, IntfPolicyDisable_Flow) {
    struct PortInfo input[] = {
        {"flow0", 6, vm1_ip, "00:00:00:01:01:01", 5, 1},
        {"flow1", 7, vm2_ip, "00:00:00:01:01:02", 5, 2},
    };
    IpamInfo ipam_info[] = {
        {"11.1.1.0", 24, "11.1.1.10"},
    };

    AddIPAM("vn5", ipam_info, 1);

    VmInterface *flow0, *flow1;

    CreateVmportEnv(input, 2, 1);
    client->WaitForIdle(5);

    EXPECT_TRUE(VmPortActive(input, 0));
    EXPECT_TRUE(VmPortActive(input, 1));
    WAIT_FOR(100, 1000, (VmPortPolicyEnable(input, 0)));
    WAIT_FOR(100, 1000, (VmPortPolicyEnable(input, 1)));

    flow0 = VmInterfaceGet(input[0].intf_id);
    assert(flow0);
    flow1 = VmInterfaceGet(input[1].intf_id);
    assert(flow1);

    AddSgEntry("sg1", "sg_acl1", 10, 1, "pass", BIDIRECTION);
    AddLink("virtual-machine-interface", "flow1", "security-group", "sg1");
    client->WaitForIdle();
    EXPECT_EQ(flow1->sg_list().list_.size(), 1U);

    TestFlow flow[] = {
        //Add a ICMP forward and reverse flow
        {  TestFlowPkt(Address::INET, vm1_ip, vm2_ip, 1, 0, 0, "vrf5",
                       flow0->id()),
        {
            new VerifyVn("vn5", "vn5"),
            new VerifyVrf("vrf5", "vrf5")
        }
        }
    };

    CreateFlow(flow, 1);
    EXPECT_EQ(2U, flow_proto_->FlowCount());

    //Since policy is enabled on reverse flow's VMI, verify that out SG rules
    //are present
    const FlowEntry *fe = flow[0].pkt_.FlowFetch();
    EXPECT_TRUE(fe->data().match_p.sg_policy.out_rule_present);

    //Delete all the flows
    client->EnqueueFlowFlush();
    client->WaitForIdle(10);
    EXPECT_EQ(0U, flow_proto_->FlowCount());

    //Disable policy on flow1 VMI
    SetPolicyDisabledStatus(&input[1], true);
    client->WaitForIdle();

    //Verify that policy status of interfaces
    WAIT_FOR(100, 1000, ((VmPortPolicyEnabled(input, 0)) == true));
    WAIT_FOR(100, 1000, ((VmPortPolicyEnabled(input, 1)) == false));

    //Setup flows again
    CreateFlow(flow, 1);
    EXPECT_EQ(2U, flow_proto_->FlowCount());

    //Since policy is disabled on reverse flow's VMI, verify that out SG rules
    //are not present
    fe = flow[0].pkt_.FlowFetch();
    EXPECT_FALSE(fe->data().match_p.sg_policy.out_rule_present);

    //Cleanup
    client->EnqueueFlowFlush();
    client->WaitForIdle(10);
    client->Reset();
    EXPECT_EQ(0U, flow_proto_->FlowCount());
    char acl_name[1024];
    uint16_t max_len = sizeof(acl_name) - 1;

    DelLink("virtual-machine-interface", "flow1", "security-group", "sg1");
    strncpy(acl_name, "sg_acl1", max_len);
    strncat(acl_name, "egress-access-control-list", max_len);
    DelLink("security-group", "sg1", "access-control-list", acl_name);
    DelNode("access-control-list", acl_name);

    strncpy(acl_name, "sg_acl1", max_len);
    strncat(acl_name, "ingress-access-control-list", max_len);
    DelLink("security-group", "sg1", "access-control-list", acl_name);
    DelNode("access-control-list", acl_name);
    DelNode("security-group", "sg1");
    client->WaitForIdle();
    EXPECT_EQ(flow1->sg_list().list_.size(), 0U);

    DeleteVmportEnv(input, 2, true, 1);
    client->WaitForIdle(3);
    DelIPAM("vn5");
    client->WaitForIdle();
    WAIT_FOR(1000, 1000, (VmPortFind(input, 0) == false));
    WAIT_FOR(1000, 1000, (VmPortFind(input, 1) == false));
    EXPECT_EQ(0U, flow_proto_->FlowCount());
}

//When policy is enabled/disabled on VMI, label of VMI changes. When a VMI is
//part of ECMP and its policy-status is changed, verify that the label of the
//VMI in CompositeNH is updated correctly.
TEST_F(PolicyTest, EcmpNH_1) {
    //Create mutliple VM interface with same IP
    struct PortInfo input1[] = {
        {"vnet1", 1, "1.1.1.1", "00:00:00:01:01:01", 1, 1},
        {"vnet2", 2, "1.1.1.1", "00:00:00:02:02:01", 1, 2}
    };

    IpamInfo ipam_info[] = {
        {"1.1.1.0", 24, "1.1.1.10"},
    };

    AddIPAM("vn1", ipam_info, 1);
    client->WaitForIdle();
    CreateVmportWithEcmp(input1, 2, 1);
    client->WaitForIdle();

    EXPECT_TRUE(VmPortActive(input1, 0));
    EXPECT_TRUE(VmPortActive(input1, 1));
    const VmInterface *intf1 = VmInterfaceGet(input1[0].intf_id);
    EXPECT_TRUE(intf1 != NULL);
    const VmInterface *intf2 = VmInterfaceGet(input1[1].intf_id);
    EXPECT_TRUE(intf2 != NULL);
    //Check that route points to composite NH,
    //with 2 members
    Ip4Address ip = Ip4Address::from_string("1.1.1.1");
    InetUnicastRouteEntry *rt = RouteGet("vrf1", ip, 32);
    EXPECT_TRUE(rt != NULL);
    const NextHop *nh = rt->GetActiveNextHop();
    EXPECT_TRUE(nh->GetType() == NextHop::COMPOSITE);
    const CompositeNH *comp_nh = static_cast<const CompositeNH *>(nh);
    EXPECT_TRUE(comp_nh->ComponentNHCount() == 2);

    //Get the MPLS label corresponding to this path and verify
    //that mpls label also has 2 component NH
    uint32_t mpls_label = rt->GetActiveLabel();
    EXPECT_TRUE(FindMplsLabel(mpls_label));
    EXPECT_TRUE(nh->GetType() == NextHop::COMPOSITE);
    comp_nh = static_cast<const CompositeNH *>(nh);
    EXPECT_TRUE(comp_nh->ComponentNHCount() == 2);
    EXPECT_TRUE(comp_nh->PolicyEnabled() == true);

    uint32_t label1 = comp_nh->Get(0)->label();
    uint32_t label2 = comp_nh->Get(1)->label();
    uint32_t vnet1_label = intf1->label();

    EXPECT_TRUE(label1 == intf1->label());
    EXPECT_TRUE(label2 == intf2->label());

    WAIT_FOR(100, 1000, ((VmPortPolicyEnabled(input1, 0)) == true));

    //Disable policy on vnet1 VMI
    SetPolicyDisabledStatus(&input1[0], true);
    client->WaitForIdle();

    //Verify that policy status of interface
    WAIT_FOR(100, 1000, ((VmPortPolicyEnabled(input1, 0)) == false));

    //Verify that vnet1's label has changed after disabling policy on it.
    EXPECT_TRUE(vnet1_label != intf1->label());

    nh = rt->GetActiveNextHop();
    EXPECT_TRUE(nh->GetType() == NextHop::COMPOSITE);
    comp_nh = static_cast<const CompositeNH *>(nh);
    //Verify that Component NH part of CompositeNH has its label updated after
    //policy status change on vnet1
    EXPECT_TRUE(label1 != comp_nh->Get(0)->label());
    EXPECT_TRUE(comp_nh->Get(0)->label() == intf1->label());

    DeleteVmportEnv(input1, 2, 1, 1, NULL, NULL, true, false);
    client->WaitForIdle();

    DelIPAM("vn1");
    client->WaitForIdle(3);

    WAIT_FOR(100, 1000, (VrfFind("vrf1") == false));
    EXPECT_FALSE(RouteFind("vrf1", ip, 32));

    //Expect MPLS label to be not present
    EXPECT_FALSE(FindMplsLabel(mpls_label));
}

//When policy is enabled/disabled on VMI, label of VMI changes. When a VMI
//has static_route associated with it and its policy-status changes verify that
//the label of the static-route's path is updated accordingly.
TEST_F(PolicyTest, IntfStaticRoute) {
    struct PortInfo input[] = {
        {"vnet1", 1, "1.1.1.10", "00:00:00:01:01:01", 1, 1}
    };

    client->Reset();
    CreateVmportEnv(input, 1, 1);
    client->WaitForIdle();
    EXPECT_TRUE(VmPortActive(input, 0));
    const VmInterface *intf1 = VmInterfaceGet(input[0].intf_id);
    EXPECT_TRUE(intf1 != NULL);

   //Add a static route
   struct TestIp4Prefix static_route[] = {
       { Ip4Address::from_string("2.1.1.10"), 32}
   };

   AddInterfaceRouteTable("static_route", 1, static_route, 1);
   AddLink("virtual-machine-interface", "vnet1",
           "interface-route-table", "static_route");
   client->WaitForIdle();

   InetUnicastRouteEntry *rt = RouteGet("vrf1", static_route[0].addr_,
                                        static_route[0].plen_);
   EXPECT_TRUE(rt != NULL);
   const AgentPath *path = rt->GetActivePath();
   EXPECT_TRUE(path != NULL);
   EXPECT_TRUE(path->label() == intf1->label());
   uint32_t vmi_label = intf1->label();

   WAIT_FOR(100, 1000, ((VmPortPolicyEnabled(input, 0)) == true));

   //Disable policy on vnet1 VMI
   SetPolicyDisabledStatus(&input[0], true);
   client->WaitForIdle();

   //Verify that policy status of interface
   WAIT_FOR(100, 1000, ((VmPortPolicyEnabled(input, 0)) == false));

   //Verify that vnet1's label has changed after disabling policy on it.
   EXPECT_TRUE(vmi_label != intf1->label());

   //Verify that label of static_route's path is updated with new label of VMI
   EXPECT_TRUE(path->label() == intf1->label());

   //Delete the link between interface and route table
   DelLink("virtual-machine-interface", "vnet1",
           "interface-route-table", "static_route");
   client->WaitForIdle();
   EXPECT_FALSE(RouteFind("vrf1", static_route[0].addr_,
                          static_route[0].plen_));

   DelNode("interface-route-table", "static_route");
   client->WaitForIdle();
   DeleteVmportEnv(input, 1, true, 1);
   client->WaitForIdle();
   WAIT_FOR(100, 1000, (VrfFind("vrf1") == false));
}

TEST_F(PolicyTest, EcmpNH_2) {
    //Create mutliple VM interface with same IP
    struct PortInfo input1[] = {
        {"vnet1", 1, "1.1.1.1", "00:00:00:01:01:01", 1, 1},
        {"vnet2", 2, "1.1.1.1", "00:00:00:02:02:01", 1, 2},
        {"vnet3", 3, "1.1.1.1", "00:00:00:02:02:03", 1, 3},
        {"vnet4", 4, "1.1.1.1", "00:00:00:02:02:04", 1, 4},
        {"vnet5", 5, "1.1.1.1", "00:00:00:02:02:05", 1, 5}
    };

    struct PortInfo input2[] = {
        {"vnet3", 3, "1.1.1.1", "00:00:00:02:02:03", 1, 3}
    };

    struct PortInfo input3[] = {
        {"vnet1", 1, "1.1.1.1", "00:00:00:01:01:01", 1, 1},
        {"vnet2", 2, "1.1.1.1", "00:00:00:02:02:01", 1, 2},
        {"vnet4", 4, "1.1.1.1", "00:00:00:02:02:04", 1, 4},
        {"vnet5", 5, "1.1.1.1", "00:00:00:02:02:05", 1, 5}
    };

    IpamInfo ipam_info[] = {
        {"1.1.1.0", 24, "1.1.1.10"},
    };

    AddIPAM("vn1", ipam_info, 1);
    client->WaitForIdle();

    CreateVmportWithEcmp(input1, 5, 1);
    client->WaitForIdle();

    //Check that route points to composite NH,
    //with 5 members
    Ip4Address ip = Ip4Address::from_string("1.1.1.1");
    InetUnicastRouteEntry *rt = RouteGet("vrf1", ip, 32);
    EXPECT_TRUE(rt != NULL);
    const NextHop *nh = rt->GetActiveNextHop();
    EXPECT_TRUE(nh->GetType() == NextHop::COMPOSITE);
    const CompositeNH *comp_nh = static_cast<const CompositeNH *>(nh);
    EXPECT_TRUE(comp_nh->ActiveComponentNHCount() == 5);

    //Delete one of the interface
    DeleteVmportEnv(input2, 1, 0);
    client->WaitForIdle();

    WAIT_FOR(100, 1000, (VmInterfaceGet(input2[0].intf_id)) == NULL);

    //Verify that ComponentNH count is 4
    nh = rt->GetActiveNextHop();
    EXPECT_TRUE(nh->GetType() == NextHop::COMPOSITE);
    comp_nh = static_cast<const CompositeNH *>(nh);
    EXPECT_TRUE(comp_nh->ActiveComponentNHCount() == 4);

    const VmInterface *intf1 = VmInterfaceGet(input1[0].intf_id);
    EXPECT_TRUE(intf1 != NULL);
    uint32_t vmi_label = intf1->label();

    WAIT_FOR(100, 1000, ((VmPortPolicyEnabled(input1, 0)) == true));

    //Disable policy on vnet1 VMI
    SetPolicyDisabledStatus(&input1[0], true);
    client->WaitForIdle();

    //Verify that policy status of interface
    WAIT_FOR(100, 1000, ((VmPortPolicyEnabled(input1, 0)) == false));

    //Verify that vnet1's label has changed after disabling policy on it.
    EXPECT_TRUE(vmi_label != intf1->label());

    //Verify that ComponentNH count is still 5 after policy change
    nh = rt->GetActiveNextHop();
    EXPECT_TRUE(nh->GetType() == NextHop::COMPOSITE);
    comp_nh = static_cast<const CompositeNH *>(nh);
    EXPECT_TRUE(comp_nh->ActiveComponentNHCount() == 4);

    //DeleteVmportEnv(input3, 4, 1);
    DeleteVmportEnv(input3, 4, 1, 1, NULL, NULL, true, false);
    client->WaitForIdle();

    DelIPAM("vn1");
    client->WaitForIdle(3);

    WAIT_FOR(100, 1000, (VrfFind("vrf1") == false));
    EXPECT_FALSE(RouteFind("vrf1", ip, 32));
}

TEST_F(PolicyTest, EcmpNH_3) {
    //Create mutliple VM interface with same IP
    struct PortInfo input1[] = {
        {"vnet1", 1, "1.1.1.1", "00:00:00:01:01:01", 1, 1},
        {"vnet2", 2, "1.1.1.1", "00:00:00:02:02:01", 1, 2},
        {"vnet3", 3, "1.1.1.1", "00:00:00:02:02:03", 1, 3}
    };

    struct PortInfo input2[] = {
        {"vnet2", 2, "1.1.1.1", "00:00:00:02:02:01", 1, 2}
    };

    struct PortInfo input3[] = {
        {"vnet1", 1, "1.1.1.1", "00:00:00:01:01:01", 1, 1},
        {"vnet3", 3, "1.1.1.1", "00:00:00:02:02:03", 1, 3}
    };

    IpamInfo ipam_info[] = {
        {"1.1.1.0", 24, "1.1.1.10"},
    };

    AddIPAM("vn1", ipam_info, 1);
    client->WaitForIdle();
    CreateVmportWithEcmp(input1, 3, 1);
    client->WaitForIdle();

    //Check that route points to composite NH,
    //with 2 members
    Ip4Address ip = Ip4Address::from_string("1.1.1.1");
    InetUnicastRouteEntry *rt = RouteGet("vrf1", ip, 32);
    EXPECT_TRUE(rt != NULL);
    const NextHop *nh = rt->GetActiveNextHop();
    EXPECT_TRUE(nh->GetType() == NextHop::COMPOSITE);
    const CompositeNH *comp_nh = static_cast<const CompositeNH *>(nh);
    EXPECT_TRUE(comp_nh->ActiveComponentNHCount() == 3);
    uint32_t label1 = comp_nh->Get(0)->label();

    //Delete one of the interface
    DeleteVmportEnv(input2, 1, 0);
    client->WaitForIdle();

    WAIT_FOR(100, 1000, (VmInterfaceGet(input2[0].intf_id)) == NULL);

    //Verify that ComponentNH count is 2
    nh = rt->GetActiveNextHop();
    EXPECT_TRUE(nh->GetType() == NextHop::COMPOSITE);
    comp_nh = static_cast<const CompositeNH *>(nh);
    EXPECT_TRUE(comp_nh->ActiveComponentNHCount() == 2);

    const VmInterface *intf1 = VmInterfaceGet(input1[0].intf_id);
    EXPECT_TRUE(intf1 != NULL);
    uint32_t vmi_label = intf1->label();

    WAIT_FOR(100, 1000, ((VmPortPolicyEnabled(input1, 0)) == true));

    //Disable policy on vnet1 VMI
    SetPolicyDisabledStatus(&input1[0], true);
    client->WaitForIdle();

    //Verify that policy status of interface
    WAIT_FOR(100, 1000, ((VmPortPolicyEnabled(input1, 0)) == false));

    //Verify that vnet1's label has changed after disabling policy on it.
    EXPECT_TRUE(vmi_label != intf1->label());

    nh = rt->GetActiveNextHop();
    EXPECT_TRUE(nh->GetType() == NextHop::COMPOSITE);
    comp_nh = static_cast<const CompositeNH *>(nh);
    //Verify that Component NH part of CompositeNH has its label updated after
    //policy status change on vnet1
    EXPECT_TRUE(label1 != comp_nh->Get(0)->label());
    EXPECT_TRUE(comp_nh->Get(0)->label() == intf1->label());

    DeleteVmportEnv(input3, 2, 1, 1, NULL, NULL, true, false);
    client->WaitForIdle();

    DelIPAM("vn1");
    client->WaitForIdle(3);

    WAIT_FOR(100, 1000, (VrfFind("vrf1") == false));
    EXPECT_FALSE(RouteFind("vrf1", ip, 32));
}

//Add and delete allowed address pair route
TEST_F(PolicyTest, AapRoute) {
    struct PortInfo input[] = {
        {"vnet1", 1, "1.1.1.10", "00:00:00:01:01:01", 1, 1}
    };

    client->Reset();
    CreateVmportEnv(input, 1, 1);
    client->WaitForIdle();
    EXPECT_TRUE(VmPortActive(input, 0));
    const VmInterface *intf1 = VmInterfaceGet(input[0].intf_id);
    EXPECT_TRUE(intf1 != NULL);

   //Add an AAP route
   Ip4Address ip = Ip4Address::from_string("10.10.10.10");
   std::vector<Ip4Address> v;
   v.push_back(ip);

   AddAap("vnet1", 1, v);
   EXPECT_TRUE(RouteFind("vrf1", ip, 32));

   InetUnicastRouteEntry *rt = RouteGet("vrf1", ip, 32);
   EXPECT_TRUE(rt != NULL);
   const AgentPath *path = rt->GetActivePath();
   EXPECT_TRUE(path != NULL);
   EXPECT_TRUE(path->label() == intf1->label());
   uint32_t vmi_label = intf1->label();

   WAIT_FOR(100, 1000, ((VmPortPolicyEnabled(input, 0)) == true));

   //Disable policy on vnet1 VMI
   AddAapWithDisablePolicy("vnet1", 1, v, true);
   client->WaitForIdle();

   //Verify that policy status of interface
   WAIT_FOR(100, 1000, ((VmPortPolicyEnabled(input, 0)) == false));

   //Verify that vnet1's label has changed after disabling policy on it.
   EXPECT_TRUE(vmi_label != intf1->label());

   //Verify that label of aap_route's path is updated with new label of VMI
   rt = RouteGet("vrf1", ip, 32);
   EXPECT_TRUE(rt != NULL);
   path = rt->GetActivePath();
   EXPECT_TRUE(path != NULL);
   EXPECT_TRUE(path->label() == intf1->label());

   //Remove AAP route
   v.clear();
   AddAap("vnet1", 1, v);
   EXPECT_FALSE(RouteFind("vrf1", ip, 32));

   //cleanup
   DeleteVmportEnv(input, 1, true, 1);
   client->WaitForIdle();
   WAIT_FOR(100, 1000, (VrfFind("vrf1") == false));
}

//Add and delete allowed address pair entries with specific MAC
//When specific MAC is configured AAP entry will have its own label.
//AAP route will point to this label instead of interface label. Also the NH of
//this AAP route will have policy-status same as that of its interface.
//When policy-status is changed on this interface (having AAP with MAC), AAP
//route should point to interface NH with updated policy-status. To achieve this
//for leaked AAP routes we change the AAP label when policy-status of interface
//changes.
//UT below will verify that when policy-status changes, AAP entry's label is
//also changed.
TEST_F(PolicyTest, AapRouteWithMac) {
    struct PortInfo input[] = {
        {"vnet1", 1, "1.1.1.10", "00:00:00:01:01:01", 1, 1}
    };

    client->Reset();
    CreateVmportEnv(input, 1, 1);
    client->WaitForIdle();
    EXPECT_TRUE(VmPortActive(input, 0));
    const VmInterface *intf1 = VmInterfaceGet(input[0].intf_id);
    EXPECT_TRUE(intf1 != NULL);

    //Add an AAP route
    Ip4Address ip = Ip4Address::from_string("10.10.10.10");
    MacAddress mac("0a:0b:0c:0d:0e:0f");

    AddAap("vnet1", 1, ip, mac.ToString());
    EXPECT_TRUE(RouteFind("vrf1", ip, 32));

    InetUnicastRouteEntry *rt = RouteGet("vrf1", ip, 32);
    EXPECT_TRUE(rt != NULL);
    const AgentPath *path = rt->GetActivePath();
    EXPECT_TRUE(path != NULL);
    EXPECT_TRUE(path->label() != intf1->label());
    uint32_t aap_label = path->label();

    WAIT_FOR(100, 1000, ((VmPortPolicyEnabled(input, 0)) == true));

    //Disable policy on vnet1 VMI
    AddAapWithMacAndDisablePolicy("vnet1", 1, ip, mac.ToString(), true);
    client->WaitForIdle();

    //Verify that policy status of interface
    WAIT_FOR(100, 1000, ((VmPortPolicyEnabled(input, 0)) == false));

    //Verify that label of aap_route's path is updated with new label of VMI
    rt = RouteGet("vrf1", ip, 32);
    EXPECT_TRUE(rt != NULL);
    path = rt->GetActivePath();
    EXPECT_TRUE(path != NULL);
    EXPECT_TRUE(path->label() != aap_label);

    //Remove AAP route
    MacAddress zero_mac;
    AddAap("vnet1", 1, Ip4Address(0), zero_mac.ToString());
    EXPECT_FALSE(RouteFind("vrf1", ip, 32));

    //cleanup
    DeleteVmportEnv(input, 1, true, 1);
    client->WaitForIdle();
    WAIT_FOR(100, 1000, (VrfFind("vrf1") == false));
}

TEST_F(PolicyTest, VxlanPolicyChange) {
    bgp_peer_ = CreateBgpPeer(Ip4Address(1), "BgpPeer1");
    VxLanNetworkIdentifierMode(false, "VXLAN", "MPLSoGRE", "MPLSoUDP");
    client->WaitForIdle();

    struct PortInfo input[] = {
        {"vnet1", 1, "1.1.1.1", "00:00:00:00:00:01", 1, 1},
    };
    IpamInfo ipam_info[] = {
        {"1.1.1.0", 24, "1.1.1.10"},
    };

    AddIPAM("vn1", ipam_info, 1);
    client->WaitForIdle();
    CreateVmportEnv(input, 1, 1);
    client->WaitForIdle();
    EXPECT_TRUE(VmPortFind(1));

    //Verify that interface has policy enabled
    VmInterface *intf = static_cast<VmInterface *>(VmPortGet(1));
    EXPECT_TRUE(intf->policy_enabled());

    MacAddress mac(intf->vm_mac());
    Ip4Address ip = Ip4Address::from_string("1.1.1.1");
    //Add bgp peer route
    VnListType vn_list;
    agent_->fabric_evpn_table()->
        AddLocalVmRouteReq(bgp_peer_, "vrf1", mac, intf, ip, 10, "",
                           SecurityGroupList(), TagList(), PathPreference(),
                           1, false);
    agent_->fabric_evpn_table()->
        AddLocalVmRouteReq(bgp_peer_, "vrf1", mac, intf, Ip4Address(0), 10, "",
                SecurityGroupList(), TagList(), PathPreference(),
                1, false);
    client->WaitForIdle();

    const NextHop *nh = L2RouteToNextHop("vrf1", mac);
    EXPECT_TRUE(nh != NULL);
    EXPECT_TRUE(nh->PolicyEnabled());

    SetPolicyDisabledStatus(input, true);
    client->WaitForIdle();

    //Verify that policy is disabled on interface
    EXPECT_FALSE(intf->policy_enabled());

    //Verify that interface's L2 route points to policy-disabled NH
    nh = L2RouteToNextHop("vrf1", mac);
    EXPECT_TRUE(nh != NULL);
    EXPECT_FALSE(nh->PolicyEnabled());

    DelEncapList();
    DeleteVmportEnv(input, 1, true, 1);
    client->WaitForIdle();
    DelIPAM("vn1");
    client->WaitForIdle();
    EXPECT_FALSE(VmPortFind(1));
    client->Reset();
    DeleteBgpPeer(bgp_peer_);
    client->WaitForIdle();
}

int main(int argc, char **argv) {
    GETUSERARGS();

    client = TestInit(init_file, ksync_init, false, false, false);

    int ret = RUN_ALL_TESTS();

    usleep(10000);
    client->WaitForIdle();
    usleep(10000);
    TestShutdown();
    delete client;

    return ret;
}

max_stars_repo_name: jnpr-pranav/contrail-controller

max_issues_repo_name: jnpr-pranav/contrail-controller

max_forks_repo_name: jnpr-pranav/contrail-controller


text:
// Copyright 2011 Dean Michael Berris <dberris@google.com>.
// Copyright 2011 Google, Inc.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef NETWORK_PROTOCOL_HTTP_CLIENT_CONNECTION_ASYNC_RESOLVER_IPP_20110911
#define NETWORK_PROTOCOL_HTTP_CLIENT_CONNECTION_ASYNC_RESOLVER_IPP_20111126

#include <string>
#include <utility>
#include <memory>
#include <unordered_map>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/udp.hpp>
#include <boost/asio/placeholders.hpp>
#include <boost/asio/strand.hpp>
#include <functional>
#include <boost/lexical_cast.hpp>
#include <boost/unordered_map.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/fusion/tuple.hpp>
#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/scoped_ptr.hpp>
#include <network/protocol/http/client/connection/async_resolver.hpp>

namespace network { namespace http {
  struct async_resolver_pimpl : std::enable_shared_from_this<async_resolver_pimpl> {
    typedef resolver_delegate::resolve_completion_function resolve_completion_function;
    async_resolver_pimpl(boost::asio::io_service & service, bool cache_resolved);
    void resolve(std::string const & host,
                 uint16_t port,
                 resolve_completion_function once_resolved);
    void clear_resolved_cache();
  private:
    boost::asio::ip::udp::resolver resolver_;
    bool cache_resolved_;
    typedef boost::asio::ip::udp::resolver::iterator
    resolver_iterator;
    typedef std::unordered_map<std::string, std::pair<resolver_iterator,resolver_iterator> >
    endpoint_cache;
    endpoint_cache endpoint_cache_;
    std::unique_ptr<boost::asio::io_service::strand> resolver_strand_;

    void handle_resolve(std::string const & host,
                        resolve_completion_function once_resolved,
                        boost::system::error_code const & ec,
                        resolver_iterator endpoint_iterator);
  };

  async_resolver_pimpl::async_resolver_pimpl(boost::asio::io_service & service, bool cache_resolved)
  : resolver_(service),
  cache_resolved_(cache_resolved),
  endpoint_cache_(),
  resolver_strand_(new(std::nothrow) boost::asio::io_service::strand(service))
  {
    // Do nothing
  }

  void async_resolver_pimpl::clear_resolved_cache() {
    if (cache_resolved_)
      endpoint_cache().swap(endpoint_cache_);
  }

  void async_resolver_pimpl::resolve(std::string const & host,
                                     boost::uint16_t port,
                                     resolve_completion_function once_resolved) {
    if (!resolver_strand_.get())
      BOOST_THROW_EXCEPTION(std::runtime_error(
                                               "Uninitialized resolver strand, ran out of memory."));

    if (cache_resolved_) {
      endpoint_cache::iterator iter =
      endpoint_cache_.find(boost::to_lower_copy(host));
      if (iter != endpoint_cache_.end()) {
        boost::system::error_code ignored;
        once_resolved(ignored, iter->second);
        return;
      }
    }

    std::string port_str = boost::lexical_cast<std::string>(port);
    boost::asio::ip::udp::resolver::query query(host, port_str);
    using namespace std::placeholders;
    resolver_.async_resolve(query,
                            resolver_strand_->wrap(std::bind(&async_resolver_pimpl::handle_resolve,
                                                             async_resolver_pimpl::shared_from_this(),
                                                             boost::to_lower_copy(host),
                                                             once_resolved,
                                                             _1,
                                                             _2)));
  }

  void async_resolver_pimpl::handle_resolve(std::string const & host,
                                            resolve_completion_function once_resolved,
                                            boost::system::error_code const & ec,
                                            resolver_iterator endpoint_iterator) {
    endpoint_cache::iterator iter;
    bool inserted = false;
    if (!ec && cache_resolved_) {
      boost::fusion::tie(iter, inserted) =
      endpoint_cache_.insert(
                             std::make_pair(host,
                                            std::make_pair(endpoint_iterator,
                                                           resolver_iterator())));
      once_resolved(ec, iter->second);
    } else {
      once_resolved(ec, std::make_pair(endpoint_iterator,resolver_iterator()));
    }
  }

  async_resolver::async_resolver(boost::asio::io_service & service, bool cache_resolved)
  : pimpl(new (std::nothrow) async_resolver_pimpl(service, cache_resolved))
  {}

  void async_resolver::resolve(std::string const & host,
                               uint16_t port,
                               resolve_completion_function once_resolved) {
    BOOST_ASSERT(pimpl.get() && "Uninitialized pimpl, probably ran out of memory.");
    pimpl->resolve(host, port, once_resolved);
  }

  void async_resolver::clear_resolved_cache() {
    BOOST_ASSERT(pimpl.get() && "Uninitialized pimpl, probably ran out of memory.");
    pimpl->clear_resolved_cache();
  }

  async_resolver::~async_resolver() {
    // Do nothing
  }

}  // namespace http
}  // namespace network

#endif /* NETWORK_PROTOCOL_HTTP_CLIENT_CONNECTION_ASYNC_RESOLVER_IPP_20111126 */

ext: ipp

max_stars_repo_name: iBeacons/cpp-netlib

max_issues_repo_name: iBeacons/cpp-netlib

max_forks_repo_name: iBeacons/cpp-netlib


text:
/***************************************************************************
   Copyright 2015 Ufora Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
****************************************************************************/
#pragma once

#include <stdint.h>
#include <boost/python.hpp>
#include <boost/python/slice.hpp>
#include <string>
#include "../debug/StackTrace.hpp"
#include "../python/utilities.hpp"
#include "MapWithIndex.hpp"
#include <boost/shared_ptr.hpp>

template<class T>
class PythonWrapper;

template<class key_type, class value_type>
class PythonWrapper<MapWithIndex<key_type, value_type> > {
public:
		typedef MapWithIndex<key_type, value_type>  map_type;

		static map_type* newImmutableFromList(boost::python::object o)
			{
			std::unique_ptr<map_type> tr(new map_type());

			for (int32_t k = 0 ;k < len(o);k++)
				{
				tr->set(
					boost::python::extract<key_type>(o[k][0]),
					boost::python::extract<value_type>(o[k][1])
					);
				}

			return tr.release();
			}
		static boost::python::object getKeys(map_type& m, const key_type& inKey)
			{
			boost::python::list tr;

			auto keySet(m.getKeys(inKey));
			for (auto it = keySet.begin(), it_end = keySet.end(); it != it_end; ++it)
				tr.append(*it);

			return tr;
			}

		template<class m_type, class k_type>
		static boost::python::object nextIterator(m_type& m, const k_type& inKey)
			{
			//it->first will be >= inKey
			auto it = m.lower_bound(inKey);

			if (it == m.end())
				return boost::python::object();

			if (inKey < it->first)
				return boost::python::object(it->first);

			++it;
			if (it == m.end())
				return boost::python::object();

			return boost::python::object(it->first);
			}

		template<class m_type, class k_type>
		static boost::python::object prevIterator(m_type& m, const k_type& inKey)
			{
			if (!m.size())
				return boost::python::object();

			//it->first will be >= inKey
			auto it = m.lower_bound(inKey);

			if (it == m.end())
				{
				it--;
				return boost::python::object(it->first);
				}

			//we found it. decrement
			if (it != m.begin())
				{
				it--;
				return boost::python::object(it->first);
				}
			else
				return boost::python::object();
			}

		static boost::python::object nextKey(map_type& m, const key_type& inKey)
			{
			return nextIterator(m.getKeyToValue(), inKey);
			}
		static boost::python::object nextValue(map_type& m, const value_type& inValue)
			{
			return nextIterator(m.getValueToKeys(), inValue);
			}

		static boost::python::object prevKey(map_type& m, const key_type& inKey)
			{
			return prevIterator(m.getKeyToValue(), inKey);
			}
		static boost::python::object prevValue(map_type& m, const value_type& inValue)
			{
			return prevIterator(m.getValueToKeys(), inValue);
			}

		static boost::python::class_<map_type, boost::shared_ptr<map_type> > exportPythonInterface(const std::string& inTypename)
			{
			using namespace Ufora::python;
			using namespace boost::python;

			return class_<map_type, boost::shared_ptr<map_type> >(
					(inTypename).c_str(), init<>())
				.def("size", &map_type::size)
				.def("__len__", &map_type::size)
				.def("keyCount", &map_type::keyCount)
				.def("valueCount", &map_type::valueCount)
				.def("hasKey", &map_type::hasKey)
				.def("hasValue", &map_type::hasValue)
				.def("getValue", &map_type::getValue)
				.def("drop", &map_type::drop)
				.def("set", &map_type::set)
				.def("__setitem__", &map_type::set)
				.def("insert", &map_type::insert)
				.def("lowestKey", &map_type::lowestKey,
						return_value_policy<copy_const_reference>()
						)
				.def("nextKey", &nextKey)
				.def("prevKey", &prevKey)
				.def("lowestValue", &map_type::lowestValue,
						return_value_policy<copy_const_reference>()
						)
				.def("nextValue", &nextValue)
				.def("prevValue", &prevValue)
				.def("highestKey", &map_type::highestKey,
						return_value_policy<copy_const_reference>()
						)
				.def("highestValue", &map_type::highestValue,
						return_value_policy<copy_const_reference>()
						)
				.def("getKeys", &getKeys)
				;
			}
};

ext: hpp

max_stars_repo_name: ufora/ufora


text:
/*
    Copyright 2007-2008 Christian Henning, Lubomir Bourdev
    Use, modification and distribution are subject to the Boost Software License,
    Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
    http://www.boost.org/LICENSE_1_0.txt).
*/

/*************************************************************************************************/

#ifndef BOOST_GIL_EXTENSION_IO_TIFF_IO_WRITE_HPP_INCLUDED
#define BOOST_GIL_EXTENSION_IO_TIFF_IO_WRITE_HPP_INCLUDED

////////////////////////////////////////////////////////////////////////////////////////
/// \file
/// \brief
/// \author Christian Henning, Lubomir Bourdev \n
///
/// \date   2007-2008 \n
///
////////////////////////////////////////////////////////////////////////////////////////

extern "C" {
#include "tiff.h"
#include "tiffio.h"
}

#include <algorithm>
#include <string>
#include <vector>
#include <boost/static_assert.hpp>
#include <boost/shared_ptr.hpp>

#include <boost/gil/extension/io_new/tiff_tags.hpp>

#include <boost/gil/extension/io_new/detail/base.hpp>
#include <boost/gil/extension/io_new/detail/io_device.hpp>

#include "device.hpp"

namespace boost { namespace gil { namespace detail {

template <typename PixelReference>
struct my_interleaved_pixel_iterator_type_from_pixel_reference
{
private:
	typedef typename remove_reference< PixelReference >::type::value_type pixel_t;
public:
	typedef typename iterator_type_from_pixel< pixel_t
	                                         , false
	                                         , false
	                                         , true
	                                         >::type type;
};

template< typename Channel
        , typename Layout
        , bool Mutable
        >
struct my_interleaved_pixel_iterator_type_from_pixel_reference< const bit_aligned_pixel_reference< byte_t
                                                                                , Channel
                                                                                , Layout
                                                                                , Mutable
                                                                                >
                                                              >
	: public iterator_type_from_pixel< const bit_aligned_pixel_reference< uint8_t
	                                                                    , Channel
	                                                                    , Layout
	                                                                    , Mutable
	                                                                    >
	                                 ,false
	                                 ,false
	                                 ,true
	                                 > {};

template < typename Channel > struct sample_format : public mpl::int_<SAMPLEFORMAT_UINT> {};
template<> struct sample_format<bits8>   : public mpl::int_<SAMPLEFORMAT_UINT> {};
template<> struct sample_format<bits16>  : public mpl::int_<SAMPLEFORMAT_UINT> {};
template<> struct sample_format<bits32>  : public mpl::int_<SAMPLEFORMAT_UINT> {};
template<> struct sample_format<bits32f> : public mpl::int_<SAMPLEFORMAT_IEEEFP> {};
template<> struct sample_format<float> : public mpl::int_<SAMPLEFORMAT_IEEEFP> {};
template<> struct sample_format<double>  : public mpl::int_<SAMPLEFORMAT_IEEEFP> {};
template<> struct sample_format<bits8s>  : public mpl::int_<SAMPLEFORMAT_INT> {};
template<> struct sample_format<bits16s> : public mpl::int_<SAMPLEFORMAT_INT> {};
template<> struct sample_format<bits32s> : public mpl::int_<SAMPLEFORMAT_INT> {};

template <typename Channel> struct photometric_interpretation {};
template<> struct photometric_interpretation< gray_t > : public mpl::int_< PHOTOMETRIC_MINISBLACK > {};
template<> struct photometric_interpretation< rgb_t  > : public mpl::int_< PHOTOMETRIC_RGB        > {};
template<> struct photometric_interpretation< rgba_t > : public mpl::int_< PHOTOMETRIC_RGB        > {};
template<> struct photometric_interpretation< cmyk_t > : public mpl::int_< PHOTOMETRIC_SEPARATED  > {};
template <int N>  struct photometric_interpretation< devicen_t<N> > : public mpl::int_< PHOTOMETRIC_RGB  > {};

template < typename Device, typename Log >
class writer< Device
            , tiff_tag
            , Log
            >
{
public:

    typedef image_write_info<tiff_tag, Log> info_t;

    writer( Device& dev )
    : _io_dev( dev ) {}

    template<typename View>
    void apply( const View& view )
    {
        typedef typename color_space_type<View>::type color_space_t;

        info_t info;

        // write photometric interpretion - Warning: This value is rather subjective.
        // The user should better set this value itself. There is no way to decide if
        // a image is PHOTOMETRIC_MINISWHITE or PHOTOMETRIC_MINISBLACK. This writer
        // will assume PHOTOMETRIC_MINISBLACK for gray_t images and PHOTOMETRIC_RGB
        // for rgb_t images.
        info._photometric_interpretation = photometric_interpretation< color_space_t >::value;

        write_view( view
                  , info );
    }

    template<typename View>
    void apply( const View&   view
              , const info_t& info )
    {
        write_view( view
                  , info );
    }

private:

    template< typename View >
    void write_view( const View&   src_view
                   , const info_t& info
                   )
    {
        typedef typename View::value_type pixel_t;

        // get the type of the first channel (heterogeneous pixels might be broken for now!)
        typedef typename channel_traits< typename element_type< pixel_t >::type >::value_type channel_t;

        // write dimensions
        tiff_image_width::type  width  = (tiff_image_width::type)  src_view.width();
        tiff_image_height::type height = (tiff_image_height::type) src_view.height();

        _io_dev.template set_property< tiff_image_width  >( width  );
        _io_dev.template set_property< tiff_image_height >( height );

        // write planar configuration
        if( is_bit_aligned< View >::value == false )
        {
            _io_dev.template set_property<tiff_planar_configuration>( info._planar_configuration );
        }

        // write samples per pixel
        tiff_samples_per_pixel::type samples_per_pixel = num_channels< pixel_t >::value;
        _io_dev.template set_property<tiff_samples_per_pixel>( samples_per_pixel );

        // write bits per sample
        // @todo: Settings this value usually requires to write for each sample the bit
        // value seperately in case they are different, like rgb556.
        tiff_bits_per_sample::type bits_per_sample = unsigned_integral_num_bits<channel_t>::value;
        _io_dev.template set_property<tiff_bits_per_sample>( bits_per_sample );

        // write sample format
        tiff_sample_format::type sampl_format = sample_format< channel_t >::type::value;
        _io_dev.template set_property<tiff_sample_format>( sampl_format );

        // write photometric format
        _io_dev.template set_property<tiff_photometric_interpretation>( info._photometric_interpretation );

        // write compression
        _io_dev.template set_property<tiff_compression>( info._compression );

        // write orientation
        _io_dev.template set_property<tiff_orientation>( info._orientation );

        // write rows per strip
        _io_dev.template set_property<tiff_rows_per_strip>( _io_dev.get_default_strip_size() );

        if(!info._is_tiled)
        {
            write_data( src_view
                      , info
                      , (src_view.width() * samples_per_pixel * bits_per_sample + 7) / 8
                      , typename is_bit_aligned< pixel_t >::type()
                      );
        }
        else
        {
            uint32 tw = info._tile_width, th = info._tile_length;
            // @todo: warn the user?
            //if(!_io_dev.check_tile_size( tw, th ))
                //;

            // tile related tags
            _io_dev.template set_property<tiff_tile_width> ( tw );
            _io_dev.template set_property<tiff_tile_length>( th );

            write_tiled_data( src_view
                            , tw
                            , th
                            , typename is_bit_aligned< pixel_t >::type()
                            );
        }
    }

    template< typename View >
    void write_data( const View&   src_view
                   , const info_t& /* info */
                   , std::size_t   row_size_in_bytes
                   , const mpl::true_&    // bit_aligned
                   )
    {
        byte_vector_t row( row_size_in_bytes );

        typedef typename View::x_iterator x_it_t;
        x_it_t row_it = x_it_t( &(*row.begin()));

        for( typename View::y_coord_t y = 0; y < src_view.height(); ++y )
        {
            std::copy( src_view.row_begin( y )
                     , src_view.row_end( y )
                     , row_it
                     );

            _io_dev.write_scaline( row
                                 , (uint32) y
                                 , 0
                                 );

            // @todo: do optional bit swapping here if you need to...
        }
    }

    template< typename View >
    void write_tiled_data( const View&   src_view
                         , uint32 tw
                         , uint32 th
                         , const mpl::true_&    // bit_aligned
                         )
    {
        byte_vector_t row( _io_dev.get_tile_size() );

        typedef typename View::x_iterator x_it_t;
        x_it_t row_it = x_it_t( &(*row.begin()));

        internal_write_tiled_data(src_view, tw, th, row, row_it);
    }

    template< typename View >
    void write_data( const View&   src_view
                   , const info_t& info
                   , std::size_t   row_size_in_bytes
                   , const mpl::false_&    // bit_aligned
                   )
    {
        byte_vector_t row( row_size_in_bytes );

        typedef typename my_interleaved_pixel_iterator_type_from_pixel_reference< typename View::reference
                                                                                >::type x_iterator;

        x_iterator row_it = x_iterator( &(*row.begin()));

        for( typename View::y_coord_t y = 0; y < src_view.height(); ++y )
        {
            std::copy( src_view.row_begin( y )
                     , src_view.row_end( y )
                     , row_it
                     );

            _io_dev.write_scaline( row
                                 , (uint32) y
                                 , 0
                                 );

            // @todo: do optional bit swapping here if you need to...
        }
    }

    template< typename View >
    void write_tiled_data( const View&   src_view
                         , uint32 tw
                         , uint32 th
                         , const mpl::false_&    // bit_aligned
                         )
    {
        byte_vector_t row( _io_dev.get_tile_size() );

        typedef typename my_interleaved_pixel_iterator_type_from_pixel_reference< typename View::reference
                                                                                >::type x_iterator;
        x_iterator row_it = x_iterator( &(*row.begin()));

        internal_write_tiled_data(src_view, tw, th, row, row_it);
    }

    template< typename View,
              typename IteratorType >
    void internal_write_tiled_data( const View&    src_view
                                  , uint32 tw
                                  , uint32 th
                                  , byte_vector_t& row
                                  , IteratorType   it
                                  )
    {
        uint32 i = 0, j = 0;
        View tile_subimage_view;
        while(i<(uint32)src_view.height())
        {
            while(j<(uint32)src_view.width())
            {
                if(j+tw<(uint32)src_view.width() && i+th<(uint32)src_view.height())
                {
                    // a tile is fully included in the image: just copy values
                    tile_subimage_view = subimage_view( src_view
                                                      , j
                                                      , i
                                                      , tw
                                                      , th
                                                      );
                    std::copy( tile_subimage_view.begin()
                             , tile_subimage_view.end()
                             , it
                             );
                }
                else
                {
                    uint32 width  = static_cast< uint32 >( src_view.width()  );
                    uint32 height = static_cast< uint32 >( src_view.height() );

                    uint32 current_tile_width  = (j+tw<width ) ? tw : width -j;
                    uint32 current_tile_length = (i+th<height) ? th : height-i;

                    tile_subimage_view = subimage_view( src_view
                                                      , j
                                                      , i
                                                      , current_tile_width
                                                      , current_tile_length
                                                      );

                    for( typename View::y_coord_t y = 0; y < tile_subimage_view.height(); ++y )
                    {
                        std::copy( tile_subimage_view.row_begin( y )
                                 , tile_subimage_view.row_end( y )
                                 , it
                                 );
                        std::advance(it, tw);
                    }
                    it = IteratorType( &(*row.begin()));
                }

                _io_dev.write_tile( row
                                  , j
                                  , i
                                  , 0
                                  , 0
                                  );
                j += tw;
            }
            j = 0;
            i += th;
        }
        // @todo: do optional bit swapping here if you need to...
    }

    Device& _io_dev;
};

struct tiff_write_is_supported
{
    template< typename View >
    struct apply
        : public is_write_supported< typename get_pixel_type< View >::type
                                   , tiff_tag
                                   >
    {};
};

template< typename Device >
class dynamic_image_writer< Device
                          , tiff_tag
                          >
    : public writer< Device
                   , tiff_tag
                   >
{
    typedef writer< Device
                  , tiff_tag
                  > parent_t;

public:

    dynamic_image_writer( Device& file )
    : parent_t( file )
    {}

    template< typename Views >
    void apply( const any_image_view< Views >& views )
    {
        dynamic_io_fnobj< tiff_write_is_supported
                        , parent_t
                        > op( this );

        apply_operation( views, op );
    }
};

} // namespace detail
} // namespace gil
} // namespace boost

#endif // BOOST_GIL_EXTENSION_IO_TIFF_IO_WRITE_HPP_INCLUDED


text:
#include "stdafx.h"

#include "problem.hpp"

#include <fstream>
#include <functional>
#include <vector>

#include <boost/algorithm/string.hpp>

struct advent_2015_2 : problem
{
	advent_2015_2() noexcept : problem(2015, 2) {
	}

	struct box
	{
		std::size_t l, w, h;
	};

protected:
	std::vector<box> boxes;

	void prepare_input(std::ifstream& fin) override {
		for(std::string line; std::getline(fin, line); ) {
			std::vector<std::string> fragments;
			boost::split(fragments, line, [](char ch) { return ch == 'x'; });
			boxes.push_back(box{ std::stoull(fragments[0]), std::stoull(fragments[1]), std::stoull(fragments[2]) });
		}
	}

	std::size_t get_paper_area(box b) {
		const std::size_t areas[] = { b.l * b.w, b.w * b.h, b.h * b.l };
		const std::size_t smallest = *std::min_element(std::begin(areas), std::end(areas));
		return 2 * std::accumulate(std::begin(areas), std::end(areas), 0ui64) + smallest;
	}

	std::size_t get_ribbon_length(box b) noexcept {
		const std::size_t semiperimeters[] = { b.l + b.w, b.w + b.h, b.h + b.l };
		const std::size_t smallest = *std::min_element(std::begin(semiperimeters), std::end(semiperimeters));
		return (2 * smallest) + (b.l * b.w * b.h);
	}

	std::string part_1() override {
		std::size_t paper_required = 0;
		for(const box b : boxes) {
			paper_required += get_paper_area(b);
		}
		return std::to_string(paper_required);
	}

	std::string part_2() override {
		std::size_t ribbon_required = 0;
		for(const box b : boxes) {
			ribbon_required += get_ribbon_length(b);
		}
		return std::to_string(ribbon_required);
	}
};

REGISTER_SOLVER(2015, 2);

ext: cpp

max_forks_repo_name: DrPizza/advent-of-code


text:
/**
 * Orthanc - A Lightweight, RESTful DICOM Store
 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 * Department, University Hospital of Liege, Belgium
 * Copyright (C) 2017-2020 Osimis S.A., Belgium
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 **/

  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
                                           DcmFileFormat& dicom,
                                           DicomTransferSyntax syntax)
  {
    E_TransferSyntax xfer;
    if (!LookupDcmtkTransferSyntax(xfer, syntax))
    {
      return false;
    }
    else if (!dicom.validateMetaInfo(xfer).good())
    {
      throw OrthancException(ErrorCode_InternalError,
                             "Cannot setup the transfer syntax to write a DICOM instance");
    }
    else
    {
      return SaveToMemoryBufferInternal(buffer, dicom, xfer);
    }
  }

  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
                                           DcmFileFormat& dicom)
  {
    E_TransferSyntax xfer = dicom.getDataset()->getCurrentXfer();
    if (xfer == EXS_Unknown)
    {
      throw OrthancException(ErrorCode_InternalError,
                             "Cannot write a DICOM instance with unknown transfer syntax");
    }
    else if (!dicom.validateMetaInfo(xfer).good())
    {
      throw OrthancException(ErrorCode_InternalError,
                             "Cannot setup the transfer syntax to write a DICOM instance");
    }
    else
    {
      return SaveToMemoryBufferInternal(buffer, dicom, xfer);
    }
  }

#include <dcmtk/dcmdata/dcostrmb.h>
#include <dcmtk/dcmdata/dcpixel.h>
#include <dcmtk/dcmdata/dcpxitem.h>

#include "../Core/DicomParsing/Internals/DicomFrameIndex.h"

namespace Orthanc
{
  class IParsedDicomImage : public boost::noncopyable
  {
  public:
    virtual ~IParsedDicomImage()
    {
    }

    virtual DicomTransferSyntax GetTransferSyntax() = 0;

    virtual std::string GetSopClassUid() = 0;

    virtual std::string GetSopInstanceUid() = 0;

    virtual unsigned int GetFramesCount() = 0;

    // Can return NULL, for compressed transfer syntaxes
    virtual ImageAccessor* GetUncompressedFrame(unsigned int frame) = 0;

    virtual void GetCompressedFrame(std::string& target,
                                    unsigned int frame) = 0;

    virtual void WriteToMemoryBuffer(std::string& target) = 0;
  };

  class IDicomImageReader : public boost::noncopyable
  {
  public:
    virtual ~IDicomImageReader()
    {
    }

    virtual IParsedDicomImage* Read(const void* data,
                                    size_t size) = 0;

    virtual IParsedDicomImage* Transcode(const void* data,
                                         size_t size,
                                         DicomTransferSyntax syntax,
                                         bool allowNewSopInstanceUid) = 0;
  };

  class DcmtkImageReader : public IDicomImageReader
  {
  private:
    class Image : public IParsedDicomImage
    {
    private:
      std::unique_ptr<DcmFileFormat>    dicom_;
      std::unique_ptr<DicomFrameIndex>  index_;
      DicomTransferSyntax               transferSyntax_;
      std::string                       sopClassUid_;
      std::string                       sopInstanceUid_;

      static std::string GetStringTag(DcmDataset& dataset,
                                      const DcmTagKey& tag)
      {
        const char* value = NULL;

        if (!dataset.findAndGetString(tag, value).good() ||
            value == NULL)
        {
          throw OrthancException(ErrorCode_BadFileFormat,
                                 "Missing SOP class/instance UID in DICOM instance");
        }
        else
        {
          return std::string(value);
        }
      }

    public:
      Image(DcmFileFormat* dicom,
            DicomTransferSyntax syntax) :
        dicom_(dicom),
        transferSyntax_(syntax)
      {
        if (dicom == NULL ||
            dicom_->getDataset() == NULL)
        {
          throw OrthancException(ErrorCode_NullPointer);
        }

        DcmDataset& dataset = *dicom_->getDataset();
        index_.reset(new DicomFrameIndex(dataset));

        sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID);
        sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID);
      }

      virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE
      {
        return transferSyntax_;
      }

      virtual std::string GetSopClassUid() ORTHANC_OVERRIDE
      {
        return sopClassUid_;
      }

      virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE
      {
        return sopInstanceUid_;
      }

      virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE
      {
        return index_->GetFramesCount();
      }

      virtual void WriteToMemoryBuffer(std::string& target) ORTHANC_OVERRIDE
      {
        assert(dicom_.get() != NULL);
        if (!FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, transferSyntax_))
        {
          throw OrthancException(ErrorCode_InternalError,
                                 "Cannot write the DICOM instance to a memory buffer");
        }
      }

      virtual ImageAccessor* GetUncompressedFrame(unsigned int frame) ORTHANC_OVERRIDE
      {
        assert(dicom_.get() != NULL &&
               dicom_->getDataset() != NULL);
        return DicomImageDecoder::Decode(*dicom_->getDataset(), frame);
      }

      virtual void GetCompressedFrame(std::string& target,
                                      unsigned int frame) ORTHANC_OVERRIDE
      {
        assert(index_.get() != NULL);
        index_->GetRawFrame(target, frame);
      }
    };

    unsigned int lossyQuality_;

    static DicomTransferSyntax DetectTransferSyntax(DcmFileFormat& dicom)
    {
      if (dicom.getDataset() == NULL)
      {
        throw OrthancException(ErrorCode_InternalError);
      }

      DcmDataset& dataset = *dicom.getDataset();

      E_TransferSyntax xfer = dataset.getCurrentXfer();
      if (xfer == EXS_Unknown)
      {
        dataset.updateOriginalXfer();
        xfer = dataset.getCurrentXfer();
        if (xfer == EXS_Unknown)
        {
          throw OrthancException(ErrorCode_BadFileFormat,
                                 "Cannot determine the transfer syntax of the DICOM instance");
        }
      }

      DicomTransferSyntax syntax;
      if (FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, xfer))
      {
        return syntax;
      }
      else
      {
        throw OrthancException(
          ErrorCode_BadFileFormat,
          "Unsupported transfer syntax: " + boost::lexical_cast<std::string>(xfer));
      }
    }

    static uint16_t GetBitsStored(DcmFileFormat& dicom)
    {
      if (dicom.getDataset() == NULL)
      {
        throw OrthancException(ErrorCode_InternalError);
      }

      uint16_t bitsStored;
      if (dicom.getDataset()->findAndGetUint16(DCM_BitsStored, bitsStored).good())
      {
        return bitsStored;
      }
      else
      {
        throw OrthancException(ErrorCode_BadFileFormat,
                               "Missing \"Bits Stored\" tag in DICOM instance");
      }
    }

  public:
    DcmtkImageReader() :
      lossyQuality_(90)
    {
    }

    void SetLossyQuality(unsigned int quality)
    {
      if (quality <= 0 ||
          quality > 100)
      {
        throw OrthancException(ErrorCode_ParameterOutOfRange);
      }
      else
      {
        lossyQuality_ = quality;
      }
    }

    unsigned int GetLossyQuality() const
    {
      return lossyQuality_;
    }

    virtual IParsedDicomImage* Read(const void* data,
                                    size_t size)
    {
      std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(data, size));
      if (dicom.get() == NULL)
      {
        throw OrthancException(ErrorCode_BadFileFormat);
      }

      DicomTransferSyntax transferSyntax = DetectTransferSyntax(*dicom);

      return new Image(dicom.release(), transferSyntax);
    }

    virtual IParsedDicomImage* Transcode(const void* data,
                                         size_t size,
                                         DicomTransferSyntax syntax,
                                         bool allowNewSopInstanceUid)
    {
      std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(data, size));
      if (dicom.get() == NULL)
      {
        throw OrthancException(ErrorCode_BadFileFormat);
      }

      const uint16_t bitsStored = GetBitsStored(*dicom);

      if (syntax == DetectTransferSyntax(*dicom))
      {
        // No transcoding is needed
        return new Image(dicom.release(), syntax);
      }

      if (syntax == DicomTransferSyntax_LittleEndianImplicit &&
          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_LittleEndianImplicit, NULL))
      {
        return new Image(dicom.release(), syntax);
      }

      if (syntax == DicomTransferSyntax_LittleEndianExplicit &&
          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_LittleEndianExplicit, NULL))
      {
        return new Image(dicom.release(), syntax);
      }

      if (syntax == DicomTransferSyntax_BigEndianExplicit &&
          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_BigEndianExplicit, NULL))
      {
        return new Image(dicom.release(), syntax);
      }

      if (syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit &&
          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL))
      {
        return new Image(dicom.release(), syntax);
      }

#if ORTHANC_ENABLE_JPEG == 1
      if (syntax == DicomTransferSyntax_JPEGProcess1 &&
          allowNewSopInstanceUid &&
          bitsStored == 8)
      {
        DJ_RPLossy rpLossy(lossyQuality_);

        if (FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_JPEGProcess1, &rpLossy))
        {
          return new Image(dicom.release(), syntax);
        }
      }
#endif

#if ORTHANC_ENABLE_JPEG == 1
      if (syntax == DicomTransferSyntax_JPEGProcess2_4 &&
          allowNewSopInstanceUid &&
          bitsStored <= 12)
      {
        DJ_RPLossy rpLossy(lossyQuality_);
        if (FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_JPEGProcess2_4, &rpLossy))
        {
          return new Image(dicom.release(), syntax);
        }
      }
#endif

      //LOG(INFO) << "Unable to transcode DICOM image using the built-in reader";
      return NULL;
    }
  };

  class IDicomTranscoder1 : public boost::noncopyable
  {
  public:
    virtual ~IDicomTranscoder1()
    {
    }

    virtual DcmFileFormat& GetDicom() = 0;

    virtual DicomTransferSyntax GetTransferSyntax() = 0;

    virtual std::string GetSopClassUid() = 0;

    virtual std::string GetSopInstanceUid() = 0;

    virtual unsigned int GetFramesCount() = 0;

    virtual ImageAccessor* DecodeFrame(unsigned int frame) = 0;

    virtual void GetCompressedFrame(std::string& target,
                                    unsigned int frame) = 0;

    // NB: Transcoding can change the value of "GetSopInstanceUid()"
    // and "GetTransferSyntax()" if lossy compression is applied
    virtual bool Transcode(std::string& target,
                           DicomTransferSyntax syntax,
                           bool allowNewSopInstanceUid) = 0;

    virtual void WriteToMemoryBuffer(std::string& target) = 0;
  };

  class DcmtkTranscoder2 : public IDicomTranscoder1
  {
  private:
    std::unique_ptr<DcmFileFormat>    dicom_;
    std::unique_ptr<DicomFrameIndex>  index_;
    DicomTransferSyntax               transferSyntax_;
    std::string                       sopClassUid_;
    std::string                       sopInstanceUid_;
    uint16_t                          bitsStored_;
    unsigned int                      lossyQuality_;

    static std::string GetStringTag(DcmDataset& dataset,
                                    const DcmTagKey& tag)
    {
      const char* value = NULL;

      if (!dataset.findAndGetString(tag, value).good() ||
          value == NULL)
      {
        throw OrthancException(ErrorCode_BadFileFormat,
                               "Missing SOP class/instance UID in DICOM instance");
      }
      else
      {
        return std::string(value);
      }
    }

    void Setup(DcmFileFormat* dicom)
    {
      lossyQuality_ = 90;

      dicom_.reset(dicom);

      if (dicom == NULL ||
          dicom_->getDataset() == NULL)
      {
        throw OrthancException(ErrorCode_NullPointer);
      }

      DcmDataset& dataset = *dicom_->getDataset();
      index_.reset(new DicomFrameIndex(dataset));

      E_TransferSyntax xfer = dataset.getCurrentXfer();
      if (xfer == EXS_Unknown)
      {
        dataset.updateOriginalXfer();
        xfer = dataset.getCurrentXfer();
        if (xfer == EXS_Unknown)
        {
          throw OrthancException(ErrorCode_BadFileFormat,
                                 "Cannot determine the transfer syntax of the DICOM instance");
        }
      }

      if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax_, xfer))
      {
        throw OrthancException(
          ErrorCode_BadFileFormat,
          "Unsupported transfer syntax: " + boost::lexical_cast<std::string>(xfer));
      }

      if (!dataset.findAndGetUint16(DCM_BitsStored, bitsStored_).good())
      {
        throw OrthancException(ErrorCode_BadFileFormat,
                               "Missing \"Bits Stored\" tag in DICOM instance");
      }

      sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID);
      sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID);
    }

  public:
    DcmtkTranscoder2(DcmFileFormat* dicom)  // Takes ownership
    {
      Setup(dicom);
    }

    DcmtkTranscoder2(const void* dicom,
                    size_t size)
    {
      Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size));
    }

    void SetLossyQuality(unsigned int quality)
    {
      if (quality <= 0 ||
          quality > 100)
      {
        throw OrthancException(ErrorCode_ParameterOutOfRange);
      }
      else
      {
        lossyQuality_ = quality;
      }
    }

    unsigned int GetLossyQuality() const
    {
      return lossyQuality_;
    }

    unsigned int GetBitsStored() const
    {
      return bitsStored_;
    }

    virtual DcmFileFormat& GetDicom()
    {
      assert(dicom_ != NULL);
      return *dicom_;
    }

    virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE
    {
      return transferSyntax_;
    }

    virtual std::string GetSopClassUid() ORTHANC_OVERRIDE
    {
      return sopClassUid_;
    }

    virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE
    {
      return sopInstanceUid_;
    }

    virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE
    {
      return index_->GetFramesCount();
    }

    virtual void WriteToMemoryBuffer(std::string& target) ORTHANC_OVERRIDE
    {
      if (!FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_))
      {
        throw OrthancException(ErrorCode_InternalError,
                               "Cannot write the DICOM instance to a memory buffer");
      }
    }

    virtual ImageAccessor* DecodeFrame(unsigned int frame) ORTHANC_OVERRIDE
    {
      assert(dicom_->getDataset() != NULL);
      return DicomImageDecoder::Decode(*dicom_->getDataset(), frame);
    }

    virtual void GetCompressedFrame(std::string& target,
                                    unsigned int frame) ORTHANC_OVERRIDE
    {
      index_->GetRawFrame(target, frame);
    }

    virtual bool Transcode(std::string& target,
                           DicomTransferSyntax syntax,
                           bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
    {
      assert(dicom_ != NULL &&
             dicom_->getDataset() != NULL);

      if (syntax == GetTransferSyntax())
      {
        printf("NO TRANSCODING\n");

        // No change in the transfer syntax => simply serialize the current dataset
        WriteToMemoryBuffer(target);
        return true;
      }

      printf(">> %d\n", bitsStored_);

      if (syntax == DicomTransferSyntax_LittleEndianImplicit &&
          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
      {
        transferSyntax_ = DicomTransferSyntax_LittleEndianImplicit;
        return true;
      }

      if (syntax == DicomTransferSyntax_LittleEndianExplicit &&
          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
      {
        transferSyntax_ = DicomTransferSyntax_LittleEndianExplicit;
        return true;
      }

      if (syntax == DicomTransferSyntax_BigEndianExplicit &&
          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
      {
        transferSyntax_ = DicomTransferSyntax_BigEndianExplicit;
        return true;
      }

      if (syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit &&
          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
      {
        transferSyntax_ = DicomTransferSyntax_DeflatedLittleEndianExplicit;
        return true;
      }

#if ORTHANC_ENABLE_JPEG == 1
      if (syntax == DicomTransferSyntax_JPEGProcess1 &&
          allowNewSopInstanceUid &&
          GetBitsStored() == 8)
      {
        DJ_RPLossy rpLossy(lossyQuality_);

        if (FromDcmtkBridge::Transcode(*dicom_, syntax, &rpLossy) &&
            FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
        {
          transferSyntax_ = DicomTransferSyntax_JPEGProcess1;
          sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID);
          return true;
        }
      }
#endif

#if ORTHANC_ENABLE_JPEG == 1
      if (syntax == DicomTransferSyntax_JPEGProcess2_4 &&
          allowNewSopInstanceUid &&
          GetBitsStored() <= 12)
      {
        DJ_RPLossy rpLossy(lossyQuality_);
        if (FromDcmtkBridge::Transcode(*dicom_, syntax, &rpLossy) &&
            FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
        {
          transferSyntax_ = DicomTransferSyntax_JPEGProcess2_4;
          sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID);
          return true;
        }
      }
#endif

      return false;
    }
  };
}

#include <boost/filesystem.hpp>

static void TestFile(const std::string& path)
{
  static unsigned int count = 0;
  count++;

  printf("** %s\n", path.c_str());

  std::string s;
  SystemToolbox::ReadFile(s, path);

  Orthanc::DcmtkTranscoder2 transcoder(s.c_str(), s.size());

  /*if (transcoder.GetBitsStored() != 8)  // TODO
    return; */

  {
    char buf[1024];
    sprintf(buf, "/tmp/source-%06d.dcm", count);
    printf(">> %s\n", buf);
    Orthanc::SystemToolbox::WriteFile(s, buf);
  }

  printf("[%s] [%s] [%s] %d %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()),
         transcoder.GetSopClassUid().c_str(), transcoder.GetSopInstanceUid().c_str(),
         transcoder.GetFramesCount(), transcoder.GetTransferSyntax());

  for (size_t i = 0; i < transcoder.GetFramesCount(); i++)
  {
    std::string f;
    transcoder.GetCompressedFrame(f, i);

    if (i == 0)
    {
      char buf[1024];
      sprintf(buf, "/tmp/frame-%06d.raw", count);
      printf(">> %s\n", buf);
      Orthanc::SystemToolbox::WriteFile(f, buf);
    }
  }

  {
    std::string t;
    transcoder.WriteToMemoryBuffer(t);

    Orthanc::DcmtkTranscoder2 transcoder2(t.c_str(), t.size());
    printf(">> %d %d ; %lu bytes\n", transcoder.GetTransferSyntax(), transcoder2.GetTransferSyntax(), t.size());
  }

  {
    std::string a = transcoder.GetSopInstanceUid();
    DicomTransferSyntax b = transcoder.GetTransferSyntax();

    DicomTransferSyntax syntax = DicomTransferSyntax_JPEGProcess2_4;
    //DicomTransferSyntax syntax = DicomTransferSyntax_LittleEndianExplicit;

    std::string t;
    bool ok = transcoder.Transcode(t, syntax, true);
    printf("Transcoding: %d\n", ok);

    if (ok)
    {
      printf("[%s] => [%s]\n", a.c_str(), transcoder.GetSopInstanceUid().c_str());
      printf("[%s] => [%s]\n", GetTransferSyntaxUid(b),
             GetTransferSyntaxUid(transcoder.GetTransferSyntax()));

      {
        char buf[1024];
        sprintf(buf, "/tmp/transcoded-%06d.dcm", count);
        printf(">> %s\n", buf);
        Orthanc::SystemToolbox::WriteFile(t, buf);
      }

      Orthanc::DcmtkTranscoder2 transcoder2(t.c_str(), t.size());
      printf("  => transcoded transfer syntax %d ; %lu bytes\n", transcoder2.GetTransferSyntax(), t.size());
    }
  }

  printf("\n");
}

TEST(Toto, DISABLED_Transcode)
{
  //OFLog::configure(OFLogger::DEBUG_LOG_LEVEL);

  if (1)
  {
    const char* const PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes";

    for (boost::filesystem::directory_iterator it(PATH);
         it != boost::filesystem::directory_iterator(); ++it)
    {
      if (boost::filesystem::is_regular_file(it->status()))
      {
        TestFile(it->path().string());
      }
    }
  }

  if (0)
  {
    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Multiframe.dcm");
    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Issue44/Monochrome1-Jpeg.dcm");
  }

  if (0)
  {
    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.1.dcm");
  }
}

TEST(Toto, DISABLED_Transcode2)
{
  for (int i = 0; i <= DicomTransferSyntax_XML; i++)
  {
    DicomTransferSyntax a = (DicomTransferSyntax) i;

    std::string path = ("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/" +
                        std::string(GetTransferSyntaxUid(a)) + ".dcm");
    if (Orthanc::SystemToolbox::IsRegularFile(path))
    {
      printf("\n======= %s\n", GetTransferSyntaxUid(a));

      std::string source;
      Orthanc::SystemToolbox::ReadFile(source, path);

      DcmtkImageReader reader;

      {
        std::unique_ptr<IParsedDicomImage> image(
          reader.Read(source.c_str(), source.size()));
        ASSERT_TRUE(image.get() != NULL);
        ASSERT_EQ(a, image->GetTransferSyntax());

        std::string target;
        image->WriteToMemoryBuffer(target);
      }

      for (int j = 0; j <= DicomTransferSyntax_XML; j++)
      {
        DicomTransferSyntax b = (DicomTransferSyntax) j;
        //if (a == b) continue;

        std::unique_ptr<IParsedDicomImage> image(
          reader.Transcode(source.c_str(), source.size(), b, true));
        if (image.get() != NULL)
        {
          printf("[%s] -> [%s]\n", GetTransferSyntaxUid(a), GetTransferSyntaxUid(b));

          std::string target;
          image->WriteToMemoryBuffer(target);

          char buf[1024];
          sprintf(buf, "/tmp/%s-%s.dcm", GetTransferSyntaxUid(a), GetTransferSyntaxUid(b));

          SystemToolbox::WriteFile(target, buf);
        }
        else if (a != DicomTransferSyntax_JPEG2000 &&
                 a != DicomTransferSyntax_JPEG2000LosslessOnly)
        {
          ASSERT_TRUE(b != DicomTransferSyntax_LittleEndianImplicit &&
                      b != DicomTransferSyntax_LittleEndianExplicit &&
                      b != DicomTransferSyntax_BigEndianExplicit &&
                      b != DicomTransferSyntax_DeflatedLittleEndianExplicit);
        }
      }
    }
  }
}

#include "../Core/DicomNetworking/DicomAssociation.h"
#include "../Core/DicomNetworking/DicomControlUserConnection.h"
#include "../Core/DicomNetworking/DicomStoreUserConnection.h"

TEST(Toto, DISABLED_DicomAssociation)
{
  DicomAssociationParameters params;
  params.SetLocalApplicationEntityTitle("ORTHANC");
  params.SetRemoteApplicationEntityTitle("PACS");
  params.SetRemotePort(2001);

#if 0
  DicomAssociation assoc;
  assoc.ProposeGenericPresentationContext(UID_StorageCommitmentPushModelSOPClass);
  assoc.ProposeGenericPresentationContext(UID_VerificationSOPClass);
  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
                                   DicomTransferSyntax_JPEGProcess1);
  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
                                   DicomTransferSyntax_JPEGProcess2_4);
  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
                                   DicomTransferSyntax_JPEG2000);

  assoc.Open(params);

  int presID = ASC_findAcceptedPresentationContextID(&assoc.GetDcmtkAssociation(), UID_ComputedRadiographyImageStorage);
  printf(">> %d\n", presID);

  std::map<DicomTransferSyntax, uint8_t> pc;
  printf(">> %d\n", assoc.LookupAcceptedPresentationContext(pc, UID_ComputedRadiographyImageStorage));

  for (std::map<DicomTransferSyntax, uint8_t>::const_iterator
         it = pc.begin(); it != pc.end(); ++it)
  {
    printf("[%s] => %d\n", GetTransferSyntaxUid(it->first), it->second);
  }
#else
  {
    DicomControlUserConnection assoc(params);

    try
    {
      printf(">> %d\n", assoc.Echo());
    }
    catch (OrthancException&)
    {
    }
  }

  params.SetRemoteApplicationEntityTitle("PACS");
  params.SetRemotePort(2000);

  {
    DicomControlUserConnection assoc(params);
    printf(">> %d\n", assoc.Echo());
  }

#endif
}

static void TestTranscode(DicomStoreUserConnection& scu,
                          const std::string& sopClassUid,
                          DicomTransferSyntax transferSyntax)
{
  std::set<DicomTransferSyntax> accepted;

  scu.LookupTranscoding(accepted, sopClassUid, transferSyntax);
  if (accepted.empty())
  {
    throw OrthancException(ErrorCode_NetworkProtocol,
                           "The SOP class is not supported by the remote modality");
  }

  {
    unsigned int count = 0;
    for (std::set<DicomTransferSyntax>::const_iterator
           it = accepted.begin(); it != accepted.end(); ++it)
    {
      LOG(INFO) << "available for transcoding " << (count++) << ": " << sopClassUid
                << " / " << GetTransferSyntaxUid(*it);
    }
  }

  if (accepted.find(transferSyntax) != accepted.end())
  {
    printf("**** OK, without transcoding !! [%s]\n", GetTransferSyntaxUid(transferSyntax));
  }
  else
  {
    // Transcoding - only in Orthanc >= 1.7.0

    const DicomTransferSyntax uncompressed[] = {
      DicomTransferSyntax_LittleEndianImplicit,  // Default transfer syntax
      DicomTransferSyntax_LittleEndianExplicit,
      DicomTransferSyntax_BigEndianExplicit
    };

    bool found = false;
    for (size_t i = 0; i < 3; i++)
    {
      if (accepted.find(uncompressed[i]) != accepted.end())
      {
        printf("**** TRANSCODING to %s\n", GetTransferSyntaxUid(uncompressed[i]));
        found = true;
        break;
      }
    }

    if (!found)
    {
      printf("**** KO KO KO\n");
    }
  }
}

TEST(Toto, DISABLED_Store)
{
  DicomAssociationParameters params;
  params.SetLocalApplicationEntityTitle("ORTHANC");
  params.SetRemoteApplicationEntityTitle("STORESCP");
  params.SetRemotePort(2000);

  DicomStoreUserConnection assoc(params);
  assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess1);
  assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess2_4);
  //assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit);

  //assoc.SetUncompressedSyntaxesProposed(false);  // Necessary for transcoding
  assoc.SetCommonClassesProposed(false);
  assoc.SetRetiredBigEndianProposed(true);
  TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit);
  TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_JPEG2000);
  TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_JPEG2000);
}

TEST(Toto, DISABLED_Store2)
{
  DicomAssociationParameters params;
  params.SetLocalApplicationEntityTitle("ORTHANC");
  params.SetRemoteApplicationEntityTitle("STORESCP");
  params.SetRemotePort(2000);

  DicomStoreUserConnection assoc(params);
  //assoc.SetCommonClassesProposed(false);
  assoc.SetRetiredBigEndianProposed(true);

  std::string s;
  Orthanc::SystemToolbox::ReadFile(s, "/tmp/i/" + std::string(GetTransferSyntaxUid(DicomTransferSyntax_BigEndianExplicit)) +".dcm");

  std::string c, i;
  assoc.Store(c, i, s.c_str(), s.size());
  printf("[%s] [%s]\n", c.c_str(), i.c_str());
}