/* -*- P4_16 -*- */
#include <core.p4>
#include <v1model.p4>

#define THE_ANSWER_TO_LIFE_THE_UNIVERSE_AND_EVERYTHING 42   /* Important constant          */
#define ROUTING_TABLE_SIZE 64                               /* maximum routes per protocol */
#define ADDRESS_TABLE_SIZE 64                               /* maximum number of addresses per protocol */
#define NDP_TABLE_SIZE 64                                   /* maximum number of multicast groups */

typedef bit<48>  mac_addr_t;
typedef bit<32>  ipv4_addr_t;
typedef bit<128> ipv6_addr_t;
typedef bit<9>   port_t;
typedef bit<16>  mcast_t;
typedef bit<16>  task_t;


const bit<16> TYPE_IPV4  = 0x0800;
const bit<16> TYPE_IPV6  = 0x86DD;
const bit<16> TYPE_CPU   = 0x4242;
const bit<16> TYPE_DEBUG = 0x2323;


const bit<8>  PROTO_ICMP = 1;
const bit<8>  PROTO_TCP  = 6;
const bit<8>  PROTO_UDP  = 17;
const bit<8>  PROTO_ICMP6  = 58;

const bit<8>  TCP_SEQ_LEN = 4;

const bit<8>  ICMP6_ECHO_REQUEST = 128;
const bit<8>  ICMP6_ECHO_REPLY   = 129;
const bit<8>  ICMP6_NS           = 135;
const bit<8>  ICMP6_NA           = 136;

const task_t TASK_ICMP6_NS       = 1;
const task_t TASK_ICMP6_GENERAL  = 2;
const task_t TASK_DEBUG          = 3;
const task_t TASK_ICMP6_REPLY    = 4;


header ethernet_t {
    mac_addr_t dst_addr;
    mac_addr_t src_addr;
    bit<16>    ethertype;
}

header ipv4_t {
    bit<4>      version;
    bit<4>      ihl;
    bit<6>      diff_serv;
    bit<2>      ecn;
    bit<16>     totalLen;
    bit<16>     identification;
    bit<3>      flags;
    bit<13>     fragOffset;
    bit<8>      ttl;
    bit<8>      protocol;
    bit<16>     hdrChecksum;
    ipv4_addr_t src_addr;
    ipv4_addr_t dst_addr;
}

/* https://en.wikipedia.org/wiki/IPv6_packet */
header ipv6_t {
    bit<4>      version;
    bit<8>      traffic_class;
    bit<20>     flow_label;
    bit<16>     payload_length;
    bit<8>      next_header;
    bit<8>      hop_limit;
    ipv6_addr_t src_addr;
    ipv6_addr_t dst_addr;
}

header tcp_t{
    bit<16> src_port;
    bit<16> dst_port;
    int<32> seqNo;
    int<32> ackNo;
    bit<4>  data_offset;
    bit<4>  res;
    bit<1>  cwr;
    bit<1>  ece;
    bit<1>  urg;
    bit<1>  ack;
    bit<1>  psh;
    bit<1>  rst;
    bit<1>  syn;
    bit<1>  fin;
    bit<16> window;
    bit<16> checksum;
    bit<16> urgentPtr;
}

header udp_t {
    bit<16> src_port;
    bit<16> dst_port;
    bit<16> payload_length;
    bit<16> checksum;
}

header icmp6_t {
    bit<8>  type;
    bit<8>  code;
    bit<16> checksum;
}

header icmp_t {
    bit<8>  type;
    bit<8>  code;
    bit<16> checksum;
    bit<32> rest;
}

header cpu_t {
    task_t task;
    bit<16> ingress_port;
    bit<16> ethertype;
}

struct headers {
    ethernet_t       ethernet;
    ipv4_t	         ipv4;
    ipv6_t	         ipv6;
    tcp_t	         tcp;
    udp_t	         udp;
    icmp6_t          icmp6;
    icmp_t           icmp;
    cpu_t            cpu;
}

struct metadata {
    bit<16> length_without_ip_header;
    bool do_cksum;
}

parser MyParser(packet_in packet,
                out       headers hdr,
                inout     metadata meta,
                inout     standard_metadata_t standard_metadata) {

    state start {
        packet.extract(hdr.ethernet);
        transition select(hdr.ethernet.ethertype){
            TYPE_IPV4: ipv4;
            TYPE_IPV6: ipv6;
            default: accept;
        }
    }

    state ipv4 {
        packet.extract(hdr.ipv4);
		meta.length_without_ip_header = hdr.ipv4.totalLen - 16w20;

        transition select(hdr.ipv4.protocol){
            PROTO_TCP: tcp;
            PROTO_UDP: udp;
            PROTO_ICMP: icmp;

            default: accept;
        }
    }

    state ipv6 {
        packet.extract(hdr.ipv6);
		meta.length_without_ip_header = hdr.ipv6.payload_length;

        transition select(hdr.ipv6.next_header){
            PROTO_TCP: tcp;
            PROTO_UDP: udp;
            PROTO_ICMP6: icmp6;
            default: accept;
        }
    }

    /* Leaf */
    state tcp {
       packet.extract(hdr.tcp);
       transition accept;
    }

    state udp {
       packet.extract(hdr.udp);
       transition accept;
    }

    state icmp6 {
       packet.extract(hdr.icmp6);
       transition accept;
    }

    state icmp {
       packet.extract(hdr.icmp);
       transition accept;
    }

}

control MyDeparser(packet_out packet, in headers hdr) {
    apply {
        /* always */
        packet.emit(hdr.ethernet);

        /* only if information is sent to the controller */
        packet.emit(hdr.cpu);

        /* either */
        packet.emit(hdr.ipv4);
        packet.emit(hdr.ipv6);

        /* either */
        packet.emit(hdr.tcp);
        packet.emit(hdr.udp);
        packet.emit(hdr.icmp);
        packet.emit(hdr.icmp6);
    }
}

control MyIngress(inout headers hdr,
    inout metadata meta,
    inout standard_metadata_t standard_metadata) {

    action icmp6_echo_reply() {
        meta.do_cksum = true;
    }

    table v6_addresses {
        key = {
            hdr.ipv6.dst_addr: exact;
        }
        actions = {
            icmp6_echo_reply;
            NoAction;
        }
        size = ADDRESS_TABLE_SIZE;
        default_action = NoAction;
    }

    apply {
        if(hdr.ipv6.isValid()) {
            v6_addresses.apply();
        }
    }
}

control MyEgress(inout headers hdr,
                 inout metadata meta,
                 inout standard_metadata_t standard_metadata) {
	apply {
	}
}

control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
    apply {}
}

control MyComputeChecksum(inout headers  hdr, inout metadata meta) {
    apply {
        update_checksum_with_payload(meta.do_cksum,
		 	{
                hdr.ipv6.src_addr,         /* 128 */
		 		hdr.ipv6.dst_addr,         /* 128 */
                24w0,                      /* 24 0's */
                PROTO_ICMP6                /* 8 */
            },
            hdr.icmp6.checksum,
            HashAlgorithm.csum16
        );
    }
}

V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;
