VOL-1311 - OpenOMCI support for ME table attributes using "get/get-next"

Added new scapy field type of OmciTableField that can be used on an ME that
has a table attribute. Internal omcc message processing will intercept
GetResponses for such table attributes and perform an active processing
of N get-next sequences to the ONU to retrieve the complete table and
parse into a list of row elements.

Updated OMCI unit test framework to support timeout and deferred testing
to verify the get/get-next squence of table reads

Removed the partial work-around that performed table-set on BRCM onu
adapter mib-download during ME creation to prevent the table read.

Change-Id: Ia8e6c26eb512a5749592542752d0a58d2ad48866
diff --git a/tests/utests/voltha/extensions/omci/mock/mock_olt_handler.py b/tests/utests/voltha/extensions/omci/mock/mock_olt_handler.py
index f5075b1..142dbd8 100644
--- a/tests/utests/voltha/extensions/omci/mock/mock_olt_handler.py
+++ b/tests/utests/voltha/extensions/omci/mock/mock_olt_handler.py
@@ -95,7 +95,8 @@
         # somewhat responsive
 
         assert 0.0 <= self.latency <= 5, 'Best practice is latency <= 5 seconds'
-        reactor.callLater(self.latency, self._deliver_proxy_message, proxy_address, response)
+        if response is not None:
+            reactor.callLater(self.latency, self._deliver_proxy_message, proxy_address, response)
 
     def _deliver_proxy_message(self, proxy_address, response):
         from common.frameio.frameio import hexify
diff --git a/tests/utests/voltha/extensions/omci/mock/mock_onu.py b/tests/utests/voltha/extensions/omci/mock/mock_onu.py
index b662d49..a515bd9 100644
--- a/tests/utests/voltha/extensions/omci/mock/mock_onu.py
+++ b/tests/utests/voltha/extensions/omci/mock/mock_onu.py
@@ -53,6 +53,7 @@
                 },
                 # Additional OMCI GET request responses here if needed
             },
+            OP.GetNext.value: {},
             OP.Create.value: {
                 # TODO: Create some OMCI CREATE request responses here.
 
@@ -176,6 +177,7 @@
             OP.Delete.value: OmciDeleteResponse,
             OP.Set.value: OmciSetResponse,
             OP.Get.value: OmciGetResponse,
+            OP.GetNext.value: OmciGetNextResponse,
             OP.MibUpload.value: OmciMibUploadResponse,
             OP.MibUploadNext.value: OmciMibUploadNextResponse,
             OP.MibReset.value: OmciMibResetResponse,
@@ -219,6 +221,15 @@
                         pass
                         pass
 
+                    if isinstance(omci_message, OmciGetNext):
+                        response = response[omci_message.fields['command_sequence_number']]
+
+                    if isinstance(response, dict):
+                        if response['failures'] > 0:
+                            response['failures'] -= 1
+                            return None
+                        else: response = response['frame']
+
                     response.fields['transaction_id'] = transaction_id
                     if 'success_code' in response.fields['omci_message'].fields:
                         response.fields['omci_message'].fields['success_code'] = status
diff --git a/tests/utests/voltha/extensions/omci/test_omci_cc.py b/tests/utests/voltha/extensions/omci/test_omci_cc.py
index de80614..f2a1991 100644
--- a/tests/utests/voltha/extensions/omci/test_omci_cc.py
+++ b/tests/utests/voltha/extensions/omci/test_omci_cc.py
@@ -13,6 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+import binascii
+from common.frameio.frameio import hexify
+from nose.twistedtools import deferred
 from unittest import TestCase, main
 from mock.mock_adapter_agent import MockAdapterAgent
 from mock.mock_onu_handler import MockOnuHandler
@@ -20,6 +23,8 @@
 from mock.mock_onu import MockOnu
 from voltha.extensions.omci.omci_defs import *
 from voltha.extensions.omci.omci_frame import *
+from voltha.extensions.omci.omci_entities import *
+from voltha.extensions.omci.omci_me import ExtendedVlanTaggingOperationConfigurationDataFrame
 from voltha.extensions.omci.omci_cc import UNKNOWN_CLASS_ATTRIBUTE_KEY
 
 DEFAULT_OLT_DEVICE_ID = 'default_olt_mock'
@@ -81,11 +86,15 @@
         self.adapter_agent.add_child_device(self.olt_handler.device,
                                             self.onu_handler.device)
 
-    def _is_omci_frame(self, results):
+    def _is_omci_frame(self, results, omci_msg_type):
         assert isinstance(results, OmciFrame), 'Not OMCI Frame'
+        assert 'omci_message' in results.fields, 'Not OMCI Frame'
+        if omci_msg_type is not None:
+            assert isinstance(results.fields['omci_message'], omci_msg_type)
         return results
 
     def _check_status(self, results, value):
+        if value is not None: assert results is not None, 'unexpected emtpy message'
         status = results.fields['omci_message'].fields['success_code']
         assert status == value,\
             'Unexpected Status Code. Got {}, Expected: {}'.format(status, value)
@@ -97,7 +106,8 @@
                 self.onu_device.mib_data_sync, value)
         return results
 
-    def _check_stats(self, results, snapshot, stat, expected):
+    def _check_stats(self, results, _, stat, expected):
+        snapshot = self._snapshot_stats()
         assert snapshot[stat] == expected, \
             'Invalid statistic "{}". Got {}, Expected: {}'.format(stat,
                                                                   snapshot[stat],
@@ -674,6 +684,159 @@
         self.assertEqual(omci_cc.rx_unknown_tid, snapshot['rx_unknown_tid'] + 1)
         self.assertEqual(omci_cc.rx_onu_frames, snapshot['rx_onu_frames'])
 
+    def test_rx_decode_extvlantagging(self):
+        self.setup_one_of_each()
+
+        omci_cc = self.onu_handler.omci_cc
+        omci_cc.enabled = True
+        snapshot = self._snapshot_stats()
+
+        msg = '030a290a00ab0201000d00000000001031323334' \
+              '3536373839303132333435363738393031323334' \
+              '000000281166d283'
+
+        omci_cc.receive_message(hex2raw(msg))
+
+        self.assertEqual(omci_cc.rx_frames, snapshot['rx_frames'] + 1)
+        self.assertEqual(omci_cc.rx_unknown_me, snapshot['rx_unknown_me'])
+        self.assertEqual(omci_cc.rx_unknown_tid, snapshot['rx_unknown_tid'] + 1)
+        self.assertEqual(omci_cc.rx_onu_frames, snapshot['rx_onu_frames'])
+
+    def _check_vlan_tag_op(self, results, attr, expected):
+        omci_msg = results.fields['omci_message']
+        data = omci_msg.fields['data']
+        val = data[attr]
+        self.assertEqual(expected, val)
+        return results
+
+    @deferred()
+    def test_rx_table_get_extvlantagging(self):
+        self.setup_one_of_each()
+
+        onu = self.onu_handler.onu_mock
+        entity_id = 1
+        vlan_tag_op1 = VlanTaggingOperation(
+                                     filter_outer_priority=15,
+                                     filter_outer_vid=4096,
+                                     filter_outer_tpid_de=2,
+                                     filter_inner_priority=15,
+                                     filter_inner_vid=4096,
+                                     filter_inner_tpid_de=0,
+                                     filter_ether_type=0,
+                                     treatment_tags_to_remove=0,
+                                     treatment_outer_priority=15,
+                                     treatment_outer_vid=1234,
+                                     treatment_outer_tpid_de=0,
+                                     treatment_inner_priority=0,
+                                     treatment_inner_vid=4091,
+                                     treatment_inner_tpid_de=4,
+                                 )
+        vlan_tag_op2 = VlanTaggingOperation(
+                                     filter_outer_priority=14,
+                                     filter_outer_vid=1234,
+                                     filter_outer_tpid_de=5,
+                                     filter_inner_priority=1,
+                                     filter_inner_vid=2345,
+                                     filter_inner_tpid_de=1,
+                                     filter_ether_type=0,
+                                     treatment_tags_to_remove=1,
+                                     treatment_outer_priority=15,
+                                     treatment_outer_vid=2222,
+                                     treatment_outer_tpid_de=1,
+                                     treatment_inner_priority=1,
+                                     treatment_inner_vid=3333,
+                                     treatment_inner_tpid_de=5,
+                                 )
+        vlan_tag_op3 = VlanTaggingOperation(
+                                     filter_outer_priority=13,
+                                     filter_outer_vid=55,
+                                     filter_outer_tpid_de=1,
+                                     filter_inner_priority=7,
+                                     filter_inner_vid=4567,
+                                     filter_inner_tpid_de=1,
+                                     filter_ether_type=0,
+                                     treatment_tags_to_remove=1,
+                                     treatment_outer_priority=2,
+                                     treatment_outer_vid=1111,
+                                     treatment_outer_tpid_de=1,
+                                     treatment_inner_priority=1,
+                                     treatment_inner_vid=3131,
+                                     treatment_inner_tpid_de=5,
+                                 )
+        tbl = [vlan_tag_op1, vlan_tag_op2, vlan_tag_op3]
+        tblstr = str(vlan_tag_op1) + str(vlan_tag_op2) + str(vlan_tag_op3)
+
+        onu._omci_response[OP.Get.value][ExtendedVlanTaggingOperationConfigurationData.class_id] = {
+            entity_id: OmciFrame(transaction_id=0,
+                         message_type=OmciGetResponse.message_id,
+                         omci_message=OmciGetResponse(
+                               entity_class=ExtendedVlanTaggingOperationConfigurationData.class_id,
+                               entity_id=1,
+                               success_code=RC.Success.value,
+                               attributes_mask=ExtendedVlanTaggingOperationConfigurationData.mask_for(
+                                   'received_frame_vlan_tagging_operation_table'),
+                               data={'received_frame_vlan_tagging_operation_table': 16 * len(tbl)}
+                         ))
+        }
+
+        rsp1 = binascii.a2b_hex(hexify(tblstr[0:OmciTableField.PDU_SIZE]))
+        rsp2 = binascii.a2b_hex(hexify(tblstr[OmciTableField.PDU_SIZE:]))
+        onu._omci_response[OP.GetNext.value][ExtendedVlanTaggingOperationConfigurationData.class_id] = {
+            entity_id: {0: {'failures':2,
+                            'frame':OmciFrame(transaction_id=0,
+                                 message_type=OmciGetNextResponse.message_id,
+                                 omci_message=OmciGetNextResponse(
+                                     entity_class=ExtendedVlanTaggingOperationConfigurationData.class_id,
+                                     entity_id=1,
+                                     success_code=RC.Success.value,
+                                     attributes_mask=ExtendedVlanTaggingOperationConfigurationData.mask_for(
+                                         'received_frame_vlan_tagging_operation_table'),
+                                     data={'received_frame_vlan_tagging_operation_table': rsp1
+                                     }
+                         ))},
+                        1: OmciFrame(transaction_id=0,
+                         message_type=OmciGetNextResponse.message_id,
+                         omci_message=OmciGetNextResponse(
+                             entity_class=ExtendedVlanTaggingOperationConfigurationData.class_id,
+                             entity_id=1,
+                             success_code=RC.Success.value,
+                             attributes_mask=ExtendedVlanTaggingOperationConfigurationData.mask_for(
+                                 'received_frame_vlan_tagging_operation_table'),
+                             data={'received_frame_vlan_tagging_operation_table': rsp2
+                             }
+                         ))
+                       }
+        }
+
+        omci_cc = self.onu_handler.omci_cc
+        omci_cc.enabled = True
+
+        msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+            entity_id,
+            attributes={'received_frame_vlan_tagging_operation_table':True}
+        )
+
+        snapshot = self._snapshot_stats()
+
+        frame = msg.get()
+        d = omci_cc.send(frame, timeout=5.0)
+
+        d.addCallbacks(self._is_omci_frame, self._default_errback, [OmciGetResponse])
+        d.addCallback(self._check_status, RC.Success)
+
+        d.addCallback(self._check_stats, snapshot, 'tx_frames', snapshot['tx_frames'] + 5)
+        d.addCallback(self._check_stats, snapshot, 'rx_frames', snapshot['rx_frames'] + 3)
+        d.addCallback(self._check_stats, snapshot, 'rx_unknown_tid', snapshot['rx_unknown_tid'])
+        d.addCallback(self._check_stats, snapshot, 'rx_onu_frames', snapshot['rx_onu_frames'])
+        d.addCallback(self._check_stats, snapshot, 'rx_onu_discards', snapshot['rx_onu_discards'])
+        d.addCallback(self._check_stats, snapshot, 'rx_unknown_me', snapshot['rx_unknown_me'])
+        d.addCallback(self._check_stats, snapshot, 'rx_timeouts', snapshot['rx_timeouts'] + 2)
+        d.addCallback(self._check_stats, snapshot, 'tx_errors', snapshot['tx_errors'])
+        d.addCallback(self._check_stats, snapshot, 'consecutive_errors', 0)
+        d.addCallback(self._check_vlan_tag_op, 'received_frame_vlan_tagging_operation_table', tbl)
+
+        return d
+
 if __name__ == '__main__':
     main()
 
diff --git a/voltha/adapters/brcm_openomci_onu/omci/brcm_mib_download_task.py b/voltha/adapters/brcm_openomci_onu/omci/brcm_mib_download_task.py
index cbdb914..3bb1e78 100644
--- a/voltha/adapters/brcm_openomci_onu/omci/brcm_mib_download_task.py
+++ b/voltha/adapters/brcm_openomci_onu/omci/brcm_mib_download_task.py
@@ -508,26 +508,8 @@
                 # trying to read back table during post-create-read-missing-attributes
                 # But, because this is a R/W attribute. Some ONU may not accept the
                 # value during create. It is repeated again in a set below.
-                received_frame_vlan_tagging_operation_table=
-                VlanTaggingOperation(
-                    filter_outer_priority=15,  # This entry is not a double-tag rule
-                    filter_outer_vid=4096,     # Do not filter on the outer VID value
-                    filter_outer_tpid_de=0,    # Do not filter on the outer TPID field
-
-                    filter_inner_priority=15,
-                    filter_inner_vid=4096,
-                    filter_inner_tpid_de=0  ,
-                    filter_ether_type=0,
-
-                    treatment_tags_to_remove=0,
-                    treatment_outer_priority=15,
-                    treatment_outer_vid=0,
-                    treatment_outer_tpid_de=0,
-
-                    treatment_inner_priority=0,
-                    treatment_inner_vid=self._cvid,
-                    treatment_inner_tpid_de=4,
-                )
+                input_tpid=self._input_tpid,  # input TPID
+                output_tpid=self._output_tpid,  # output TPID
             )
 
             msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
@@ -546,7 +528,19 @@
                 input_tpid=self._input_tpid,    # input TPID
                 output_tpid=self._output_tpid,  # output TPID
                 downstream_mode=0,              # inverse of upstream
+            )
 
+            msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
+                attributes=attributes
+            )
+
+            frame = msg.set()
+            self.log.debug('openomci-msg', msg=msg)
+            results = yield omci_cc.send(frame)
+            self.check_status_and_state(results, 'set-extended-vlan-tagging-operation-configuration-data')
+
+            attributes = dict(
                 # parameters: Entity Id ( 0x900), Filter Inner Vlan Id(0x1000-4096,do not filter on Inner vid,
                 #             Treatment Inner Vlan Id : 2
 
@@ -585,7 +579,7 @@
             frame = msg.set()
             self.log.debug('openomci-msg', msg=msg)
             results = yield omci_cc.send(frame)
-            self.check_status_and_state(results, 'set-extended-vlan-tagging-operation-configuration-data')
+            self.check_status_and_state(results, 'set-extended-vlan-tagging-operation-configuration-data-table')
 
         except TimeoutError as e:
             self.log.warn('rx-timeout-2', e=e)
diff --git a/voltha/extensions/omci/database/mib_db_ext.py b/voltha/extensions/omci/database/mib_db_ext.py
index 1a895c5..13d19dc 100644
--- a/voltha/extensions/omci/database/mib_db_ext.py
+++ b/voltha/extensions/omci/database/mib_db_ext.py
@@ -17,7 +17,8 @@
 from voltha.protos.omci_mib_db_pb2 import MibInstanceData, MibClassData, \
     MibDeviceData, MibAttributeData, MessageType, ManagedEntity
 from voltha.extensions.omci.omci_entities import *
-from scapy.fields import StrField, FieldListField
+from voltha.extensions.omci.omci_fields import *
+from scapy.fields import StrField, FieldListField, PacketField
 
 
 class MibDbStatistic(object):
@@ -160,7 +161,7 @@
                 from voltha.extensions.omci.omci_cc import UNKNOWN_CLASS_ATTRIBUTE_KEY
                 field = StrFixedLenField(UNKNOWN_CLASS_ATTRIBUTE_KEY, None, 24)
 
-            if isinstance(field, StrFixedLenField):
+            if isinstance(field, StrFixedLenField) or isinstance(field, MultipleTypeField):
                 from scapy.base_classes import Packet_metaclass
                 #  For StrFixedLenField, value is a str already (or possibly JSON encoded)
                 if hasattr(value, 'to_json') and not isinstance(value, basestring):
@@ -230,10 +231,13 @@
                 from voltha.extensions.omci.omci_cc import UNKNOWN_CLASS_ATTRIBUTE_KEY
                 field = StrFixedLenField(UNKNOWN_CLASS_ATTRIBUTE_KEY, None, 24)
 
-            if isinstance(field, StrFixedLenField):
+            if isinstance(field, StrFixedLenField) or isinstance(field, MultipleTypeField):
                 from scapy.base_classes import Packet_metaclass
-                if isinstance(field.default, Packet_metaclass) and \
-                        hasattr(field.default, 'to_json'):
+                default = field.default
+                if isinstance(field.default, PacketField):
+                    default = default.cls
+                if isinstance(default, Packet_metaclass) and \
+                        hasattr(default, 'to_json'):
                     value = json.loads(str_value)
                 else:
                     value = str_value
diff --git a/voltha/extensions/omci/omci_cc.py b/voltha/extensions/omci/omci_cc.py
index f8b2be0..22babf0 100644
--- a/voltha/extensions/omci/omci_cc.py
+++ b/voltha/extensions/omci/omci_cc.py
@@ -20,10 +20,12 @@
 import sys
 import arrow
 from twisted.internet import reactor, defer
-from twisted.internet.defer import DeferredQueue, TimeoutError, CancelledError, failure, fail
+from twisted.internet.defer import DeferredQueue, TimeoutError, CancelledError, failure, fail, inlineCallbacks
 from common.frameio.frameio import hexify
 from voltha.extensions.omci.omci import *
 from voltha.extensions.omci.omci_me import OntGFrame, OntDataFrame
+from voltha.extensions.omci.me_frame import MEFrame
+from voltha.extensions.omci.omci_defs import ReasonCodes
 from common.event_bus import EventBusClient
 from enum import IntEnum
 from binascii import hexlify
@@ -283,7 +285,7 @@
                     omci_entities.entity_id_to_class_map = saved_me_map     # Always restore it.
 
                 try:
-                    (ts, d, tx_frame, _) = self._requests.pop(rx_tid)
+                    (ts, d, tx_frame, timeout) = self._requests.pop(rx_tid)
 
                     ts_diff = now - arrow.Arrow.utcfromtimestamp(ts)
                     secs = ts_diff.total_seconds()
@@ -307,15 +309,97 @@
                         return d.errback(failure.Failure(e))
                     return
 
-                # Notify sender of completed request
-                reactor.callLater(0, d.callback, rx_frame)
-
-                # Publish Rx event to listeners in a different task
-                reactor.callLater(0, self._publish_rx_frame, tx_frame, rx_frame)
+                reactor.callLater(0, self._process_rx_frame, timeout, secs, rx_frame, d, tx_frame)
 
             except Exception as e:
                 self.log.exception('rx-msg', e=e)
 
+    @inlineCallbacks
+    def _process_rx_frame(self, timeout, secs, rx_frame, d, tx_frame):
+        omci_msg = rx_frame.fields['omci_message']
+        if isinstance(omci_msg, OmciGetResponse) and 'table_attribute_mask' in omci_msg.fields['data']:
+            try:
+                entity_class = omci_msg.fields['entity_class']
+                entity_id = omci_msg.fields['entity_id']
+                table_attributes = omci_msg.fields['data']['table_attribute_mask']
+
+                device = self._adapter_agent.get_device(self._device_id)
+                if entity_class in self._me_map:
+                    ec = self._me_map[entity_class]
+                    for index in xrange(16):
+                        attr_mask = 1 << index
+
+                        if attr_mask & table_attributes:
+                            eca = ec.attributes[index]
+                            self.log.debug('omcc-get-table-attribute', table_name=eca.field.name)
+
+                            seq_no = 0
+                            data_buffer = ''
+                            count = omci_msg.fields['data'][eca.field.name + '_size']
+
+                            # Original timeout must be chopped up into each individual get-next request
+                            # in order for total transaction to complete within the timeframe of the
+                            # original get() timeout.
+                            number_transactions = 1 +  (count + OmciTableField.PDU_SIZE - 1) / OmciTableField.PDU_SIZE
+                            timeout /= (1 + number_transactions)
+
+                            # Start the loop
+                            vals = []
+                            for offset in xrange(0, count, OmciTableField.PDU_SIZE):
+                                frame = MEFrame(ec, entity_id, {eca.field.name: seq_no}).get_next()
+                                seq_no += 1
+
+                                max_retries = 3
+                                retries = max_retries
+                                while True:
+                                    try:
+                                        results = yield self.send(frame,
+                                            min(timeout / max_retries,
+                                                secs * 2 * (max_retries - retries + 1)))
+
+                                        omci_getnext_msg = results.fields['omci_message']
+                                        status = omci_getnext_msg.fields['success_code']
+
+                                        if status != ReasonCodes.Success.value:
+                                            raise Exception('omci-status ' + status)
+
+                                        break
+                                    except Exception as e:
+                                        self.log.exception('get-next-error ' + eca.field.name, e=e)
+                                        retries -= 1
+                                        if retries <= 0:
+                                            raise e
+
+                                # Extract the data
+                                num_octets = count - offset
+                                if num_octets > OmciTableField.PDU_SIZE:
+                                    num_octets = OmciTableField.PDU_SIZE
+
+                                data = omci_getnext_msg.fields['data'][eca.field.name]
+                                data_buffer += data[:num_octets]
+
+                            while data_buffer:
+                                data_buffer, val = eca.field.getfield(None, data_buffer)
+                                vals.append(val)
+
+                            omci_msg.fields['data'][eca.field.name] = vals;
+                            del omci_msg.fields['data'][eca.field.name + '_size']
+                            self.log.debug('omcc-got-table-attribute-rows', table_name=eca.field.name,
+                                          row_count=len(vals))
+                del omci_msg.fields['data']['table_attribute_mask']
+
+            except Exception as e:
+                self.log.exception('get-next-error', e=e)
+                d.errback(failure.Failure(e))
+                return
+
+        # Notify sender of completed request
+        reactor.callLater(0, d.callback, rx_frame)
+
+        # Publish Rx event to listeners in a different task except for internally-consumed get-next-response
+        if not isinstance(omci_msg, OmciGetNextResponse):
+            reactor.callLater(0, self._publish_rx_frame, tx_frame, rx_frame)
+
     def _decode_unknown_me(self, msg):
         """
         Decode an ME for an unsupported class ID.  This should only occur for a subset
diff --git a/voltha/extensions/omci/omci_entities.py b/voltha/extensions/omci/omci_entities.py
index b523ebf..f97fc2d 100644
--- a/voltha/extensions/omci/omci_entities.py
+++ b/voltha/extensions/omci/omci_entities.py
@@ -20,12 +20,12 @@
 from bitstring import BitArray
 import json
 from scapy.fields import ByteField, ShortField, MACField, BitField, IPField
-from scapy.fields import IntField, StrFixedLenField, LongField, FieldListField
+from scapy.fields import IntField, StrFixedLenField, LongField, FieldListField, PacketLenField
 from scapy.packet import Packet
 
 from voltha.extensions.omci.omci_defs import OmciUninitializedFieldError, \
     AttributeAccess, OmciNullPointer, EntityOperations, OmciInvalidTypeError
-from voltha.extensions.omci.omci_fields import OmciSerialNumberField
+from voltha.extensions.omci.omci_fields import OmciSerialNumberField, OmciTableField
 from voltha.extensions.omci.omci_defs import bitpos_from_mask
 
 class EntityClassAttribute(object):
@@ -92,6 +92,7 @@
         'MACField': lambda val: True,   # TODO: Add a constraint for this field type
         'BitField': lambda val: True,   # TODO: Add a constraint for this field type
         'IPField': lambda val: True,    # TODO: Add a constraint for this field type
+        'OmciTableField': lambda val: True,
 
         # TODO: As additional Scapy field types are used, add constraints
     }
@@ -630,8 +631,9 @@
         ECA(ShortField("output_tpid", None), {AA.R, AA.W}),
         ECA(ByteField("downstream_mode", None), {AA.R, AA.W},
             range_check=lambda x: 0 <= x <= 8),
-        ECA(StrFixedLenField("received_frame_vlan_tagging_operation_table",
-                             VlanTaggingOperation, 16), {AA.R, AA.W}),
+        ECA(OmciTableField(
+            PacketLenField("received_frame_vlan_tagging_operation_table", None,
+                VlanTaggingOperation, length_from=lambda pkt: 16)), {AA.R, AA.W}),
         ECA(ShortField("associated_me_pointer", None), {AA.R, AA.W, AA.SBC}),
         ECA(FieldListField("dscp_to_p_bit_mapping", None,
                            BitField('',  0, size=3), count_from=lambda _: 64),
diff --git a/voltha/extensions/omci/omci_fields.py b/voltha/extensions/omci/omci_fields.py
index a6df815..b7241bf 100644
--- a/voltha/extensions/omci/omci_fields.py
+++ b/voltha/extensions/omci/omci_fields.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 #
 import binascii
-from scapy.fields import Field, StrFixedLenField, PadField
+from scapy.fields import Field, StrFixedLenField, PadField, IntField, FieldListField, ByteField, StrField, StrFixedLenField
 from scapy.packet import Raw
 
 class FixedLenField(PadField):
@@ -70,6 +70,96 @@
     def m2i(self, pkt, x):
         return None if x is None else binascii.b2a_hex(x)
 
+class MultipleTypeField(object):
+    """MultipleTypeField are used for fields that can be implemented by
+        various Field subclasses, depending on conditions on the packet.
+
+        It is initialized with `flds` and `default`.
+
+        `default` is the default field type, to be used when none of the
+        conditions matched the current packet.
+
+        `flds` is a list of tuples (`fld`, `cond`), where `fld` if a field
+        type, and `cond` a "condition" to determine if `fld` is the field type
+        that should be used.
+
+        `cond` is either:
+
+        - a callable `cond_pkt` that accepts one argument (the packet) and
+            returns True if `fld` should be used, False otherwise.
+
+          - a tuple (`cond_pkt`, `cond_pkt_val`), where `cond_pkt` is the same
+            as in the previous case and `cond_pkt_val` is a callable that
+            accepts two arguments (the packet, and the value to be set) and
+            returns True if `fld` should be used, False otherwise.
+
+        See scapy.layers.l2.ARP (type "help(ARP)" in Scapy) for an example of
+        use.
+    """
+
+    __slots__ = ["flds", "default", "name"]
+
+    def __init__(self, flds, default):
+        self.flds  = flds
+        self.default = default
+        self.name = self.default.name
+
+    def _find_fld_pkt(self, pkt):
+        """Given a Packet instance `pkt`, returns the Field subclass to be
+            used. If you know the value to be set (e.g., in .addfield()), use
+            ._find_fld_pkt_val() instead.
+        """
+        for fld, cond in self.flds:
+            if isinstance(cond, tuple):
+                cond = cond[0]
+            if cond(pkt):
+                return fld
+        return self.default
+
+    def _find_fld_pkt_val(self, pkt, val):
+        """Given a Packet instance `pkt` and the value `val` to be set,
+            returns the Field subclass to be used.
+        """
+        for fld, cond in self.flds:
+            if isinstance(cond, tuple):
+                if cond[1](pkt, val):
+                    return fld
+            elif cond(pkt):
+                return fld
+        return self.default
+
+    def getfield(self, pkt, s):
+        return self._find_fld_pkt(pkt).getfield(pkt, s)
+
+    def addfield(self, pkt, s, val):
+        return self._find_fld_pkt_val(pkt, val).addfield(pkt, s, val)
+
+    def any2i(self, pkt, val):
+        return self._find_fld_pkt_val(pkt, val).any2i(pkt, val)
+
+    def h2i(self, pkt, val):
+        return self._find_fld_pkt_val(pkt, val).h2i(pkt, val)
+
+    def i2h(self, pkt, val):
+        return self._find_fld_pkt_val(pkt, val).i2h(pkt, val)
+
+    def i2m(self, pkt, val):
+        return self._find_fld_pkt_val(pkt, val).i2m(pkt, val)
+
+    def i2len(self, pkt, val):
+        return self._find_fld_pkt_val(pkt, val).i2len(pkt, val)
+
+    def i2repr(self, pkt, val):
+        return self._find_fld_pkt_val(pkt, val).i2repr(pkt, val)
+
+    def register_owner(self, cls):
+        for fld, _ in self.flds:
+            fld.owners.append(cls)
+        self.dflt.owners.append(cls)
+
+    def __getattr__(self, attr):
+        return getattr(self._find_fld(), attr)
+
 class OmciSerialNumberField(StrCompoundField):
     def __init__(self, name, default=None):
         assert default is None or (isinstance(default, str) and len(default) == 12), 'invalid default serial number'
@@ -78,3 +168,28 @@
         super(OmciSerialNumberField, self).__init__(name,
             [StrFixedLenField('vendor_id', vendor_default, 4),
             XStrFixedLenField('vendor_serial_number', vendor_serial_default, 4)])
+
+class OmciTableField(MultipleTypeField):
+    def __init__(self, tblfld):
+        super(OmciTableField, self).__init__(
+            [
+            (IntField('table_length', 0), (self.cond_pkt, self.cond_pkt_val)),
+            (PadField(StrField('me_type_table', None), OmciTableField.PDU_SIZE),
+                (self.cond_pkt2, self.cond_pkt_val2))
+            ], tblfld)
+
+    PDU_SIZE = 29 # Baseline message set raw get-next PDU size
+    OmciGetResponseMessageId = 0x29 # Ugh circular dependency
+    OmciGetNextResponseMessageId = 0x3a # Ugh circular dependency
+
+    def cond_pkt(self, pkt):
+        return pkt is not None and pkt.message_id == self.OmciGetResponseMessageId
+
+    def cond_pkt_val(self, pkt, val):
+        return pkt is not None and pkt.message_id == self.OmciGetResponseMessageId
+
+    def cond_pkt2(self, pkt):
+        return pkt is not None and pkt.message_id == self.OmciGetNextResponseMessageId
+
+    def cond_pkt_val2(self, pkt, val):
+        return pkt is not None and pkt.message_id == self.OmciGetNextResponseMessageId
diff --git a/voltha/extensions/omci/omci_messages.py b/voltha/extensions/omci/omci_messages.py
index b836199..dab467b 100644
--- a/voltha/extensions/omci/omci_messages.py
+++ b/voltha/extensions/omci/omci_messages.py
@@ -19,6 +19,7 @@
 from scapy.packet import Packet
 
 from voltha.extensions.omci.omci_defs import AttributeAccess
+from  voltha.extensions.omci.omci_fields import OmciTableField
 import voltha.extensions.omci.omci_entities as omci_entities
 
 
@@ -88,6 +89,7 @@
         entity_class = omci_entities.entity_id_to_class_map[class_id]
         indices = entity_class.attribute_indices_from_mask(attribute_mask)
         data = {}
+        table_attribute_mask = 0
         for index in indices:
             try:
                 fld = entity_class.attributes[index].field
@@ -99,7 +101,13 @@
                 s, value = fld.getfield(pkt, s)
             except Exception, e:
                 raise
-            data[fld.name] = value
+            if isinstance(pkt, OmciGetResponse) and isinstance(fld, OmciTableField):
+                data[fld.name + '_size'] = value
+                table_attribute_mask = table_attribute_mask | (1 << index)
+            else:
+                data[fld.name] = value
+        if table_attribute_mask:
+            data['table_attribute_mask'] = table_attribute_mask
         return s, data