Integration with Nathan for Tibit packet in (raw)

Change-Id: I0fb9a2b020eae9b45da7db4bc68a6ed98835e5db
diff --git a/cli/main.py b/cli/main.py
index d684f85..3f2dd7a 100755
--- a/cli/main.py
+++ b/cli/main.py
@@ -50,10 +50,10 @@
 )
 
 banner = """\
-         _ _   _              _ _
-__ _____| | |_| |_  __ _   __| (_)
-\ V / _ \ |  _| ' \/ _` | / _| | |
- \_/\___/_|\__|_||_\__,_| \__|_|_|
+         _ _   _            ___ _    ___
+__ _____| | |_| |_  __ _   / __| |  |_ _|
+\ V / _ \ |  _| ' \/ _` | | (__| |__ | |
+ \_/\___/_|\__|_||_\__,_|  \___|____|___|
 (to exit type quit or hit Ctrl-D)
 """
 
diff --git a/tests/utests/common/utils/test_bpf.py b/tests/utests/common/utils/test_bpf.py
new file mode 100644
index 0000000..9bbcb76
--- /dev/null
+++ b/tests/utests/common/utils/test_bpf.py
@@ -0,0 +1,35 @@
+from unittest import TestCase, main
+
+from scapy.layers.l2 import Ether, Dot1Q
+
+from common.frameio.frameio import BpfProgramFilter
+
+
+class TestBpf(TestCase):
+
+    def test_bpf1(self):
+        vid = 4090
+        pcp = 7
+        frame_match = 'ether[14:2] = 0x{:01x}{:03x}'.format(pcp << 1, vid)
+        filter = BpfProgramFilter(frame_match)
+        self.assertTrue(filter(str(Ether()/Dot1Q(prio=pcp, vlan=vid))))
+        self.assertFalse(filter(str(Ether()/Dot1Q(prio=pcp, vlan=4000))))
+
+    def test_bpf2(self):
+        vid1 = 4090
+        pcp1 = 7
+        frame_match_case1 = 'ether[14:2] = 0x{:01x}{:03x}'.format(
+            pcp1 << 1, vid1)
+
+        vid2 = 4000
+        frame_match_case2 = '(ether[14:2] & 0xfff) = 0x{:03x}'.format(vid2)
+
+        filter = BpfProgramFilter('{} or {}'.format(
+            frame_match_case1, frame_match_case2))
+        self.assertTrue(filter(str(Ether()/Dot1Q(prio=pcp1, vlan=vid1))))
+        self.assertTrue(filter(str(Ether()/Dot1Q(vlan=vid2))))
+        self.assertFalse(filter(str(Ether()/Dot1Q(vlan=4001))))
+
+
+if __name__ == '__main__':
+    main()
diff --git a/voltha/adapters/tibit_olt/tibit_olt.py b/voltha/adapters/tibit_olt/tibit_olt.py
index b2b3f5b..0d260c3 100644
--- a/voltha/adapters/tibit_olt/tibit_olt.py
+++ b/voltha/adapters/tibit_olt/tibit_olt.py
@@ -28,13 +28,13 @@
 from twisted.internet.defer import DeferredQueue, inlineCallbacks
 from zope.interface import implementer
 
-from common.frameio.frameio import BpfProgramFilter
+from common.frameio.frameio import BpfProgramFilter, hexify
 from voltha.adapters.interface import IAdapterInterface
 from voltha.extensions.eoam.EOAM import EOAMPayload, DPoEOpcode_SetRequest
 from voltha.extensions.eoam.EOAM_TLV import DOLTObject, \
     PortIngressRuleClauseMatchLength02, PortIngressRuleResultForward, \
     PortIngressRuleResultSet, PortIngressRuleResultInsert, \
-    PortIngressRuleTerminator, AddPortIngressRule, CablelabsOUI
+    PortIngressRuleTerminator, AddPortIngressRule, CablelabsOUI, PonPortObject
 from voltha.extensions.eoam.EOAM_TLV import PortIngressRuleHeader
 from voltha.extensions.eoam.EOAM_TLV import ClauseSubtypeEnum as Clause
 from voltha.core.flow_decomposer import *
@@ -53,10 +53,18 @@
 log = structlog.get_logger()
 
 # Match on the MGMT VLAN, Priority 7
-TIBIT_MGMT_VLAN=4090
-TIBIT_MGMT_PRIORITY=7
-frame_match = 'ether[14:2] = 0x{:01x}{:03x}'.format(TIBIT_MGMT_PRIORITY << 1, TIBIT_MGMT_VLAN)
-is_tibit_frame = BpfProgramFilter(frame_match)
+TIBIT_MGMT_VLAN = 4090
+TIBIT_MGMT_PRIORITY = 7
+frame_match_case1 = 'ether[14:2] = 0x{:01x}{:03x}'.format(
+    TIBIT_MGMT_PRIORITY << 1, TIBIT_MGMT_VLAN)
+
+TIBIT_PACKET_IN_VLAN = 4000
+frame_match_case2 = '(ether[14:2] & 0xfff) = 0x{:03x}'.format(
+    TIBIT_PACKET_IN_VLAN)
+
+is_tibit_frame = BpfProgramFilter('{} or {}'.format(
+    frame_match_case1, frame_match_case2))
+
 #is_tibit_frame = lambda x: True
 
 
@@ -95,6 +103,7 @@
         self.io_port = None
         self.incoming_queues = {}  # OLT mac_address -> DeferredQueue()
         self.device_ids = {}  # OLT mac_address -> device_id
+        self.vlan_to_device_ids = {}  # c-vid -> (device_id, logical_device_id)
 
     def start(self):
         log.debug('starting', interface=self.interface)
@@ -269,7 +278,7 @@
             # Convert from string to colon separated form
             tibit_mac = ':'.join(s.encode('hex') for s in tibit_mac.decode('hex'))
 
-            gemport, vlan_id = self._olt_side_onu_activation(int(macid['macid'][-4:-2], 16))
+            vlan_id = self._olt_side_onu_activation(int(macid['macid'][-4:-2], 16))
             self.adapter_agent.child_device_detected(
                 parent_device_id=device.id,
                 parent_port_no=1,
@@ -282,6 +291,10 @@
                 vlan=vlan_id
                 )
 
+            # also record the vlan_id -> (device_id, logical_device_id) for
+            # later use
+            self.vlan_to_device_ids[vlan_id] = (device.id, device.parent_id)
+
     def _olt_side_onu_activation(self, serial):
         """
         This is where if this was a real OLT, the OLT-side activation for
@@ -289,40 +302,73 @@
         be able to provide tunneled (proxy) communication to the given ONU,
         using the returned information.
         """
-        gemport = serial
         vlan_id = serial + 200
-        return gemport, vlan_id
+        return vlan_id
 
     def _rcv_io(self, port, frame):
 
-        log.info('frame-received')
+        log.info('frame-received', frame=hexify(frame))
 
         # make into frame to extract source mac
         response = Ether(frame)
 
         if response.haslayer(Dot1Q):
-            # All responses from the OLT should have a TIBIT_MGMT_VLAN.
-            # Responses from the ONUs should have a TIBIT_MGMT_VLAN followed by a ONU CTAG
-            if response.getlayer(Dot1Q).type == 0x8100:
-                ## Responses from the ONU
-                ## Since the type of the first layer is 0x8100,
-                ## then the frame must have an inner tag layer
-                olt_mac = response.src
-                device_id = self.device_ids[olt_mac]
-                channel_id = response[Dot1Q:2].vlan
-                log.info('received_channel_id', channel_id=channel_id,
-                         device_id=device_id)
 
-                proxy_address=Device.ProxyAddress(
-                    device_id=device_id,
-                    channel_id=channel_id
-                    )
-                # pop dot1q header(s)
-                msg = response.payload.payload
-                self.adapter_agent.receive_proxied_message(proxy_address, msg)
+            # All OAM responses from the OLT should have a TIBIT_MGMT_VLAN.
+            # Responses from the ONUs should have a TIBIT_MGMT_VLAN followed by a ONU CTAG
+            # All packet-in frames will have the TIBIT_PACKET_IN_VLAN.
+            if response.getlayer(Dot1Q).type == 0x8100:
+
+                if response.getlayer(Dot1Q).vlan == TIBIT_PACKET_IN_VLAN:
+
+                    inner_tag_and_rest = response.payload.payload
+
+                    inner_tag_and_rest.show()  # TODO remove this soon
+
+                    if isinstance(inner_tag_and_rest, Dot1Q):
+
+                        cvid = inner_tag_and_rest.vlan
+
+                        frame = Ether(src=response.src,
+                                      dst=response.dst,
+                                      type=inner_tag_and_rest.type) /\
+                                inner_tag_and_rest.payload
+
+                        _, logical_device_id = self.vlan_to_device_ids.get(cvid)
+                        if logical_device_id is None:
+                            log.error('invalid-cvid', cvid=cvid)
+                        else:
+                            self.adapter_agent.send_packet_in(
+                                logical_device_id=logical_device_id,
+                                logical_port_no=cvid,  # C-VID encodes port no
+                                packet=str(frame))
+
+                    else:
+                        log.error('packet-in-single-tagged',
+                                  frame=hexify(response))
+
+                else:
+                    ## Responses from the ONU
+                    ## Since the type of the first layer is 0x8100,
+                    ## then the frame must have an inner tag layer
+                    olt_mac = response.src
+                    device_id = self.device_ids[olt_mac]
+                    channel_id = response[Dot1Q:2].vlan
+                    log.info('received_channel_id', channel_id=channel_id,
+                             device_id=device_id)
+
+                    proxy_address=Device.ProxyAddress(
+                        device_id=device_id,
+                        channel_id=channel_id
+                        )
+                    # pop dot1q header(s)
+                    msg = response.payload.payload
+                    self.adapter_agent.receive_proxied_message(proxy_address, msg)
+
             else:
                 ## Respones from the OLT
                 ## enqueue incoming parsed frame to right device
+                log.info('received-dot1q-not-8100')
                 self.incoming_queues[response.src].put(response)
 
     def _make_ping_frame(self, mac_address):
@@ -361,7 +407,7 @@
 
             elif in_port == 1:
                 # Upstream rule
-                req = DOLTObject()
+                req = PonPortObject()
                 req /= PortIngressRuleHeader(precedence=precedence)
 
                 for field in get_ofb_fields(flow):
diff --git a/voltha/extensions/eoam/EOAM_TLV.py b/voltha/extensions/eoam/EOAM_TLV.py
index c0f431c..9598967 100644
--- a/voltha/extensions/eoam/EOAM_TLV.py
+++ b/voltha/extensions/eoam/EOAM_TLV.py
@@ -162,6 +162,15 @@
                    XByteField("num", 0)
                    ]
 
+class PonPortObject(Packet):
+    """ Object Context: PON Port Object """
+    name = "Object Context: PON Port Object"
+    fields_desc = [XByteField("branch", 0xD6),
+                   XShortField("leaf", 0x0001),
+                   XByteField("length", 1),
+                   XByteField("num", 1)
+                   ]
+
 class UnicastLogicalLink(Packet):
     """ Object Context: Unicast Logical Link """
     name = "Object Context: Unicast Logical Link"
diff --git a/voltha/protos/events.proto b/voltha/protos/events.proto
new file mode 100644
index 0000000..628a3f5
--- /dev/null
+++ b/voltha/protos/events.proto
@@ -0,0 +1,32 @@
+syntax = "proto3";
+
+package voltha;
+
+import "meta.proto";
+import "google/api/annotations.proto";
+
+
+message KpiEventType {
+    enum KpiEventType {
+        slice = 0; // slice: a set of path/metric data for same time-stamp
+        ts = 1; // time-series: array of data for same metric
+    }
+}
+
+/*
+message KpiEvent {
+
+    KpiEventType type = 1;
+
+    // Fields used when for slice:
+
+    float ts = 2;  // UTC time-stamp of data in slice mode (seconds since epoc)
+
+    message MetricSamples {
+        map<string, float> metric_samples = 1;
+    }
+
+    map<string, MetricSamples> data = 3;
+
+}
+*/