Projects STRLCPY CatSniffer Commits 83aae960
🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■ ■
    firmware/pycatsniffer/devpycatsniffer00.py
     1 +import serial
     2 +import argparse
     3 +import binascii
     4 +import errno
     5 +import io
     6 +import logging.handlers
     7 +import os
     8 +import select
     9 +import stat
     10 +import struct
     11 +import sys
     12 +import threading
     13 +import time
     14 + 
     15 +__version__ = '0.0.1'
     16 + 
     17 +defaults = {
     18 + 'hex_file': 'ccsniffpiper.hexdump',
     19 + 'out_fifo': '/tmp/ccsniffpiper',
     20 + 'pcap_file': 'ccsniffpiper.pcap',
     21 + 'debug_level': 'WARNING',
     22 + 'log_level': 'INFO',
     23 + 'log_file': 'ccsniffpiper.log',
     24 + 'channel': 11,
     25 +}
     26 + 
     27 +logger = logging.getLogger(__name__)
     28 +stats = {}
     29 + 
     30 +ping = bytearray([0x40, 0x53, 0x40, 0x00, 0x00, 0x40, 0x40, 0x45])
     31 +stop = bytearray([0x40, 0x53, 0x42, 0x00, 0x00, 0x42, 0x40, 0x45])
     32 +cfgphy = bytearray([0x40, 0x53, 0x47, 0x01, 0x00, 0x09, 0x51, 0x40, 0x45])
     33 +cfgfreq = bytearray([0x40, 0x53, 0x45, 0x04, 0x00, 0x62, 0x09, 0x00, 0x00, 0xb4, 0x40, 0x45])
     34 +initiator = bytearray([0x40, 0x53, 0x70, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x40, 0x45])
     35 +letsgo = bytearray([0x40, 0x53, 0x41, 0x00, 0x00, 0x41, 0x40, 0x45])
     36 + 
     37 + 
     38 +class Frame(object):
     39 + PCAP_FRAME_HDR_FMT = '<LLLL'
     40 + 
     41 + def __init__(self, macPDUByteArray, timestampBy32):
     42 + self.__macPDUByteArray = macPDUByteArray
     43 + self.timestampBy32 = timestampBy32
     44 + self.timestampUsec = timestampBy32 / 32.0
     45 + self.len = len(self.__macPDUByteArray)
     46 + 
     47 + self.__pcap_hdr = self.__generate_frame_hdr()
     48 + 
     49 + self.pcap = self.__pcap_hdr + self.__macPDUByteArray
     50 + self.hex = ''.join('%02x ' % c
     51 + for c in self.__macPDUByteArray).rstrip()
     52 + 
     53 + def __generate_frame_hdr(self):
     54 + sec = int(self.timestampUsec / 1000000)
     55 + usec = int(self.timestampUsec - sec)
     56 + return struct.pack(Frame.PCAP_FRAME_HDR_FMT, sec, usec, self.len,
     57 + self.len)
     58 + 
     59 + def get_pcap(self):
     60 + return self.pcap
     61 + 
     62 + def get_hex(self):
     63 + return self.hex
     64 + 
     65 + def get_timestamp(self):
     66 + return self.timestampUsec
     67 + 
     68 + 
     69 +#####################################
     70 + 
     71 + 
     72 +class PCAPHelper:
     73 + LINKTYPE_IEEE802_15_4_NOFCS = 230
     74 + LINKTYPE_IEEE802_15_4 = 195
     75 + MAGIC_NUMBER = 0xA1B2C3D4
     76 + VERSION_MAJOR = 2
     77 + VERSION_MINOR = 4
     78 + THISZONE = 0
     79 + SIGFIGS = 0
     80 + SNAPLEN = 0xFFFF
     81 + NETWORK = LINKTYPE_IEEE802_15_4
     82 + 
     83 + PCAP_GLOBAL_HDR_FMT = '<LHHlLLL'
     84 + 
     85 + @staticmethod
     86 + def writeGlobalHeader():
     87 + return struct.pack(PCAPHelper.PCAP_GLOBAL_HDR_FMT,
     88 + PCAPHelper.MAGIC_NUMBER, PCAPHelper.VERSION_MAJOR,
     89 + PCAPHelper.VERSION_MINOR, PCAPHelper.THISZONE,
     90 + PCAPHelper.SIGFIGS, PCAPHelper.SNAPLEN,
     91 + PCAPHelper.NETWORK)
     92 + 
     93 + 
     94 +class FifoHandler(object):
     95 + def __init__(self, out_fifo):
     96 + self.out_fifo = out_fifo
     97 + self.of = None
     98 + self.needs_pcap_hdr = True
     99 + self.thread = None
     100 + self.running = False
     101 + stats['Piped'] = 0
     102 + stats['Not Piped'] = 0
     103 + self.__create_fifo()
     104 + self.__start()
     105 + 
     106 + def __del__(self):
     107 + self.__stop()
     108 + 
     109 + def __start(self):
     110 + logger.debug("start FIFO watcher thread")
     111 + self.running = True
     112 + self.thread = threading.Thread(target=self.__fifo_watcher)
     113 + self.thread.daemon = True
     114 + self.thread.start()
     115 + 
     116 + def __stop(self):
     117 + logger.debug("stop FIFO watcher thread")
     118 + self.running = False
     119 + self.thread.join()
     120 + 
     121 + def __fifo_watcher(self):
     122 + while self.running:
     123 + self.__open_fifo(keepalive=True)
     124 + time.sleep(0.01)
     125 + 
     126 + def __create_fifo(self):
     127 + try:
     128 + os.mkfifo(self.out_fifo)
     129 + logger.info(f'Opened FIFO {self.out_fifo}')
     130 + except OSError as e:
     131 + if e.errno == errno.EEXIST:
     132 + if stat.S_ISFIFO(os.stat(self.out_fifo).st_mode) is False:
     133 + logger.error(
     134 + 'File {self.out_fifo} exists and is not a FIFO')
     135 + sys.exit(1)
     136 + else:
     137 + logger.warning(f'FIFO {self.out_fifo} exists. Using it')
     138 + else:
     139 + raise
     140 + 
     141 + def __open_fifo(self, keepalive=False):
     142 + try:
     143 + fd = os.open(self.out_fifo, os.O_NONBLOCK | os.O_WRONLY)
     144 + self.of = os.fdopen(fd, 'wb')
     145 + except OSError as e:
     146 + if e.errno == errno.ENXIO:
     147 + if not keepalive:
     148 + logger.warning('Remote end not reading')
     149 + stats['Not Piped'] += 1
     150 + self.of = None
     151 + self.needs_pcap_hdr = True
     152 + else:
     153 + raise
     154 + 
     155 + def triggerNewGlobalHeader(self):
     156 + self.needs_pcap_hdr = True
     157 + 
     158 + def handle(self, data):
     159 + if self.of is None:
     160 + self.__open_fifo()
     161 + 
     162 + if self.of is not None:
     163 + try:
     164 + if self.needs_pcap_hdr is True:
     165 + logger.info('Write global PCAP header')
     166 + self.of.write(PCAPHelper.writeGlobalHeader())
     167 + self.needs_pcap_hdr = False
     168 + self.of.write(data.pcap)
     169 + self.of.flush()
     170 + logger.debug(f'Wrote a frame of size {data.len} bytes')
     171 + stats['Piped'] += 1
     172 + except IOError as e:
     173 + if e.errno == errno.EPIPE:
     174 + logger.info('Remote end stopped reading')
     175 + stats['Not Piped'] += 1
     176 + self.of = None
     177 + self.needs_pcap_hdr = True
     178 + else:
     179 + raise
     180 + 
     181 + 
     182 +#####################################
     183 +class PcapDumpHandler(object):
     184 + def __init__(self, filename):
     185 + self.filename = filename
     186 + stats['Dumped to PCAP'] = 0
     187 + 
     188 + try:
     189 + self.of = open(self.filename, 'wb')
     190 + self.of.write(PCAPHelper.writeGlobalHeader())
     191 + logger.info(f'Dumping PCAP to {self.filename}')
     192 + except IOError as e:
     193 + self.of = None
     194 + logger.warning(
     195 + f'Error opening {self.filename} to save pcap. Skipping')
     196 + logger.warning(f'The error was: {e.args}')
     197 + 
     198 + def handle(self, frame):
     199 + if self.of is None:
     200 + return
     201 + self.of.write(frame.get_pcap())
     202 + self.of.flush()
     203 + logger.info(
     204 + f'PcapDumpHandler: Dumped a frame of size {frame.len} bytes')
     205 + stats['Dumped to PCAP'] += 1
     206 + 
     207 + 
     208 +class HexdumpHandler(object):
     209 + def __init__(self, filename):
     210 + self.filename = filename
     211 + stats['Dumped as Hex'] = 0
     212 + try:
     213 + self.of = open(self.filename, 'wb')
     214 + logger.info(f'Dumping hex to {self.filename}')
     215 + except IOError as e:
     216 + logger.warning(
     217 + f'Error opening {self.filename} for hex dumps. Skipping')
     218 + logger.warning(f'The error was: {e.args}')
     219 + self.of = None
     220 + 
     221 + def handle(self, frame):
     222 + if self.of is None:
     223 + return
     224 + 
     225 + try:
     226 + # Prepend the original timestamp in big-endian format
     227 + self.of.write(
     228 + binascii.hexlify(
     229 + struct.pack(">I ", int(frame.get_timestamp() * 32))))
     230 + #self.of.write(str(frame.get_timestamp()))
     231 + self.of.write(bytes(" ", 'ascii'))
     232 + # self.of.write('0000 ')
     233 + self.of.write(bytes(frame.get_hex(), 'ascii'))
     234 + self.of.write(bytes('\n', 'ascii'))
     235 + self.of.flush()
     236 + stats['Dumped as Hex'] += 1
     237 + logger.info(
     238 + f'HexdumpHandler: Dumped a frame of size {frame.len} bytes')
     239 + except IOError as e:
     240 + logger.warning(
     241 + f'Error writing hex to {self.of} for hex dumps. Skipping')
     242 + logger.warning(f'The error was: {e.args}')
     243 + 
     244 + 
     245 + 
     246 + 
     247 +class CC1352:
     248 + 
     249 + DEFAULT_CHANNEL = 0x0B # 11
     250 + 
     251 + DATA_EP = 0x83
     252 + DATA_TIMEOUT = 2500
     253 + 
     254 + DIR_OUT = 0x40
     255 + DIR_IN = 0xc0
     256 + 
     257 + GET_IDENT = 0xc0
     258 + SET_POWER = 0xc5
     259 + GET_POWER = 0xc6
     260 + 
     261 + SET_START = 0xd0 # bulk in starts
     262 + SET_STOP = 0xd1 # bulk in stops
     263 + SET_CHAN = 0xd2 # 0x0d (idx 0) + data)0x00 (idx 1)
     264 + 
     265 + HEARTBEAT_FRAME = 0x01
     266 + COMMAND_FRAME = 0x00
     267 +
     268 + BYTE_STREAM = 0
     269 + 
     270 + def __init__(self, port, callback, channel=DEFAULT_CHANNEL):
     271 + 
     272 + baudrate = 921600
     273 + rts_cts = False
     274 +
     275 + stats['Captured'] = 0
     276 + stats['Non-Frame'] = 0
     277 + 
     278 + self.channel = channel
     279 + self.callback = callback
     280 + self.thread = None
     281 + self.running = False
     282 +
     283 + try:
     284 + #self.serial_port = serial.Serial(port, baudrate, 8, 'N', 1, timeout=1)
     285 + self.serial_port = serial.Serial(port = port,
     286 + baudrate = baudrate,
     287 + bytesize = serial.EIGHTBITS,
     288 + parity = serial.PARITY_NONE,
     289 + stopbits = serial.STOPBITS_ONE,
     290 + xonxoff = False,
     291 + rtscts = rts_cts,
     292 + timeout = 0.1)
     293 + self.serial_port.flushInput()
     294 + self.serial_port.flushOutput()
     295 + except (serial.SerialException, ValueError, IOError, OSError) as e:
     296 + logger.error('Error opening port: %s' % (port,))
     297 + logger.error('The error was: %s' % (e.args,))
     298 + sys.exit(1)
     299 + logger.info('Serial port %s opened' % (self.serial_port.name))
     300 +
     301 + def close(self):
     302 + self.serial_port.close()
     303 + 
     304 + def pingc(self):
     305 + self.serial_port.write(ping)
     306 +
     307 + def stopc(self):
     308 + self.serial_port.write(stop)
     309 +
     310 + def cfgphyc(self):
     311 + self.serial_port.write(cfgphy)
     312 +
     313 + def cfgfreqc(self):
     314 + self.serial_port.write(cfgfreq)
     315 + 
     316 + def initiatorc(self):
     317 + self.serial_port.write(initiator)
     318 + 
     319 + #def startc(self):
     320 + # self.serial_port.write(letsgo)
     321 +
     322 + 
     323 + def startc(self):
     324 + # start sniffing
     325 + self.running = True
     326 + #self.dev.ctrl_transfer(CC2531.DIR_OUT, CC2531.SET_START)
     327 +
     328 + self.serial_port.write(letsgo)
     329 +
     330 + self.thread = threading.Thread(target=self.recv)
     331 + #self.thread.daemon = True
     332 + self.thread.start()
     333 + 
     334 + def stop(self):
     335 + # end sniffing
     336 + self.serial_port.write(stop)
     337 + self.running = False
     338 + self.thread.join()
     339 + #self.dev.ctrl_transfer(CC2531.DIR_OUT, CC2531.SET_STOP)
     340 + 
     341 + def isRunning(self):
     342 + return self.running
     343 + 
     344 + 
     345 + def recv(self):
     346 +
     347 + while self.running:
     348 + if self.serial_port.in_waiting > 0:
     349 + bytestream = self.serial_port.read(self.serial_port.in_waiting)
     350 + #print ("RECV>> %s" % binascii.hexlify(bytestream))
     351 + time.sleep(0.5)
     352 + start_index = 0
     353 +
     354 + while True:
     355 + # Find the index of the next occurrence of 0x40 0x53
     356 + start_index = bytestream.find(b'\x40\x53', start_index)
     357 + # If not found, break out of the loop
     358 + if start_index == -1:
     359 + break
     360 + # Find the index of the next occurrence of 0x40 0x45 after the start index
     361 + end_index = bytestream.find(b'\x40\x45', start_index)
     362 + # If not found, break out of the loop
     363 + if end_index == -1:
     364 + break
     365 + # Get the substring between start_index and end_index
     366 + substring = bytestream[start_index:end_index+2]
     367 + 
     368 + packet = self.parse_packet(substring)
     369 + if packet:
     370 + print("HELL O WORLD!")
     371 + self.callback(packet)
     372 + 
     373 + # Do something with the substring
     374 + #print(substring)
     375 + 
     376 + print ("SUBSRECV>> %s" % binascii.hexlify(substring))
     377 + 
     378 + # Set the start index to end_index + 2 (to skip over the 0x40 0x45 bytes)
     379 + start_index = end_index + 2
     380 +
     381 +
     382 + #if bytestream[0:2] == bytes([0x40, 0x53]):
     383 + # packet = self.parse_packet(bytestream)
     384 + #if packet:
     385 + # self.callback(packet)
     386 +
     387 + 
     388 + def set_channel(self, channel):
     389 + was_running = self.running
     390 + 
     391 + if channel >= 11 and channel <= 26:
     392 + if self.running:
     393 + self.stop()
     394 + 
     395 + self.channel = channel
     396 + 
     397 + # set channel command
     398 + #self.dev.ctrl_transfer(CC2531.DIR_OUT, CC2531.SET_CHAN, 0, 0,
     399 + # [channel])
     400 + #self.dev.ctrl_transfer(CC2531.DIR_OUT, CC2531.SET_CHAN, 0, 1,
     401 + # [0x00])
     402 + 
     403 + self.get_channel()
     404 + 
     405 + if was_running:
     406 + self.start()
     407 + 
     408 + else:
     409 + raise ValueError("Channel must be between 11 and 26")
     410 + 
     411 + def get_channel(self):
     412 + return self.channel
     413 +
     414 + def parse_packet(self, packet):
     415 + 
     416 + #print("***HELL***")
     417 + 
     418 + packetlen = packet[3:5]
     419 + 
     420 + #if len(packet) - 3 != packetlen:
     421 + # return None
     422 + 
     423 + # unknown header produced by the radio chip
     424 + header = packet[0:2]
     425 + 
     426 + # the data in the payload
     427 + payload = packet[11:-4]
     428 + 
     429 + # length of the payload
     430 + #payloadlen = packet[7] - 2 # without fcs
     431 + 
     432 + #if len(payload) != payloadlen:
     433 + # return None
     434 + 
     435 + # current time
     436 + timestamp = time.gmtime()
     437 + 
     438 + # used to derive other values
     439 + fcs1, fcs2 = packet[-4:-2]
     440 + 
     441 + # rssi is the signed value at fcs1
     442 + rssi = (fcs1 + 2**7) % 2**8 - 2**7 - 73
     443 + 
     444 + # crc ok is the 7th bit in fcs2
     445 + crc_ok = fcs2 & (1 << 7) > 0
     446 + 
     447 + # correlation value is the unsigned 0th-6th bit in fcs2
     448 + corr = fcs2 & 0x7f
     449 + 
     450 + return Packet(timestamp, self.channel, header, payload, rssi, crc_ok, corr)
     451 +
     452 + 
     453 + def __repr__(self):
     454 + 
     455 + if self.dev:
     456 + return "%s <Channel: %d>" % (self.name, self.channel)
     457 + else:
     458 + return "Not connected"
     459 + 
     460 +class Packet:
     461 + 
     462 + def __init__(self, timestamp, channel, header, payload, rssi, crc_ok, correlation):
     463 + self.timestamp = timestamp
     464 + self.channel = channel
     465 + self.header = header
     466 + self.payload = payload
     467 + self.rssi = rssi
     468 + self.crc_ok = crc_ok
     469 + self.correlation = correlation
     470 + 
     471 + def __repr__(self):
     472 +
     473 + ret = []
     474 + ret.append("Channel: %d" % self.channel)
     475 + ret.append("Timestamp: %s" % time.strftime("%H:%M:%S", self.timestamp))
     476 + ret.append("Header: %s" % binascii.hexlify(self.header))
     477 + ret.append("RSSI: %d" % self.rssi)
     478 + ret.append("CRC OK: %s" % self.crc_ok)
     479 + ret.append("Correlation: %d" % self.correlation)
     480 + ret.append("Payload: %s" % binascii.hexlify(self.payload))
     481 + 
     482 + return "\n".join(ret)
     483 + 
     484 +if __name__ == "__main__":
     485 + 
     486 + def callback(packet):
     487 + print("-"*30)
     488 + print(packet)
     489 + print("-"*30)
     490 +
     491 + sniffer = CC1352('/dev/ttyACM0', callback)
     492 + #sniffer = CC1352(callback)
     493 +
     494 + #print(sniffer)
     495 + #sniffer.startc()
     496 + #sniffer.pingc()
     497 + #time.sleep(2)
     498 + #sniffer.stopc()
     499 +
     500 + sniffer.pingc()
     501 + sniffer.stopc()
     502 + sniffer.cfgphyc()
     503 + sniffer.cfgfreqc()
     504 + sniffer.initiatorc()
     505 + sniffer.startc()
     506 + print ("start")
     507 + time.sleep(1)
     508 + sniffer.stop()
     509 + sniffer.close()
     510 + 
Please wait...
Page is in error, reload to recover