OMCI library expansion

Includes:
- Minor refactoring to accommodate increased codebase
- Support and test for all requests expected to be used
  for the Broadcom ONU with Maple
- Much increased coverage for OMCI message types
- Many new entity types defined now

Change-Id: Ib0a1c396276b194a0aa08bede3b00b95a6a22d1e
diff --git a/tests/utests/voltha/adapters/microsemi/test_chat.py b/tests/utests/voltha/adapters/microsemi/test_chat.py
index 94eb805..9c538c0 100644
--- a/tests/utests/voltha/adapters/microsemi/test_chat.py
+++ b/tests/utests/voltha/adapters/microsemi/test_chat.py
@@ -1,8 +1,8 @@
 from unittest import TestCase, main
 
 from voltha.adapters.microsemi.chat import *
-from voltha.extensions.omci.omci import OMCIFrame, OMCIGetRequest, \
-    OMCIGetResponse
+from voltha.extensions.omci.omci import OmciFrame, OmciGet, \
+    OmciGetResponse
 
 
 class TestChat(TestCase):
@@ -440,10 +440,10 @@
                 port_type=PON_PORT_PON,
                 port_id=0,
                 management_frame=PON_TRUE,
-                frame=OMCIFrame(
+                frame=OmciFrame(
                     transaction_id=0,
                     message_type=0x49,
-                    omci_message=OMCIGetRequest(
+                    omci_message=OmciGet(
                         entity_class=6,
                         entity_id=0x101,
                         # there is a more programmer friendly way to express it
@@ -505,10 +505,10 @@
                 l4_offset=19,
                 # ignored, yet we get a non-zero value from olt
                 ignored=0x2000,
-                frame=OMCIFrame(
+                frame=OmciFrame(
                     transaction_id=0,
                     message_type=0x29,
-                    omci_message=OMCIGetResponse(
+                    omci_message=OmciGetResponse(
                         entity_class=6,
                         entity_id=0x101,
                         success_code=0,
diff --git a/tests/utests/voltha/extensions/omci/test_omci.py b/tests/utests/voltha/extensions/omci/test_omci.py
index c045d76..1900508 100644
--- a/tests/utests/voltha/extensions/omci/test_omci.py
+++ b/tests/utests/voltha/extensions/omci/test_omci.py
@@ -1,10 +1,6 @@
 from unittest import TestCase, main
 
-from hexdump import hexdump
-
-from voltha.extensions.omci.omci import CircuitPackEntity, bitpos_from_mask, \
-    OmciUninitializedFieldError, OMCIGetResponse, OMCIFrame, OMCIGetRequest
-from voltha.extensions.omci.omci import EntityClass
+from voltha.extensions.omci.omci import *
 
 
 def hexify(buffer):
@@ -21,7 +17,7 @@
     return ''.join(chr(int(byte, 16)) for byte in chunk(hex_string, 2))
 
 
-class TestOmci(TestCase):
+class TestOmciFundamentals(TestCase):
 
     def test_bitpos_from_mask(self):
 
@@ -50,18 +46,18 @@
 
     def test_entity_attribute_serialization(self):
 
-        e = CircuitPackEntity(vendor_id='F')
+        e = CircuitPack(vendor_id='F')
         self.assertEqual(e.serialize(), 'F\x00\x00\x00')
 
-        e = CircuitPackEntity(vendor_id='FOOX')
+        e = CircuitPack(vendor_id='FOOX')
         self.assertEqual(e.serialize(), 'FOOX')
 
-        e = CircuitPackEntity(vendor_id='FOOX', number_of_ports=16)
+        e = CircuitPack(vendor_id='FOOX', number_of_ports=16)
         self.assertEqual(e.serialize(), '\x10FOOX')
 
     def test_entity_attribute_serialization_mask_based(self):
 
-        e = CircuitPackEntity(
+        e = CircuitPack(
             number_of_ports=4,
             serial_number='123-123A',
             version='a1c12fba91de',
@@ -81,7 +77,7 @@
         self.assertRaises(OmciUninitializedFieldError, e.serialize, 0xc00)
 
     def test_omci_mask_value_gen(self):
-        cls = CircuitPackEntity
+        cls = CircuitPack
         self.assertEqual(cls.mask_for('vendor_id'), 0x800)
         self.assertEqual(
             cls.mask_for('vendor_id', 'bridged_or_ip_ind'), 0x900)
@@ -118,19 +114,19 @@
 
     def test_omci_frame_serialization(self):
 
-        frame = OMCIFrame(
+        frame = OmciFrame(
             transaction_id=0,
-            message_type=0x49,
-            omci_message=OMCIGetRequest(
-                entity_class=CircuitPackEntity.class_id,
+            message_type=OmciGet.message_id,
+            omci_message=OmciGet(
+                entity_class=CircuitPack.class_id,
                 entity_id=0x101,
-                attributes_mask=CircuitPackEntity.mask_for('vendor_id')
+                attributes_mask=CircuitPack.mask_for('vendor_id')
             )
         )
         self.assertEqual(hexify(str(frame)), self.reference_get_request_hex)
 
     def test_omci_frame_deserialization_no_data(self):
-        frame = OMCIFrame(self.reference_get_request_raw)
+        frame = OmciFrame(self.reference_get_request_raw)
         self.assertEqual(frame.transaction_id, 0)
         self.assertEqual(frame.message_type, 0x49)
         self.assertEqual(frame.omci, 10)
@@ -140,7 +136,7 @@
         self.assertEqual(frame.omci_trailer, 0x28)
 
     def test_omci_frame_deserialization_with_data(self):
-        frame = OMCIFrame(self.reference_get_response_raw)
+        frame = OmciFrame(self.reference_get_response_raw)
         self.assertEqual(frame.transaction_id, 0)
         self.assertEqual(frame.message_type, 0x29)
         self.assertEqual(frame.omci, 10)
@@ -153,5 +149,386 @@
     def test_entity_attribute_deserialization(self):
         pass
 
+
+class TestSelectMessageGeneration(TestCase):
+
+    def assertGeneratedFrameEquals(self, frame, ref):
+        assert isinstance(frame, Packet)
+        serialized_hexified_frame = hexify(str(frame)).upper()
+        ref = ref.upper()
+        if serialized_hexified_frame != ref:
+            self.fail('Mismatch:\nReference:\n{}\nGenerated (bad):\n{}'.format(
+                ref, serialized_hexified_frame
+            ))
+
+    def test_mib_reset_message_serialization(self):
+        ref = '00014F0A000200000000000000000000' \
+              '00000000000000000000000000000000' \
+              '000000000000000000000028'
+        frame = OmciFrame(
+            transaction_id=1,
+            message_type=OmciMibReset.message_id,
+            omci_message=OmciMibReset(
+                entity_class=OntData.class_id
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_create_gal_ethernet_profile(self):
+        ref = '0002440A011000010030000000000000' \
+              '00000000000000000000000000000000' \
+              '000000000000000000000028'
+        frame = OmciFrame(
+            transaction_id=2,
+            message_type=OmciCreate.message_id,
+            omci_message=OmciCreate(
+                entity_class=GalEthernetProfile.class_id,
+                entity_id=1,
+                data=dict(
+                    max_gem_payload_size=48
+                )
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_set_tcont_1(self):
+        ref = '0003480A010680008000040000000000' \
+              '00000000000000000000000000000000' \
+              '000000000000000000000028'
+        data = dict(
+            alloc_id=0x400
+        )
+        frame = OmciFrame(
+            transaction_id=3,
+            message_type=OmciSet.message_id,
+            omci_message=OmciSet(
+                entity_class=Tcont.class_id,
+                entity_id=0x8000,
+                attributes_mask=Tcont.mask_for(*data.keys()),
+                data=data
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_set_tcont_2(self):
+        ref = '0004480A010680018000040100000000' \
+              '00000000000000000000000000000000' \
+              '000000000000000000000028'
+        data = dict(
+            alloc_id=0x401
+        )
+        frame = OmciFrame(
+            transaction_id=4,
+            message_type=OmciSet.message_id,
+            omci_message=OmciSet(
+                entity_class=Tcont.class_id,
+                entity_id=0x8001,
+                attributes_mask=Tcont.mask_for(*data.keys()),
+                data=data
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_create_8021p_mapper_service_profile(self):
+        ref = '0007440A00828000ffffffffffffffff' \
+              'ffffffffffffffffffff000000000000' \
+              '000000000000000000000028'
+        frame = OmciFrame(
+            transaction_id=7,
+            message_type=OmciCreate.message_id,
+            omci_message=OmciCreate(
+                entity_class=Ieee8021pMapperServiceProfile.class_id,
+                entity_id=0x8000,
+                data=dict(
+                    tp_pointer=OmciNullPointer,
+                    interwork_tp_pointer_for_p_bit_priority_0=OmciNullPointer,
+                )
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_create_mac_bridge_service_profile(self):
+        ref = '000B440A002D02010001008000140002' \
+              '000f0001000000000000000000000000' \
+              '000000000000000000000028'
+        frame = OmciFrame(
+            transaction_id=11,
+            message_type=OmciCreate.message_id,
+            omci_message=OmciCreate(
+                entity_class=MacBridgeServiceProfile.class_id,
+                entity_id=0x201,
+                data=dict(
+                    spanning_tree_ind=False,
+                    learning_ind=True,
+                    priority=0x8000,
+                    max_age=20 * 256,
+                    hello_time=2 * 256,
+                    forward_delay=15 * 256,
+                    unknown_mac_address_discard=True
+                )
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_create_gem_port_network_ctp(self):
+        ref = '000C440A010C01000400800003010000' \
+              '00000000000000000000000000000000' \
+              '000000000000000000000028'
+        frame = OmciFrame(
+            transaction_id=12,
+            message_type=OmciCreate.message_id,
+            omci_message=OmciCreate(
+                entity_class=GemPortNetworkCtp.class_id,
+                entity_id=0x100,
+                data=dict(
+                    port_id=0x400,
+                    tcont_pointer=0x8000,
+                    direction=3,
+                    traffic_management_pointer_upstream=0x100
+                )
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_multicast_gem_interworking_tp(self):
+        ref = '0011440A011900060104000001000000' \
+              '00000000000000000000000000000000' \
+              '000000000000000000000028'
+        frame = OmciFrame(
+            transaction_id=17,
+            message_type=OmciCreate.message_id,
+            omci_message=OmciCreate(
+                entity_class=MulticastGemInterworkingTp.class_id,
+                entity_id=0x6,
+                data=dict(
+                    gem_port_network_ctp_pointer=0x104,
+                    interworking_option=0,
+                    service_profile_pointer=0x1,
+                )
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_create_gem_inteworking_tp(self):
+        ref = '0012440A010A80010100058000000000' \
+              '01000000000000000000000000000000' \
+              '000000000000000000000028'
+        frame = OmciFrame(
+            transaction_id=18,
+            message_type=OmciCreate.message_id,
+            omci_message=OmciCreate(
+                entity_class=GemInterworkingTp.class_id,
+                entity_id=0x8001,
+                data=dict(
+                    gem_port_network_ctp_pointer=0x100,
+                    interworking_option=5,
+                    service_profile_pointer=0x8000,
+                    interworking_tp_pointer=0x0,
+                    gal_profile_pointer=0x1
+                )
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_set_8021p_mapper_service_profile(self):
+        ref = '0016480A008280004000800100000000' \
+              '00000000000000000000000000000000' \
+              '000000000000000000000028'
+        data = dict(
+            interwork_tp_pointer_for_p_bit_priority_0=0x8001
+        )
+        frame = OmciFrame(
+            transaction_id=22,
+            message_type=OmciSet.message_id,
+            omci_message=OmciSet(
+                entity_class=Ieee8021pMapperServiceProfile.class_id,
+                entity_id=0x8000,
+                attributes_mask=Ieee8021pMapperServiceProfile.mask_for(
+                    *data.keys()),
+                data=data
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_create_mac_bridge_port_configuration_data(self):
+        ref = '001A440A002F21010201020380000000' \
+              '00000000000000000000000000000000' \
+              '000000000000000000000028'
+        frame = OmciFrame(
+            transaction_id=26,
+            message_type=OmciCreate.message_id,
+            omci_message=OmciCreate(
+                entity_class=MacBridgePortConfigurationData.class_id,
+                entity_id=0x2101,
+                data=dict(
+                    bridge_id_pointer=0x201,
+                    port_num=2,
+                    tp_type=3,
+                    tp_pointer=0x8000
+                )
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_create_vlan_tagging_filter_data(self):
+        ref = '001F440A005421010400000000000000' \
+              '00000000000000000000000000000000' \
+              '100100000000000000000028'
+        frame = OmciFrame(
+            transaction_id=31,
+            message_type=OmciCreate.message_id,
+            omci_message=OmciCreate(
+                entity_class=VlanTaggingFilterData.class_id,
+                entity_id=0x2101,
+                data=dict(
+                    vlan_filter_0=0x0400,
+                    forward_operation=0x10,
+                    number_of_entries=1
+                )
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_create_extended_vlan_tagging_operation_configuration_data(self):
+        ref = '0023440A00AB02020A04010000000000' \
+              '00000000000000000000000000000000' \
+              '000000000000000000000028'
+        frame = OmciFrame(
+            transaction_id=35,
+            message_type=OmciCreate.message_id,
+            omci_message=OmciCreate(
+                entity_class=\
+                    ExtendedVlanTaggingOperationConfigurationData.class_id,
+                entity_id=0x202,
+                data=dict(
+                    association_type=10,
+                    associated_me_pointer=0x401
+                )
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_set_extended_vlan_tagging_operation_configuration_data(self):
+        ref = '0024480A00AB02023800810081000000' \
+              '00000000000000000000000000000000' \
+              '000000000000000000000028'
+        data = dict(
+            input_tpid=0x8100,
+            output_tpid=0x8100,
+            downstream_mode=0,  # inverse of upstream
+        )
+        frame = OmciFrame(
+            transaction_id=36,
+            message_type=OmciSet.message_id,
+            omci_message=OmciSet(
+                entity_class=\
+                    ExtendedVlanTaggingOperationConfigurationData.class_id,
+                entity_id=0x202,
+                attributes_mask= \
+                    ExtendedVlanTaggingOperationConfigurationData.mask_for(
+                        *data.keys()),
+                data=data
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_set_extended_vlan_tagging_1(self):
+        ref = '0025480A00AB02020400f00000008200' \
+              '5000402f000000082004000000000000' \
+              '000000000000000000000028'
+        data = dict(
+            received_frame_vlan_tagging_operation_table=\
+                VlanTaggingOperation(
+                    filter_outer_priority=15,
+                    filter_inner_priority=8,
+                    filter_inner_vid=1024,
+                    filter_inner_tpid_de=5,
+                    filter_ether_type=0,
+                    treatment_tags_to_remove=1,
+                    pad3=2,
+                    treatment_outer_priority=15,
+                    treatment_inner_priority=8,
+                    treatment_inner_vid=1024,
+                    treatment_inner_tpid_de=4
+                )
+        )
+        frame = OmciFrame(
+            transaction_id=37,
+            message_type=OmciSet.message_id,
+            omci_message=OmciSet(
+                entity_class=\
+                    ExtendedVlanTaggingOperationConfigurationData.class_id,
+                entity_id=0x202,
+                attributes_mask= \
+                    ExtendedVlanTaggingOperationConfigurationData.mask_for(
+                        *data.keys()),
+                data=data
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_set_extended_vlan_tagging_2(self):
+        ref = '0026480A00AB02020400F00000008200' \
+              'd000402f00000008200c000000000000' \
+              '000000000000000000000028'
+        data = dict(
+            received_frame_vlan_tagging_operation_table=\
+                VlanTaggingOperation(
+                    filter_outer_priority=15,
+                    filter_inner_priority=8,
+                    filter_inner_vid=1025,
+                    filter_inner_tpid_de=5,
+                    filter_ether_type=0,
+                    treatment_tags_to_remove=1,
+                    pad3=2,
+                    treatment_outer_priority=15,
+                    treatment_inner_priority=8,
+                    treatment_inner_vid=1025,
+                    treatment_inner_tpid_de=4
+                )
+        )
+        frame = OmciFrame(
+            transaction_id=38,
+            message_type=OmciSet.message_id,
+            omci_message=OmciSet(
+                entity_class=\
+                    ExtendedVlanTaggingOperationConfigurationData.class_id,
+                entity_id=0x202,
+                attributes_mask= \
+                    ExtendedVlanTaggingOperationConfigurationData.mask_for(
+                        *data.keys()),
+                data=data
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+
+    def test_create_mac_bridge_port_configuration_data2(self):
+        ref = '0029440A002F02010201010b04010000' \
+              '00000000000000000000000000000000' \
+              '000000000000000000000028'
+        frame = OmciFrame(
+            transaction_id=41,
+            message_type=OmciCreate.message_id,
+            omci_message=OmciCreate(
+                entity_class=MacBridgePortConfigurationData.class_id,
+                entity_id=0x201,
+                data=dict(
+                    bridge_id_pointer=0x201,
+                    encapsulation_methods=0,
+                    port_num=1,
+                    port_priority=0,
+                    port_path_cost=0,
+                    port_spanning_tree_in=0,
+                    lan_fcs_ind=0,
+                    tp_type=11,
+                    tp_pointer=0x401
+                )
+            )
+        )
+        self.assertGeneratedFrameEquals(frame, ref)
+        frame2 = OmciFrame(hex2raw(ref))
+        self.assertEqual(frame2, frame)
+
+
 if __name__ == '__main__':
     main()
diff --git a/voltha/adapters/microsemi/chat.py b/voltha/adapters/microsemi/chat.py
index 945533b..cc69731 100755
--- a/voltha/adapters/microsemi/chat.py
+++ b/voltha/adapters/microsemi/chat.py
@@ -13,7 +13,7 @@
 from scapy.packet import Packet, bind_layers
 from scapy.volatile import RandSInt
 
-from voltha.extensions.omci.omci import OMCIFrame
+from voltha.extensions.omci.omci import OmciFrame
 
 
 src_mac = "68:05:ca:05:f2:ef"
@@ -567,7 +567,7 @@
         LEShortField("l4_offset", None),
         LEShortField("ignored", 0), # TODO these do receive values, but there is no code in PMC using it
         ConditionalField(PacketField("frame", None, Packet), lambda pkt: pkt.management_frame==PON_FALSE),
-        ConditionalField(PacketField("frame", None, OMCIFrame), lambda pkt: pkt.management_frame==PON_TRUE)
+        ConditionalField(PacketField("frame", None, OmciFrame), lambda pkt: pkt.management_frame==PON_TRUE)
     ]
 
 
diff --git a/voltha/adapters/microsemi/sniff.py b/voltha/adapters/microsemi/sniff.py
index f0d026d..7ebf950 100755
--- a/voltha/adapters/microsemi/sniff.py
+++ b/voltha/adapters/microsemi/sniff.py
@@ -11,7 +11,7 @@
 from scapy.utils import rdpcap
 from scapy.volatile import RandSInt
 
-from voltha.extensions.omci.omci import OMCIFrame
+from voltha.extensions.omci.omci import OmciFrame
 
 
 src_mac = "68:05:ca:05:f2:ef"
@@ -588,7 +588,7 @@
         LEShortField("l4_offset", None),
         LEShortField("ignored", 0), # TODO these do receive values, but there is no code in PMC using it
         ConditionalField(PacketField("frame", None, Packet), lambda pkt: pkt.management_frame==PON_FALSE),
-        ConditionalField(PacketField("frame", None, OMCIFrame), lambda pkt: pkt.management_frame==PON_TRUE)
+        ConditionalField(PacketField("frame", None, OmciFrame), lambda pkt: pkt.management_frame==PON_TRUE)
     ]
 
 
diff --git a/voltha/extensions/omci/omci.py b/voltha/extensions/omci/omci.py
index 8288308..40c718f 100644
--- a/voltha/extensions/omci/omci.py
+++ b/voltha/extensions/omci/omci.py
@@ -1,304 +1,23 @@
-import inspect
-import sys
-from enum import Enum
-from scapy.fields import ByteField, Field, ShortField, PacketField, PadField, \
-    ConditionalField
-from scapy.fields import StrFixedLenField, IntField
-from scapy.packet import Packet, Raw
+#
+# Copyright 2016 the original author or authors.
+#
+# 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.
+#
 
+"""
+Omci message generator and parser implementation using scapy
+"""
 
-class OmciUninitializedFieldError(Exception): pass
-
-
-class FixedLenField(PadField):
-    """
-    This Pad field limits parsing of its content to its size
-    """
-    def __init__(self, fld, align, padwith='\x00'):
-        super(FixedLenField, self).__init__(fld, align, padwith)
-
-    def getfield(self, pkt, s):
-        remain, val = self._fld.getfield(pkt, s[:self._align])
-        if isinstance(val.payload, Raw) and \
-                not val.payload.load.replace(self._padwith, ''):
-            # raw payload is just padding
-            val.remove_payload()
-        return remain + s[self._align:], val
-
-
-def bitpos_from_mask(mask, lsb_pos=0, increment=1):
-    """
-    Turn a decimal value (bitmask) into a list of indices where each
-    index value corresponds to the bit position of a bit that was set (1)
-    in the mask. What numbers are assigned to the bit positions is controlled
-    by lsb_pos and increment, as explained below.
-    :param mask: a decimal value used as a bit mask
-    :param lsb_pos: The decimal value associated with the LSB bit
-    :param increment: If this is +i, then the bit next to LSB will take
-    the decimal value of lsb_pos + i.
-    :return: List of bit positions where the bit was set in mask
-    """
-    out = []
-    while mask:
-        if mask & 0x01:
-            out.append(lsb_pos)
-        lsb_pos += increment
-        mask >>= 1
-    return sorted(out)
-
-
-class AttributeAccess(Enum):
-    Readable = 1
-    R = 1
-    Writable = 2
-    W = 2
-    SetByCreate = 3
-    SBC = 3
-
-
-class EntityOperations(Enum):
-    Get = 1  # TODO adjust encoding to match msg_type field
-    Set = 2
-    Create = 3
-    Delete = 4
-    Reboot = 10
-    Test = 11
-
-
-class EntityClassAttribute(object):
-
-    def __init__(self, fld, access=set(), optional=False):
-        self._fld = fld
-        self._access = access
-        self._optional = optional
-
-class EntityClassMeta(type):
-    """
-    Metaclass for EntityClass to generate secondary class attributes
-    for class attributes of the derived classes.
-    """
-    def __init__(cls, name, bases, dct):
-        super(EntityClassMeta, cls).__init__(name, bases, dct)
-
-        # initialize attribute_name_to_index_map
-        cls.attribute_name_to_index_map = dict(
-            (a._fld.name, idx) for idx, a in enumerate(cls.attributes))
-
-
-class EntityClass(object):
-
-    class_id = 'to be filled by subclass'
-    attributes = []
-    mandatory_operations = {}
-    optional_operations = {}
-
-    # will be map of attr_name -> index in attributes, initialized by metaclass
-    attribute_name_to_index_map = None
-    __metaclass__ = EntityClassMeta
-
-    def __init__(self, **kw):
-        assert(isinstance(kw, dict))
-        for k, v in kw.iteritems():
-            assert(k in self.attribute_name_to_index_map)
-        self._data = kw
-
-    def serialize(self, mask=None, operation=None):
-        bytes = ''
-
-        # generate ordered list of attribute indices needed to be processed
-        # if mask is provided, we use that explicitly
-        # if mask is not provided, we determine attributes from the self._data content
-        # also taking into account the type of operation in hand
-        if mask is not None:
-            attribute_indices = EntityClass.attribute_indices_from_mask(mask)
-        else:
-            attribute_indices = self.attribute_indices_from_data()
-
-        # Serialize each indexed field (ignoring entity id)
-        for index in attribute_indices:
-            field = self.attributes[index]._fld
-            try:
-                value = self._data[field.name]
-            except KeyError:
-                raise OmciUninitializedFieldError(
-                    'Entity field "{}" not set'.format(field.name) )
-            bytes = field.addfield(None, bytes, value)
-
-        return bytes
-
-    def attribute_indices_from_data(self):
-        return sorted(
-            self.attribute_name_to_index_map[attr_name]
-            for attr_name in self._data.iterkeys())
-
-    byte1_mask_to_attr_indices = dict(
-        (m, bitpos_from_mask(m, 8, -1)) for m in range(256))
-    byte2_mask_to_attr_indices = dict(
-        (m, bitpos_from_mask(m, 16, -1)) for m in range(256))
-    @classmethod
-    def attribute_indices_from_mask(cls, mask):
-        # each bit in the 2-byte field denote an attribute index; we use a
-        # lookup table to make lookup a bit faster
-        return \
-            cls.byte1_mask_to_attr_indices[(mask >> 8) & 0xff] + \
-            cls.byte2_mask_to_attr_indices[(mask & 0xff)]
-
-    @classmethod
-    def mask_for(cls, *attr_names):
-        """
-        Return mask value corresponding to given attributes names
-        :param attr_names: Attribute names
-        :return: integer mask value
-        """
-        mask = 0
-        for attr_name in attr_names:
-            index = cls.attribute_name_to_index_map[attr_name]
-            mask |= (1 << (16 - index))
-        return mask
-
-
-# abbreviations
-ECA = EntityClassAttribute
-AA = AttributeAccess
-OP = EntityOperations
-
-
-class CircuitPackEntity(EntityClass):
-    class_id = 6
-    attributes = [
-        ECA(StrFixedLenField("managed_entity_id", None, 22), {AA.R, AA.SBC}),
-        ECA(ByteField("type", None), {AA.R, AA.SBC}),
-        ECA(ByteField("number_of_ports", None), {AA.R}, optional=True),
-        ECA(StrFixedLenField("serial_number", None, 8), {AA.R}),
-        ECA(StrFixedLenField("version", None, 14), {AA.R}),
-        ECA(StrFixedLenField("vendor_id", None, 4), {AA.R}),
-        ECA(ByteField("administrative_state", None), {AA.R, AA.W, AA.SBC}),
-        ECA(ByteField("operational_state", None), {AA.R}, optional=True),
-        ECA(ByteField("bridged_or_ip_ind", None), {AA.R, AA.W}, optional=True),
-        ECA(StrFixedLenField("equipment_id", None, 20), {AA.R}, optional=True),
-        ECA(ByteField("card_configuration", None), {AA.R, AA.W, AA.SBC}), # not really mandatory, see spec
-        ECA(ByteField("total_tcont_buffer_number", None), {AA.R}),
-        ECA(ByteField("total_priority_queue_number", None), {AA.R}),
-        ECA(ByteField("total_traffic_scheduler_number", None), {AA.R}),
-        ECA(IntField("power_sched_override", None), {AA.R, AA.W}, optional=True)
-    ]
-    mandatory_operations = {OP.Get, OP.Set, OP.Reboot}
-    optional_operations = {OP.Create, OP.Delete, OP.Test}
-
-
-# entity class lookup table from entity_class values
-entity_classes_name_map = dict(
-    inspect.getmembers(sys.modules[__name__],
-    lambda o: inspect.isclass(o) and \
-              issubclass(o, EntityClass) and \
-              o is not EntityClass)
-)
-
-entity_classes = [c for c in entity_classes_name_map.itervalues()]
-entity_id_to_class_map = dict((c.class_id, c) for c in entity_classes)
-
-
-class OMCIData(Field):
-
-    __slots__ = Field.__slots__ + ['_entity_class', '_attributes_mask']
-
-    def __init__(self, name, entity_class="entity_class",
-                 attributes_mask="attributes_mask"):
-        Field.__init__(self, name=name, default=None, fmt='s')
-        self._entity_class = entity_class
-        self._attributes_mask = attributes_mask
-
-    def addfield(self, pkt, s, val):
-        class_id = getattr(pkt, self._entity_class)
-        attribute_mask = getattr(pkt, self._attributes_mask)
-        entity_class = entity_id_to_class_map.get(class_id)
-        indices = entity_class.attribute_indices_from_mask(attribute_mask)
-        for index in indices:
-            fld = entity_class.attributes[index]._fld
-            s = fld.addfield(pkt, s, val[fld.name])
-        return s
-
-    def getfield(self, pkt, s):
-        """Extract an internal value from a string"""
-        class_id = getattr(pkt, self._entity_class)
-        attribute_mask = getattr(pkt, self._attributes_mask)
-        entity_class = entity_id_to_class_map.get(class_id)
-        indices = entity_class.attribute_indices_from_mask(attribute_mask)
-        data = {}
-        for index in indices:
-            fld = entity_class.attributes[index]._fld
-            s, value = fld.getfield(pkt, s)
-            data[fld.name] = value
-        return  s, data
-
-
-class OMCIMessage(Packet):
-    name = "OMCIMessage"
-    message_id = None  # OMCI message_type value, filled by derived classes
-    fields_desc = []
-
-
-class OMCIGetRequest(OMCIMessage):
-    name = "OMCIGetRequest"
-    message_id = 0x49
-    fields_desc = [
-        ShortField("entity_class", None),
-        ShortField("entity_id", 0),
-        ShortField("attributes_mask", None)
-    ]
-
-
-class OMCIGetResponse(OMCIMessage):
-    name = "OMCIGetResponse"
-    message_id = 0x29
-    fields_desc = [
-        ShortField("entity_class", None),
-        ShortField("entity_id", 0),
-        ByteField("success_code", 0),
-        ShortField("attributes_mask", None),
-        ConditionalField(
-            OMCIData("data", entity_class="entity_class",
-                     attributes_mask="attributes_mask"),
-            lambda pkt: pkt.success_code == 0)
-    ]
-
-
-class OMCIFrame(Packet):
-    name = "OMCIFrame"
-    fields_desc = [
-        ShortField("transaction_id", 0),
-        ByteField("message_type", None),
-        ByteField("omci", 0x0a),
-        ConditionalField(FixedLenField(PacketField("omci_message", None,
-                                              OMCIGetRequest), align=36),
-                         lambda pkt: pkt.message_type == 0x49),
-        ConditionalField(FixedLenField(PacketField("omci_message", None,
-                                              OMCIGetResponse), align=36),
-                         lambda pkt: pkt.message_type == 0x29),
-        # TODO add additional message types here as padded conditionals...
-
-        IntField("omci_trailer", 0x00000028)
-    ]
-
-    # We needed to patch the do_dissect(...) method of Packet, because
-    # it wiped out already dissected conditional fields with None if they
-    # referred to the same field name. We marked the only new line of code
-    # with "Extra condition added".
-    def do_dissect(self, s):
-        raw = s
-        self.raw_packet_cache_fields = {}
-        for f in self.fields_desc:
-            if not s:
-                break
-            s, fval = f.getfield(self, s)
-            # We need to track fields with mutable values to discard
-            # .raw_packet_cache when needed.
-            if f.islist or f.holds_packets:
-                self.raw_packet_cache_fields[f.name] = f.do_copy(fval)
-            # Extra condition added
-            if fval is not None or f.name not in self.fields:
-                self.fields[f.name] = fval
-        assert(raw.endswith(s))
-        self.raw_packet_cache = raw[:-len(s)] if s else raw
-        self.explicit = 1
-        return s
+from omci_frame import OmciFrame
+from omci_messages import *
+from omci_entities import *
diff --git a/voltha/extensions/omci/omci_defs.py b/voltha/extensions/omci/omci_defs.py
new file mode 100644
index 0000000..c06cbbe
--- /dev/null
+++ b/voltha/extensions/omci/omci_defs.py
@@ -0,0 +1,100 @@
+#
+# Copyright 2016 the original author or authors.
+#
+# 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.
+#
+from enum import Enum
+from scapy.fields import PadField
+from scapy.packet import Raw
+
+
+class OmciUninitializedFieldError(Exception): pass
+
+
+class FixedLenField(PadField):
+    """
+    This Pad field limits parsing of its content to its size
+    """
+    def __init__(self, fld, align, padwith='\x00'):
+        super(FixedLenField, self).__init__(fld, align, padwith)
+
+    def getfield(self, pkt, s):
+        remain, val = self._fld.getfield(pkt, s[:self._align])
+        if isinstance(val.payload, Raw) and \
+                not val.payload.load.replace(self._padwith, ''):
+            # raw payload is just padding
+            val.remove_payload()
+        return remain + s[self._align:], val
+
+
+def bitpos_from_mask(mask, lsb_pos=0, increment=1):
+    """
+    Turn a decimal value (bitmask) into a list of indices where each
+    index value corresponds to the bit position of a bit that was set (1)
+    in the mask. What numbers are assigned to the bit positions is controlled
+    by lsb_pos and increment, as explained below.
+    :param mask: a decimal value used as a bit mask
+    :param lsb_pos: The decimal value associated with the LSB bit
+    :param increment: If this is +i, then the bit next to LSB will take
+    the decimal value of lsb_pos + i.
+    :return: List of bit positions where the bit was set in mask
+    """
+    out = []
+    while mask:
+        if mask & 0x01:
+            out.append(lsb_pos)
+        lsb_pos += increment
+        mask >>= 1
+    return sorted(out)
+
+
+class AttributeAccess(Enum):
+    Readable = 1
+    R = 1
+    Writable = 2
+    W = 2
+    SetByCreate = 3
+    SBC = 3
+
+
+OmciNullPointer = 0xffff
+
+
+class EntityOperations(Enum):
+    # keep these numbers match msg_type field per OMCI spec
+    Create = 4
+    CreateComplete = 5
+    Delete = 6
+    Set = 8
+    Get = 9
+    GetComplete = 10
+    GetAllAlarms = 11
+    GetAllAlarmsNext = 12
+    MibUpload = 13
+    MibUploadNext = 14
+    MibReset = 15
+    AlarmNotification = 16
+    AttributeValueChange = 17
+    Test = 18
+    StartSoftwareDownload = 19
+    DownlaodSection = 20
+    EndSoftwareDownload = 21
+    ActivateSoftware = 22
+    CommitSoftware = 23
+    SynchronizeTime = 24
+    Reboot = 25
+    GetNext = 26
+    TestResult = 27
+    GetCurrentData = 28
+
+
diff --git a/voltha/extensions/omci/omci_entities.py b/voltha/extensions/omci/omci_entities.py
new file mode 100644
index 0000000..cbeeec5
--- /dev/null
+++ b/voltha/extensions/omci/omci_entities.py
@@ -0,0 +1,404 @@
+#
+# Copyright 2016 the original author or authors.
+#
+# 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.
+#
+import inspect
+
+import sys
+from scapy.fields import ByteField, ShortField, MACField, BitField
+from scapy.fields import IntField, StrFixedLenField
+from scapy.packet import Packet
+
+from voltha.extensions.omci.omci_defs import OmciUninitializedFieldError, \
+    AttributeAccess, OmciNullPointer, EntityOperations
+from voltha.extensions.omci.omci_defs import bitpos_from_mask
+
+
+class EntityClassAttribute(object):
+
+    def __init__(self, fld, access=set(), optional=False):
+        self._fld = fld
+        self._access = access
+        self._optional = optional
+
+
+class EntityClassMeta(type):
+    """
+    Metaclass for EntityClass to generate secondary class attributes
+    for class attributes of the derived classes.
+    """
+    def __init__(cls, name, bases, dct):
+        super(EntityClassMeta, cls).__init__(name, bases, dct)
+
+        # initialize attribute_name_to_index_map
+        cls.attribute_name_to_index_map = dict(
+            (a._fld.name, idx) for idx, a in enumerate(cls.attributes))
+
+
+class EntityClass(object):
+
+    class_id = 'to be filled by subclass'
+    attributes = []
+    mandatory_operations = {}
+    optional_operations = {}
+
+    # will be map of attr_name -> index in attributes, initialized by metaclass
+    attribute_name_to_index_map = None
+    __metaclass__ = EntityClassMeta
+
+    def __init__(self, **kw):
+        assert(isinstance(kw, dict))
+        for k, v in kw.iteritems():
+            assert(k in self.attribute_name_to_index_map)
+        self._data = kw
+
+    def serialize(self, mask=None, operation=None):
+        bytes = ''
+
+        # generate ordered list of attribute indices needed to be processed
+        # if mask is provided, we use that explicitly
+        # if mask is not provided, we determine attributes from the self._data
+        # content also taking into account the type of operation in hand
+        if mask is not None:
+            attribute_indices = EntityClass.attribute_indices_from_mask(mask)
+        else:
+            attribute_indices = self.attribute_indices_from_data()
+
+        # Serialize each indexed field (ignoring entity id)
+        for index in attribute_indices:
+            field = self.attributes[index]._fld
+            try:
+                value = self._data[field.name]
+            except KeyError:
+                raise OmciUninitializedFieldError(
+                    'Entity field "{}" not set'.format(field.name) )
+            bytes = field.addfield(None, bytes, value)
+
+        return bytes
+
+    def attribute_indices_from_data(self):
+        return sorted(
+            self.attribute_name_to_index_map[attr_name]
+            for attr_name in self._data.iterkeys())
+
+    byte1_mask_to_attr_indices = dict(
+        (m, bitpos_from_mask(m, 8, -1)) for m in range(256))
+    byte2_mask_to_attr_indices = dict(
+        (m, bitpos_from_mask(m, 16, -1)) for m in range(256))
+    @classmethod
+    def attribute_indices_from_mask(cls, mask):
+        # each bit in the 2-byte field denote an attribute index; we use a
+        # lookup table to make lookup a bit faster
+        return \
+            cls.byte1_mask_to_attr_indices[(mask >> 8) & 0xff] + \
+            cls.byte2_mask_to_attr_indices[(mask & 0xff)]
+
+    @classmethod
+    def mask_for(cls, *attr_names):
+        """
+        Return mask value corresponding to given attributes names
+        :param attr_names: Attribute names
+        :return: integer mask value
+        """
+        mask = 0
+        for attr_name in attr_names:
+            index = cls.attribute_name_to_index_map[attr_name]
+            mask |= (1 << (16 - index))
+        return mask
+
+
+# abbreviations
+ECA = EntityClassAttribute
+AA = AttributeAccess
+OP = EntityOperations
+
+
+class OntData(EntityClass):
+    class_id = 2
+    attributes = [
+        ECA(ShortField("managed_entity_id", None), {AA.R}),
+        ECA(ByteField("mib_data_sync", 0), {AA.R, AA.W})
+    ]
+    mandatory_operations = {OP.Get, OP.Set,
+                            OP.GetAllAlarms, OP.GetAllAlarmsNext,
+                            OP.MibReset, OP.MibUpload, OP.MibUploadNext}
+    optional_operations = {}
+
+
+class CircuitPack(EntityClass):
+    class_id = 6
+    attributes = [
+        ECA(StrFixedLenField("managed_entity_id", None, 22), {AA.R, AA.SBC}),
+        ECA(ByteField("type", None), {AA.R, AA.SBC}),
+        ECA(ByteField("number_of_ports", None), {AA.R}, optional=True),
+        ECA(StrFixedLenField("serial_number", None, 8), {AA.R}),
+        ECA(StrFixedLenField("version", None, 14), {AA.R}),
+        ECA(StrFixedLenField("vendor_id", None, 4), {AA.R}),
+        ECA(ByteField("administrative_state", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ByteField("operational_state", None), {AA.R}, optional=True),
+        ECA(ByteField("bridged_or_ip_ind", None), {AA.R, AA.W}, optional=True),
+        ECA(StrFixedLenField("equipment_id", None, 20), {AA.R}, optional=True),
+        ECA(ByteField("card_configuration", None), {AA.R, AA.W, AA.SBC}), # not really mandatory, see spec
+        ECA(ByteField("total_tcont_buffer_number", None), {AA.R}),
+        ECA(ByteField("total_priority_queue_number", None), {AA.R}),
+        ECA(ByteField("total_traffic_scheduler_number", None), {AA.R}),
+        ECA(IntField("power_sched_override", None), {AA.R, AA.W},
+            optional=True)
+    ]
+    mandatory_operations = {OP.Get, OP.Set, OP.Reboot}
+    optional_operations = {OP.Create, OP.Delete, OP.Test}
+
+
+class MacBridgeServiceProfile(EntityClass):
+    class_id = 45
+    attributes = [
+        ECA(ShortField("managed_entity_id", None), {AA.R, AA.SBC}),
+        ECA(ByteField("spanning_tree_ind", False),
+            {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ByteField("learning_ind", False), {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ByteField("port_bridging_ind", False),
+            {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ShortField("priority", None), {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ShortField("max_age", None), {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ShortField("hello_time", None), {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ShortField("forward_delay", None), {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ByteField("unknown_mac_address_discard", False),
+            {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ByteField("mac_learning_depth", 0),
+            {AA.R, AA.W, AA.SetByCreate}, optional=True)
+
+    ]
+    mandatory_operations = {OP.Create, OP.Delete, OP.Get, OP.Set}
+
+
+class MacBridgePortConfigurationData(EntityClass):
+    class_id = 47
+    attributes = [
+        ECA(ShortField("managed_entity_id", None), {AA.R, AA.SBC}),
+        ECA(ShortField("bridge_id_pointer", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ByteField("port_num", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ByteField("tp_type", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("tp_pointer", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("port_priority", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("port_path_cost", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ByteField("port_spanning_tree_in", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ByteField("encapsulation_methods", None), {AA.R, AA.W, AA.SBC},
+            optional=True),
+        ECA(ByteField("lan_fcs_ind", None), {AA.R, AA.W, AA.SBC},
+            optional=True),
+        ECA(MACField("port_mac_address", None), {AA.R}, optional=True),
+        ECA(ShortField("outbound_td_pointer", None), {AA.R, AA.W},
+            optional=True),
+        ECA(ShortField("inbound_td_pointer", None), {AA.R, AA.W},
+            optional=True),
+    ]
+    mandatory_operations = {OP.Create, OP.Delete, OP.Get, OP.Set}
+
+
+class VlanTaggingFilterData(EntityClass):
+    class_id = 84
+    attributes = [
+        ECA(ShortField("managed_entity_id", None), {AA.R, AA.SBC}),
+        ECA(ShortField("vlan_filter_0", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("vlan_filter_1", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("vlan_filter_2", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("vlan_filter_3", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("vlan_filter_4", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("vlan_filter_5", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("vlan_filter_6", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("vlan_filter_7", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("vlan_filter_8", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("vlan_filter_9", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("vlan_filter_10", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("vlan_filter_11", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ByteField("forward_operation", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ByteField("number_of_entries", None), {AA.R, AA.W, AA.SBC})
+    ]
+    mandatory_operations = {OP.Create, OP.Delete, OP.Get, OP.Set}
+
+
+class Ieee8021pMapperServiceProfile(EntityClass):
+    class_id = 130
+    attributes = [
+        ECA(ShortField("managed_entity_id", None), {AA.R, AA.SBC}),
+        ECA(ShortField("tp_pointer", OmciNullPointer),
+            {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ShortField(
+            "interwork_tp_pointer_for_p_bit_priority_0", OmciNullPointer),
+            {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ShortField(
+            "interwork_tp_pointer_for_p_bit_priority_1", OmciNullPointer),
+            {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ShortField(
+            "interwork_tp_pointer_for_p_bit_priority_2", OmciNullPointer),
+            {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ShortField(
+            "interwork_tp_pointer_for_p_bit_priority_3", OmciNullPointer),
+            {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ShortField(
+            "interwork_tp_pointer_for_p_bit_priority_4", OmciNullPointer),
+            {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ShortField(
+            "interwork_tp_pointer_for_p_bit_priority_5", OmciNullPointer),
+            {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ShortField(
+            "interwork_tp_pointer_for_p_bit_priority_6", OmciNullPointer),
+            {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ShortField(
+            "interwork_tp_pointer_for_p_bit_priority_7", OmciNullPointer),
+            {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ByteField("unmarked_frame_option", None),
+            {AA.R, AA.W, AA.SetByCreate}),
+        ECA(StrFixedLenField("dscp_to_p_bit_mapping", None, length=24),
+            {AA.R, AA.W}),
+        ECA(ByteField("default_p_bit_marking", None),
+            {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ByteField("tp_type", None), {AA.R, AA.W, AA.SetByCreate},
+            optional=True)
+    ]
+    mandatory_operations = {OP.Create, OP.Delete, OP.Get, OP.Set}
+
+
+class VlanTaggingOperation(Packet):
+    name = "VlanTaggingOperation"
+    fields_desc = [
+        BitField("filter_outer_priority", 0, 4),
+        BitField("filter_outer_vid", 0, 13),
+        BitField("filter_outer_tpid_de", 0, 3),
+        BitField("pad1", 0, 12),
+
+        BitField("filter_inner_priority", 0, 4),
+        BitField("filter_inner_vid", 0, 13),
+        BitField("filter_inner_tpid_de", 0, 3),
+        BitField("pad2", 0, 8),
+        BitField("filter_ether_type", 0, 4),
+
+        BitField("treatment_tags_to_remove", 0, 2),
+        BitField("pad3", 0, 10),
+        BitField("treatment_outer_priority", 0, 4),
+        BitField("treatment_outer_vid", 0, 13),
+        BitField("treatment_outer_tpid_de", 0, 3),
+
+        BitField("pad4", 0, 12),
+        BitField("treatment_inner_priority", 0, 4),
+        BitField("treatment_inner_vid", 0, 13),
+        BitField("treatment_inner_tpid_de", 0, 3),
+    ]
+
+
+class ExtendedVlanTaggingOperationConfigurationData(EntityClass):
+    class_id = 171
+    attributes = [
+        ECA(ShortField("managed_entity_id", None), {AA.R, AA.SetByCreate}),
+        ECA(ByteField("association_type", None), {AA.R, AA.W, AA.SetByCreate}),
+        ECA(ShortField("received_vlan_tagging_operation_table_max_size", None),
+            {AA.R}),
+        ECA(ShortField("input_tpid", None), {AA.R, AA.W}),
+        ECA(ShortField("output_tpid", None), {AA.R, AA.W}),
+        ECA(ByteField("downstream_mode", None), {AA.R, AA.W}),
+        ECA(StrFixedLenField(
+            "received_frame_vlan_tagging_operation_table", None, 16),
+        # "received_frame_vlan_tagging_operation_table", None, VlanTaggingOperation),
+    {AA.R, AA.W}),
+        ECA(ShortField("associated_me_pointer", None), {AA.R, AA.W, AA.SBC})
+    ]
+    mandatory_operations = {OP.Create, OP.Delete, OP.Set, OP.Get, OP.GetNext}
+
+
+class Tcont(EntityClass):
+    class_id = 262
+    attributes = [
+        ECA(ShortField("managed_entity_id", None), {AA.R}),
+        ECA(ShortField("alloc_id", 0x00ff), {AA.R, AA.W}),
+        ECA(ByteField("mode_indicator", 1), {AA.R}),
+        ECA(ByteField("policy", None), {AA.R, AA.W}),  # addendum makes it R/W
+    ]
+    mandatory_operations = {OP.Get, OP.Set}
+
+
+class GemInterworkingTp(EntityClass):
+    class_id = 266
+    attributes = [
+        ECA(ShortField("managed_entity_id", None), {AA.R, AA.SetByCreate}),
+        ECA(ShortField("gem_port_network_ctp_pointer", None),
+            {AA.R, AA.W, AA.SBC}),
+        ECA(ByteField("interworking_option", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("service_profile_pointer", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("interworking_tp_pointer", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ByteField("pptp_counter", None), {AA.R}, optional=True),
+        ECA(ByteField("operational_state", None), {AA.R}, optional=True),
+        ECA(ShortField("gal_profile_pointer", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ByteField("gal_loopback_configuration", None),
+            {AA.R, AA.W}),
+    ]
+    mandatory_operations = {}  # TODO
+
+
+class GemPortNetworkCtp(EntityClass):
+    class_id = 268
+    attributes = [
+        ECA(ShortField("managed_entity_id", None), {AA.R, AA.SBC}),
+        ECA(ShortField("port_id", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("tcont_pointer", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ByteField("direction", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("traffic_management_pointer_upstream", None),
+            {AA.R, AA.W, AA.SBC}),
+        ECA(ShortField("traffic_descriptor_profile_pointer", None),
+            {AA.R, AA.W, AA.SBC}, optional=True),
+        ECA(ByteField("uni_counter", None), {AA.R}, optional=True),
+        ECA(ShortField("priority_queue_pointer_downstream", None),
+            {AA.R, AA.W, AA.SBC}),
+        ECA(ByteField("encryption_state", None), {AA.R}, optional=True)
+    ]
+    mandatory_operations = {OP.Create, OP.Delete, OP.Get, OP.Set}
+
+
+class GalEthernetProfile(EntityClass):
+    class_id = 272
+    attributes = [
+        ECA(ShortField("managed_entity_id", None), {AA.R, AA.SBC}),
+        ECA(ShortField("max_gem_payload_size", None), {AA.R, AA.W, AA.SBC}),
+    ]
+    mandatory_operations = {OP.Create, OP.Delete, OP.Get, OP.Set}
+
+
+class MulticastGemInterworkingTp(EntityClass):
+    class_id = 281
+    attributes = [
+        ECA(ShortField("managed_entity_id", None), {AA.R, AA.SBC}),
+        ECA(ShortField("gem_port_network_ctp_pointer", None), {AA.R, AA.SBC}),
+        ECA(ByteField("interworking_option", None), {AA.R, AA.SBC}),
+        ECA(ShortField("service_profile_pointer", None), {AA.R, AA.SBC}),
+        ECA(ShortField("interworking_tp_pointer", 0), {AA.R, AA.SBC}),
+        ECA(ByteField("pptp_counter", None), {AA.R}, optional=True),
+        ECA(ByteField("operational_state", None), {AA.R}, optional=True),
+        ECA(ShortField("gal_profile_pointer", None), {AA.R, AA.W, AA.SBC}),
+        ECA(ByteField("gal_loopback_configuration", None),
+            {AA.R, AA.W, AA.SBC}),
+        # TODO add multicast_address_table here (page 85 of spec.)
+        # ECA(...("multicast_address_table", None), {AA.R, AA.W})
+    ]
+    mandatory_operations = {OP.Create, OP.Delete, OP.Get, OP.GetNext, OP.Set}
+
+
+# entity class lookup table from entity_class values
+entity_classes_name_map = dict(
+    inspect.getmembers(sys.modules[__name__],
+    lambda o: inspect.isclass(o) and \
+              issubclass(o, EntityClass) and \
+              o is not EntityClass)
+)
+
+entity_classes = [c for c in entity_classes_name_map.itervalues()]
+entity_id_to_class_map = dict((c.class_id, c) for c in entity_classes)
diff --git a/voltha/extensions/omci/omci_frame.py b/voltha/extensions/omci/omci_frame.py
new file mode 100644
index 0000000..5c8dd20
--- /dev/null
+++ b/voltha/extensions/omci/omci_frame.py
@@ -0,0 +1,116 @@
+#
+# Copyright 2016 the original author or authors.
+#
+# 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.
+#
+from scapy.fields import ByteField, PacketField, IntField
+from scapy.fields import ShortField, ConditionalField
+from scapy.packet import Packet
+
+from voltha.extensions.omci.omci_defs import FixedLenField
+from voltha.extensions.omci.omci_messages import OmciCreate, OmciDelete, \
+    OmciDeleteResponse, OmciSet, OmciSetResponse, OmciGet, OmciGetResponse, \
+    OmciGetAllAlarms, OmciGetAllAlarmsResponse, OmciGetAllAlarmsNext, \
+    OmciMibResetResponse, OmciMibReset, OmciMibUploadNextResponse, \
+    OmciMibUploadNext, OmciMibUploadResponse, OmciMibUpload, \
+    OmciGetAllAlarmsNextResponse
+from voltha.extensions.omci.omci_messages import OmciCreateResponse
+
+
+class OmciFrame(Packet):
+    name = "OmciFrame"
+    fields_desc = [
+        ShortField("transaction_id", 0),
+        ByteField("message_type", None),
+        ByteField("omci", 0x0a),
+        ConditionalField(FixedLenField(
+            PacketField("omci_message", None, OmciCreate), align=36),
+            lambda pkt: pkt.message_type == OmciCreate.message_id),
+        ConditionalField(FixedLenField(
+            PacketField("omci_message", None, OmciCreateResponse), align=36),
+            lambda pkt: pkt.message_type == OmciCreateResponse.message_id),
+        ConditionalField(FixedLenField(
+            PacketField("omci_message", None, OmciDelete), align=36),
+            lambda pkt: pkt.message_type == OmciDelete.message_id),
+        ConditionalField(FixedLenField(
+            PacketField("omci_message", None, OmciDeleteResponse), align=36),
+            lambda pkt: pkt.message_type == OmciDeleteResponse.message_id),
+        ConditionalField(FixedLenField(
+            PacketField("omci_message", None, OmciSet), align=36),
+            lambda pkt: pkt.message_type == OmciSet.message_id),
+        ConditionalField(FixedLenField(
+            PacketField("omci_message", None, OmciSetResponse), align=36),
+            lambda pkt: pkt.message_type == OmciSetResponse.message_id),
+        ConditionalField(FixedLenField(
+            PacketField("omci_message", None, OmciGet), align=36),
+            lambda pkt: pkt.message_type == OmciGet.message_id),
+        ConditionalField(FixedLenField(
+            PacketField("omci_message", None, OmciGetResponse), align=36),
+            lambda pkt: pkt.message_type == OmciGetResponse.message_id),
+        ConditionalField(FixedLenField(
+            PacketField("omci_message", None, OmciGetAllAlarms), align=36),
+            lambda pkt: pkt.message_type == OmciGetAllAlarms.message_id),
+        ConditionalField(FixedLenField(
+            PacketField(
+                "omci_message", None, OmciGetAllAlarmsResponse), align=36),
+                lambda pkt:
+                pkt.message_type == OmciGetAllAlarmsResponse.message_id),
+        ConditionalField(FixedLenField(
+            PacketField("omci_message", None, OmciGetAllAlarmsNext), align=36),
+            lambda pkt: pkt.message_type == OmciGetAllAlarmsNext.message_id),
+        ConditionalField(FixedLenField(
+            PacketField(
+                "omci_message", None, OmciGetAllAlarmsNextResponse), align=36),
+                lambda pkt:
+                pkt.message_type == OmciGetAllAlarmsNextResponse.message_id),
+        ConditionalField(FixedLenField(
+            PacketField("omci_message", None, OmciMibUpload), align=36),
+            lambda pkt: pkt.message_type == OmciMibUploadResponse.message_id),
+        ConditionalField(FixedLenField(
+            PacketField("omci_message", None, OmciMibUploadNext), align=36),
+            lambda pkt:
+                pkt.message_type == OmciMibUploadNextResponse.message_id),
+        ConditionalField(FixedLenField(
+            PacketField("omci_message", None, OmciMibReset), align=36),
+            lambda pkt: pkt.message_type == OmciMibReset.message_id),
+        ConditionalField(FixedLenField(
+            PacketField("omci_message", None, OmciMibResetResponse), align=36),
+            lambda pkt: pkt.message_type == OmciMibResetResponse.message_id),
+
+        # TODO add entries for remaining OMCI message types
+
+        IntField("omci_trailer", 0x00000028)
+    ]
+
+    # We needed to patch the do_dissect(...) method of Packet, because
+    # it wiped out already dissected conditional fields with None if they
+    # referred to the same field name. We marked the only new line of code
+    # with "Extra condition added".
+    def do_dissect(self, s):
+        raw = s
+        self.raw_packet_cache_fields = {}
+        for f in self.fields_desc:
+            if not s:
+                break
+            s, fval = f.getfield(self, s)
+            # We need to track fields with mutable values to discard
+            # .raw_packet_cache when needed.
+            if f.islist or f.holds_packets:
+                self.raw_packet_cache_fields[f.name] = f.do_copy(fval)
+            # Extra condition added
+            if fval is not None or f.name not in self.fields:
+                self.fields[f.name] = fval
+        assert(raw.endswith(s))
+        self.raw_packet_cache = raw[:-len(s)] if s else raw
+        self.explicit = 1
+        return s
diff --git a/voltha/extensions/omci/omci_messages.py b/voltha/extensions/omci/omci_messages.py
new file mode 100644
index 0000000..3710702
--- /dev/null
+++ b/voltha/extensions/omci/omci_messages.py
@@ -0,0 +1,287 @@
+#
+# Copyright 2016 the original author or authors.
+#
+# 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.
+#
+from enum import Enum
+from scapy.fields import ByteField, StrFixedLenField, ConditionalField, Field
+from scapy.fields import ShortField
+from scapy.packet import Packet
+
+from voltha.extensions.omci.omci_defs import AttributeAccess
+from voltha.extensions.omci.omci_entities import entity_id_to_class_map
+
+
+class OmciData(Field):
+
+    __slots__ = Field.__slots__ + ['_entity_class']
+
+    def __init__(self, name, entity_class="entity_class"):
+        Field.__init__(self, name=name, default=None, fmt='s')
+        self._entity_class = entity_class
+
+    def addfield(self, pkt, s, val):
+        class_id = getattr(pkt, self._entity_class)
+        entity_class = entity_id_to_class_map.get(class_id)
+        for attribute in entity_class.attributes:
+            if AttributeAccess.SetByCreate not in attribute._access:
+                continue
+            if attribute._fld.name == 'managed_entity_id':
+                continue
+            fld = attribute._fld
+            s = fld.addfield(pkt, s, val.get(fld.name, fld.default))
+        return s
+
+    def getfield(self, pkt, s):
+        """Extract an internal value from a string"""
+        class_id = getattr(pkt, self._entity_class)
+        entity_class = entity_id_to_class_map.get(class_id)
+        data = {}
+        for attribute in entity_class.attributes:
+            if AttributeAccess.SetByCreate not in attribute._access:
+                continue
+            if attribute._fld.name == 'managed_entity_id':
+                continue
+            fld = attribute._fld
+            s, value = fld.getfield(pkt, s)
+            data[fld.name] = value
+        return s, data
+
+
+class OmciMaskedData(Field):
+
+    __slots__ = Field.__slots__ + ['_entity_class', '_attributes_mask']
+
+    def __init__(self, name, entity_class="entity_class",
+                 attributes_mask="attributes_mask"):
+        Field.__init__(self, name=name, default=None, fmt='s')
+        self._entity_class = entity_class
+        self._attributes_mask = attributes_mask
+
+    def addfield(self, pkt, s, val):
+        class_id = getattr(pkt, self._entity_class)
+        attribute_mask = getattr(pkt, self._attributes_mask)
+        entity_class = entity_id_to_class_map.get(class_id)
+        indices = entity_class.attribute_indices_from_mask(attribute_mask)
+        for index in indices:
+            fld = entity_class.attributes[index]._fld
+            s = fld.addfield(pkt, s, val[fld.name])
+        return s
+
+    def getfield(self, pkt, s):
+        """Extract an internal value from a string"""
+        class_id = getattr(pkt, self._entity_class)
+        attribute_mask = getattr(pkt, self._attributes_mask)
+        entity_class = entity_id_to_class_map.get(class_id)
+        indices = entity_class.attribute_indices_from_mask(attribute_mask)
+        data = {}
+        for index in indices:
+            fld = entity_class.attributes[index]._fld
+            s, value = fld.getfield(pkt, s)
+            data[fld.name] = value
+        return  s, data
+
+
+class OmciMessage(Packet):
+    name = "OmciMessage"
+    message_id = None  # OMCI message_type value, filled by derived classes
+    fields_desc = []
+
+
+class OmciCreate(OmciMessage):
+    name = "OmciCreate"
+    message_id = 0x44
+    fields_desc = [
+        ShortField("entity_class", None),
+        ShortField("entity_id", 0),
+        OmciData("data")
+    ]
+
+
+class OmciCreateResponse(OmciMessage):
+    name = "OmciCreateResponse"
+    message_id = 0x24
+    fields_desc = [
+        ShortField("entity_class", None),
+        ShortField("entity_id", None),
+        ByteField("success_code", 0),
+        ShortField("parameter_error_attributes_mask", None),
+    ]
+
+
+class OmciDelete(OmciMessage):
+    name = "OmciDelete"
+    message_id = 0x46
+    fields_desc = [
+        ShortField("entity_class", None),
+        ShortField("entity_id", None),
+    ]
+
+
+class OmciDeleteResponse(OmciMessage):
+    name = "OmciDeleteResponse"
+    message_id = 0x26
+    fields_desc = [
+        ShortField("entity_class", None),
+        ShortField("entity_id", None),
+        ByteField("success_code", 0),
+    ]
+
+
+class OmciSet(OmciMessage):
+    name = "OmciSet"
+    message_id = 0x48
+    fields_desc = [
+        ShortField("entity_class", None),
+        ShortField("entity_id", 0),
+        ShortField("attributes_mask", None),
+        OmciMaskedData("data")
+    ]
+
+
+class OmciSetResponse(OmciMessage):
+    name = "OmciSetResponse"
+    message_id = 0x28
+    fields_desc = [
+        ShortField("entity_class", None),
+        ShortField("entity_id", None),
+        ByteField("success_code", 0),
+        ShortField("unsupported_attributes_mask", None),
+        ShortField("failed_attributes_mask", None),
+    ]
+
+
+class OmciGet(OmciMessage):
+    name = "OmciGet"
+    message_id = 0x49
+    fields_desc = [
+        ShortField("entity_class", None),
+        ShortField("entity_id", 0),
+        ShortField("attributes_mask", None)
+    ]
+
+
+class OmciGetResponse(OmciMessage):
+    name = "OmciGetResponse"
+    message_id = 0x29
+    fields_desc = [
+        ShortField("entity_class", None),
+        ShortField("entity_id", 0),
+        ByteField("success_code", 0),
+        ShortField("attributes_mask", None),
+        ConditionalField(
+            OmciMaskedData("data"), lambda pkt: pkt.success_code == 0)
+    ]
+
+
+class OmciGetAllAlarms(OmciMessage):
+    name = "OmciGetAllAlarms"
+    message_id = 0x4b
+    fields_desc = [
+        ShortField("entity_class", 2),  # Always 2 (ONT data)
+        ShortField("entity_id", 0),  # Always 0 (ONT instance)
+        ByteField("alarm_retrieval_mode", 0)  # 0 or 1
+    ]
+
+
+class OmciGetAllAlarmsResponse(OmciMessage):
+    name = "OmciGetAllAlarmsResponse"
+    message_id = 0x2b
+    fields_desc = [
+        ShortField("entity_class", 2),  # Always 2 (ONT data)
+        ShortField("entity_id", 0),
+        ShortField("number_of_commands", None)
+    ]
+
+
+class OmciGetAllAlarmsNext(OmciMessage):
+    name = "OmciGetAllAlarmsNext"
+    message_id = 0x4c
+    fields_desc = [
+        ShortField("entity_class", 2),  # Always 2 (ONT data)
+        ShortField("entity_id", 0),
+        ShortField("command_sequence_number", None)
+    ]
+
+
+class OmciGetAllAlarmsNextResponse(OmciMessage):
+    name = "OmciGetAllAlarmsNextResponse"
+    message_id = 0x2c
+    fields_desc = [
+        ShortField("entity_class", 2),  # Always 2 (ONT data)
+        ShortField("entity_id", 0),
+        ShortField("alarmed_entity_class", None),
+        ShortField("alarmed_entity_id", 0),
+        StrFixedLenField("alarm_bit_map", None, 27)  # TODO better type?
+    ]
+
+
+class OmciMibUpload(OmciMessage):
+    name = "OmciMibUpload"
+    message_id = 0x4d
+    fields_desc = [
+        ShortField("entity_class", 2),  # Always 2 (ONT data)
+        ShortField("entity_id", 0),
+    ]
+
+
+class OmciMibUploadResponse(OmciMessage):
+    name = "OmciMibUploadResponse"
+    message_id = 0x2d
+    fields_desc = [
+        ShortField("entity_class", 2),  # Always 2 (ONT data)
+        ShortField("entity_id", 0),
+        ShortField("number_of_commands", None)
+    ]
+
+
+class OmciMibUploadNext(OmciMessage):
+    name = "OmciMibUploadNext"
+    message_id = 0x4e
+    fields_desc = [
+        ShortField("entity_class", 2),  # Always 2 (ONT data)
+        ShortField("entity_id", 0),
+        ShortField("command_sequence_number", None)
+    ]
+
+
+class OmciMibUploadNextResponse(OmciMessage):
+    name = "OmciMibUploadNextResponse"
+    message_id = 0x2e
+    fields_desc = [
+        ShortField("entity_class", 2),  # Always 2 (ONT data)
+        ShortField("entity_id", 0),
+        ShortField("object_entity_class", None),
+        ShortField("object_entity_id", 0),
+        ShortField("object_attributes_mask", None),
+        OmciMaskedData("object_data")
+    ]
+
+
+class OmciMibReset(OmciMessage):
+    name = "OmciMibReset"
+    message_id = 0x4f
+    fields_desc = [
+        ShortField("entity_class", None),
+        ShortField("entity_id", 0)
+    ]
+
+
+class OmciMibResetResponse(OmciMessage):
+    name = "OmciMibResetResponse"
+    message_id = 0x2f
+    fields_desc = [
+        ShortField("entity_class", None),
+        ShortField("entity_id", 0),
+        ByteField("success_code", 0)
+    ]