VOL-1395: Common shared libraries needed for Python based device adapters.
This is an initial check-in of code from the master branch. Additional work
is expected on a few items to work with the new go-core and will be covered
by separate JIRAs and commits.
Change-Id: I0856ec6b79b8d3e49082c609eb9c7eedd75b1708
diff --git a/python/adapters/extensions/omci/omci_fields.py b/python/adapters/extensions/omci/omci_fields.py
new file mode 100644
index 0000000..56e985b
--- /dev/null
+++ b/python/adapters/extensions/omci/omci_fields.py
@@ -0,0 +1,242 @@
+#
+# Copyright 2017 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 binascii
+import json
+from scapy.fields import Field, StrFixedLenField, PadField, IntField, FieldListField, ByteField, StrField, \
+ StrFixedLenField, PacketField
+from scapy.packet import Raw
+
+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
+
+
+class StrCompoundField(Field):
+ __slots__ = ['flds']
+
+ def __init__(self, name, flds):
+ super(StrCompoundField, self).__init__(name=name, default=None, fmt='s')
+ self.flds = flds
+ for fld in self.flds:
+ assert not fld.holds_packets, 'compound field cannot have packet field members'
+
+ def addfield(self, pkt, s, val):
+ for fld in self.flds:
+ # run though fake add/get to consume the relevant portion of the input value for this field
+ x, extracted = fld.getfield(pkt, fld.addfield(pkt, '', val))
+ l = len(extracted)
+ s = fld.addfield(pkt, s, val[0:l])
+ val = val[l:]
+ return s;
+
+ def getfield(self, pkt, s):
+ data = ''
+ for fld in self.flds:
+ s, value = fld.getfield(pkt, s)
+ if not isinstance(value, str):
+ value = fld.i2repr(pkt, value)
+ data += value
+ return s, data
+
+
+class XStrFixedLenField(StrFixedLenField):
+ """
+ XStrFixedLenField which value is printed as hexadecimal.
+ """
+ def i2m(self, pkt, x):
+ l = self.length_from(pkt) * 2
+ return None if x is None else binascii.a2b_hex(x)[0:l+1]
+
+ 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'
+ vendor_default = default[0:4] if default is not None else None
+ vendor_serial_default = default[4:12] if default is not None else None
+ 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):
+ assert isinstance(tblfld, PacketField)
+ assert hasattr(tblfld.cls, 'index'), 'No index() method defined for OmciTableField row object'
+ assert hasattr(tblfld.cls, 'is_delete'), 'No delete() method defined for OmciTableField row object'
+ 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
+
+ def to_json(self, new_values, old_values_json):
+ if not isinstance(new_values, list): new_values = [new_values] # If setting a scalar, augment the old table
+ else: old_values_json = None # If setting a vector of new values, erase all old_values
+
+ key_value_pairs = dict()
+
+ old_table = self.load_json(old_values_json)
+ for old in old_table:
+ index = old.index()
+ key_value_pairs[index] = old
+ for new in new_values:
+ index = new.index()
+ if new.is_delete():
+ del key_value_pairs[index]
+ else:
+ key_value_pairs[index] = new
+
+ new_table = []
+ for k, v in sorted(key_value_pairs.iteritems()):
+ assert isinstance(v, self.default.cls), 'object type for Omci Table row object invalid'
+ new_table.append(v.fields)
+
+ str_values = json.dumps(new_table, separators=(',', ':'))
+
+ return str_values
+
+ def load_json(self, json_str):
+ if json_str is None: json_str = '[]'
+ json_values = json.loads(json_str)
+ key_value_pairs = dict()
+ for json_value in json_values:
+ v = self.default.cls(**json_value)
+ index = v.index()
+ key_value_pairs[index] = v
+ table = []
+ for k, v in sorted(key_value_pairs.iteritems()):
+ table.append(v)
+ return table
\ No newline at end of file