/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */

#ifdef NS3_OPENFLOW

// An essential include is test.h
#include "ns3/test.h"

#include "switch-net-device.h"
#include "openflow-interface.h"

// Do not put your test classes in namespace ns3.  You may find it useful
// to use the using directive to access the ns3 namespace directly
using namespace ns3;

// This is an example TestCase.
class SwitchFlowTableTestCase : public TestCase
{
public:
  SwitchFlowTableTestCase () : TestCase ("Switch test case")
  {
    m_chain = chain_create ();
  }

  virtual ~SwitchFlowTableTestCase ()
  {
    chain_destroy (m_chain);
  }

private:
  virtual void DoRun (void);

  sw_chain* m_chain;
};

void
SwitchFlowTableTestCase::DoRun (void)
{
  // Flow Table implementation is used by the SwitchNetDevice under the chain_ methods
  // we should test its implementation to verify the flow table works.

  // Initialization
  time_init (); // OFSI requires this, otherwise we crash before we can do anything.

  size_t actions_len = 0; // Flow is created with 0 actions.
  int output_port = 0; // Flow will be modified later with an action to output on port 0.

  Mac48Address dl_src ("00:00:00:00:00:00"), dl_dst ("00:00:00:00:00:01");
  Ipv4Address nw_src ("192.168.1.1"), nw_dst ("192.168.1.2");
  int tp_src = 5000, tp_dst = 80;

  // Create an sw_flow_key; in actual usage this is generated from the received packet's headers.
  sw_flow_key key;
  key.wildcards = 0;

  key.flow.in_port = htons (0);

  key.flow.dl_vlan = htons (OFP_VLAN_NONE);
  key.flow.dl_type = htons (ETH_TYPE_IP);
  key.flow.nw_proto = htons (IP_TYPE_UDP);

  key.flow.reserved = 0;
  key.flow.mpls_label1 = htonl (MPLS_INVALID_LABEL);
  key.flow.mpls_label2 = htonl (MPLS_INVALID_LABEL);

  // Set Mac Addresses
  dl_src.CopyTo (key.flow.dl_src);
  dl_dst.CopyTo (key.flow.dl_dst);

  // Set IP Addresses
  key.flow.nw_src = htonl (nw_src.Get ());
  key.flow.nw_dst = htonl (nw_dst.Get ());

  // Set TCP/UDP Ports
  key.flow.tp_src = htonl (tp_src);
  key.flow.tp_dst = htonl (tp_dst);

  // Create flow
  ofp_flow_mod ofm;
  ofm.header.version = OFP_VERSION;
  ofm.header.type = OFPT_FLOW_MOD;
  ofm.header.length = htons (sizeof (ofp_flow_mod) + actions_len);
  ofm.command = htons (OFPFC_ADD);
  ofm.idle_timeout = htons (OFP_FLOW_PERMANENT);
  ofm.hard_timeout = htons (OFP_FLOW_PERMANENT);
  ofm.buffer_id = htonl (-1);
  ofm.priority = OFP_DEFAULT_PRIORITY;

  ofm.match.wildcards = key.wildcards;                                 // Wildcard fields
  ofm.match.in_port = key.flow.in_port;                                // Input switch port
  memcpy (ofm.match.dl_src, key.flow.dl_src, sizeof ofm.match.dl_src); // Ethernet source address.
  memcpy (ofm.match.dl_dst, key.flow.dl_dst, sizeof ofm.match.dl_dst); // Ethernet destination address.
  ofm.match.dl_vlan = key.flow.dl_vlan;                                // Input VLAN OFP_VLAN_NONE;
  ofm.match.dl_type = key.flow.dl_type;                                // Ethernet frame type ETH_TYPE_IP;
  ofm.match.nw_proto = key.flow.nw_proto;                              // IP Protocol
  ofm.match.nw_src = key.flow.nw_src;                                  // IP source address
  ofm.match.nw_dst = key.flow.nw_dst;                                  // IP destination address
  ofm.match.tp_src = key.flow.tp_src;                                  // TCP/UDP source port
  ofm.match.tp_dst = key.flow.tp_dst;                                  // TCP/UDP destination port
  ofm.match.mpls_label1 = key.flow.mpls_label1;                        // Top of label stack
  ofm.match.mpls_label2 = key.flow.mpls_label1;                        // Second label (if available)

  // Build a sw_flow from the ofp_flow_mod
  sw_flow *flow = flow_alloc (actions_len);
  NS_TEST_ASSERT_MSG_NE (flow, 0, "Cannot allocate memory for the flow.");

  flow_extract_match (&flow->key, &ofm.match);

  // Fill out flow.
  flow->priority = flow->key.wildcards ? ntohs (ofm.priority) : -1;
  flow->idle_timeout = ntohs (ofm.idle_timeout);
  flow->hard_timeout = ntohs (ofm.hard_timeout);
  flow->used = flow->created = time_now ();
  flow->sf_acts->actions_len = actions_len;
  flow->byte_count = 0;
  flow->packet_count = 0;
  memcpy (flow->sf_acts->actions, ofm.actions, actions_len);

  // Insert the flow into the Flow Table
  NS_TEST_ASSERT_MSG_EQ (chain_insert (m_chain, flow), 0, "Flow table failed to insert Flow.");

  // Use key to match the flow to verify we created it correctly.
  NS_TEST_ASSERT_MSG_NE (chain_lookup (m_chain, &key), 0, "Key provided doesn't match to the flow that was created from it.");

  // Modify key to make sure the flow doesn't match it.
  dl_dst.CopyTo (key.flow.dl_src);
  dl_src.CopyTo (key.flow.dl_dst);
  key.flow.nw_src = htonl (nw_dst.Get ());
  key.flow.nw_dst = htonl (nw_src.Get ());
  key.flow.tp_src = htonl (tp_dst);
  key.flow.tp_dst = htonl (tp_src);

  NS_TEST_ASSERT_MSG_EQ (chain_lookup (m_chain, &key), 0, "Key provided shouldn't match the flow but it does.");

  // Modify key back to matching the flow so we can test flow modification.
  dl_dst.CopyTo (key.flow.dl_dst);
  dl_src.CopyTo (key.flow.dl_src);
  key.flow.nw_src = htonl (nw_src.Get ());
  key.flow.nw_dst = htonl (nw_dst.Get ());
  key.flow.tp_src = htonl (tp_src);
  key.flow.tp_dst = htonl (tp_dst);

  // Testing Flow Modification; chain_modify should return 1, for 1 flow modified.
  // Create output-to-port action
  ofp_action_output acts[1];
  acts[0].type = htons (OFPAT_OUTPUT);
  acts[0].len = htons (sizeof (ofp_action_output));
  acts[0].port = output_port;

  uint16_t priority = key.wildcards ? ntohs (ofm.priority) : -1;
  NS_TEST_ASSERT_MSG_EQ (chain_modify (m_chain, &key, priority, false, (const ofp_action_header*)acts, sizeof (acts)), 1, "Flow table failed to modify Flow.");

  // Testing Flow Deletion; chain_delete should return 1, for 1 flow deleted.
  // Note: By providing chain_delete with output_port, the flow must have an action that outputs on that port in order to delete the flow.
  // This is how we verify that our action was truly added via the flow modification.
  NS_TEST_ASSERT_MSG_EQ (chain_delete (m_chain, &key, output_port, 0, 0), 1, "Flow table failed to delete Flow.");
  NS_TEST_ASSERT_MSG_EQ (chain_lookup (m_chain, &key), 0, "Key provided shouldn't match the flow but it does.");
}

class SwitchTestSuite : public TestSuite
{
public:
  SwitchTestSuite ();
};

SwitchTestSuite::SwitchTestSuite () : TestSuite ("switch", UNIT)
{
  AddTestCase (new SwitchFlowTableTestCase);
}

// Do not forget to allocate an instance of this TestSuite
SwitchTestSuite switchTestSuite;

#endif // NS3_OPENFLOW