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