def parse(self, data): mhr_start = data.tell() fc, seq = struct.unpack("<HB", data.read(3)) frame_type = fc & 0x0007 security_enabled = bool(fc & 0x0008) frame_pending = bool(fc & 0x0010) ack_request = bool(fc & 0x0020) panid_compression = bool(fc & 0x0040) dest_addr_mode = (fc & 0x0c00) >> 10 frame_version = (fc & 0x3000) >> 12 source_addr_mode = (fc & 0xc000) >> 14 if frame_type == MacHeader.FrameType.ACK: fcs = self._parse_fcs(data, data.tell()) self.header = MacHeader(frame_type, frame_pending, ack_request, frame_version, seq, fcs=fcs) self.payload = None return # Presence of PAN Ids is not fully implemented yet but should be enough for Thread. dest_pan_id = struct.unpack("<H", data.read(2))[0] dest_address = self._parse_address(data, dest_addr_mode) if not panid_compression: src_pan_id = struct.unpack("<H", data.read(2))[0] else: src_pan_id = dest_pan_id src_address = self._parse_address(data, source_addr_mode) mhr_end = data.tell() if security_enabled: aux_sec_header = self._parse_aux_sec_header(data) aux_sec_header_end = data.tell() else: aux_sec_header = None # Check end of MAC frame if frame_type == MacHeader.FrameType.COMMAND: command_type = ord(data.read(1)) else: command_type = None payload_pos = data.tell() data.seek(-2, io.SEEK_END) fcs_start = data.tell() if aux_sec_header and aux_sec_header.security_level: mic, payload_end = self._parse_mic(data, aux_sec_header.security_level) else: payload_end = data.tell() mic = None fcs = self._parse_fcs(data, fcs_start) # Create Header object self.header = MacHeader(frame_type, frame_pending, ack_request, frame_version, seq, dest_pan_id, dest_address, src_pan_id, src_address, command_type, aux_sec_header, mic, fcs) # Create Payload object payload_len = payload_end - payload_pos data.seek(payload_pos) payload = data.read(payload_len) if security_enabled: mhr_len = mhr_end - mhr_start data.seek(mhr_start) mhr_bytes = data.read(mhr_len) aux_sec_header_len = aux_sec_header_end - mhr_end aux_sec_hdr_bytes = data.read(aux_sec_header_len) non_payload_fields = bytearray([]) if command_type is not None: non_payload_fields.append(command_type) message_info = MessageInfo() message_info.aux_sec_hdr = aux_sec_header message_info.aux_sec_hdr_bytes = aux_sec_hdr_bytes message_info.nonpayload_fields = non_payload_fields message_info.mhr_bytes = mhr_bytes if src_address.type == MacAddressType.SHORT: message_info.source_mac_address = DeviceDescriptors.get_extended( src_address).mac_address else: message_info.source_mac_address = src_address.mac_address sec_obj = CryptoEngine( MacCryptoMaterialCreator(config.DEFAULT_MASTER_KEY)) self.payload = MacPayload( sec_obj.decrypt(payload, mic, message_info)) else: self.payload = MacPayload(payload)
def parse(self, data): """Parse a MAC 802.15.4 frame Format of MAC 802.15.4 Frame: | Frame Header | ASH | IE | Frame Payload | MIC | FCS | Frame Header: Frame Control | Sequence Number | Addressing Field ASH: Auxiliary Security Header (optional) IE: Header IE | Payload IE (optional) Frame Payload: Data payload (optional) MIC: To authenticate the frame (optional) In the parsing process, Frame Header is parsed first. Then it can be determined if security is enabled for this frame. If it's enabled, then ASH should be parsed. After that, go directly to the end to parse FCS first instead of parsing IE or Frame Payload. This is because sometimes, IE doesn't have a termination IE and parsing needs to know the start of MIC to determine where's the end of IEs(In such cases there are no payload). After parsing FCS, MIC is parsed if security is enabled. Then the end of IE(or payload) is known. And at last IEs and payload are parsed. And after parsing everything, build MacHeader and MacPayload with the things parsed. """ mhr_start = data.tell() fc, seq = struct.unpack("<HB", data.read(3)) frame_type = fc & 0x0007 security_enabled = bool(fc & 0x0008) frame_pending = bool(fc & 0x0010) ack_request = bool(fc & 0x0020) panid_compression = bool(fc & 0x0040) dest_addr_mode = (fc & 0x0C00) >> 10 frame_version = (fc & 0x3000) >> 12 source_addr_mode = (fc & 0xC000) >> 14 ie_present = bool(fc & 0x0200) if frame_type == MacHeader.FrameType.ACK: fcs = self._parse_fcs(data, data.tell()) self.header = MacHeader( frame_type, frame_pending, ack_request, frame_version, seq, fcs=fcs, ) self.payload = None return dest_addr_present = dest_addr_mode != MacHeader.AddressMode.NOT_PRESENT src_addr_present = source_addr_mode != MacHeader.AddressMode.NOT_PRESENT if frame_version < 2: dest_pan_present = dest_addr_present else: dest_pan_present = True if not src_addr_present: dest_pan_present = src_pan_present != panid_compression elif not dest_addr_present: dest_pan_present = False else: if dest_addr_mode == MacHeader.AddressMode.EXTENDED and source_addr_mode == MacHeader.AddressMode.EXTENDED and panid_compression: dest_pan_present = False if dest_pan_present: dest_pan_id = struct.unpack("<H", data.read(2))[0] else: dest_pan_id = None dest_address = self._parse_address(data, dest_addr_mode) src_pan_present = not panid_compression while src_pan_present: if frame_version < 2: break if frame_version == 3: assert False if dest_addr_mode == 0: break if dest_addr_mode == 2: break if dest_addr_mode == 3 and source_addr_mode == 2: break src_pan_present = False if src_pan_present: src_pan_id = struct.unpack("<H", data.read(2))[0] else: src_pan_id = dest_pan_id src_address = self._parse_address(data, source_addr_mode) mhr_end = data.tell() if security_enabled: aux_sec_header = self._parse_aux_sec_header(data) aux_sec_header_end = data.tell() else: aux_sec_header = None # Record the beginning position of header IE. # Or the position of payload if there is no header IE. cur_pos = data.tell() header_ie_start = cur_pos data.seek(-2, io.SEEK_END) fcs_start = data.tell() fcs = self._parse_fcs(data, fcs_start) data.seek(fcs_start) if aux_sec_header and aux_sec_header.security_level: mic, payload_end = self._parse_mic(data, aux_sec_header.security_level) else: payload_end = fcs_start mic = None # There may be no termination IE. In such case, there is no payload, get # IE until the beginning position of MIC or a termination IE. header_ie_list = [] data.seek(cur_pos) while ie_present and cur_pos + 2 < payload_end: header_ie = struct.unpack("<H", data.read(2))[0] id = (header_ie & MacFrame.IEEE802154_HEADER_IE_ID_MASK) >> 7 # Currently, payload IE doesn't exist in the code. So only HT2 is required. # TODO: support HT1 when there are Payload IEs in our code assert id != MacFrame.IEEE802154_HEADER_IE_HT1, \ 'Currently there should be no HT1!' header_ie_length = (header_ie & MacFrame.IEEE802154_HEADER_IE_LENGTH_MASK) assert cur_pos + 2 + header_ie_length <= payload_end, \ 'Parsing Header IE error, IE id:{} length:{}'.format(id, header_ie_length) header_ie_content = data.read(header_ie_length) header_ie_list.append( InformationElement(id, header_ie_length, header_ie_content)) cur_pos += 2 + header_ie_length if id == MacFrame.IEEE802154_HEADER_IE_HT2: break header_ie_end = cur_pos payload_pos = cur_pos payload_len = payload_end - payload_pos data.seek(payload_pos) payload = data.read(payload_len) if security_enabled: mhr_len = mhr_end - mhr_start data.seek(mhr_start) mhr_bytes = data.read(mhr_len) aux_sec_header_len = aux_sec_header_end - mhr_end aux_sec_hdr_bytes = data.read(aux_sec_header_len) extra_open_fields = bytearray([]) if ie_present: data.seek(header_ie_start) extra_open_fields += data.read(header_ie_end - header_ie_start) message_info = MessageInfo() message_info.aux_sec_hdr = aux_sec_header message_info.aux_sec_hdr_bytes = aux_sec_hdr_bytes message_info.extra_open_fields = extra_open_fields message_info.mhr_bytes = mhr_bytes open_payload = [] private_payload = payload # Check end of MAC frame if frame_type == MacHeader.FrameType.COMMAND: if frame_version < MacFrame.IEEE802154_VERSION_2015: extra_open_fields.append(payload[0]) open_payload = payload[0:1] private_payload = payload[1:] message_info.open_payload_length = 1 if src_address.type == MacAddressType.SHORT: message_info.source_mac_address = DeviceDescriptors.get_extended( src_address).mac_address else: message_info.source_mac_address = src_address.mac_address sec_obj = CryptoEngine( MacCryptoMaterialCreator(config.DEFAULT_NETWORK_KEY)) self.payload = MacPayload( bytearray(open_payload) + sec_obj.decrypt(private_payload, mic, message_info)) else: self.payload = MacPayload(payload) if frame_type == MacHeader.FrameType.COMMAND: command_type = self.payload.data[0] else: command_type = None # Create Header object self.header = MacHeader( frame_type, frame_pending, ack_request, frame_version, seq, dest_pan_id, dest_address, src_pan_id, src_address, command_type, aux_sec_header, mic, fcs, )