def unpack(self, buffer, offset=0): header = UBInt16() header.unpack(buffer[offset:offset+2]) self.type = header.value >> 9 length = header.value & 511 begin, end = offset + 2, offset + 2 + length sub_type = UBInt8() sub_type.unpack(buffer[begin:begin+1]) self.sub_type = sub_type.value self.sub_value = BinaryData(buffer[begin+1:end])
def __init__(self, tlv_type=1, sub_type=7, sub_value=None): """Create an instance and set its attributes. Args: tlv_type (int): Type used by this class. Defaults to 1. sub_type (int): Sub type value used by this class. Defaults to 7. sub_value (:class:`~pyof.foundation.basic_types.BinaryData`): Data stored by TLVWithSubType. Defaults to empty BinaryData. """ super().__init__(tlv_type) self.sub_type = sub_type self.sub_value = BinaryData() if sub_value is None else sub_value
def __init__(self, tlv_type=1, sub_type=7, sub_value=None): """Create an instance and set its attributes.""" super().__init__(tlv_type=tlv_type) self.sub_type = sub_type if sub_value is None: sub_value = BinaryData() self.sub_value = sub_value
def _get_body_instance(self): """Return the body instance.""" simple_body = { MultipartTypes.OFPMP_FLOW: FlowStatsRequest, MultipartTypes.OFPMP_AGGREGATE: AggregateStatsRequest, MultipartTypes.OFPMP_PORT_STATS: PortStatsRequest, MultipartTypes.OFPMP_QUEUE: QueueStatsRequest, MultipartTypes.OFPMP_GROUP: GroupStatsRequest, MultipartTypes.OFPMP_METER: MeterMultipartRequest, MultipartTypes.OFPMP_EXPERIMENTER: ExperimenterMultipartHeader } array_of_bodies = {MultipartTypes.OFPMP_TABLE_FEATURES: TableFeatures} if isinstance(self.multipart_type, UBInt16): self.multipart_type = self.multipart_type.enum_ref( self.multipart_type.value) pyof_class = simple_body.get(self.multipart_type, None) if pyof_class: return pyof_class() array_of_class = array_of_bodies.get(self.multipart_type, None) if array_of_class: return FixedTypeList(pyof_class=array_of_class) return BinaryData(b'')
class HelloElemHeader(GenericStruct): """Common header for all Hello Elements.""" element_type = UBInt16() length = UBInt16() content = BinaryData() def __init__(self, element_type=None, length=None, content=b''): """Create a HelloElemHeader with the optional parameters below. Args: element_type: One of OFPHET_*. length: Length in bytes of the element, including this header, excluding padding. """ super().__init__() self.element_type = element_type self.length = length self.content = content def pack(self, value=None): """Update the length and pack the massege into binary data. Returns: bytes: A binary data that represents the Message. Raises: Exception: If there are validation errors. """ if value is None: self.update_length() return super().pack() if isinstance(value, type(self)): return value.pack() msg = "{} is not an instance of {}".format(value, type(self).__name__) raise PackException(msg) def update_length(self): """Update length attribute.""" self.length = self.get_size() def unpack(self, buff=None, offset=0): """Unpack *buff* into this object. This method will convert a binary data into a readable value according to the attribute format. Args: buff (bytes): Binary buffer. offset (int): Where to begin unpacking. Raises: :exc:`~.exceptions.UnpackException`: If unpack fails. """ length = UBInt16() length.unpack(buff, offset=offset+2) super().unpack(buff[:offset+length.value], offset)
class TCP(GenericStruct): """TCP """ #: data (:class:`BinaryData`): The content of the packet in binary format. data = BinaryData() def __init__(self): super().__init__() def pack(self, value=None): """Pack the struct in a binary representation. Merge some fields to ensure correct packing. Args: value: """ return super().pack() def unpack(self, buffer, offset=0): """Unpack a binary message into this object's attributes. Unpack the binary value *buff* and update this object attributes based on the results. Args: buffer (bytes): Binary data package to be unpacked. offset (int): Where to begin unpacking. Raises: Exception: If there is a struct unpacking error. """ pass
def test_GenTLV_value_unpack(self): """Value attribute should be the same after unpacking.""" value = BinaryData(b'test') tlv = GenericTLV(value=value) tlv_unpacked = GenericTLV() tlv_unpacked.unpack(tlv.pack()) self.assertEqual(tlv.value.value, tlv_unpacked.value.value)
class MultipartReply(GenericMessage): """Reply datapath state. While the system is running, the controller may reply state from the datapath using the OFPT_MULTIPART_Reply message. """ #: :class:`~.common.header.Header` header = Header(message_type=Type.OFPT_PORT_MOD) #: One of the OFPMP_* constants. multipart_type = UBInt16(enum_ref=MultipartTypes) #: OFPMPF_REPLY_* flags. flags = UBInt16(enum_ref=MultipartReplyFlags) #: Padding pad = Pad(4) #: Body of the reply body = BinaryData() def __init__(self, xid=None, multipart_type=None, flags=None, body=b''): """The constructor just assings parameters to object attributes. Args: xid (int): xid to the header. multipart_type (int): One of the OFPMP_* constants. flags (int): OFPMPF_REPLY_* flags. body (bytes): Body of the reply. """ super().__init__(xid) self.multipart_type = multipart_type self.flags = flags self.body = body
def _get_body_instance(self): """Return the body instance.""" exp_header = ExperimenterMultipartHeader simple_body = {MultipartType.OFPMP_DESC: Desc, MultipartType.OFPMP_GROUP_FEATURES: GroupFeatures, MultipartType.OFPMP_METER_FEATURES: MeterFeatures, MultipartType.OFPMP_EXPERIMENTER: exp_header} array_of_bodies = {MultipartType.OFPMP_FLOW: FlowStats, MultipartType.OFPMP_AGGREGATE: AggregateStatsReply, MultipartType.OFPMP_TABLE: TableStats, MultipartType.OFPMP_PORT_STATS: PortStats, MultipartType.OFPMP_QUEUE: QueueStats, MultipartType.OFPMP_GROUP: GroupStats, MultipartType.OFPMP_GROUP_DESC: GroupDescStats, MultipartType.OFPMP_METER: MeterStats, MultipartType.OFPMP_METER_CONFIG: MeterConfig, MultipartType.OFPMP_TABLE_FEATURES: TableFeatures, MultipartType.OFPMP_PORT_DESC: Port} if isinstance(self.multipart_type, UBInt16): self.multipart_type = self.multipart_type.enum_ref( self.multipart_type.value) pyof_class = simple_body.get(self.multipart_type, None) if pyof_class: return pyof_class() array_of_class = array_of_bodies.get(self.multipart_type, None) if array_of_class: return FixedTypeList(pyof_class=array_of_class) return BinaryData(b'')
class StatsRequest(GenericMessage): """Request statistics to switch.""" #: OpenFlow :class:`~pyof.v0x01.common.header.Header` header = Header(message_type=Type.OFPT_STATS_REQUEST) body_type = UBInt16(enum_ref=StatsType) flags = UBInt16() body = BinaryData() def __init__(self, xid=None, body_type=None, flags=0, body=b''): """Create a StatsRequest with the optional parameters below. Args: xid (int): xid to be used on the message header. body_type (StatsType): One of the OFPST_* constants. flags (int): OFPSF_REQ_* flags (none yet defined). body (BinaryData): Body of the request. """ super().__init__(xid) self.body_type = body_type self.flags = flags self.body = body def pack(self, value=None): """Pack according to :attr:`body_type`. Make `body` a binary pack before packing this object. Then, restore body. """ backup = self.body if not value: value = self.body if hasattr(value, 'pack'): self.body = value.pack() stats_request_packed = super().pack() self.body = backup return stats_request_packed def unpack(self, buff, offset=0): """Unpack according to :attr:`body_type`.""" super().unpack(buff) class_name = self._get_body_class() buff = self.body.value self.body = FixedTypeList(pyof_class=class_name) self.body.unpack(buff) def _get_body_class(self): if isinstance(self.body_type, (int, UBInt16)): self.body_type = self.body_type.enum_ref(self.body_type.value) module = import_module('pyof.v0x01.controller2switch.common') body_name = self.body_type.name.replace('OFPST_', '').title() for class_name in module.__all__: if 'Request' in class_name and body_name in class_name: return getattr(module, class_name) return None
def unpack(self, buffer, offset=0): # TODO: Unpack is duplicated. We need to fix this. header = UBInt16() header.unpack(buffer[offset:offset+2]) self.type = header.value >> 9 length = header.value & 511 begin, end = offset + 2, offset + 2 + length self.value = BinaryData(buffer[begin:end])
class Hello(GenericMessage): """OpenFlow Hello Message. This message does not contain a body beyond the OpenFlow Header. """ header = Header(message_type=Type.OFPT_HELLO, length=8) elements = BinaryData()
class TLVWithSubType(GenericTLV): """Modify the :class:`GenericTLV` to a Organization Specific TLV structure. Beyond the standard TLV (type, length, value), we can also have a more specific structure, with the :attr:`value` field being splitted into a :attr:`sub_type` field and a new :attr:`sub_value` field. """ def __init__(self, tlv_type=1, sub_type=7, sub_value=None): """Create an instance and set its attributes. Args: tlv_type (int): Type used by this class. Defaults to 1. sub_type (int): Sub type value used by this class. Defaults to 7. sub_value (:class:`~pyof.foundation.basic_types.BinaryData`): Data stored by TLVWithSubType. Defaults to empty BinaryData. """ super().__init__(tlv_type) self.sub_type = sub_type self.sub_value = BinaryData() if sub_value is None else sub_value @property def value(self): """Return sub type and sub value as binary data. Returns: :class:`~pyof.foundation.basic_types.BinaryData`: BinaryData calculated. """ binary = UBInt8(self.sub_type).pack() + self.sub_value.pack() return BinaryData(binary) def unpack(self, buff, offset=0): """Unpack a binary message into this object's attributes. Unpack the binary value *buff* and update this object attributes based on the results. Args: buff (bytes): Binary data package to be unpacked. offset (int): Where to begin unpacking. Raises: Exception: If there is a struct unpacking error. """ header = UBInt16() header.unpack(buff[offset:offset+2]) self.tlv_type = header.value >> 9 length = header.value & 511 begin, end = offset + 2, offset + 2 + length sub_type = UBInt8() sub_type.unpack(buff[begin:begin+1]) self.sub_type = sub_type.value self.sub_value = BinaryData(buff[begin+1:end])
class TLVWithSubType(GenericTLV): """Modify the :class:`GenericTLV` to a Organization Specific TLV structure. Beyond the standard TLV (type, length, value), we can also have a more specific structure, with the :attr:`value` field being splitted into a :attr:`sub_type` field and a new :attr:`sub_value` field. """ def __init__(self, tlv_type=1, sub_type=7, sub_value=None): """Create an instance and set its attributes. Args: tlv_type (int): Type used by this class. Defaults to 1. sub_type (int): Sub type value used by this class. Defaults to 7. sub_value (:class:`~pyof.foundation.basic_types.BinaryData`): Data stored by TLVWithSubType. Defaults to empty BinaryData. """ super().__init__(tlv_type) self.sub_type = sub_type self.sub_value = BinaryData() if sub_value is None else sub_value @property def value(self): """Return sub type and sub value as binary data. Returns: :class:`~pyof.foundation.basic_types.BinaryData`: BinaryData calculated. """ binary = UBInt8(self.sub_type).pack() + self.sub_value.pack() return BinaryData(binary) def unpack(self, buff, offset=0): """Unpack a binary message into this object's attributes. Unpack the binary value *buff* and update this object attributes based on the results. Args: buff (bytes): Binary data package to be unpacked. offset (int): Where to begin unpacking. Raises: Exception: If there is a struct unpacking error. """ header = UBInt16() header.unpack(buff[offset:offset+2]) self.tlv_type = header.value >> 9 length = header.value & 511 begin, end = offset + 2, offset + 2 + length sub_type = UBInt8() sub_type.unpack(buff[begin:begin+1]) self.sub_type = sub_type.value self.sub_value = BinaryData(buff[begin+1:end])
def _get_body_instance(self): """Return the body instance.""" pyof_class = self._get_body_class() if pyof_class is None: return BinaryData(b'') elif pyof_class is DescStats: return pyof_class() return FixedTypeList(pyof_class=pyof_class)
def value(self): """Return sub type and sub value as binary data. Returns: :class:`~pyof.foundation.basic_types.BinaryData`: BinaryData calculated. """ binary = UBInt8(self.sub_type).pack() + self.sub_value.pack() return BinaryData(binary)
class StatsRequest(GenericMessage): """Request statistics to switch.""" #: OpenFlow :class:`.Header` header = Header(message_type=Type.OFPT_STATS_REQUEST) body_type = UBInt16(enum_ref=StatsTypes) flags = UBInt16() body = BinaryData() def __init__(self, xid=None, body_type=None, flags=0, body=b''): """The constructor just assings parameters to object attributes. Args: body_type (StatsTypes): One of the OFPST_* constants. flags (int): OFPSF_REQ_* flags (none yet defined). body (BinaryData): Body of the request. """ super().__init__(xid) self.body_type = body_type self.flags = flags self.body = body def pack(self): """Pack according to :attr:`body_type`. Make `body` a binary pack before packing this object. Then, restore body. """ if self.body_type == StatsTypes.OFPST_PORT or \ self.body_type == StatsTypes.OFPST_FLOW or \ self.body_type == StatsTypes.OFPST_AGGREGATE: backup = self.body self.body = self.body.pack() pack = super().pack() self.body = backup return pack else: return super().pack() def unpack(self, buff): """Unpack according to :attr:`body_type`.""" super().unpack(buff) if self.body_type == StatsTypes.OFPST_PORT: buff = self.body.value self.body = PortStatsRequest() self.body.unpack(buff) elif self.body_type == StatsTypes.OFPST_FLOW: buff = self.body.value self.body = FlowStatsRequest() self.body.unpack(buff) elif self.body_type == StatsTypes.OFPST_AGGREGATE: buff = self.body.value self.body = AggregateStatsRequest() self.body.unpack(buff)
class VLAN(GenericStruct): """VLAN """ #: _prio_cfi_id: (:class:`UBInt16`): Priority, CFI and VID _prio_cfi_id = UBInt16() #: ether_type (:class:`UBInt16`): The EtherType of the packet. ether_type = UBInt16() #: data (:class:`BinaryData`): The content of the packet in binary format. data = BinaryData() def __init__(self, vid=0, pcp=0, cfi=0, ether_type=0, data=b''): super().__init__() self.ether_type = ether_type self.pcp = pcp # Default 0 means Best Effort queue self.cfi = cfi # Default is 0 for Ethernet self.vid = vid self.data = data self._prio_cfi_id = 1 def pack(self, value=None): """Pack the struct in a binary representation. Merge some fields to ensure correct packing. """ self._prio_cfi_id = self.pcp << 13 | self.cfi << 12 | self.vid return super().pack() def unpack(self, buffer, offset=0): """Unpack a binary message into this object's attributes. Unpack the binary value *buff* and update this object attributes based on the results. Args: buffer (bytes): Binary data package to be unpacked. offset (int): Where to begin unpacking. Raises: Exception: If there is a struct unpacking error. """ prio_cfi_id = UBInt16() prio_cfi_id.unpack(buffer[offset:2]) self.pcp = prio_cfi_id.value >> 13 self.cfi = (prio_cfi_id.value & 0x1000) >> 12 self.vid = prio_cfi_id.value & 0xfff etype = UBInt16() etype.unpack(buffer[offset+2:4]) self.ether_type = etype.value
def __init__(self, tlv_type=127, value=None): """Create an instance and set its attributes. Args: tlv_type (int): Type used by this class. Defaults to 127. value (:class:`~pyof.foundation.basic_types.BinaryData`): Value stored by GenericTLV. """ super().__init__() self.tlv_type = tlv_type self._value = BinaryData() if value is None else value
def __init__(self, tlv_type=1, sub_type=7, sub_value=None): """Create an instance and set its attributes. Args: tlv_type (int): Type used by this class. Defaults to 1. sub_type (int): Sub type value used by this class. Defaults to 7. sub_value (:class:`~pyof.foundation.basic_types.BinaryData`): Data stored by TLVWithSubType. Defaults to empty BinaryData. """ super().__init__(tlv_type) self.sub_type = sub_type self.sub_value = BinaryData() if sub_value is None else sub_value
def setUp(self): """ :return: None """ self.type = UBInt8(3) self.length = UBInt16(6) self.xid = UBInt32(23) self.data = BinaryData(b'001100111101') self.test_object = EchoRequest() self.test_object.header.__init__(self.type,self.length,self.xid) self.test_object1 = EchoRequest(23, b'001100111101')
class StatsReply(GenericMessage): """Class implements the response to the stats request.""" #: OpenFlow :class:`.Header` header = Header(message_type=Type.OFPT_STATS_REPLY) body_type = UBInt16(enum_ref=StatsTypes) flags = UBInt16() body = BinaryData() def __init__(self, xid=None, body_type=None, flags=None, body=b''): """The constructor just assings parameters to object attributes. Args: body_type (StatsTypes): One of the OFPST_* constants. flags (int): OFPSF_REQ_* flags (none yet defined). body (BinaryData): Body of the request. """ super().__init__(xid) self.body_type = body_type self.flags = flags self.body = body def unpack(self, buff): """Unpack a binary message into this object's attributes. Unpack the binary value *buff* and update this object attributes based on the results. It is an inplace method and it receives the binary data of the message **without the header**. This class' unpack method is like the :meth:`.GenericMessage.unpack` one, except for the ``body`` attribute which has its type determined by the ``body_type`` attribute. Args: buff (bytes): Binary data package to be unpacked, without the header. """ super().unpack(buff) if self.body_type == StatsTypes.OFPST_PORT: self._unpack_body(FixedTypeList(pyof_class=PortStats)) elif self.body_type == StatsTypes.OFPST_AGGREGATE: self._unpack_body(FixedTypeList(pyof_class=AggregateStatsReply)) elif self.body_type == StatsTypes.OFPST_FLOW: self._unpack_body(FixedTypeList(pyof_class=FlowStats)) elif self.body_type == StatsTypes.OFPST_DESC: self._unpack_body(DescStats()) def _unpack_body(self, obj): """Unpack `body` using `obj` and replace it by the result.""" obj.unpack(self.body.value) self.body = obj
class PacketIn(GenericMessage): """Packet received on port (datapath -> controller).""" #: :class:`~.header.Header`: OpenFlow Header header = Header(message_type=Type.OFPT_PACKET_IN) #: ID assigned by datapath. buffer_id = UBInt32() #: Full length of frame. total_len = UBInt16() #: Reason packet is being sent (one of OFPR_*), reason = UBInt8(enum_ref=PacketInReason) #: ID of the table that was looked up. table_id = UBInt8() #: Cookie of the flow entry that was looked up. cookie = UBInt64() #: Packet metadata. Variable size. match = Match() #: Align to 64 bit + 16 bit pad = Pad(2) #: Ethernet frame whose length is inferred from header.length. #: The padding bytes preceding the Ethernet frame ensure that the IP #: header (if any) following the Ethernet header is 32-bit aligned. data = BinaryData() def __init__(self, xid=None, buffer_id=None, total_len=None, reason=None, table_id=None, cookie=None, match=None, data=b''): """Assign parameters to object attributes. Args: xid (int): Header's xid. buffer_id (int): ID assigned by datapath. total_len (int): Full length of frame. reason (PacketInReason): The reason why the packet is being sent table_id (int): ID of the table that was looked up cookie (int): Cookie of the flow entry that was looked up match (:class:`~.common.flow_match.Match`): Packet metadata. Variable size. data (bytes): Ethernet frame, halfway through 32-bit word, so the IP header is 32-bit aligned. The amount of data is inferred from the length field in the header. Because of padding, offsetof(struct ofp_packet_in, data) == sizeof(struct ofp_packet_in) - 2. """ super().__init__(xid) self.buffer_id = buffer_id self.total_len = total_len self.reason = reason self.table_id = table_id self.cookie = cookie self.match = match self.data = data
def unpack(self, buff, offset=0): """Unpack a binary message into this object's attributes. Unpack the binary value *buff* and update this object attributes based on the results. Args: buff (bytes): Binary data package to be unpacked. offset (int): Where to begin unpacking. Raises: Exception: If there is a struct unpacking error. """ header = UBInt16() header.unpack(buff[offset:offset + 2]) self.tlv_type = header.value >> 9 length = header.value & 511 begin, end = offset + 2, offset + 2 + length sub_type = UBInt8() sub_type.unpack(buff[begin:begin + 1]) self.sub_type = sub_type.value self.sub_value = BinaryData(buff[begin + 1:end])
def _unpack_data(self): if self.data == b'': return BinaryData() # header unpacking header = Header() header_size = header.get_size() header_data = self.data.value[:header_size] header.unpack(header_data) # message unpacking msg = common.utils.new_message_from_header(header) msg_data = self.data.value[header_size:] msg.unpack(msg_data) return msg
class Ethernet(GenericStruct): destination = HWAddress() source = HWAddress() type = UBInt16() data = BinaryData() def __init__(self, destination=None, source=None, type=None, data=b''): super().__init__() self.destination = destination self.source = source self.type = type self.data = data def get_hash(self): return hash(self.pack())
class VendorStats(GenericStruct): """Vendor extension.""" vendor = UBInt32() body = BinaryData() def __init__(self, vendor=None, body=b''): """Create instance attributes. Args: vendor (int): 32-bit vendor ID. body (bytes): Vendor-defined body """ super().__init__() self.vendor = vendor self.body = body
class HelloElemVersionbitmap(HelloElemHeader): """Version bitmap Hello Element.""" #: List of bitmaps - supported versions bitmaps = BinaryData() def __init__(self, bitmaps=b''): """Create a HelloElemVersionbitmap with the optional parameters below. Args: bitmaps(BinaryData): A BinaryData with exactly (length - 4) bytes containing the bitmaps, then exactly (length + 7)/8*8 - (length) (between 0 and 7) bytes of all-zero bytes. """ super().__init__(element_type=HelloElemType.OFPHET_VERSIONBITMAP, length=None) self.bitmaps = bitmaps
class EchoReply(GenericMessage): """OpenFlow Reply message. This message does not contain a body beyond the OpenFlow Header. """ header = Header(message_type=Type.OFPT_ECHO_REPLY, length=8) data = BinaryData() def __init__(self, xid=None, data=None): """Create an EchoReply with the optional parameters below. Args: xid (int): xid to be used on the message header. data (bytes): arbitrary-length data field. """ super().__init__(xid) self.data = data
class EchoRequest(GenericMessage): """OpenFlow Reply message. This message does not contain a body after the OpenFlow Header. """ header = Header(message_type=Type.OFPT_ECHO_REQUEST, length=8) data = BinaryData() def __init__(self, xid=None, data=None): """The constructor takes the parameters below. Args: xid (int): xid to be used on the message header. data (bytes): arbitrary-length data field. """ super().__init__(xid) self.data = data
class Ethernet(GenericStruct): """Ethernet struct.""" destination = HWAddress() source = HWAddress() type = UBInt16() data = BinaryData() def __init__(self, destination=None, source=None, eth_type=None, data=b''): """Create an instance and set its attributes.""" super().__init__() self.destination = destination self.source = source self.type = eth_type self.data = data def get_hash(self): """Return a hash that identifies this instance.""" return hash(self.pack())
class ExperimenterHeader(GenericMessage): """OpenFlow Experimenter message. The experimenter field is a 32-bit value that uniquely identifies the experimenter. If the most significant byte is zero, the next three bytes are the experimenter’s IEEE OUI. If the most significant byte is not zero, it is a value allocated by the Open Networking Foundation. If experimenter does not have (or wish to use) their OUI, they should contact the Open Networking Foundation to obtain an unique experimenter ID. The rest of the body is uninterpreted by standard OpenFlow processing and is arbitrarily defined by the corresponding experimenter. If a switch does not understand an experimenter extension, it must send an OFPT_ERROR message with a OFPBRC_BAD_EXPERIMENTER error code and OFPET_BAD_REQUEST error type. """ header = Header(message_type=Type.OFPT_EXPERIMENTER) experimenter = UBInt32() exp_type = UBInt32() experimenter_data = BinaryData() def __init__(self, xid=None, experimenter=None, exp_type=None, experimenter_data=b''): """Create a ExperimenterHeader with the optional parameters below. Args: xid (int): xid to be used on the message header. experimenter (int): Vendor ID: MSB 0: low-order bytes are IEEE OUI. MSB != 0: defined by ONF. exp_type (int): Experimenter defined. experimenter_data (Binary): Experimenter-defined arbitrary additional data. """ super().__init__(xid) self.experimenter = experimenter self.exp_type = exp_type self.experimenter_data = experimenter_data
def unpack(self, buff, offset=0): """Unpack a binary message into this object's attributes. Unpack the binary value *buff* and update this object attributes based on the results. Args: buff (bytes): Binary data package to be unpacked. offset (int): Where to begin unpacking. Raises: Exception: If there is a struct unpacking error. """ header = UBInt16() header.unpack(buff[offset:offset+2]) self.tlv_type = header.value >> 9 length = header.value & 511 begin, end = offset + 2, offset + 2 + length sub_type = UBInt8() sub_type.unpack(buff[begin:begin+1]) self.sub_type = sub_type.value self.sub_value = BinaryData(buff[begin+1:end])