Implement Egress TPID flow table test

Change-Id: I6a9aaef52f9400877420dabdcc65197a07e9850c
diff --git a/accton/accton_util.py b/accton/accton_util.py
index 29cb64d..5cdfad3 100755
--- a/accton/accton_util.py
+++ b/accton/accton_util.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -52,6 +51,9 @@
 MCAST_ROUTING_FLOW_TABLE=40
 BRIDGE_FLOW_TABLE=50
 ACL_FLOW_TABLE=60
+EGRESS_TPID_FLOW_TABLE = 235
+
+ONF_EXPERIMENTER_ID = 0x4F4E4600
 
 EGRESS_VLAN_FLOW_TABLE=210
 EGRESS_VLAN_1_FLOW_TABLE=211
@@ -2037,6 +2039,35 @@
     ctrl.message_send(request)
     return mpls_group_id, request
 
+def add_one_egress_vlan_tpid_table_flow(ctrl, of_port):
+    # Used for changing ethertype of outer vlan header to 0x88a8
+
+    match = ofp.match()
+    match.oxm_list.append(ofp.oxm.exp4ByteValue(ofp.oxm.OFDPA_EXP_TYPE_ACTSET_OUTPUT, of_port, ONF_EXPERIMENTER_ID))
+    match.oxm_list.append(ofp.oxm.vlan_vid_masked(ofp.OFPVID_PRESENT, ofp.OFPVID_PRESENT))
+
+    actions = []
+    actions.append(ofp.action.copy_field(
+        12, 0, 0, ['\x80\x00\x0c\x02', ofp.oxm.exp4ByteReg(oxm_field = 1).pack()])) # VLAN_VID, PACKET_REG(1)
+    actions.append(ofp.action.pop_vlan())
+    actions.append(ofp.action.push_vlan(0x88a8))
+    actions.append(ofp.action.copy_field(
+        12, 0, 0, [ofp.oxm.exp4ByteReg(oxm_field = 1).pack(), '\x80\x00\x0c\x02'])) # PACKET_REG(1), VLAN_VID
+
+    request = ofp.message.flow_add(
+        table_id=EGRESS_TPID_FLOW_TABLE,
+        cookie=42,
+        match=match,
+        instructions=[
+            ofp.instruction.apply_actions(
+                actions=actions
+            ),
+        ],
+        priority=0)
+
+    ctrl.message_send(request)
+
+    return
 
 """
 display
diff --git a/ofdpa/flows.py b/ofdpa/flows.py
index 28261d9..1ae6280 100755
--- a/ofdpa/flows.py
+++ b/ofdpa/flows.py
@@ -2952,3 +2952,98 @@
             delete_groups( self.controller, Groups )
             delete_all_groups( self.controller )
 
+class UntaggedToDoubleChangeEthertype ( base_tests.SimpleDataPlane ):
+
+    def runTest( self ):
+        Groups = Queue.LifoQueue()
+        try:
+            if len( config[ "port_map" ] ) < 2:
+                logging.info( "Port count less than 2, can't run this case" )
+                return
+
+            input_src_mac = [ 0x00, 0x00, 0x00, 0xcc, 0xcc, 0xcc ]
+            input_src_mac_str = ':'.join( [ '%02X' % x for x in input_src_mac ] )
+
+            input_dst_mac = [ 0x00, 0x00, 0x00, 0x22, 0x22, 0x00 ]
+            input_dst_mac_str = ':'.join( [ '%02X' % x for x in input_dst_mac ] )
+
+            output_dst_mac = [ 0x00, 0x00, 0x00, 0x33, 0x33, 0x00 ]
+            output_dst_mac_str = ':'.join( [ '%02X' % x for x in output_dst_mac ] )
+
+            dip = 0xc0a80001
+            ports = config[ "port_map" ].keys( )
+
+            inner_vlan = 66
+            outer_vlan = 77
+            id = 10
+
+            port = ports[0]
+            out_port = ports[1]
+
+            # add l2 unfiltered interface group
+            l2_gid, l2_msg = add_one_l2_unfiltered_group( self.controller, out_port, True)
+
+            l3_msg = add_l3_unicast_group( self.controller, out_port, vlanid=4094, id=id,
+                        src_mac=input_dst_mac, dst_mac=output_dst_mac, gid=l2_gid)
+
+            do_barrier( self.controller )
+
+            # add vlan flow table
+            add_one_vlan_table_flow( self.controller, port, 1, 4094,
+                    flag=VLAN_TABLE_FLAG_ONLY_BOTH )
+
+            # add termination flow
+            if config["switch_type"] == "qmx":
+                add_termination_flow( self.controller, 0, 0x0800, input_dst_mac, 4094 )
+            else:
+                add_termination_flow( self.controller, port, 0x0800, input_dst_mac, 4094 )
+
+            add_unicast_routing_flow( self.controller, 0x0800, dip, 0xffffffff, l3_msg.group_id,
+                    vrf=0 )
+
+            add_one_egress_vlan_table_flow( self.controller, out_port, 4094 , inner_vlan, outer_vlan)
+
+            Groups._put( l2_gid )
+            Groups._put( l3_msg.group_id )
+
+            # add vlan flow table
+            add_one_egress_vlan_tpid_table_flow( self.controller, out_port, outer_vlan+0x1000 )
+            do_barrier( self.controller )
+
+            ip_src = '192.168.5.5'
+            ip_dst = '192.168.0.1'
+            parsed_pkt = simple_tcp_packet( pktlen=100,
+                                            dl_vlan_enable=False,
+                                            eth_dst=input_dst_mac_str,
+                                            eth_src=input_src_mac_str,
+                                            ip_ttl=64,
+                                            ip_src=ip_src,
+                                            ip_dst=ip_dst )
+            pkt = str( parsed_pkt )
+
+            print("Input Packet %s" % format_packet(pkt))
+
+            self.dataplane.send( port, pkt )
+
+            # build expect packet
+            exp_pkt = simple_tcp_packet_two_vlan( pktlen=108,
+                                                  out_dl_vlan_enable=True,
+                                                  out_vlan_vid=outer_vlan,
+                                                  out_vlan_tpid=0x88a8,
+                                                  in_dl_vlan_enable=True,
+                                                  in_vlan_vid=inner_vlan,
+                                                  eth_dst=output_dst_mac_str,
+                                                  eth_src=input_dst_mac_str,
+                                                  ip_ttl=63,
+                                                  ip_src=ip_src,
+                                                  ip_dst=ip_dst )
+            pkt = str( exp_pkt )
+
+            print("Expected Packet %s" % format_packet(pkt))
+
+            verify_packet( self, pkt, out_port )
+            verify_no_other_packets( self )
+        finally:
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )
\ No newline at end of file
diff --git a/src/python/loxi/of13/action.py b/src/python/loxi/of13/action.py
index a7d6697..4875e74 100755
--- a/src/python/loxi/of13/action.py
+++ b/src/python/loxi/of13/action.py
@@ -2119,3 +2119,87 @@
         q.text('}')
 
 ofdpa.subtypes[OFDPA_ACT_OAM_SET_COUNTER_FIELDS] = ofdpa_oam_set_counter_field
+
+class copy_field(action):
+    type = 65535
+    experimenter = 0x4f4e4600  # ONF_EXPERIMENTER_ID
+    exp_type = 3200            # ONFTFP_ET_WRITE_COPYFIELD
+
+    def __init__(self, n_bits=None, src_offset=None, dst_offset=None, oxm_ids=None):
+        if n_bits != None:
+            self.n_bits = n_bits
+        else:
+            self.n_bits = 0
+        if src_offset != None:
+            self.src_offset = src_offset
+        else:
+            self.src_offset = 0
+        if dst_offset != None:
+            self.dst_offset = dst_offset
+        else:
+            self.dst_offset = 0
+        if oxm_ids != None:
+            self.oxm_ids = oxm_ids
+        else:
+            self.oxm_ids = [ ]
+        return
+
+    def pack( self ):
+        packed = [ ]
+        packed.append( struct.pack( "!H", self.type ) )
+        packed.append( struct.pack( "!H", 0 ) )  # placeholder for len at index 1
+        packed.append( struct.pack( "!L", self.experimenter ) )
+        packed.append( struct.pack( "!H", self.exp_type ) )
+        packed.append( '\x00' * 2 )
+        packed.append( struct.pack( "!H", self.n_bits ) )
+        packed.append( struct.pack( "!H", self.src_offset ) )
+        packed.append( struct.pack( "!H", self.dst_offset ) )
+        packed.append( '\x00' * 2 )
+        packed.append( "".join(self.oxm_ids ) )
+        length = sum( [ len( x ) for x in packed ] )
+        packed[ 1 ] = struct.pack( "!H", length )
+        return ''.join( packed )
+
+    @staticmethod
+    def unpack( reader ):
+        obj = copy_field()
+        _type = reader.read( "!H" )[ 0 ]
+        _len = reader.read( "!H" )[ 0 ]
+        orig_reader = reader
+        reader = orig_reader.slice( _len, 4 )
+        obj.n_bits = reader.read( "!H" )[ 0 ]
+        obj.src_offset = reader.read( "!H" )[ 0 ]
+        obj.dst_offset = reader.read( "!H" )[ 0 ]
+        reader.skip( 2 )
+        obj.oxm_ids = loxi.generic_util.unpack_list( reader, ofp.oxm.oxm.unpack )
+        return obj
+
+    def __eq__( self, other ):
+        if type( self ) != type( other ): return False
+        if self.n_bits != other.n_bits: return False
+        if self.src_offset != other.src_offset: return False
+        if self.dst_offset != other.dst_offset: return False
+        if self.oxm_ids != other.oxm_ids: return False
+        return True
+
+    def pretty_print( self, q ):
+        q.text( "copy_field {" )
+        with q.group():
+            with q.indent( 2 ):
+                q.breakable()
+                q.text( "n_bits = " )
+                q.text( "%#x" % self.n_bits )
+                q.text( "," )
+                q.breakable()
+                q.text( "src_offset = " )
+                q.text( "%#x" % self.src_offset )
+                q.text( "," )
+                q.breakable()
+                q.text( "dst_offset = " )
+                q.text( "%#x" % self.dst_offset )
+                q.text( "," )
+                q.breakable()
+                q.text( "oxm_ids = " )
+                q.pp( self.oxm_ids )
+            q.breakable()
+        q.text( '}' )
diff --git a/src/python/loxi/of13/oxm.py b/src/python/loxi/of13/oxm.py
index fa62add..7541ecf 100755
--- a/src/python/loxi/of13/oxm.py
+++ b/src/python/loxi/of13/oxm.py
@@ -6271,10 +6271,11 @@
 class exp4ByteValue(oxm):
     type_len = 0xffff0008
 
-    def __init__(self, exp_type=0, value=None):
+    def __init__(self, exp_type=0, value=None, experimenter=OFDPA_EXPERIMETER):
         if value != None:
             self.value = value
             self.exp_type=exp_type
+            self.experimenter = experimenter
         else:
             self.value = 0
         return
@@ -6282,7 +6283,7 @@
     def pack(self):
         packed = []
         packed.append(struct.pack("!L", self.type_len | (self.exp_type <<9)))
-        packed.append(struct.pack("!L", OFDPA_EXPERIMETER))
+        packed.append(struct.pack("!L", self.experimenter))
         packed.append(struct.pack("!L", self.value))
         return ''.join(packed)
 
@@ -6314,3 +6315,14 @@
         q.text('}')
 
 oxm.subtypes[0xffff000a] = exp4ByteValue
+
+class exp4ByteReg( oxm ):
+    type_len = 0x80010000
+
+    def __init__( self, oxm_field=0 ):
+        self.oxm_field = oxm_field
+
+    def pack( self ):
+        packed = [ ]
+        packed.append( struct.pack( "!L", self.type_len | (self.oxm_field << 9) ) )
+        return ''.join( packed )
diff --git a/src/python/oftest/testutils.py b/src/python/oftest/testutils.py
index 57fe0b6..abf4a27 100755
--- a/src/python/oftest/testutils.py
+++ b/src/python/oftest/testutils.py
@@ -304,6 +304,7 @@
                       in_dl_vlan_enable=False,
                       out_vlan_vid=0,
                       out_vlan_pcp=0,
+                      out_vlan_tpid=0x8100,
                       out_dl_vlan_cfi=0,
                       in_vlan_vid=0,
                       in_vlan_pcp=0,
@@ -346,7 +347,7 @@
 
     # Note Dot1Q.id is really CFI
     if (out_dl_vlan_enable and in_dl_vlan_enable):
-        pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src, type=out_vlan_tpid)/ \
             scapy.Dot1Q(prio=out_vlan_pcp, id=out_dl_vlan_cfi, vlan=out_vlan_vid)
 
         if in_dl_vlan_enable: