/*
 * Copyright 2019-present GEANT RARE project
 *
 * 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.
 */

#ifndef _IG_CTL_BRIDGE_P4_
#define _IG_CTL_BRIDGE_P4_

control IngressControlBridge(inout headers hdr,
                             inout ingress_metadata_t ig_md,
                             inout standard_metadata_t ig_intr_md) {

    direct_counter(CounterType.packets_and_bytes) statsRx;

    direct_counter(CounterType.packets_and_bytes) statsTx;

    action send_to_cpu() {
        ig_md.nexthop_id = CPU_PORT;
        // Packets sent to the controller needs to be prepended with the
        // packet-in header. By setting it valid we make sure it will be
        // deparsed on the wire (see c_deparser).
    }


    action act_set_bridge_port() {
        ig_md.bridge_src = 1;
    }

    action act_bridge_miss() {
        ig_md.bridge_src = 0;
    }

    table tbl_bridge_learn {
        key = {
ig_md.bridge_id:
            exact;
hdr.ethernet.src_mac_addr:
            exact;
        }
        actions = {
            act_set_bridge_port;
            act_bridge_miss;
        }
        size = MAC_TABLE_SIZE;
        default_action = act_bridge_miss();
        counters = statsRx;
    }


    action act_set_bridge_out(SubIntId_t port) {
        ig_md.bridge_trg = port;
        ig_md.target_id = port;
    }

    action act_set_bridge_ppprouted(NextHopId_t nexthop) {
        ig_md.bridge_trg = MAX_PORT;
        ig_md.vrf = 0;
        ig_md.nsh_valid = 0;
        ig_md.polka_valid = 0;
        ig_md.mpls0_valid = 0;
        ig_md.mpls1_valid = 0;
        ig_md.arp_valid = 0;
        ig_md.llc_valid = 0;
        ig_md.ipv4_valid = 0;
        ig_md.ipv6_valid = 0;
        hdr.pppbr.setValid();
        hdr.pppbr.flags = 1;
        hdr.eth4.setValid();
        hdr.eth4.dst_mac_addr = hdr.ethernet.dst_mac_addr;
        hdr.eth4.src_mac_addr = hdr.ethernet.src_mac_addr;
        hdr.eth4.ethertype = ig_md.ethertype;
        ig_md.nexthop_id = nexthop;
        ig_md.ethertype = ETHERTYPE_ROUTEDMAC;
        ig_md.vlan_size = ig_md.vlan_size - 16;
    }

    action act_set_bridge_routed(NextHopId_t nexthop) {
        ig_md.bridge_trg = MAX_PORT;
        ig_md.vrf = 0;
        ig_md.nsh_valid = 0;
        ig_md.polka_valid = 0;
        ig_md.mpls0_valid = 0;
        ig_md.mpls1_valid = 0;
        ig_md.arp_valid = 0;
        ig_md.llc_valid = 0;
        ig_md.ipv4_valid = 0;
        ig_md.ipv6_valid = 0;
        hdr.eth4.setValid();
        hdr.eth4.dst_mac_addr = hdr.ethernet.dst_mac_addr;
        hdr.eth4.src_mac_addr = hdr.ethernet.src_mac_addr;
        hdr.eth4.ethertype = ig_md.ethertype;
        ig_md.nexthop_id = nexthop;
        ig_md.ethertype = ETHERTYPE_ROUTEDMAC;
        ig_md.vlan_size = ig_md.vlan_size - 14;
    }

    action act_set_bridge_vpls(NextHopId_t port, label_t lab_tun, label_t lab_svc) {
        ig_md.bridge_trg = MAX_PORT;
        ig_md.vrf = 0;
        ig_md.nsh_valid = 0;
        ig_md.polka_valid = 0;
        ig_md.mpls0_valid = 0;
        ig_md.mpls1_valid = 0;
        ig_md.nsh_remove = 0;
        ig_md.polka_valid = 0;
        ig_md.mpls0_remove = 0;
        ig_md.mpls1_remove = 0;
        ig_md.arp_valid = 0;
        ig_md.llc_valid = 0;
        ig_md.ipv4_valid = 0;
        ig_md.ipv6_valid = 0;
        hdr.eth2.setValid();
        hdr.eth2.dst_mac_addr = hdr.ethernet.dst_mac_addr;
        hdr.eth2.src_mac_addr = hdr.ethernet.src_mac_addr;
        hdr.eth2.ethertype = ig_md.ethertype;
        ig_md.nexthop_id = port;
        ig_md.ethertype = ETHERTYPE_MPLS_UCAST;
        hdr.mpls0.setValid();
        hdr.mpls0.label = lab_tun;
        hdr.mpls0.ttl = 255;
        hdr.mpls0.bos = 0;
        hdr.mpls1.setValid();
        hdr.mpls1.label = lab_svc;
        hdr.mpls1.ttl = 255;
        hdr.mpls1.bos = 1;
        ig_md.mpls_op_type = 3;
    }

    action act_set_bridge_srv(NextHopId_t port, ipv6_addr_t target) {
        ig_md.bridge_trg = MAX_PORT;
        ig_md.vrf = 0;
        ig_md.nsh_valid = 0;
        ig_md.polka_valid = 0;
        ig_md.mpls0_valid = 0;
        ig_md.mpls1_valid = 0;
        ig_md.arp_valid = 0;
        ig_md.llc_valid = 0;
        ig_md.ipv4_valid = 0;
        ig_md.ipv6_valid = 0;
        hdr.vlan.setInvalid();
        hdr.vlanq.setInvalid();
        hdr.eth2.setValid();
        hdr.eth2 = hdr.ethernet;
        hdr.eth2.ethertype = ig_md.ethertype;
        ig_md.nexthop_id = port;
        ig_md.ethertype = ETHERTYPE_IPV6;
        hdr.ipv6c.setValid();
        hdr.ipv6c.version = 6;
        hdr.ipv6c.payload_len = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size;
        hdr.ipv6c.next_hdr = IP_PROTOCOL_SRL2;
        hdr.ipv6c.hop_limit = 255;
        hdr.ipv6c.src_addr = target;
        hdr.ipv6c.dst_addr = target;
    }




    action act_set_bridge_vxlan4(NextHopId_t nexthop, ipv4_addr_t dst_ip_addr, ipv4_addr_t src_ip_addr, bit<24> instance) {
        ig_md.bridge_trg = MAX_PORT;
        ig_md.vrf = 0;
        ig_md.nsh_valid = 0;
        ig_md.polka_valid = 0;
        ig_md.mpls0_valid = 0;
        ig_md.mpls1_valid = 0;
        ig_md.arp_valid = 0;
        ig_md.llc_valid = 0;
        ig_md.ipv4_valid = 0;
        ig_md.ipv6_valid = 0;
        hdr.vlan.setInvalid();
        hdr.vlanq.setInvalid();
        hdr.eth2.setValid();
        hdr.eth2 = hdr.ethernet;
        hdr.eth2.ethertype = ig_md.ethertype;
        ig_md.nexthop_id = nexthop;

        hdr.vxlan2.setValid();
        hdr.vxlan2.flags = 0x800;
        hdr.vxlan2.policy = 0;
        hdr.vxlan2.instance = instance;
        hdr.vxlan2.reserved = 0;

        hdr.udp2.setValid();
        hdr.udp2.src_port = 4789;
        hdr.udp2.dst_port = 4789;
        hdr.udp2.length = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size - 14 + 30;
        hdr.udp2.checksum = 0;

        hdr.ipv4d.setValid();
        hdr.ipv4d.version = 4;
        hdr.ipv4d.ihl = 5;
        hdr.ipv4d.diffserv = 0;
        hdr.ipv4d.total_len = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size - 14 + 50;
        hdr.ipv4d.identification = 0;
        hdr.ipv4d.flags = 0;
        hdr.ipv4d.frag_offset = 0;
        hdr.ipv4d.ttl = 255;
        hdr.ipv4d.protocol = IP_PROTOCOL_UDP;
        hdr.ipv4d.hdr_checksum = 0;
        hdr.ipv4d.src_addr = src_ip_addr;
        hdr.ipv4d.dst_addr = dst_ip_addr;
        ig_md.ethertype = ETHERTYPE_IPV4;
    }




    action act_set_bridge_vxlan6(NextHopId_t nexthop, ipv6_addr_t dst_ip_addr, ipv6_addr_t src_ip_addr, bit<24> instance) {
        ig_md.bridge_trg = MAX_PORT;
        ig_md.vrf = 0;
        ig_md.nsh_valid = 0;
        ig_md.polka_valid = 0;
        ig_md.mpls0_valid = 0;
        ig_md.mpls1_valid = 0;
        ig_md.arp_valid = 0;
        ig_md.llc_valid = 0;
        ig_md.ipv4_valid = 0;
        ig_md.ipv6_valid = 0;
        hdr.vlan.setInvalid();
        hdr.vlanq.setInvalid();
        hdr.eth2.setValid();
        hdr.eth2 = hdr.ethernet;
        hdr.eth2.ethertype = ig_md.ethertype;
        ig_md.nexthop_id = nexthop;

        hdr.vxlan2.setValid();
        hdr.vxlan2.flags = 0x800;
        hdr.vxlan2.policy = 0;
        hdr.vxlan2.instance = instance;
        hdr.vxlan2.reserved = 0;

        hdr.udp2.setValid();
        hdr.udp2.src_port = 4789;
        hdr.udp2.dst_port = 4789;
        hdr.udp2.length = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size - 14 + 30;
        hdr.udp2.checksum = 0;

        hdr.ipv6d.setValid();
        hdr.ipv6d.version = 6;
        hdr.ipv6d.traffic_class = 0;
        hdr.ipv6d.flow_label = 0;
        hdr.ipv6d.payload_len = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size - 14 + 30;
        hdr.ipv6d.next_hdr = IP_PROTOCOL_UDP;
        hdr.ipv6d.hop_limit = 255;
        hdr.ipv6d.src_addr = src_ip_addr;
        hdr.ipv6d.dst_addr = dst_ip_addr;
        ig_md.ethertype = ETHERTYPE_IPV6;
    }




    action act_set_bridge_etherip4(NextHopId_t nexthop, ipv4_addr_t dst_ip_addr, ipv4_addr_t src_ip_addr) {
        ig_md.bridge_trg = MAX_PORT;
        ig_md.vrf = 0;
        ig_md.nsh_valid = 0;
        ig_md.polka_valid = 0;
        ig_md.mpls0_valid = 0;
        ig_md.mpls1_valid = 0;
        ig_md.arp_valid = 0;
        ig_md.llc_valid = 0;
        ig_md.ipv4_valid = 0;
        ig_md.ipv6_valid = 0;
        hdr.vlan.setInvalid();
        hdr.vlanq.setInvalid();
        hdr.eth2.setValid();
        hdr.eth2 = hdr.ethernet;
        hdr.eth2.ethertype = ig_md.ethertype;
        ig_md.nexthop_id = nexthop;

        hdr.etherip2.setValid();
        hdr.etherip2.version = 0x300;

        hdr.ipv4d.setValid();
        hdr.ipv4d.version = 4;
        hdr.ipv4d.ihl = 5;
        hdr.ipv4d.diffserv = 0;
        hdr.ipv4d.total_len = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size - 14 + 36;
        hdr.ipv4d.identification = 0;
        hdr.ipv4d.flags = 0;
        hdr.ipv4d.frag_offset = 0;
        hdr.ipv4d.ttl = 255;
        hdr.ipv4d.protocol = IP_PROTOCOL_ETHERIP;
        hdr.ipv4d.hdr_checksum = 0;
        hdr.ipv4d.src_addr = src_ip_addr;
        hdr.ipv4d.dst_addr = dst_ip_addr;
        ig_md.ethertype = ETHERTYPE_IPV4;
    }




    action act_set_bridge_etherip6(NextHopId_t nexthop, ipv6_addr_t dst_ip_addr, ipv6_addr_t src_ip_addr) {
        ig_md.bridge_trg = MAX_PORT;
        ig_md.vrf = 0;
        ig_md.nsh_valid = 0;
        ig_md.polka_valid = 0;
        ig_md.mpls0_valid = 0;
        ig_md.mpls1_valid = 0;
        ig_md.arp_valid = 0;
        ig_md.llc_valid = 0;
        ig_md.ipv4_valid = 0;
        ig_md.ipv6_valid = 0;
        hdr.vlan.setInvalid();
        hdr.vlanq.setInvalid();
        hdr.eth2.setValid();
        hdr.eth2 = hdr.ethernet;
        hdr.eth2.ethertype = ig_md.ethertype;
        ig_md.nexthop_id = nexthop;

        hdr.etherip2.setValid();
        hdr.etherip2.version = 0x300;

        hdr.ipv6d.setValid();
        hdr.ipv6d.version = 6;
        hdr.ipv6d.traffic_class = 0;
        hdr.ipv6d.flow_label = 0;
        hdr.ipv6d.payload_len = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size - 14 + 16;
        hdr.ipv6d.next_hdr = IP_PROTOCOL_ETHERIP;
        hdr.ipv6d.hop_limit = 255;
        hdr.ipv6d.src_addr = src_ip_addr;
        hdr.ipv6d.dst_addr = dst_ip_addr;
        ig_md.ethertype = ETHERTYPE_IPV6;
    }




    action act_set_bridge_eoip4(NextHopId_t nexthop, ipv4_addr_t dst_ip_addr, ipv4_addr_t src_ip_addr, bit<16> instance) {
        ig_md.bridge_trg = MAX_PORT;
        ig_md.vrf = 0;
        ig_md.nsh_valid = 0;
        ig_md.polka_valid = 0;
        ig_md.mpls0_valid = 0;
        ig_md.mpls1_valid = 0;
        ig_md.arp_valid = 0;
        ig_md.llc_valid = 0;
        ig_md.ipv4_valid = 0;
        ig_md.ipv6_valid = 0;
        hdr.vlan.setInvalid();
        hdr.vlanq.setInvalid();
        hdr.eth2.setValid();
        hdr.eth2 = hdr.ethernet;
        hdr.eth2.ethertype = ig_md.ethertype;
        ig_md.nexthop_id = nexthop;

        hdr.gre2.setValid();
        hdr.gre2.flags = 0x2001;
        hdr.gre2.gretyp = 0x6400;
        hdr.eoip2.setValid();
        hdr.eoip2.length = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size - 14 + 14;
        hdr.eoip2.tunid = instance;

        hdr.ipv4d.setValid();
        hdr.ipv4d.version = 4;
        hdr.ipv4d.ihl = 5;
        hdr.ipv4d.diffserv = 0;
        hdr.ipv4d.total_len = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size - 14 + 42;
        hdr.ipv4d.identification = 0;
        hdr.ipv4d.flags = 0;
        hdr.ipv4d.frag_offset = 0;
        hdr.ipv4d.ttl = 255;
        hdr.ipv4d.protocol = IP_PROTOCOL_GRE;
        hdr.ipv4d.hdr_checksum = 0;
        hdr.ipv4d.src_addr = src_ip_addr;
        hdr.ipv4d.dst_addr = dst_ip_addr;
        ig_md.ethertype = ETHERTYPE_IPV4;
    }




    action act_set_bridge_eoip6(NextHopId_t nexthop, ipv6_addr_t dst_ip_addr, ipv6_addr_t src_ip_addr, bit<16> instance) {
        ig_md.bridge_trg = MAX_PORT;
        ig_md.vrf = 0;
        ig_md.nsh_valid = 0;
        ig_md.polka_valid = 0;
        ig_md.mpls0_valid = 0;
        ig_md.mpls1_valid = 0;
        ig_md.arp_valid = 0;
        ig_md.llc_valid = 0;
        ig_md.ipv4_valid = 0;
        ig_md.ipv6_valid = 0;
        hdr.vlan.setInvalid();
        hdr.vlanq.setInvalid();
        hdr.eth2.setValid();
        hdr.eth2 = hdr.ethernet;
        hdr.eth2.ethertype = ig_md.ethertype;
        ig_md.nexthop_id = nexthop;

        hdr.gre2.setValid();
        hdr.gre2.flags = 0x2001;
        hdr.gre2.gretyp = 0x6400;
        hdr.eoip2.setValid();
        hdr.eoip2.length = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size - 14 + 14;
        hdr.eoip2.tunid = instance;

        hdr.ipv6d.setValid();
        hdr.ipv6d.version = 6;
        hdr.ipv6d.traffic_class = 0;
        hdr.ipv6d.flow_label = 0;
        hdr.ipv6d.payload_len = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size - 14 + 22;
        hdr.ipv6d.next_hdr = IP_PROTOCOL_GRE;
        hdr.ipv6d.hop_limit = 255;
        hdr.ipv6d.src_addr = src_ip_addr;
        hdr.ipv6d.dst_addr = dst_ip_addr;
        ig_md.ethertype = ETHERTYPE_IPV6;
    }





    action act_set_bridge_pckoudp4(NextHopId_t nexthop, ipv4_addr_t dst_ip_addr, ipv4_addr_t src_ip_addr, bit<16> src_port, bit<16> dst_port) {
        ig_md.bridge_trg = MAX_PORT;
        ig_md.vrf = 0;
        ig_md.nsh_valid = 0;
        ig_md.polka_valid = 0;
        ig_md.mpls0_valid = 0;
        ig_md.mpls1_valid = 0;
        ig_md.arp_valid = 0;
        ig_md.llc_valid = 0;
        ig_md.ipv4_valid = 0;
        ig_md.ipv6_valid = 0;
        hdr.vlan.setInvalid();
        hdr.vlanq.setInvalid();
        hdr.eth2.setValid();
        hdr.eth2 = hdr.ethernet;
        hdr.eth2.ethertype = ig_md.ethertype;
        ig_md.nexthop_id = nexthop;

        hdr.udp2.setValid();
        hdr.udp2.src_port = src_port;
        hdr.udp2.dst_port = dst_port;
        hdr.udp2.length = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size - 14 + 22;
        hdr.udp2.checksum = 0;

        hdr.ipv4d.setValid();
        hdr.ipv4d.version = 4;
        hdr.ipv4d.ihl = 5;
        hdr.ipv4d.diffserv = 0;
        hdr.ipv4d.total_len = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size - 14 + 42;
        hdr.ipv4d.identification = 0;
        hdr.ipv4d.flags = 0;
        hdr.ipv4d.frag_offset = 0;
        hdr.ipv4d.ttl = 255;
        hdr.ipv4d.protocol = IP_PROTOCOL_UDP;
        hdr.ipv4d.hdr_checksum = 0;
        hdr.ipv4d.src_addr = src_ip_addr;
        hdr.ipv4d.dst_addr = dst_ip_addr;
        ig_md.ethertype = ETHERTYPE_IPV4;
    }




    action act_set_bridge_pckoudp6(NextHopId_t nexthop, ipv6_addr_t dst_ip_addr, ipv6_addr_t src_ip_addr, bit<16> src_port, bit<16> dst_port) {
        ig_md.bridge_trg = MAX_PORT;
        ig_md.vrf = 0;
        ig_md.nsh_valid = 0;
        ig_md.polka_valid = 0;
        ig_md.mpls0_valid = 0;
        ig_md.mpls1_valid = 0;
        ig_md.arp_valid = 0;
        ig_md.llc_valid = 0;
        ig_md.ipv4_valid = 0;
        ig_md.ipv6_valid = 0;
        hdr.vlan.setInvalid();
        hdr.vlanq.setInvalid();
        hdr.eth2.setValid();
        hdr.eth2 = hdr.ethernet;
        hdr.eth2.ethertype = ig_md.ethertype;
        ig_md.nexthop_id = nexthop;

        hdr.udp2.setValid();
        hdr.udp2.src_port = src_port;
        hdr.udp2.dst_port = dst_port;
        hdr.udp2.length = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size - 14 + 22;
        hdr.udp2.checksum = 0;

        hdr.ipv6d.setValid();
        hdr.ipv6d.version = 6;
        hdr.ipv6d.traffic_class = 0;
        hdr.ipv6d.flow_label = 0;
        hdr.ipv6d.payload_len = (bit<16>)ig_intr_md.packet_length - ig_md.vlan_size - 14 + 22;
        hdr.ipv6d.next_hdr = IP_PROTOCOL_UDP;
        hdr.ipv6d.hop_limit = 255;
        hdr.ipv6d.src_addr = src_ip_addr;
        hdr.ipv6d.dst_addr = dst_ip_addr;
        ig_md.ethertype = ETHERTYPE_IPV6;
    }







    action act_bridge_punt() {
        ig_md.bridge_trg = 0;
    }

    table tbl_bridge_target {
        key = {
ig_md.bridge_id:
            exact;
hdr.ethernet.dst_mac_addr:
            exact;
        }
        actions = {
            act_set_bridge_out;
            act_set_bridge_routed;
            act_set_bridge_ppprouted;
            act_set_bridge_vpls;
            act_set_bridge_srv;
            act_set_bridge_vxlan4;
            act_set_bridge_vxlan6;
            act_set_bridge_etherip4;
            act_set_bridge_etherip6;
            act_set_bridge_eoip4;
            act_set_bridge_eoip6;
            act_set_bridge_pckoudp4;
            act_set_bridge_pckoudp6;
            act_bridge_punt;
        }
        size = MAC_TABLE_SIZE;
        default_action = act_bridge_punt();
        counters = statsTx;
    }


    apply {
        if (ig_md.bridge_id == 0) {
            return;
        }
        ig_md.vrf = 0;
        ig_md.nsh_valid = 0;
        ig_md.polka_valid = 0;
        ig_md.mpls0_valid = 0;
        ig_md.mpls1_valid = 0;
        ig_md.arp_valid = 0;
        ig_md.llc_valid = 0;
        ig_md.ipv4_valid = 0;
        ig_md.ipv6_valid = 0;
        if (hdr.eth6.isValid()) {
            hdr.ethernet.dst_mac_addr = hdr.eth6.dst_mac_addr;
            hdr.ethernet.src_mac_addr = hdr.eth6.src_mac_addr;
        }
        if (tbl_bridge_learn.apply().hit) tbl_bridge_target.apply();
        if ((ig_md.bridge_src == 0) || (ig_md.bridge_trg == 0)) {
            send_to_cpu();
            return;
        }
        if (hdr.eth6.isValid()) {
            ig_md.ethertype = hdr.eth6.ethertype;
            hdr.eth6.setInvalid();
            hdr.eth5.setInvalid();
        }
        if (hdr.mpls1.isValid() && (ig_md.mpls_op_type != 3)) {
            hdr.eth2.setInvalid();
            hdr.mpls1.setInvalid();
            hdr.mpls0.setInvalid();
            hdr.vlan.setInvalid();
            hdr.vlanq.setInvalid();
            ig_md.mpls0_remove = 0;
            ig_md.mpls1_remove = 0;
        }
    }
}

#endif // _IG_CTL_BRIDGE_P4_

