#ifndef NICO_NAT64_GENERIC
#define NICO_NAT64_GENERIC

#include "actions_egress.p4"
#include "actions_delta_checksum.p4"

/********************** NAT64 / NAT46 ACTIONS GENERIC ***********************************/

/* changes for icmp6 -> icmp */
action nat64_icmp6_generic()
{
    hdr.icmp.setValid();
    hdr.ipv4.protocol = PROTO_ICMP; // overwrite generic same protocol assumption

    /* trigger checksumming */
    meta.switch_task = TASK_CHECKSUM_ICMP;

    /* FIXME: for delta */
    meta.chk_icmp = 1;

    hdr.icmp6.setInvalid();

    /* not needed, as we don't translate them (yet/ever) */
    hdr.icmp6_na_ns.setInvalid();
    hdr.icmp6_option_link_layer_addr.setInvalid();
}

/* NAT64 protocol unspecific changes */
action nat64_generic(ipv4_addr_t src, ipv4_addr_t dst) {
	hdr.ipv4.setValid();

    /* Stuff that might need to be fixed */
    hdr.ipv4.version        = (bit<4>)4;
    hdr.ipv4.ihl            = (bit<4>)5;                             // internet header length: 4*5 = 20
                                                                     /* 5 is ok as long as
                                                                       we don't use options / padding /
                                                                       anything after the destination address */
    hdr.ipv4.diff_serv      = (bit<6>)0;                             // no ToS
	hdr.ipv4.ecn            = (bit<2>)0;                             // unsupported
    hdr.ipv4.totalLen       = (bit<16>) hdr.ipv6.payload_length + 20; // ok under above constraints
    hdr.ipv4.identification = (bit<16>) 0;                           // no support for fragments
    hdr.ipv4.flags          = (bit<3>)  0;                           // DF bit and more fragments
    hdr.ipv4.fragOffset     = (bit<13>) 0;                           // 0 as there are no fragments
    hdr.ipv4.ttl            = hdr.ipv6.hop_limit;
    hdr.ipv4.protocol       = hdr.ipv6.next_header;
    hdr.ipv4.src_addr       = src;
    hdr.ipv4.dst_addr       = dst;

    /* Stuff that should be fine */
	hdr.ethernet.ethertype  = TYPE_IPV4;

    #ifdef _SUME_SWITCH_P4_
    delta_ipv4_from_v6_to_v4();
    #else
    meta.chk_ipv4 = 1; /* need to calculate the hdrchecksum */
    #endif

    hdr.ipv6.setInvalid();
}

/* nat64_prefix is the same as the matching key, but without the mask */
action nat64_static(ipv6_addr_t v6_src, ipv4_addr_t v4_dst, ipv6_addr_t nat64_prefix) {
    ipv6_addr_t src_offset = hdr.ipv6.src_addr - v6_src;
    ipv4_addr_t src        = v4_dst + (ipv4_addr_t) src_offset;

    ipv4_addr_t dst        = (ipv4_addr_t) (hdr.ipv6.dst_addr - nat64_prefix);

    nat64_generic(src, dst);
}

/* From https://tools.ietf.org/html/rfc792 (IPv4)

Echo or Echo Reply Message

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     Type      |     Code      |          Checksum             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           Identifier          |        Sequence Number        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     Data ...
   +-+-+-+-+-


    From https://tools.ietf.org/html/rfc4443#section-4.1

4.1.  Echo Request Message

       0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |     Type      |     Code      |          Checksum             |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |           Identifier          |        Sequence Number        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |     Data ...
      +-+-+-+-+-

4.2.  Echo Reply Message

       0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |     Type      |     Code      |          Checksum             |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |           Identifier          |        Sequence Number        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |     Data ...
      +-+-+-+-+-


    Type / code are different in ICMP4 and ICMP6!

    */


/* changes for icmp6 -> icmp */
action nat46_icmp_generic()
{
    hdr.icmp6.setValid();
    hdr.ipv6.next_header = PROTO_ICMP6;

    meta.chk_icmp6 = 1;
    meta.cast_length = (bit<32>) hdr.ipv6.payload_length;

    hdr.icmp.setInvalid();
}

/* NAT46: protocol unspecific changes */
action nat46_generic(ipv6_addr_t src, ipv6_addr_t dst) {
		hdr.ipv6.setValid();
        hdr.ipv4.setInvalid();

		hdr.ethernet.ethertype  = TYPE_IPV6;

		hdr.ipv6.dst_addr       = dst;
		hdr.ipv6.src_addr       = src;
		hdr.ipv6.version        = (bit<4>)  6;
		hdr.ipv6.traffic_class  = (bit<8>)  hdr.ipv4.diff_serv;
		hdr.ipv6.flow_label     = (bit<20>) 0;
		hdr.ipv6.payload_length = (bit<16>) hdr.ipv4.totalLen - 20;
		hdr.ipv6.next_header    = hdr.ipv4.protocol;
		hdr.ipv6.hop_limit      = hdr.ipv4.ttl;
	}


/* matching key: v4_network specified again */
action nat46_static(ipv6_addr_t v6_src, ipv4_addr_t v4_dst, ipv6_addr_t nat64_prefix) {
    ipv6_addr_t src = nat64_prefix + (ipv6_addr_t) hdr.ipv4.src_addr;

    ipv4_addr_t dst_offset = hdr.ipv4.dst_addr - v4_dst;
    ipv6_addr_t dst = v6_src + (ipv6_addr_t) dst_offset;

    nat46_generic(src, dst);
}

    table nat64 {
    key = {
        #ifndef _SUME_SWITCH_P4_
        hdr.ipv6.dst_addr: lpm;
        #else
        hdr.ipv6.dst_addr: exact;
        #endif
        }
        actions = {
            controller_debug;
            nat64_static;
            controller_debug_table_id;
            NoAction;
        }
        size = NAT64_TABLE_SIZE;
        default_action = NoAction;
    }

    table nat46 {
        key = {
        #ifndef _SUME_SWITCH_P4_
        hdr.ipv4.dst_addr: lpm;
        #else
        hdr.ipv4.dst_addr: exact;
        #endif
        }
        actions = {
            controller_debug;
            nat46_static;
            controller_debug_table_id;
            NoAction;
        }
    size = NAT64_TABLE_SIZE;
    #ifndef _SUME_SWITCH_P4_
    default_action = controller_debug_table_id(TABLE_NAT46);
    #else
    default_action = controller_debug;
    #endif

    }

#endif