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)
+ ]