def __dissect_pkt(self, packet): ''' internal routine to deconstruct serial text received from device packet will be in the format: "{{num} {oflo} {seq} {per} {err} {lqi} {rssi}{ed} {gain} {status} {time} {fp}{length}{payload}}" @type packet: String @param: packet: The raw packet to dissect @rtype: List @return: Returns None if packet is not in correct format. When a packet is correct, a list is returned, in the form [ String: Frame | Bool: Valid CRC | Int: Unscaled RSSI ] ''' data = packet[1:].replace('{', ' ').replace('}', ' ').split() # should be 12 fields + payload length + payload if not data or not len(data[13:]) == int(data[12], 16): print "Error parsing stream received from device (payload size error):", packet return None # payload is in the form e.g. "0x03 0x08 0xA3 0xFF 0xFF 0xFF 0xFF 0x07" so we need to convert to a string frame = '' for x in data[13:]: try: frame += chr(int(x, 16)) except: print "Error parsing stream received from device (invalid payload):", packet return None # Parse other useful fields try: rssi = int(data[6]) # sniffer doesn't give us the CRC so we must add it, but must be correct or we would not have received it validcrc = True frame += makeFCS(frame) except: print "Error parsing stream received from device (invalid rssi or FCS build error):", packet return None return [frame, validcrc, rssi]
def pnext(self, timeout=100): ''' Returns a dictionary containing packet data, else None. @type timeout: Integer @param timeout: Timeout to wait for packet reception in usec @rtype: List @return: Returns None is timeout expires and no packet received. When a packet is received, a dictionary is returned with the keys bytes (string of packet bytes), validcrc (boolean if a vaid CRC), rssi (unscaled RSSI), and location (may be set to None). For backwards compatibility, keys for 0,1,2 are provided such that it can be treated as if a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ] ''' if self.__stream_open == False: self.sniffer_on() #start sniffing packet = None; start = datetime.utcnow() while (packet is None and (start + timedelta(microseconds=timeout) > datetime.utcnow())): packet = self.handle.RF_rxpacket() rssi = self.handle.RF_getrssi() #TODO calibrate if packet is None: return None frame = packet[1:] if frame[-2:] == makeFCS(frame[:-2]): validcrc = True else: validcrc = False #Return in a nicer dictionary format, so we don't have to reference by number indicies. #Note that 0,1,2 indicies inserted twice for backwards compatibility. result = {0:frame, 1:validcrc, 2:rssi, 'bytes':frame, 'validcrc':validcrc, 'rssi':rssi, 'location':None} result['dbm'] = rssi - 45 #TODO tune specifically to the Apimote platform (does ext antenna need to different?) result['datetime'] = datetime.utcnow() return result
def pnext(self, timeout=100): ''' Returns a dictionary containing packet data, else None. @type timeout: Integer @param timeout: Timeout to wait for packet reception in usec @rtype: List @return: Returns None is timeout expires and no packet received. When a packet is received, a dictionary is returned with the keys bytes (string of packet bytes), validcrc (boolean if a vaid CRC), rssi (unscaled RSSI), and location (may be set to None). For backwards compatibility, keys for 0,1,2 are provided such that it can be treated as if a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ] ''' if self.__stream_open == False: self.sniffer_on() packet = None start = datetime.utcnow() while (packet is None and (start + timedelta(microseconds=timeout) > datetime.utcnow())): packet = self.handle.RF_rxpacket() rssi = self.handle.RF_getrssi() #TODO calibrate if packet is None: return None frame = packet[1:] validcrc = False if frame[-2:] == makeFCS(frame[:-2]): validcrc = True #Return in a nicer dictionary format, so we don't have to reference by number indicies. #Note that 0,1,2 indicies inserted twice for backwards compatibility. result = {0:frame, 1:validcrc, 2:rssi, 'bytes':frame, 'validcrc':validcrc, 'rssi':rssi, 'location':None} result['dbm'] = rssi - 45 #TODO tune specifically to the Apimote platform (does ext antenna need to different?) result['datetime'] = datetime.utcnow() return result
def indirect_inject(self, packet, channel=None, oneshot=False): ''' Preloads ApiMote with payload provided as packet argument. Reflexively jams all traffic while serving forged frame pending ACKs and the preloaded cmd. @type packet: String @param packet: Packet contents to pre-load, excluding length and FCS (generated below). @type channel: Integer @param channel: Sets the channel. Optional. @type oneshot: Bool @param oneshot: Inject sequence indefinitely if true, exactly once if false. Optional. ''' # Hardware must be able to detect the beginning of a packet; through SFD pin for ApiMote self.capabilities.require(KBCapabilities.PHYJAM_REFLEX) self.handle.RF_promiscuity(1) #Configure the radio self.handle.RF_autocrc(0) if channel != None: self.set_channel(channel) self.handle.CC_RFST_RX() #Put the radio in RX mode to begin #Prepare command by calculating/appending the FCS and prepending the oneshot value and length. fcs = [ord(x) for x in makeFCS(packet)] gfready = [ord(x) for x in packet] gfready.append(fcs[0]) gfready.append(fcs[1]) gfready.insert(0, len(gfready)) gfready.insert(0, 0x00 if oneshot is False else 0x01) #Send the command to the ApiMote self.handle.RF_reflexjam_indirect(gfready)
def pnext_rec(self, timeout=100): pdata = '' ldata = '' self.handle.timeout = timeout # Allow pySerial to handle timeout startChar = self.handle.read() if startChar == None: return None # Sense timeout case and return # Listens for serial message of general format R!<data>; if startChar == "R": # Get packet data if self.handle.read() == "!": x = self.handle.read() while (x != ";"): pdata += x x = self.handle.read() if startChar == "L": # Get location data if self.handle.read() == "!": x = self.handle.read() while (x != ";"): ldata += x x = self.handle.read() if startChar == "[": # Sense when done reading from EEPROM if self.handle.read( 40) == "{[ DONE READING BACK ALL LOGGED DATA ]}]": raise StopIteration("All Data Read") # If location received, update our local variables: if ldata != None and ldata != '': self.processLocationUpdate(ldata) if pdata == None or pdata == '': return None # Parse received data as <rssi>!<time>!<packtlen>!<frame> data = pdata.split("!", 3) try: rssi = ord(data[0]) frame = data[3] if frame[-2:] == makeFCS(frame[:-2]): validcrc = True else: validcrc = False except: print "Error parsing stream received from device:", pdata, data return None #Return in a nicer dictionary format, so we don't have to reference by number indicies. #Note that 0,1,2 indicies inserted twice for backwards compatibility. result = { 0: frame, 1: validcrc, 2: rssi, 'bytes': frame, 'validcrc': validcrc, 'rssi': rssi } result[ 'dbm'] = None #TODO calculate dBm antenna signal based on RSSI formula result['datetime'] = self.getCaptureDateTime(data) result['location'] = (self.lon, self.lat, self.alt) return result
def pnext_rec(self, timeout=100): pdata = '' ldata = '' self.handle.timeout=timeout # Allow pySerial to handle timeout startChar = self.handle.read() if startChar == None: return None # Sense timeout case and return # Listens for serial message of general format R!<data>; if startChar == "R": # Get packet data if self.handle.read() == "!": x = self.handle.read() while (x != ";"): pdata += x x = self.handle.read() if startChar == "L": # Get location data if self.handle.read() == "!": x = self.handle.read() while (x != ";"): ldata += x x = self.handle.read() if startChar == "[": # Sense when done reading from EEPROM if self.handle.read(40) == "{[ DONE READING BACK ALL LOGGED DATA ]}]": raise StopIteration("All Data Read") # If location received, update our local variables: if ldata != None and ldata != '': self.processLocationUpdate(ldata) if pdata == None or pdata == '': return None # Parse received data as <rssi>!<time>!<packtlen>!<frame> data = pdata.split("!", 3) try: rssi = ord(data[0]) frame = data[3] if frame[-2:] == makeFCS(frame[:-2]): validcrc = True else: validcrc = False except: print "Error parsing stream received from device:", pdata, data return None #Return in a nicer dictionary format, so we don't have to reference by number indicies. #Note that 0,1,2 indicies inserted twice for backwards compatibility. result = {0:frame, 1:validcrc, 2:rssi, 'bytes':frame, 'validcrc':validcrc, 'rssi':rssi} result['dbm'] = None #TODO calculate dBm antenna signal based on RSSI formula result['datetime'] = self.getCaptureDateTime(data) result['location'] = (self.lon, self.lat, self.alt) return result
def __parse_zep_v2(data): ''' Parse the packet from the ZigBee encapsulation protocol version 2/3 and return the fields desired for usage by pnext(). There is support here for some oddities specific to the Sewio implementation of ZEP and the packet, such as CC24xx format FCS headers being expected. The ZEP protocol parsing is mainly based on Wireshark source at: http://anonsvn.wireshark.org/wireshark/trunk/epan/dissectors/packet-zep.c * ZEP v2 Header will have the following format (if type=1/Data): * |Preamble|Version| Type |Channel ID|Device ID|CRC/LQI Mode|LQI Val|NTP Timestamp|Sequence#|Reserved|Length| * |2 bytes |1 byte |1 byte| 1 byte | 2 bytes | 1 byte |1 byte | 8 bytes | 4 bytes |10 bytes|1 byte| * ZEP v2 Header will have the following format (if type=2/Ack): * |Preamble|Version| Type |Sequence#| * |2 bytes |1 byte |1 byte| 4 bytes | #define ZEP_PREAMBLE "EX" #define ZEP_V2_HEADER_LEN 32 #define ZEP_V2_ACK_LEN 8 #define ZEP_V2_TYPE_DATA 1 #define ZEP_V2_TYPE_ACK 2 #define ZEP_LENGTH_MASK 0x7F ''' # Unpack constant part of ZEPv2 (preamble, version, zeptype) = unpack('<HBB', data[:4]) if preamble != 22597 or version < 2: # 'EX'==22597, and v3 is compat with v2 (I think??) raise Exception( "Can not parse provided data as ZEP due to incorrect preamble or unsupported version." ) if zeptype == 1: #data (ch, devid, crcmode, lqival, ntpsec, ntpnsec, seqnum, length) = unpack(">BHBBIII10xB", data[4:32]) #print "Data ZEP:", ch, devid, crcmode, lqival, ntpsec, ntpnsec, seqnum, length #We could convert the NTP timestamp received to system time, but the # Sewio firmware uses "relative timestamping" where it begins at 0 each time # the sniffer is started. Thus, it isn't that useful to us, so we just add the # time the packet is received at the host instead. #print "\tConverted time:", ntp_to_system_time(ntpsec, ntpnsec) recdtime = datetime.utcnow() #The LQI comes in ZEP, but the RSSI comes in the first byte of the FCS, # if the FCS was correct. If the byte is 0xb1, Wireshark appears to do 0xb1-256 = -79 dBm. # It appears that if CRC/LQI Mode field == 1, then checksum was bad, so the RSSI isn't # available, as the CRC is left in the packet. If it == 0, then the first byte of FCS is the RSSI. # From Wireshark: #define IEEE802154_CC24xx_CRC_OK 0x8000 #define IEEE802154_CC24xx_RSSI 0x00FF frame = data[32:] # A length vs len(frame) check is not used here but is an # additional way to verify that all is good (length == len(frame)). if crcmode == 0: validcrc = ((ord(data[-1]) & 0x80) == 0x80) rssi = ord(data[-2]) # We have to trust the sniffer that the FCS was OK, so we compute # what a good FCS should be and patch it back into the packet. frame = frame[:-2] + makeFCS(frame[:-2]) else: validcrc = False rssi = None return (frame, ch, validcrc, rssi, lqival, recdtime) elif zeptype == 2: #ack frame = data[8:] (seqnum) = unpack(">I", data[4:8]) recdtime = datetime.utcnow() validcrc = (frame[-2:] == makeFCS(frame[:-2])) return (frame, None, validcrc, None, None, recdtime) return None
def pnext(self, timeout=100): """ Returns a dictionary containing packet data, else None. @type timeout: Integer @param timeout: Timeout to wait for packet reception in usec @rtype: List @return: Returns None is timeout expires and no packet received. When a packet is received, a dictionary is returned with the keys bytes (string of packet bytes), validcrc (boolean if a vaid CRC), rssi (unscaled RSSI), and location (may be set to None). For backwards compatibility, keys for 0,1,2 are provided such that it can be treated as if a list is returned, in the form [ String: packet contents | Bool: Valid CRC | Int: Unscaled RSSI ] """ if self.__stream_open == False: self.sniffer_on() #start sniffing ret = None framedata = [] explen = 0 # expected remaining packet length while True: pdata = None try: pdata = self.dev.read(self._data_ep, self._maxPacketSize, timeout=timeout) except usb.core.USBError as e: if e.errno != 110: #Operation timed out print("Error args: {}".format(e.args)) raise e #TODO error handling enhancements for USB 1.0 else: return None # Accumulate in 'framedata' until we have an entire frame for byteval in pdata: framedata.append(struct.pack("B", byteval)) if len(pdata) < 64: if len(pdata) < 2: #print "ERROR: Very short frame" return None framelen = ord(framedata[1]) if len(framedata) - 3 != framelen: #print "ERROR: Bad frame length: expected {0}, got {1}".format(framelen, len(framedata)) return None if framedata[0] != '\x00': #print "Not a capture frame:", framedata return None payloadlen = ord(framedata[7]) # Includes TI format FCS payload = framedata[8:] if len(payload) != payloadlen: # TODO: Log "ERROR: Bad payload length" return None # See TI Smart RF User Guide for usage of 'CC24XX' format FCS fields # in last two bytes of framedata. Note that we remove these before return of the frame. # RSSI is signed value, offset by 73 (see CC2530 data sheet for offset) rssi = struct.unpack("b", framedata[-2])[0] - 73 fcsx = ord(framedata[-1]) # validcrc is the bit 7 in fcsx validcrc = (fcsx & 0x80) == 0x80 # correlation value is bits 0-6 in fcsx correlation = fcsx & 0x7f ret = {1:validcrc, 2:rssi, 'validcrc':validcrc, 'rssi':rssi, 'lqi':correlation, 'dbm':rssi,'datetime':datetime.utcnow()} # Convert the framedata to a string for the return value, and replace the TI FCS with a real FCS # if the radio told us that the FCS had passed validation. if validcrc: ret[0] = ''.join(payload[:-2]) + makeFCS(payload[:-2]) else: ret[0] = ''.join(payload) ret['bytes'] = ret[0] return ret
def __parse_zep_v2(data): ''' Parse the packet from the ZigBee encapsulation protocol version 2/3 and return the fields desired for usage by pnext(). There is support here for some oddities specific to the Sewio implementation of ZEP and the packet, such as CC24xx format FCS headers being expected. The ZEP protocol parsing is mainly based on Wireshark source at: http://anonsvn.wireshark.org/wireshark/trunk/epan/dissectors/packet-zep.c * ZEP v2 Header will have the following format (if type=1/Data): * |Preamble|Version| Type |Channel ID|Device ID|CRC/LQI Mode|LQI Val|NTP Timestamp|Sequence#|Reserved|Length| * |2 bytes |1 byte |1 byte| 1 byte | 2 bytes | 1 byte |1 byte | 8 bytes | 4 bytes |10 bytes|1 byte| * ZEP v2 Header will have the following format (if type=2/Ack): * |Preamble|Version| Type |Sequence#| * |2 bytes |1 byte |1 byte| 4 bytes | #define ZEP_PREAMBLE "EX" #define ZEP_V2_HEADER_LEN 32 #define ZEP_V2_ACK_LEN 8 #define ZEP_V2_TYPE_DATA 1 #define ZEP_V2_TYPE_ACK 2 #define ZEP_LENGTH_MASK 0x7F ''' # Unpack constant part of ZEPv2 (preamble, version, zeptype) = unpack('<HBB', data[:4]) if preamble != 22597 or version < 2: # 'EX'==22597, and v3 is compat with v2 (I think??) raise Exception("Can not parse provided data as ZEP due to incorrect preamble or unsupported version.") if zeptype == 1: #data (ch, devid, crcmode, lqival, ntpsec, ntpnsec, seqnum, length) = unpack(">BHBBIII10xB", data[4:32]) #print "Data ZEP:", ch, devid, crcmode, lqival, ntpsec, ntpnsec, seqnum, length #We could convert the NTP timestamp received to system time, but the # Sewio firmware uses "relative timestamping" where it begins at 0 each time # the sniffer is started. Thus, it isn't that useful to us, so we just add the # time the packet is received at the host instead. #print "\tConverted time:", ntp_to_system_time(ntpsec, ntpnsec) recdtime = datetime.utcnow() #The LQI comes in ZEP, but the RSSI comes in the first byte of the FCS, # if the FCS was correct. If the byte is 0xb1, Wireshark appears to do 0xb1-256 = -79 dBm. # It appears that if CRC/LQI Mode field == 1, then checksum was bad, so the RSSI isn't # available, as the CRC is left in the packet. If it == 0, then the first byte of FCS is the RSSI. # From Wireshark: #define IEEE802154_CC24xx_CRC_OK 0x8000 #define IEEE802154_CC24xx_RSSI 0x00FF frame = data[32:] # A length vs len(frame) check is not used here but is an # additional way to verify that all is good (length == len(frame)). if crcmode == 0: validcrc = ((ord(data[-1]) & 0x80) == 0x80) rssi = ord(data[-2]) # We have to trust the sniffer that the FCS was OK, so we compute # what a good FCS should be and patch it back into the packet. frame = frame[:-2] + makeFCS(frame[:-2]) else: validcrc = False rssi = None return (frame, ch, validcrc, rssi, lqival, recdtime) elif zeptype == 2: #ack frame = data[8:] (seqnum) = unpack(">I", data[4:8]) recdtime = datetime.utcnow() validcrc = (frame[-2:] == makeFCS(frame[:-2])) return (frame, None, validcrc, None, None, recdtime) return None