Apply black
This commit is contained in:
parent
9dafacebda
commit
cc301951fc
14 changed files with 684 additions and 387 deletions
|
|
@ -86,6 +86,6 @@ class CaptureFileHandler:
|
||||||
def writePacket(self, packet):
|
def writePacket(self, packet):
|
||||||
with open(self.filename, "ab") as f:
|
with open(self.filename, "ab") as f:
|
||||||
packet = Pcap.create_packet(
|
packet = Pcap.create_packet(
|
||||||
bytes([packet.boardId] + packet.getList()),
|
bytes([packet.boardId] + packet.getList()), packet.time
|
||||||
packet.time)
|
)
|
||||||
f.write(packet)
|
f.write(packet)
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
from . import Notifications
|
from . import Notifications
|
||||||
import logging, threading
|
import logging, threading
|
||||||
|
|
||||||
|
|
||||||
class DeviceList(Notifications.Notifier):
|
class DeviceList(Notifications.Notifier):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
Notifications.Notifier.__init__(self, *args, **kwargs)
|
Notifications.Notifier.__init__(self, *args, **kwargs)
|
||||||
|
|
@ -69,11 +70,15 @@ class DeviceList(Notifications.Notifier):
|
||||||
self.append(newDevice)
|
self.append(newDevice)
|
||||||
else:
|
else:
|
||||||
updated = False
|
updated = False
|
||||||
if (newDevice.name != "\"\"") and (existingDevice.name == "\"\""):
|
if (newDevice.name != '""') and (existingDevice.name == '""'):
|
||||||
existingDevice.name = newDevice.name
|
existingDevice.name = newDevice.name
|
||||||
updated = True
|
updated = True
|
||||||
|
|
||||||
if (newDevice.RSSI != 0 and (existingDevice.RSSI < (newDevice.RSSI - 5)) or (existingDevice.RSSI > (newDevice.RSSI+2))):
|
if (
|
||||||
|
newDevice.RSSI != 0
|
||||||
|
and (existingDevice.RSSI < (newDevice.RSSI - 5))
|
||||||
|
or (existingDevice.RSSI > (newDevice.RSSI + 2))
|
||||||
|
):
|
||||||
existingDevice.RSSI = newDevice.RSSI
|
existingDevice.RSSI = newDevice.RSSI
|
||||||
updated = True
|
updated = True
|
||||||
|
|
||||||
|
|
@ -126,6 +131,7 @@ class DeviceList(Notifications.Notifier):
|
||||||
def asList(self):
|
def asList(self):
|
||||||
return self.devices[:]
|
return self.devices[:]
|
||||||
|
|
||||||
|
|
||||||
class Device:
|
class Device:
|
||||||
def __init__(self, address, name, RSSI):
|
def __init__(self, address, name, RSSI):
|
||||||
self.address = address
|
self.address = address
|
||||||
|
|
@ -134,7 +140,8 @@ class Device:
|
||||||
self.followed = False
|
self.followed = False
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Bluetooth LE device "'+self.name+'" ('+str(self.address)+')'
|
return 'Bluetooth LE device "' + self.name + '" (' + str(self.address) + ")"
|
||||||
|
|
||||||
|
|
||||||
def listToString(list):
|
def listToString(list):
|
||||||
str = ""
|
str = ""
|
||||||
|
|
|
||||||
|
|
@ -38,23 +38,29 @@
|
||||||
class SnifferTimeout(Exception):
|
class SnifferTimeout(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UARTPacketError(Exception):
|
class UARTPacketError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LockedException(Exception):
|
class LockedException(Exception):
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
class InvalidPacketException(Exception):
|
class InvalidPacketException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidAdvChannel(Exception):
|
class InvalidAdvChannel(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Internal Use
|
# Internal Use
|
||||||
class SnifferWatchDogTimeout(SnifferTimeout):
|
class SnifferWatchDogTimeout(SnifferTimeout):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Internal Use
|
# Internal Use
|
||||||
class ExitCodeException(Exception):
|
class ExitCodeException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import os
|
||||||
import logging
|
import logging
|
||||||
from sys import platform
|
from sys import platform
|
||||||
|
|
||||||
if platform == 'linux':
|
if platform == "linux":
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
from . import Exceptions
|
from . import Exceptions
|
||||||
|
|
@ -16,8 +16,9 @@ from . import Exceptions
|
||||||
# HDB UUCP lock file format:
|
# HDB UUCP lock file format:
|
||||||
# process identifier (PID) as a ten byte ASCII decimal number, with a trailing newline
|
# process identifier (PID) as a ten byte ASCII decimal number, with a trailing newline
|
||||||
|
|
||||||
|
|
||||||
def lockpid(lockfile):
|
def lockpid(lockfile):
|
||||||
if (os.path.isfile(lockfile)):
|
if os.path.isfile(lockfile):
|
||||||
with open(lockfile) as fd:
|
with open(lockfile) as fd:
|
||||||
lockpid = fd.read()
|
lockpid = fd.read()
|
||||||
|
|
||||||
|
|
@ -30,17 +31,13 @@ def lockpid(lockfile):
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def lock(port):
|
def lock(port):
|
||||||
if platform != 'linux':
|
if platform != "linux":
|
||||||
return
|
return
|
||||||
|
|
||||||
tty = os.path.basename(port)
|
tty = os.path.basename(port)
|
||||||
lockfile = os.path.join(
|
lockfile = os.path.join("/run", "user", f"{os.getuid()}", f"{tty}.lock")
|
||||||
'/run',
|
|
||||||
'user',
|
|
||||||
f'{os.getuid()}',
|
|
||||||
f'{tty}.lock'
|
|
||||||
)
|
|
||||||
|
|
||||||
lockedpid = lockpid(lockfile)
|
lockedpid = lockpid(lockfile)
|
||||||
if lockedpid:
|
if lockedpid:
|
||||||
|
|
@ -53,16 +50,17 @@ def lock(port):
|
||||||
logging.info("Lockfile is stale. Overriding it..")
|
logging.info("Lockfile is stale. Overriding it..")
|
||||||
os.remove(lockfile)
|
os.remove(lockfile)
|
||||||
|
|
||||||
fd = open(lockfile, 'w')
|
fd = open(lockfile, "w")
|
||||||
with open(lockfile, 'w') as fd:
|
with open(lockfile, "w") as fd:
|
||||||
fd.write(f'{os.getpid():10}')
|
fd.write(f"{os.getpid():10}")
|
||||||
|
|
||||||
|
|
||||||
def unlock(port):
|
def unlock(port):
|
||||||
if platform != 'linux':
|
if platform != "linux":
|
||||||
return
|
return
|
||||||
|
|
||||||
tty = os.path.basename(port)
|
tty = os.path.basename(port)
|
||||||
lockfile = f'/var/lock/LCK..{tty}'
|
lockfile = f"/var/lock/LCK..{tty}"
|
||||||
|
|
||||||
lockedpid = lockpid(lockfile)
|
lockedpid = lockpid(lockfile)
|
||||||
if lockedpid == os.getpid():
|
if lockedpid == os.getpid():
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,11 @@ import logging.handlers as logHandlers
|
||||||
# will result in the line being appended to the log file #
|
# will result in the line being appended to the log file #
|
||||||
#################################################################
|
#################################################################
|
||||||
|
|
||||||
appdata = os.getenv('appdata')
|
appdata = os.getenv("appdata")
|
||||||
if appdata:
|
if appdata:
|
||||||
DEFAULT_LOG_FILE_DIR = os.path.join(appdata, 'Nordic Semiconductor', 'Sniffer', 'logs')
|
DEFAULT_LOG_FILE_DIR = os.path.join(
|
||||||
|
appdata, "Nordic Semiconductor", "Sniffer", "logs"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
DEFAULT_LOG_FILE_DIR = "/tmp/logs"
|
DEFAULT_LOG_FILE_DIR = "/tmp/logs"
|
||||||
|
|
||||||
|
|
@ -89,8 +91,12 @@ def initLogger():
|
||||||
global logFlusher
|
global logFlusher
|
||||||
global logHandlerArray
|
global logHandlerArray
|
||||||
|
|
||||||
logHandler = MyRotatingFileHandler(logFileName, mode='a', maxBytes=myMaxBytes, backupCount=3)
|
logHandler = MyRotatingFileHandler(
|
||||||
logFormatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s', datefmt='%d-%b-%Y %H:%M:%S (%z)')
|
logFileName, mode="a", maxBytes=myMaxBytes, backupCount=3
|
||||||
|
)
|
||||||
|
logFormatter = logging.Formatter(
|
||||||
|
"%(asctime)s %(levelname)s: %(message)s", datefmt="%d-%b-%Y %H:%M:%S (%z)"
|
||||||
|
)
|
||||||
logHandler.setFormatter(logFormatter)
|
logHandler.setFormatter(logFormatter)
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.addHandler(logHandler)
|
logger.addHandler(logHandler)
|
||||||
|
|
@ -154,6 +160,7 @@ def addLogHandler(logHandler):
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
logHandlerArray.append(logHandler)
|
logHandlerArray.append(logHandler)
|
||||||
|
|
||||||
|
|
||||||
def removeLogHandler(logHandler):
|
def removeLogHandler(logHandler):
|
||||||
global logHandlerArray
|
global logHandlerArray
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
@ -200,7 +207,7 @@ class LogFlusher(threading.Thread):
|
||||||
self.exit.set()
|
self.exit.set()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
initLogger()
|
initLogger()
|
||||||
for i in range(50):
|
for i in range(50):
|
||||||
logging.info("test log no. " + str(i))
|
logging.info("test log no. " + str(i))
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,8 @@
|
||||||
|
|
||||||
import threading, logging
|
import threading, logging
|
||||||
|
|
||||||
class Notification():
|
|
||||||
|
class Notification:
|
||||||
def __init__(self, key, msg=None):
|
def __init__(self, key, msg=None):
|
||||||
if type(key) is not str:
|
if type(key) is not str:
|
||||||
raise TypeError("Invalid notification key: " + str(key))
|
raise TypeError("Invalid notification key: " + str(key))
|
||||||
|
|
@ -47,7 +48,8 @@ class Notification():
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "Notification (key: %s, msg: %s)" % (str(self.key), str(self.msg))
|
return "Notification (key: %s, msg: %s)" % (str(self.key), str(self.msg))
|
||||||
|
|
||||||
class Notifier():
|
|
||||||
|
class Notifier:
|
||||||
def __init__(self, callbacks=[], **kwargs):
|
def __init__(self, callbacks=[], **kwargs):
|
||||||
self.callbacks = {}
|
self.callbacks = {}
|
||||||
self.callbackLock = threading.RLock()
|
self.callbackLock = threading.RLock()
|
||||||
|
|
|
||||||
|
|
@ -119,11 +119,15 @@ class PacketReader(Notifications.Notifier):
|
||||||
if complete_timeout is not None:
|
if complete_timeout is not None:
|
||||||
time_start = time.time()
|
time_start = time.time()
|
||||||
|
|
||||||
while not startOfPacket and (complete_timeout is None or (time.time() - time_start < complete_timeout)):
|
while not startOfPacket and (
|
||||||
|
complete_timeout is None or (time.time() - time_start < complete_timeout)
|
||||||
|
):
|
||||||
res = self.getSerialByte(timeout)
|
res = self.getSerialByte(timeout)
|
||||||
startOfPacket = (res == SLIP_START)
|
startOfPacket = res == SLIP_START
|
||||||
|
|
||||||
while not endOfPacket and (complete_timeout is None or (time.time() - time_start < complete_timeout)):
|
while not endOfPacket and (
|
||||||
|
complete_timeout is None or (time.time() - time_start < complete_timeout)
|
||||||
|
):
|
||||||
serialByte = self.getSerialByte(timeout)
|
serialByte = self.getSerialByte(timeout)
|
||||||
if serialByte == SLIP_END:
|
if serialByte == SLIP_END:
|
||||||
endOfPacket = True
|
endOfPacket = True
|
||||||
|
|
@ -140,7 +144,9 @@ class PacketReader(Notifications.Notifier):
|
||||||
else:
|
else:
|
||||||
dataBuffer.append(serialByte)
|
dataBuffer.append(serialByte)
|
||||||
if not endOfPacket:
|
if not endOfPacket:
|
||||||
raise Exceptions.UARTPacketError("Exceeded max timeout of %f seconds." % complete_timeout)
|
raise Exceptions.UARTPacketError(
|
||||||
|
"Exceeded max timeout of %f seconds." % complete_timeout
|
||||||
|
)
|
||||||
return dataBuffer
|
return dataBuffer
|
||||||
|
|
||||||
# This function read byte chuncks from the serial port and return one byte at a time
|
# This function read byte chuncks from the serial port and return one byte at a time
|
||||||
|
|
@ -153,13 +159,23 @@ class PacketReader(Notifications.Notifier):
|
||||||
|
|
||||||
def handlePacketHistory(self, packet):
|
def handlePacketHistory(self, packet):
|
||||||
# Reads and validates packet counter
|
# Reads and validates packet counter
|
||||||
if self.lastReceivedPacket is not None \
|
if (
|
||||||
and packet.packetCounter != (self.lastReceivedPacket.packetCounter + 1) % PACKET_COUNTER_CAP \
|
self.lastReceivedPacket is not None
|
||||||
and self.lastReceivedPacket.packetCounter != 0:
|
and packet.packetCounter
|
||||||
|
!= (self.lastReceivedPacket.packetCounter + 1) % PACKET_COUNTER_CAP
|
||||||
|
and self.lastReceivedPacket.packetCounter != 0
|
||||||
|
):
|
||||||
|
|
||||||
logging.info("gap in packets, between " + str(self.lastReceivedPacket.packetCounter) + " and "
|
logging.info(
|
||||||
+ str(packet.packetCounter) + " packet before: " + str(self.lastReceivedPacket.packetList)
|
"gap in packets, between "
|
||||||
+ " packet after: " + str(packet.packetList))
|
+ str(self.lastReceivedPacket.packetCounter)
|
||||||
|
+ " and "
|
||||||
|
+ str(packet.packetCounter)
|
||||||
|
+ " packet before: "
|
||||||
|
+ str(self.lastReceivedPacket.packetList)
|
||||||
|
+ " packet after: "
|
||||||
|
+ str(packet.packetList)
|
||||||
|
)
|
||||||
|
|
||||||
self.lastReceivedPacket = packet
|
self.lastReceivedPacket = packet
|
||||||
if packet.id in [EVENT_PACKET_DATA_PDU, EVENT_PACKET_ADV_PDU]:
|
if packet.id in [EVENT_PACKET_DATA_PDU, EVENT_PACKET_ADV_PDU]:
|
||||||
|
|
@ -198,10 +214,14 @@ class PacketReader(Notifications.Notifier):
|
||||||
|
|
||||||
# Convert time-stamp to End to Start delta
|
# Convert time-stamp to End to Start delta
|
||||||
time_delta = 0
|
time_delta = 0
|
||||||
if self.lastReceivedTimestampPacket is not None and self.lastReceivedTimestampPacket.valid:
|
if (
|
||||||
time_delta = (packet.timestamp -
|
self.lastReceivedTimestampPacket is not None
|
||||||
(self.lastReceivedTimestampPacket.timestamp +
|
and self.lastReceivedTimestampPacket.valid
|
||||||
self.getPacketTime(self.lastReceivedTimestampPacket)))
|
):
|
||||||
|
time_delta = packet.timestamp - (
|
||||||
|
self.lastReceivedTimestampPacket.timestamp
|
||||||
|
+ self.getPacketTime(self.lastReceivedTimestampPacket)
|
||||||
|
)
|
||||||
|
|
||||||
time_delta = toLittleEndian(time_delta, 4)
|
time_delta = toLittleEndian(time_delta, 4)
|
||||||
packet.packetList[TIMESTAMP_POS] = time_delta[0]
|
packet.packetList[TIMESTAMP_POS] = time_delta[0]
|
||||||
|
|
@ -209,14 +229,19 @@ class PacketReader(Notifications.Notifier):
|
||||||
packet.packetList[TIMESTAMP_POS + 2] = time_delta[2]
|
packet.packetList[TIMESTAMP_POS + 2] = time_delta[2]
|
||||||
packet.packetList[TIMESTAMP_POS + 3] = time_delta[3]
|
packet.packetList[TIMESTAMP_POS + 3] = time_delta[3]
|
||||||
|
|
||||||
|
|
||||||
def handlePacketCompatibility(self, packet):
|
def handlePacketCompatibility(self, packet):
|
||||||
if self.supportedProtocolVersion == PROTOVER_V2 and packet.packetList[PROTOVER_POS] > PROTOVER_V2:
|
if (
|
||||||
|
self.supportedProtocolVersion == PROTOVER_V2
|
||||||
|
and packet.packetList[PROTOVER_POS] > PROTOVER_V2
|
||||||
|
):
|
||||||
self.convertPacketListProtoVer2(packet)
|
self.convertPacketListProtoVer2(packet)
|
||||||
|
|
||||||
def setSupportedProtocolVersion(self, supportedProtocolVersion):
|
def setSupportedProtocolVersion(self, supportedProtocolVersion):
|
||||||
if (supportedProtocolVersion != PROTOVER_V3):
|
if supportedProtocolVersion != PROTOVER_V3:
|
||||||
logging.info("Using packet compatibility, converting packets to protocol version %d", supportedProtocolVersion)
|
logging.info(
|
||||||
|
"Using packet compatibility, converting packets to protocol version %d",
|
||||||
|
supportedProtocolVersion,
|
||||||
|
)
|
||||||
self.supportedProtocolVersion = supportedProtocolVersion
|
self.supportedProtocolVersion = supportedProtocolVersion
|
||||||
|
|
||||||
def getPacket(self, timeout=None):
|
def getPacket(self, timeout=None):
|
||||||
|
|
@ -234,7 +259,14 @@ class PacketReader(Notifications.Notifier):
|
||||||
return packet
|
return packet
|
||||||
|
|
||||||
def sendPacket(self, id, payload):
|
def sendPacket(self, id, payload):
|
||||||
packetList = [HEADER_LENGTH] + [len(payload)] + [PROTOVER_V1] + toLittleEndian(self.packetCounter, 2) + [id] + payload
|
packetList = (
|
||||||
|
[HEADER_LENGTH]
|
||||||
|
+ [len(payload)]
|
||||||
|
+ [PROTOVER_V1]
|
||||||
|
+ toLittleEndian(self.packetCounter, 2)
|
||||||
|
+ [id]
|
||||||
|
+ payload
|
||||||
|
)
|
||||||
packetList = self.encodeToSLIP(packetList)
|
packetList = self.encodeToSLIP(packetList)
|
||||||
self.packetCounter += 1
|
self.packetCounter += 1
|
||||||
self.uart.writeList(packetList)
|
self.uart.writeList(packetList)
|
||||||
|
|
@ -244,7 +276,13 @@ class PacketReader(Notifications.Notifier):
|
||||||
self.sendPacket(REQ_SCAN_CONT, [flags0])
|
self.sendPacket(REQ_SCAN_CONT, [flags0])
|
||||||
logging.info("Scan flags: %s" % bin(flags0))
|
logging.info("Scan flags: %s" % bin(flags0))
|
||||||
|
|
||||||
def sendFollow(self, addr, followOnlyAdvertisements = False, followOnlyLegacy = False, followCoded = False):
|
def sendFollow(
|
||||||
|
self,
|
||||||
|
addr,
|
||||||
|
followOnlyAdvertisements=False,
|
||||||
|
followOnlyLegacy=False,
|
||||||
|
followCoded=False,
|
||||||
|
):
|
||||||
flags0 = followOnlyAdvertisements | (followOnlyLegacy << 1) | (followCoded << 2)
|
flags0 = followOnlyAdvertisements | (followOnlyLegacy << 1) | (followCoded << 2)
|
||||||
logging.info("Follow flags: %s" % bin(flags0))
|
logging.info("Follow flags: %s" % bin(flags0))
|
||||||
self.sendPacket(REQ_FOLLOW, addr + [flags0])
|
self.sendPacket(REQ_FOLLOW, addr + [flags0])
|
||||||
|
|
@ -253,7 +291,7 @@ class PacketReader(Notifications.Notifier):
|
||||||
self.sendPacket(PING_REQ, [])
|
self.sendPacket(PING_REQ, [])
|
||||||
|
|
||||||
def getBytes(self, value, size):
|
def getBytes(self, value, size):
|
||||||
if (len(value) < size):
|
if len(value) < size:
|
||||||
value = [0] * (size - len(value)) + value
|
value = [0] * (size - len(value)) + value
|
||||||
else:
|
else:
|
||||||
value = value[:size]
|
value = value[:size]
|
||||||
|
|
@ -294,7 +332,9 @@ class PacketReader(Notifications.Notifier):
|
||||||
def sendHopSequence(self, hopSequence):
|
def sendHopSequence(self, hopSequence):
|
||||||
for chan in hopSequence:
|
for chan in hopSequence:
|
||||||
if chan not in VALID_ADV_CHANS:
|
if chan not in VALID_ADV_CHANS:
|
||||||
raise Exceptions.InvalidAdvChannel("%s is not an adv channel" % str(chan))
|
raise Exceptions.InvalidAdvChannel(
|
||||||
|
"%s is not an adv channel" % str(chan)
|
||||||
|
)
|
||||||
payload = [len(hopSequence)] + hopSequence + [37] * (3 - len(hopSequence))
|
payload = [len(hopSequence)] + hopSequence + [37] * (3 - len(hopSequence))
|
||||||
self.sendPacket(SET_ADV_CHANNEL_HOP_SEQ, payload)
|
self.sendPacket(SET_ADV_CHANNEL_HOP_SEQ, payload)
|
||||||
self.notify("NEW_ADV_HOP_SEQ", {"hopSequence": hopSequence})
|
self.notify("NEW_ADV_HOP_SEQ", {"hopSequence": hopSequence})
|
||||||
|
|
@ -313,21 +353,31 @@ class Packet:
|
||||||
def __init__(self, packetList):
|
def __init__(self, packetList):
|
||||||
try:
|
try:
|
||||||
if not packetList:
|
if not packetList:
|
||||||
raise Exceptions.InvalidPacketException("packet list not valid: %s" % str(packetList))
|
raise Exceptions.InvalidPacketException(
|
||||||
|
"packet list not valid: %s" % str(packetList)
|
||||||
|
)
|
||||||
|
|
||||||
self.protover = packetList[PROTOVER_POS]
|
self.protover = packetList[PROTOVER_POS]
|
||||||
|
|
||||||
if self.protover > PROTOVER_V3:
|
if self.protover > PROTOVER_V3:
|
||||||
logging.exception("Unsupported protocol version %s" % str(self.protover))
|
logging.exception(
|
||||||
raise RuntimeError("Unsupported protocol version %s" % str(self.protover))
|
"Unsupported protocol version %s" % str(self.protover)
|
||||||
|
)
|
||||||
|
raise RuntimeError(
|
||||||
|
"Unsupported protocol version %s" % str(self.protover)
|
||||||
|
)
|
||||||
|
|
||||||
self.packetCounter = parseLittleEndian(packetList[PACKETCOUNTER_POS:PACKETCOUNTER_POS + 2])
|
self.packetCounter = parseLittleEndian(
|
||||||
|
packetList[PACKETCOUNTER_POS : PACKETCOUNTER_POS + 2]
|
||||||
|
)
|
||||||
self.id = packetList[ID_POS]
|
self.id = packetList[ID_POS]
|
||||||
|
|
||||||
if int(self.protover) == PROTOVER_V1:
|
if int(self.protover) == PROTOVER_V1:
|
||||||
self.payloadLength = packetList[PAYLOAD_LEN_POS_V1]
|
self.payloadLength = packetList[PAYLOAD_LEN_POS_V1]
|
||||||
else:
|
else:
|
||||||
self.payloadLength = parseLittleEndian(packetList[PAYLOAD_LEN_POS:PAYLOAD_LEN_POS + 2])
|
self.payloadLength = parseLittleEndian(
|
||||||
|
packetList[PAYLOAD_LEN_POS : PAYLOAD_LEN_POS + 2]
|
||||||
|
)
|
||||||
|
|
||||||
self.packetList = packetList
|
self.packetList = packetList
|
||||||
self.readPayload(packetList)
|
self.readPayload(packetList)
|
||||||
|
|
@ -350,7 +400,9 @@ class Packet:
|
||||||
self.OK = False
|
self.OK = False
|
||||||
|
|
||||||
if not self.validatePacketList(packetList):
|
if not self.validatePacketList(packetList):
|
||||||
raise Exceptions.InvalidPacketException("packet list not valid: %s" % str(packetList))
|
raise Exceptions.InvalidPacketException(
|
||||||
|
"packet list not valid: %s" % str(packetList)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.valid = True
|
self.valid = True
|
||||||
|
|
||||||
|
|
@ -365,9 +417,13 @@ class Packet:
|
||||||
self.channel = packetList[CHANNEL_POS]
|
self.channel = packetList[CHANNEL_POS]
|
||||||
self.rawRSSI = packetList[RSSI_POS]
|
self.rawRSSI = packetList[RSSI_POS]
|
||||||
self.RSSI = -self.rawRSSI
|
self.RSSI = -self.rawRSSI
|
||||||
self.eventCounter = parseLittleEndian(packetList[EVENTCOUNTER_POS:EVENTCOUNTER_POS+2])
|
self.eventCounter = parseLittleEndian(
|
||||||
|
packetList[EVENTCOUNTER_POS : EVENTCOUNTER_POS + 2]
|
||||||
|
)
|
||||||
|
|
||||||
self.timestamp = parseLittleEndian(packetList[TIMESTAMP_POS:TIMESTAMP_POS+4])
|
self.timestamp = parseLittleEndian(
|
||||||
|
packetList[TIMESTAMP_POS : TIMESTAMP_POS + 4]
|
||||||
|
)
|
||||||
|
|
||||||
# The hardware adds a padding byte which isn't sent on air.
|
# The hardware adds a padding byte which isn't sent on air.
|
||||||
# We remove it, and update the payload length in the packet list.
|
# We remove it, and update the payload length in the packet list.
|
||||||
|
|
@ -390,15 +446,22 @@ class Packet:
|
||||||
if self.OK:
|
if self.OK:
|
||||||
try:
|
try:
|
||||||
if self.protover >= PROTOVER_V3:
|
if self.protover >= PROTOVER_V3:
|
||||||
packet_type = (PACKET_TYPE_ADVERTISING
|
packet_type = (
|
||||||
if self.id == EVENT_PACKET_ADV_PDU else
|
PACKET_TYPE_ADVERTISING
|
||||||
PACKET_TYPE_DATA)
|
if self.id == EVENT_PACKET_ADV_PDU
|
||||||
|
else PACKET_TYPE_DATA
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
packet_type = (PACKET_TYPE_ADVERTISING
|
packet_type = (
|
||||||
if packetList[BLEPACKET_POS : BLEPACKET_POS + 4] == ADV_ACCESS_ADDRESS else
|
PACKET_TYPE_ADVERTISING
|
||||||
PACKET_TYPE_DATA)
|
if packetList[BLEPACKET_POS : BLEPACKET_POS + 4]
|
||||||
|
== ADV_ACCESS_ADDRESS
|
||||||
|
else PACKET_TYPE_DATA
|
||||||
|
)
|
||||||
|
|
||||||
self.blePacket = BlePacket(packet_type, packetList[BLEPACKET_POS:], self.phy)
|
self.blePacket = BlePacket(
|
||||||
|
packet_type, packetList[BLEPACKET_POS:], self.phy
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception("blePacket error %s" % str(e))
|
logging.exception("blePacket error %s" % str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -407,11 +470,15 @@ class Packet:
|
||||||
self.OK = False
|
self.OK = False
|
||||||
elif self.id == PING_RESP:
|
elif self.id == PING_RESP:
|
||||||
if self.protover < PROTOVER_V3:
|
if self.protover < PROTOVER_V3:
|
||||||
self.version = parseLittleEndian(packetList[PAYLOAD_POS:PAYLOAD_POS+2])
|
self.version = parseLittleEndian(
|
||||||
|
packetList[PAYLOAD_POS : PAYLOAD_POS + 2]
|
||||||
|
)
|
||||||
elif self.id == RESP_VERSION:
|
elif self.id == RESP_VERSION:
|
||||||
self.version = ''.join([chr(i) for i in packetList[PAYLOAD_POS:]])
|
self.version = "".join([chr(i) for i in packetList[PAYLOAD_POS:]])
|
||||||
elif self.id == RESP_TIMESTAMP:
|
elif self.id == RESP_TIMESTAMP:
|
||||||
self.timestamp = parseLittleEndian(packetList[PAYLOAD_POS:PAYLOAD_POS+4])
|
self.timestamp = parseLittleEndian(
|
||||||
|
packetList[PAYLOAD_POS : PAYLOAD_POS + 4]
|
||||||
|
)
|
||||||
elif self.id == SWITCH_BAUD_RATE_RESP or self.id == SWITCH_BAUD_RATE_REQ:
|
elif self.id == SWITCH_BAUD_RATE_RESP or self.id == SWITCH_BAUD_RATE_REQ:
|
||||||
self.baudRate = parseLittleEndian(packetList[PAYLOAD_POS : PAYLOAD_POS + 4])
|
self.baudRate = parseLittleEndian(packetList[PAYLOAD_POS : PAYLOAD_POS + 4])
|
||||||
else:
|
else:
|
||||||
|
|
@ -438,7 +505,8 @@ class Packet:
|
||||||
logging.exception("Invalid packet: %s" % str(packetList))
|
logging.exception("Invalid packet: %s" % str(packetList))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class BlePacket():
|
|
||||||
|
class BlePacket:
|
||||||
def __init__(self, type, packetList, phy):
|
def __init__(self, type, packetList, phy):
|
||||||
self.type = type
|
self.type = type
|
||||||
|
|
||||||
|
|
@ -458,7 +526,6 @@ class BlePacket():
|
||||||
offset = self.extractAddresses(packetList, offset)
|
offset = self.extractAddresses(packetList, offset)
|
||||||
self.extractName(packetList, offset)
|
self.extractName(packetList, offset)
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "BLE packet, AAddr: " + str(self.accessAddress)
|
return "BLE packet, AAddr: " + str(self.accessAddress)
|
||||||
|
|
||||||
|
|
@ -519,7 +586,7 @@ class BlePacket():
|
||||||
offset += 6
|
offset += 6
|
||||||
|
|
||||||
if self.advType == 7:
|
if self.advType == 7:
|
||||||
ext_header_len = packetList[offset] & 0x3f
|
ext_header_len = packetList[offset] & 0x3F
|
||||||
offset += 1
|
offset += 1
|
||||||
|
|
||||||
ext_header_offset = offset
|
ext_header_offset = offset
|
||||||
|
|
@ -558,9 +625,9 @@ class BlePacket():
|
||||||
name = ""
|
name = ""
|
||||||
for j in nameList:
|
for j in nameList:
|
||||||
name += chr(j)
|
name += chr(j)
|
||||||
i += (length+1)
|
i += length + 1
|
||||||
name = '"' + name + '"'
|
name = '"' + name + '"'
|
||||||
elif (self.advType == 1):
|
elif self.advType == 1:
|
||||||
name = "[ADV_DIRECT_IND]"
|
name = "[ADV_DIRECT_IND]"
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
@ -569,15 +636,16 @@ class BlePacket():
|
||||||
self.length = packetList[offset]
|
self.length = packetList[offset]
|
||||||
return offset + 1
|
return offset + 1
|
||||||
|
|
||||||
|
|
||||||
def parseLittleEndian(list):
|
def parseLittleEndian(list):
|
||||||
total = 0
|
total = 0
|
||||||
for i in range(len(list)):
|
for i in range(len(list)):
|
||||||
total+=(list[i] << (8*i))
|
total += list[i] << (8 * i)
|
||||||
return total
|
return total
|
||||||
|
|
||||||
|
|
||||||
def toLittleEndian(value, size):
|
def toLittleEndian(value, size):
|
||||||
list = [0] * size
|
list = [0] * size
|
||||||
for i in range(size):
|
for i in range(size):
|
||||||
list[i] = (value >> (i * 8)) % 256
|
list[i] = (value >> (i * 8)) % 256
|
||||||
return list
|
return list
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,14 +44,16 @@ import struct
|
||||||
# - https://github.com/pcapng/pcapng
|
# - https://github.com/pcapng/pcapng
|
||||||
# - https://www.tcpdump.org/linktypes/LINKTYPE_NORDIC_BLE.html
|
# - https://www.tcpdump.org/linktypes/LINKTYPE_NORDIC_BLE.html
|
||||||
PACKET_HEADER = struct.Struct("<LLLL")
|
PACKET_HEADER = struct.Struct("<LLLL")
|
||||||
GLOBAL_HEADER = struct.pack("<LHHIILL",
|
GLOBAL_HEADER = struct.pack(
|
||||||
0xa1b2c3d4, # PCAP magic number
|
"<LHHIILL",
|
||||||
|
0xA1B2C3D4, # PCAP magic number
|
||||||
2, # PCAP major version
|
2, # PCAP major version
|
||||||
4, # PCAP minor version
|
4, # PCAP minor version
|
||||||
0, # Reserved
|
0, # Reserved
|
||||||
0, # Reserved
|
0, # Reserved
|
||||||
0x0000ffff, # Max length of capture frame
|
0x0000FFFF, # Max length of capture frame
|
||||||
272) # Nordic BLE link type
|
272,
|
||||||
|
) # Nordic BLE link type
|
||||||
|
|
||||||
|
|
||||||
def get_global_header():
|
def get_global_header():
|
||||||
|
|
@ -72,8 +74,9 @@ def create_packet(packet: bytes, timestamp_seconds: float):
|
||||||
timestamp_floor = int(timestamp_seconds)
|
timestamp_floor = int(timestamp_seconds)
|
||||||
timestamp_offset_us = int((timestamp_seconds - timestamp_floor) * 1_000_000)
|
timestamp_offset_us = int((timestamp_seconds - timestamp_floor) * 1_000_000)
|
||||||
|
|
||||||
return struct.pack("<LLLL",
|
return (
|
||||||
timestamp_floor,
|
struct.pack(
|
||||||
timestamp_offset_us,
|
"<LLLL", timestamp_floor, timestamp_offset_us, len(packet), len(packet)
|
||||||
len(packet),
|
)
|
||||||
len(packet)) + packet
|
+ packet
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ from . import Logger
|
||||||
from . import UART
|
from . import UART
|
||||||
|
|
||||||
from .Types import *
|
from .Types import *
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .version import VERSION_STRING
|
from .version import VERSION_STRING
|
||||||
except:
|
except:
|
||||||
|
|
@ -55,25 +56,28 @@ def initLog():
|
||||||
initLog()
|
initLog()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import sys, os, threading
|
import sys, os, threading
|
||||||
from . import SnifferCollector
|
from . import SnifferCollector
|
||||||
|
|
||||||
|
|
||||||
class Sniffer(threading.Thread, SnifferCollector.SnifferCollector):
|
class Sniffer(threading.Thread, SnifferCollector.SnifferCollector):
|
||||||
|
|
||||||
# Sniffer constructor. portnum argument is optional. If not provided,
|
# Sniffer constructor. portnum argument is optional. If not provided,
|
||||||
# the software will try to locate the firwmare automatically (may take time).
|
# the software will try to locate the firwmare automatically (may take time).
|
||||||
# NOTE: portnum is 0-indexed, while Windows names are 1-indexed
|
# NOTE: portnum is 0-indexed, while Windows names are 1-indexed
|
||||||
def __init__(self, portnum=None, baudrate=UART.SNIFFER_OLD_DEFAULT_BAUDRATE, **kwargs):
|
def __init__(
|
||||||
|
self, portnum=None, baudrate=UART.SNIFFER_OLD_DEFAULT_BAUDRATE, **kwargs
|
||||||
|
):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
SnifferCollector.SnifferCollector.__init__(self, portnum, baudrate=baudrate, **kwargs)
|
SnifferCollector.SnifferCollector.__init__(
|
||||||
|
self, portnum, baudrate=baudrate, **kwargs
|
||||||
|
)
|
||||||
self.daemon = True
|
self.daemon = True
|
||||||
|
|
||||||
self.subscribe("COMPORT_FOUND", self.comPortFound)
|
self.subscribe("COMPORT_FOUND", self.comPortFound)
|
||||||
|
|
||||||
# API STARTS HERE
|
# API STARTS HERE
|
||||||
|
|
||||||
|
|
||||||
# Get [number] number of packets since last fetch (-1 means all)
|
# Get [number] number of packets since last fetch (-1 means all)
|
||||||
# Note that the packet buffer is limited to about 80000 packets.
|
# Note that the packet buffer is limited to about 80000 packets.
|
||||||
# Returns: A list of Packet objects
|
# Returns: A list of Packet objects
|
||||||
|
|
@ -93,8 +97,16 @@ class Sniffer(threading.Thread, SnifferCollector.SnifferCollector):
|
||||||
# "device" argument is of type Device
|
# "device" argument is of type Device
|
||||||
# if "followOnlyAdvertisements" is True, the sniffer will not follow the device into a connection.
|
# if "followOnlyAdvertisements" is True, the sniffer will not follow the device into a connection.
|
||||||
# Returns nothing
|
# Returns nothing
|
||||||
def follow(self, device=None, followOnlyAdvertisements = False, followOnlyLegacy = False, followCoded = False):
|
def follow(
|
||||||
self._startFollowing(device, followOnlyAdvertisements, followOnlyLegacy, followCoded)
|
self,
|
||||||
|
device=None,
|
||||||
|
followOnlyAdvertisements=False,
|
||||||
|
followOnlyLegacy=False,
|
||||||
|
followCoded=False,
|
||||||
|
):
|
||||||
|
self._startFollowing(
|
||||||
|
device, followOnlyAdvertisements, followOnlyLegacy, followCoded
|
||||||
|
)
|
||||||
|
|
||||||
# Clear the list of devices
|
# Clear the list of devices
|
||||||
def clearDevices(self):
|
def clearDevices(self):
|
||||||
|
|
@ -217,20 +229,30 @@ class Sniffer(threading.Thread, SnifferCollector.SnifferCollector):
|
||||||
try:
|
try:
|
||||||
self._setup()
|
self._setup()
|
||||||
self.runSniffer()
|
self.runSniffer()
|
||||||
except (KeyboardInterrupt) as e:
|
except KeyboardInterrupt as e:
|
||||||
_, _, exc_tb = sys.exc_info()
|
_, _, exc_tb = sys.exc_info()
|
||||||
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
|
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
|
||||||
lineno = exc_tb.tb_lineno
|
lineno = exc_tb.tb_lineno
|
||||||
logging.info("exiting ("+str(type(e))+" in "+fname+" at "+str(lineno)+"): "+str(e))
|
logging.info(
|
||||||
|
"exiting ("
|
||||||
|
+ str(type(e))
|
||||||
|
+ " in "
|
||||||
|
+ fname
|
||||||
|
+ " at "
|
||||||
|
+ str(lineno)
|
||||||
|
+ "): "
|
||||||
|
+ str(e)
|
||||||
|
)
|
||||||
self.goodExit = False
|
self.goodExit = False
|
||||||
except (BrokenPipeError, OSError):
|
except (BrokenPipeError, OSError):
|
||||||
logging.info("capture pipe closed before sniffer thread was stopped")
|
logging.info("capture pipe closed before sniffer thread was stopped")
|
||||||
self.goodExit = True
|
self.goodExit = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
logging.exception("CRASH: {}".format(e))
|
logging.exception("CRASH: {}".format(e))
|
||||||
logging.exception(traceback.format_exc())
|
logging.exception(traceback.format_exc())
|
||||||
logging.exception('internal error: {}'.format(repr(e)))
|
logging.exception("internal error: {}".format(repr(e)))
|
||||||
self.goodExit = False
|
self.goodExit = False
|
||||||
else:
|
else:
|
||||||
self.goodExit = True
|
self.goodExit = True
|
||||||
|
|
|
||||||
|
|
@ -43,21 +43,25 @@ STATE_INITIALIZING = 0
|
||||||
STATE_SCANNING = 1
|
STATE_SCANNING = 1
|
||||||
STATE_FOLLOWING = 2
|
STATE_FOLLOWING = 2
|
||||||
|
|
||||||
|
|
||||||
class SnifferCollector(Notifications.Notifier):
|
class SnifferCollector(Notifications.Notifier):
|
||||||
def __init__(self, portnum=None, baudrate=None, *args, **kwargs):
|
def __init__(self, portnum=None, baudrate=None, *args, **kwargs):
|
||||||
Notifications.Notifier.__init__(self, *args, **kwargs)
|
Notifications.Notifier.__init__(self, *args, **kwargs)
|
||||||
self._portnum = portnum
|
self._portnum = portnum
|
||||||
self._fwversion = "Unknown version"
|
self._fwversion = "Unknown version"
|
||||||
self._setState(STATE_INITIALIZING)
|
self._setState(STATE_INITIALIZING)
|
||||||
self._captureHandler = CaptureFiles.CaptureFileHandler(capture_file_path=kwargs.get("capture_file_path", None))
|
self._captureHandler = CaptureFiles.CaptureFileHandler(
|
||||||
|
capture_file_path=kwargs.get("capture_file_path", None)
|
||||||
|
)
|
||||||
self._exit = False
|
self._exit = False
|
||||||
self._connectionAccessAddress = None
|
self._connectionAccessAddress = None
|
||||||
self._packetListLock = threading.RLock()
|
self._packetListLock = threading.RLock()
|
||||||
with self._packetListLock:
|
with self._packetListLock:
|
||||||
self._packets = []
|
self._packets = []
|
||||||
|
|
||||||
self._packetReader = Packet.PacketReader(self._portnum, baudrate=baudrate,
|
self._packetReader = Packet.PacketReader(
|
||||||
callbacks=[("*", self.passOnNotification)])
|
self._portnum, baudrate=baudrate, callbacks=[("*", self.passOnNotification)]
|
||||||
|
)
|
||||||
self._devices = Devices.DeviceList(callbacks=[("*", self.passOnNotification)])
|
self._devices = Devices.DeviceList(callbacks=[("*", self.passOnNotification)])
|
||||||
|
|
||||||
self._missedPackets = 0
|
self._missedPackets = 0
|
||||||
|
|
@ -84,10 +88,10 @@ class SnifferCollector(Notifications.Notifier):
|
||||||
|
|
||||||
def _makeBoardId(self):
|
def _makeBoardId(self):
|
||||||
try:
|
try:
|
||||||
if sys.platform == 'win32':
|
if sys.platform == "win32":
|
||||||
boardId = int(self._packetReader.portnum.split("COM")[1])
|
boardId = int(self._packetReader.portnum.split("COM")[1])
|
||||||
logging.info("board ID: %d" % boardId)
|
logging.info("board ID: %d" % boardId)
|
||||||
elif sys.platform == 'linux':
|
elif sys.platform == "linux":
|
||||||
boardId = int(self._packetReader.portnum.split("ttyACM")[1])
|
boardId = int(self._packetReader.portnum.split("ttyACM")[1])
|
||||||
logging.info("board ID: %d" % boardId)
|
logging.info("board ID: %d" % boardId)
|
||||||
else:
|
else:
|
||||||
|
|
@ -95,6 +99,7 @@ class SnifferCollector(Notifications.Notifier):
|
||||||
raise IndexError()
|
raise IndexError()
|
||||||
except (IndexError, AttributeError):
|
except (IndexError, AttributeError):
|
||||||
import random
|
import random
|
||||||
|
|
||||||
random.seed()
|
random.seed()
|
||||||
boardId = random.randint(0, 255)
|
boardId = random.randint(0, 255)
|
||||||
logging.info("board ID (random): %d" % boardId)
|
logging.info("board ID (random): %d" % boardId)
|
||||||
|
|
@ -131,7 +136,7 @@ class SnifferCollector(Notifications.Notifier):
|
||||||
if packet.timestamp < self._last_timestamp:
|
if packet.timestamp < self._last_timestamp:
|
||||||
time_diff = (1 << 32) - (self._last_timestamp - packet.timestamp)
|
time_diff = (1 << 32) - (self._last_timestamp - packet.timestamp)
|
||||||
else:
|
else:
|
||||||
time_diff = (packet.timestamp - self._last_timestamp)
|
time_diff = packet.timestamp - self._last_timestamp
|
||||||
|
|
||||||
packet.time = self._last_time + (time_diff / 1_000_000)
|
packet.time = self._last_time + (time_diff / 1_000_000)
|
||||||
|
|
||||||
|
|
@ -155,16 +160,25 @@ class SnifferCollector(Notifications.Notifier):
|
||||||
self._connectionAccessAddress = packet.blePacket.accessAddress
|
self._connectionAccessAddress = packet.blePacket.accessAddress
|
||||||
|
|
||||||
if self.state == STATE_FOLLOWING and packet.blePacket.advType == 4:
|
if self.state == STATE_FOLLOWING and packet.blePacket.advType == 4:
|
||||||
newDevice = Devices.Device(address=packet.blePacket.advAddress, name=packet.blePacket.name, RSSI=packet.RSSI)
|
newDevice = Devices.Device(
|
||||||
|
address=packet.blePacket.advAddress,
|
||||||
|
name=packet.blePacket.name,
|
||||||
|
RSSI=packet.RSSI,
|
||||||
|
)
|
||||||
self._devices.appendOrUpdate(newDevice)
|
self._devices.appendOrUpdate(newDevice)
|
||||||
|
|
||||||
if self.state == STATE_SCANNING:
|
if self.state == STATE_SCANNING:
|
||||||
if (packet.blePacket.advType in [0, 1, 2, 4, 6, 7] and
|
if (
|
||||||
packet.blePacket.advAddress != None and
|
packet.blePacket.advType in [0, 1, 2, 4, 6, 7]
|
||||||
packet.crcOK and
|
and packet.blePacket.advAddress != None
|
||||||
not packet.direction
|
and packet.crcOK
|
||||||
|
and not packet.direction
|
||||||
):
|
):
|
||||||
newDevice = Devices.Device(address=packet.blePacket.advAddress, name=packet.blePacket.name, RSSI=packet.RSSI)
|
newDevice = Devices.Device(
|
||||||
|
address=packet.blePacket.advAddress,
|
||||||
|
name=packet.blePacket.name,
|
||||||
|
RSSI=packet.RSSI,
|
||||||
|
)
|
||||||
self._devices.appendOrUpdate(newDevice)
|
self._devices.appendOrUpdate(newDevice)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -187,7 +201,10 @@ class SnifferCollector(Notifications.Notifier):
|
||||||
except Exceptions.InvalidPacketException:
|
except Exceptions.InvalidPacketException:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if packet.id == EVENT_PACKET_DATA_PDU or packet.id == EVENT_PACKET_ADV_PDU:
|
if (
|
||||||
|
packet.id == EVENT_PACKET_DATA_PDU
|
||||||
|
or packet.id == EVENT_PACKET_ADV_PDU
|
||||||
|
):
|
||||||
self._processBLEPacket(packet)
|
self._processBLEPacket(packet)
|
||||||
elif packet.id == EVENT_FOLLOW:
|
elif packet.id == EVENT_FOLLOW:
|
||||||
# This packet has no value for the user.
|
# This packet has no value for the user.
|
||||||
|
|
@ -196,25 +213,35 @@ class SnifferCollector(Notifications.Notifier):
|
||||||
self._connectEventPacketCounterValue = packet.packetCounter
|
self._connectEventPacketCounterValue = packet.packetCounter
|
||||||
self._inConnection = True
|
self._inConnection = True
|
||||||
# copy it because packets are eventually deleted
|
# copy it because packets are eventually deleted
|
||||||
self._currentConnectRequest = copy.copy(self._findPacketByPacketCounter(self._connectEventPacketCounterValue-1))
|
self._currentConnectRequest = copy.copy(
|
||||||
|
self._findPacketByPacketCounter(
|
||||||
|
self._connectEventPacketCounterValue - 1
|
||||||
|
)
|
||||||
|
)
|
||||||
elif packet.id == EVENT_DISCONNECT:
|
elif packet.id == EVENT_DISCONNECT:
|
||||||
if self._inConnection:
|
if self._inConnection:
|
||||||
self._packetsInLastConnection = packet.packetCounter - self._connectEventPacketCounterValue
|
self._packetsInLastConnection = (
|
||||||
|
packet.packetCounter - self._connectEventPacketCounterValue
|
||||||
|
)
|
||||||
self._inConnection = False
|
self._inConnection = False
|
||||||
elif packet.id == SWITCH_BAUD_RATE_RESP and self._switchingBaudRate:
|
elif packet.id == SWITCH_BAUD_RATE_RESP and self._switchingBaudRate:
|
||||||
self._switchingBaudRate = False
|
self._switchingBaudRate = False
|
||||||
if (packet.baudRate == self._proposedBaudRate):
|
if packet.baudRate == self._proposedBaudRate:
|
||||||
self._packetReader.switchBaudRate(self._proposedBaudRate)
|
self._packetReader.switchBaudRate(self._proposedBaudRate)
|
||||||
else:
|
else:
|
||||||
self._switchBaudRate(packet.baudRate)
|
self._switchBaudRate(packet.baudRate)
|
||||||
elif packet.id == PING_RESP:
|
elif packet.id == PING_RESP:
|
||||||
if hasattr(packet, 'version'):
|
if hasattr(packet, "version"):
|
||||||
versions = { 1116: '3.1.0',
|
versions = {
|
||||||
1115: '3.0.0',
|
1116: "3.1.0",
|
||||||
1114: '2.0.0',
|
1115: "3.0.0",
|
||||||
1113: '2.0.0-beta-3',
|
1114: "2.0.0",
|
||||||
1112: '2.0.0-beta-1' }
|
1113: "2.0.0-beta-3",
|
||||||
self._fwversion = versions.get(packet.version, 'SVN rev: %d' % packet.version)
|
1112: "2.0.0-beta-1",
|
||||||
|
}
|
||||||
|
self._fwversion = versions.get(
|
||||||
|
packet.version, "SVN rev: %d" % packet.version
|
||||||
|
)
|
||||||
logging.info("Firmware version %s" % self._fwversion)
|
logging.info("Firmware version %s" % self._fwversion)
|
||||||
elif packet.id == RESP_VERSION:
|
elif packet.id == RESP_VERSION:
|
||||||
self._fwversion = packet.version
|
self._fwversion = packet.version
|
||||||
|
|
@ -226,8 +253,10 @@ class SnifferCollector(Notifications.Notifier):
|
||||||
|
|
||||||
lt = time.localtime(self._last_time)
|
lt = time.localtime(self._last_time)
|
||||||
usecs = int((self._last_time - int(self._last_time)) * 1_000_000)
|
usecs = int((self._last_time - int(self._last_time)) * 1_000_000)
|
||||||
logging.info(f'Firmware timestamp {self._last_timestamp} reference: '
|
logging.info(
|
||||||
f'{time.strftime("%b %d %Y %X", lt)}.{usecs} {time.strftime("%Z", lt)}')
|
f"Firmware timestamp {self._last_timestamp} reference: "
|
||||||
|
f'{time.strftime("%b %d %Y %X", lt)}.{usecs} {time.strftime("%Z", lt)}'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logging.info("Unknown packet ID")
|
logging.info("Unknown packet ID")
|
||||||
|
|
||||||
|
|
@ -257,10 +286,24 @@ class SnifferCollector(Notifications.Notifier):
|
||||||
self.clearCallbacks()
|
self.clearCallbacks()
|
||||||
self._devices.clearCallbacks()
|
self._devices.clearCallbacks()
|
||||||
|
|
||||||
def _startFollowing(self, device, followOnlyAdvertisements = False, followOnlyLegacy = False, followCoded = False):
|
def _startFollowing(
|
||||||
|
self,
|
||||||
|
device,
|
||||||
|
followOnlyAdvertisements=False,
|
||||||
|
followOnlyLegacy=False,
|
||||||
|
followCoded=False,
|
||||||
|
):
|
||||||
self._devices.setFollowed(device)
|
self._devices.setFollowed(device)
|
||||||
logging.info("Sniffing device " + str(self._devices.index(device)) + ' - "'+device.name+'"')
|
logging.info(
|
||||||
self._packetReader.sendFollow(device.address, followOnlyAdvertisements, followOnlyLegacy, followCoded)
|
"Sniffing device "
|
||||||
|
+ str(self._devices.index(device))
|
||||||
|
+ ' - "'
|
||||||
|
+ device.name
|
||||||
|
+ '"'
|
||||||
|
)
|
||||||
|
self._packetReader.sendFollow(
|
||||||
|
device.address, followOnlyAdvertisements, followOnlyLegacy, followCoded
|
||||||
|
)
|
||||||
self._setState(STATE_FOLLOWING)
|
self._setState(STATE_FOLLOWING)
|
||||||
|
|
||||||
def _clearDevices(self):
|
def _clearDevices(self):
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ from . import Packet
|
||||||
from . import Filelock
|
from . import Filelock
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
if os.name == "posix":
|
if os.name == "posix":
|
||||||
import termios
|
import termios
|
||||||
|
|
||||||
|
|
@ -61,8 +62,13 @@ def find_sniffer(write_data=False):
|
||||||
for port in [x.device for x in open_ports]:
|
for port in [x.device for x in open_ports]:
|
||||||
for rate in SNIFFER_BAUDRATES:
|
for rate in SNIFFER_BAUDRATES:
|
||||||
reader = None
|
reader = None
|
||||||
l_errors = [serial.SerialException, ValueError, Exceptions.LockedException, OSError]
|
l_errors = [
|
||||||
if os.name == 'posix':
|
serial.SerialException,
|
||||||
|
ValueError,
|
||||||
|
Exceptions.LockedException,
|
||||||
|
OSError,
|
||||||
|
]
|
||||||
|
if os.name == "posix":
|
||||||
l_errors.append(termios.error)
|
l_errors.append(termios.error)
|
||||||
try:
|
try:
|
||||||
reader = Packet.PacketReader(portnum=port, baudrate=rate)
|
reader = Packet.PacketReader(portnum=port, baudrate=rate)
|
||||||
|
|
@ -115,17 +121,14 @@ class Uart:
|
||||||
if baudrate is not None and baudrate not in SNIFFER_BAUDRATES:
|
if baudrate is not None and baudrate not in SNIFFER_BAUDRATES:
|
||||||
raise Exception("Invalid baudrate: " + str(baudrate))
|
raise Exception("Invalid baudrate: " + str(baudrate))
|
||||||
|
|
||||||
logging.info('Opening serial port {}'.format(portnum))
|
logging.info("Opening serial port {}".format(portnum))
|
||||||
|
|
||||||
self.portnum = portnum
|
self.portnum = portnum
|
||||||
if self.portnum:
|
if self.portnum:
|
||||||
Filelock.lock(portnum)
|
Filelock.lock(portnum)
|
||||||
|
|
||||||
self.ser = serial.Serial(
|
self.ser = serial.Serial(
|
||||||
port=portnum,
|
port=portnum, baudrate=9600, rtscts=True, exclusive=True
|
||||||
baudrate=9600,
|
|
||||||
rtscts=True,
|
|
||||||
exclusive=True
|
|
||||||
)
|
)
|
||||||
self.ser.baudrate = baudrate
|
self.ser.baudrate = baudrate
|
||||||
|
|
||||||
|
|
@ -217,8 +220,10 @@ def list_serial_ports():
|
||||||
# Scan for available ports.
|
# Scan for available ports.
|
||||||
return list_ports.comports()
|
return list_ports.comports()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import time
|
import time
|
||||||
|
|
||||||
t_start = time.time()
|
t_start = time.time()
|
||||||
s = find_sniffer()
|
s = find_sniffer()
|
||||||
tn = time.time()
|
tn = time.time()
|
||||||
|
|
|
||||||
|
|
@ -35,4 +35,3 @@
|
||||||
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
VERSION_STRING = "4.1.1"
|
VERSION_STRING = "4.1.1"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,12 +50,17 @@ import struct
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from SnifferAPI import Logger
|
from SnifferAPI import Logger
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import serial
|
import serial
|
||||||
except ImportError:
|
except ImportError:
|
||||||
Logger.initLogger()
|
Logger.initLogger()
|
||||||
logging.error(f'pyserial not found, please run: "{sys.executable} -m pip install -r requirements.txt" and retry')
|
logging.error(
|
||||||
sys.exit(f'pyserial not found, please run: "{sys.executable} -m pip install -r requirements.txt" and retry')
|
f'pyserial not found, please run: "{sys.executable} -m pip install -r requirements.txt" and retry'
|
||||||
|
)
|
||||||
|
sys.exit(
|
||||||
|
f'pyserial not found, please run: "{sys.executable} -m pip install -r requirements.txt" and retry'
|
||||||
|
)
|
||||||
|
|
||||||
from SnifferAPI import Sniffer, UART, Devices, Pcap, Exceptions
|
from SnifferAPI import Sniffer, UART, Devices, Pcap, Exceptions
|
||||||
|
|
||||||
|
|
@ -129,18 +134,30 @@ capture_scan_response = True
|
||||||
capture_scan_aux_pointer = True
|
capture_scan_aux_pointer = True
|
||||||
capture_coded = False
|
capture_coded = False
|
||||||
|
|
||||||
|
|
||||||
def extcap_config(interface):
|
def extcap_config(interface):
|
||||||
"""List configuration for the given interface"""
|
"""List configuration for the given interface"""
|
||||||
print("arg {number=0}{call=--only-advertising}{display=Only advertising packets}"
|
print(
|
||||||
"{tooltip=The sniffer will only capture advertising packets from the selected device}{type=boolflag}{save=true}")
|
"arg {number=0}{call=--only-advertising}{display=Only advertising packets}"
|
||||||
print("arg {number=1}{call=--only-legacy-advertising}{display=Only legacy advertising packets}"
|
"{tooltip=The sniffer will only capture advertising packets from the selected device}{type=boolflag}{save=true}"
|
||||||
"{tooltip=The sniffer will only capture legacy advertising packets from the selected device}{type=boolflag}{save=true}")
|
)
|
||||||
print("arg {number=2}{call=--scan-follow-rsp}{display=Find scan response data}"
|
print(
|
||||||
"{tooltip=The sniffer will follow scan requests and scan responses in scan mode}{type=boolflag}{default=true}{save=true}")
|
"arg {number=1}{call=--only-legacy-advertising}{display=Only legacy advertising packets}"
|
||||||
print("arg {number=3}{call=--scan-follow-aux}{display=Find auxiliary pointer data}"
|
"{tooltip=The sniffer will only capture legacy advertising packets from the selected device}{type=boolflag}{save=true}"
|
||||||
"{tooltip=The sniffer will follow aux pointers in scan mode}{type=boolflag}{default=true}{save=true}")
|
)
|
||||||
print("arg {number=3}{call=--coded}{display=Scan and follow devices on LE Coded PHY}"
|
print(
|
||||||
"{tooltip=Scan for devices and follow advertiser on LE Coded PHY}{type=boolflag}{default=false}{save=true}")
|
"arg {number=2}{call=--scan-follow-rsp}{display=Find scan response data}"
|
||||||
|
"{tooltip=The sniffer will follow scan requests and scan responses in scan mode}{type=boolflag}{default=true}{save=true}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"arg {number=3}{call=--scan-follow-aux}{display=Find auxiliary pointer data}"
|
||||||
|
"{tooltip=The sniffer will follow aux pointers in scan mode}{type=boolflag}{default=true}{save=true}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"arg {number=3}{call=--coded}{display=Scan and follow devices on LE Coded PHY}"
|
||||||
|
"{tooltip=Scan for devices and follow advertiser on LE Coded PHY}{type=boolflag}{default=false}{save=true}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def extcap_dlts(interface):
|
def extcap_dlts(interface):
|
||||||
"""List DLTs for the given interface"""
|
"""List DLTs for the given interface"""
|
||||||
|
|
@ -148,14 +165,18 @@ def extcap_dlts(interface):
|
||||||
|
|
||||||
|
|
||||||
def get_baud_rates(interface):
|
def get_baud_rates(interface):
|
||||||
if not hasattr(serial, "__version__") or not serial.__version__.startswith('3.'):
|
if not hasattr(serial, "__version__") or not serial.__version__.startswith("3."):
|
||||||
raise RuntimeError("Too old version of python 'serial' Library. Version 3 required.")
|
raise RuntimeError(
|
||||||
|
"Too old version of python 'serial' Library. Version 3 required."
|
||||||
|
)
|
||||||
return UART.find_sniffer_baudrates(interface)
|
return UART.find_sniffer_baudrates(interface)
|
||||||
|
|
||||||
|
|
||||||
def get_interfaces():
|
def get_interfaces():
|
||||||
if not hasattr(serial, "__version__") or not serial.__version__.startswith('3.'):
|
if not hasattr(serial, "__version__") or not serial.__version__.startswith("3."):
|
||||||
raise RuntimeError("Too old version of python 'serial' Library. Version 3 required.")
|
raise RuntimeError(
|
||||||
|
"Too old version of python 'serial' Library. Version 3 required."
|
||||||
|
)
|
||||||
|
|
||||||
devices = UART.find_sniffer()
|
devices = UART.find_sniffer()
|
||||||
return devices
|
return devices
|
||||||
|
|
@ -163,44 +184,105 @@ def get_interfaces():
|
||||||
|
|
||||||
def extcap_interfaces():
|
def extcap_interfaces():
|
||||||
"""List available interfaces to capture from"""
|
"""List available interfaces to capture from"""
|
||||||
print("extcap {version=%s}{display=nRF Sniffer for Bluetooth LE}"
|
print(
|
||||||
|
"extcap {version=%s}{display=nRF Sniffer for Bluetooth LE}"
|
||||||
"{help=https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Sniffer-for-Bluetooth-LE}"
|
"{help=https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Sniffer-for-Bluetooth-LE}"
|
||||||
% Sniffer.VERSION_STRING)
|
% Sniffer.VERSION_STRING
|
||||||
|
)
|
||||||
|
|
||||||
for interface_port in get_interfaces():
|
for interface_port in get_interfaces():
|
||||||
if sys.platform == 'win32':
|
if sys.platform == "win32":
|
||||||
print("interface {value=%s-%s}{display=nRF Sniffer for Bluetooth LE %s}" % (interface_port, extcap_version, interface_port))
|
print(
|
||||||
|
"interface {value=%s-%s}{display=nRF Sniffer for Bluetooth LE %s}"
|
||||||
|
% (interface_port, extcap_version, interface_port)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print("interface {value=%s-%s}{display=nRF Sniffer for Bluetooth LE}" % (interface_port, extcap_version))
|
print(
|
||||||
|
"interface {value=%s-%s}{display=nRF Sniffer for Bluetooth LE}"
|
||||||
|
% (interface_port, extcap_version)
|
||||||
|
)
|
||||||
|
|
||||||
print("control {number=%d}{type=selector}{display=Device}{tooltip=Device list}" % CTRL_ARG_DEVICE)
|
print(
|
||||||
print("control {number=%d}{type=selector}{display=Key}{tooltip=}" % CTRL_ARG_KEY_TYPE)
|
"control {number=%d}{type=selector}{display=Device}{tooltip=Device list}"
|
||||||
print("control {number=%d}{type=string}{display=Value}"
|
% CTRL_ARG_DEVICE
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"control {number=%d}{type=selector}{display=Key}{tooltip=}" % CTRL_ARG_KEY_TYPE
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"control {number=%d}{type=string}{display=Value}"
|
||||||
"{tooltip=6 digit passkey or 16 or 32 bytes encryption key in hexadecimal starting with '0x', big endian format."
|
"{tooltip=6 digit passkey or 16 or 32 bytes encryption key in hexadecimal starting with '0x', big endian format."
|
||||||
"If the entered key is shorter than 16 or 32 bytes, it will be zero-padded in front'}"
|
"If the entered key is shorter than 16 or 32 bytes, it will be zero-padded in front'}"
|
||||||
"{validation=\\b^(([0-9]{6})|(0x[0-9a-fA-F]{1,64})|([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2}) (public|random))$\\b}" % CTRL_ARG_KEY_VAL)
|
"{validation=\\b^(([0-9]{6})|(0x[0-9a-fA-F]{1,64})|([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2}) (public|random))$\\b}"
|
||||||
print("control {number=%d}{type=string}{display=Adv Hop}"
|
% CTRL_ARG_KEY_VAL
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"control {number=%d}{type=string}{display=Adv Hop}"
|
||||||
"{default=37,38,39}"
|
"{default=37,38,39}"
|
||||||
"{tooltip=Advertising channel hop sequence. "
|
"{tooltip=Advertising channel hop sequence. "
|
||||||
"Change the order in which the sniffer switches advertising channels. "
|
"Change the order in which the sniffer switches advertising channels. "
|
||||||
"Valid channels are 37, 38 and 39 separated by comma.}"
|
"Valid channels are 37, 38 and 39 separated by comma.}"
|
||||||
r"{validation=^\s*((37|38|39)\s*,\s*){0,2}(37|38|39){1}\s*$}{required=true}" % CTRL_ARG_ADVHOP)
|
r"{validation=^\s*((37|38|39)\s*,\s*){0,2}(37|38|39){1}\s*$}{required=true}"
|
||||||
print("control {number=%d}{type=button}{display=Clear}{tooltop=Clear or remove device from Device list}" % CTRL_ARG_DEVICE_CLEAR)
|
% CTRL_ARG_ADVHOP
|
||||||
print("control {number=%d}{type=button}{role=help}{display=Help}{tooltip=Access user guide (launches browser)}" % CTRL_ARG_HELP)
|
)
|
||||||
print("control {number=%d}{type=button}{role=restore}{display=Defaults}{tooltip=Resets the user interface and clears the log file}" % CTRL_ARG_RESTORE)
|
print(
|
||||||
print("control {number=%d}{type=button}{role=logger}{display=Log}{tooltip=Log per interface}" % CTRL_ARG_LOG)
|
"control {number=%d}{type=button}{display=Clear}{tooltop=Clear or remove device from Device list}"
|
||||||
|
% CTRL_ARG_DEVICE_CLEAR
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"control {number=%d}{type=button}{role=help}{display=Help}{tooltip=Access user guide (launches browser)}"
|
||||||
|
% CTRL_ARG_HELP
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"control {number=%d}{type=button}{role=restore}{display=Defaults}{tooltip=Resets the user interface and clears the log file}"
|
||||||
|
% CTRL_ARG_RESTORE
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"control {number=%d}{type=button}{role=logger}{display=Log}{tooltip=Log per interface}"
|
||||||
|
% CTRL_ARG_LOG
|
||||||
|
)
|
||||||
|
|
||||||
print("value {control=%d}{value= }{display=All advertising devices}{default=true}" % CTRL_ARG_DEVICE)
|
print(
|
||||||
print("value {control=%d}{value=%s}{display=Follow IRK}" % (CTRL_ARG_DEVICE, zero_addr))
|
"value {control=%d}{value= }{display=All advertising devices}{default=true}"
|
||||||
|
% CTRL_ARG_DEVICE
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"value {control=%d}{value=%s}{display=Follow IRK}"
|
||||||
|
% (CTRL_ARG_DEVICE, zero_addr)
|
||||||
|
)
|
||||||
|
|
||||||
print("value {control=%d}{value=%d}{display=Legacy Passkey}{default=true}" % (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_PASSKEY))
|
print(
|
||||||
print("value {control=%d}{value=%d}{display=Legacy OOB data}" % (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_OOB))
|
"value {control=%d}{value=%d}{display=Legacy Passkey}{default=true}"
|
||||||
print("value {control=%d}{value=%d}{display=Legacy LTK}" % (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_LEGACY_LTK))
|
% (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_PASSKEY)
|
||||||
print("value {control=%d}{value=%d}{display=SC LTK}" % (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_SC_LTK))
|
)
|
||||||
print("value {control=%d}{value=%d}{display=SC Private Key}" % (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_DH_PRIVATE_KEY))
|
print(
|
||||||
print("value {control=%d}{value=%d}{display=IRK}" % (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_IRK))
|
"value {control=%d}{value=%d}{display=Legacy OOB data}"
|
||||||
print("value {control=%d}{value=%d}{display=Add LE address}" % (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_ADD_ADDR))
|
% (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_OOB)
|
||||||
print("value {control=%d}{value=%d}{display=Follow LE address}" % (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_FOLLOW_ADDR))
|
)
|
||||||
|
print(
|
||||||
|
"value {control=%d}{value=%d}{display=Legacy LTK}"
|
||||||
|
% (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_LEGACY_LTK)
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"value {control=%d}{value=%d}{display=SC LTK}"
|
||||||
|
% (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_SC_LTK)
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"value {control=%d}{value=%d}{display=SC Private Key}"
|
||||||
|
% (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_DH_PRIVATE_KEY)
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"value {control=%d}{value=%d}{display=IRK}"
|
||||||
|
% (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_IRK)
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"value {control=%d}{value=%d}{display=Add LE address}"
|
||||||
|
% (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_ADD_ADDR)
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"value {control=%d}{value=%d}{display=Follow LE address}"
|
||||||
|
% (CTRL_ARG_KEY_TYPE, CTRL_KEY_TYPE_FOLLOW_ADDR)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def string_address(address):
|
def string_address(address):
|
||||||
|
|
@ -208,16 +290,16 @@ def string_address(address):
|
||||||
if len(address) < 7:
|
if len(address) < 7:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
addr_string = ''
|
addr_string = ""
|
||||||
|
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
addr_string += (format(address[i], '02x') + ':')
|
addr_string += format(address[i], "02x") + ":"
|
||||||
addr_string += format(address[5], '02x') + ' '
|
addr_string += format(address[5], "02x") + " "
|
||||||
|
|
||||||
if address[6]:
|
if address[6]:
|
||||||
addr_string += ' random '
|
addr_string += " random "
|
||||||
else:
|
else:
|
||||||
addr_string += ' public '
|
addr_string += " public "
|
||||||
|
|
||||||
return addr_string
|
return addr_string
|
||||||
|
|
||||||
|
|
@ -231,7 +313,7 @@ def control_read():
|
||||||
# > empty string ('')
|
# > empty string ('')
|
||||||
return None, None, None
|
return None, None, None
|
||||||
|
|
||||||
_, _, length, arg, typ = struct.unpack('>sBHBB', header)
|
_, _, length, arg, typ = struct.unpack(">sBHBB", header)
|
||||||
|
|
||||||
payload = bytearray()
|
payload = bytearray()
|
||||||
if length > 2:
|
if length > 2:
|
||||||
|
|
@ -239,6 +321,7 @@ def control_read():
|
||||||
|
|
||||||
return arg, typ, payload
|
return arg, typ, payload
|
||||||
|
|
||||||
|
|
||||||
def control_write(arg, typ, message):
|
def control_write(arg, typ, message):
|
||||||
"""Write the message to the control channel"""
|
"""Write the message to the control channel"""
|
||||||
|
|
||||||
|
|
@ -247,8 +330,8 @@ def control_write(arg, typ, message):
|
||||||
return
|
return
|
||||||
|
|
||||||
packet = bytearray()
|
packet = bytearray()
|
||||||
packet += struct.pack('>BBHBB', ord('T'), 0, len(message) + 2, arg, typ)
|
packet += struct.pack(">BBHBB", ord("T"), 0, len(message) + 2, arg, typ)
|
||||||
packet += message.encode('utf-8')
|
packet += message.encode("utf-8")
|
||||||
|
|
||||||
fn_ctrl_out.write(packet)
|
fn_ctrl_out.write(packet)
|
||||||
|
|
||||||
|
|
@ -278,11 +361,13 @@ def device_added(notification):
|
||||||
# Extcap selector uses \0 character to separate value and display value,
|
# Extcap selector uses \0 character to separate value and display value,
|
||||||
# therefore the display value cannot contain the \0 character as this
|
# therefore the display value cannot contain the \0 character as this
|
||||||
# would lead to truncation of the display value.
|
# would lead to truncation of the display value.
|
||||||
display = (device.name.replace('\0', '\\0') +
|
display = (
|
||||||
(" " + str(device.RSSI) + " dBm " if device.RSSI != 0 else " ") +
|
device.name.replace("\0", "\\0")
|
||||||
string_address(device.address))
|
+ (" " + str(device.RSSI) + " dBm " if device.RSSI != 0 else " ")
|
||||||
|
+ string_address(device.address)
|
||||||
|
)
|
||||||
|
|
||||||
message = str(device.address) + '\0' + display
|
message = str(device.address) + "\0" + display
|
||||||
|
|
||||||
control_write(CTRL_ARG_DEVICE, CTRL_CMD_ADD, message)
|
control_write(CTRL_ARG_DEVICE, CTRL_CMD_ADD, message)
|
||||||
|
|
||||||
|
|
@ -298,33 +383,35 @@ def device_removed(notification):
|
||||||
control_write(CTRL_ARG_DEVICE, CTRL_CMD_REMOVE, message)
|
control_write(CTRL_ARG_DEVICE, CTRL_CMD_REMOVE, message)
|
||||||
logging.info("Removed: " + display)
|
logging.info("Removed: " + display)
|
||||||
|
|
||||||
|
|
||||||
def devices_cleared(notification):
|
def devices_cleared(notification):
|
||||||
"""Devices have been cleared"""
|
"""Devices have been cleared"""
|
||||||
message = ""
|
message = ""
|
||||||
control_write(CTRL_ARG_DEVICE, CTRL_CMD_REMOVE, message)
|
control_write(CTRL_ARG_DEVICE, CTRL_CMD_REMOVE, message)
|
||||||
|
|
||||||
control_write(CTRL_ARG_DEVICE, CTRL_CMD_ADD, " " + '\0' + "All advertising devices")
|
control_write(CTRL_ARG_DEVICE, CTRL_CMD_ADD, " " + "\0" + "All advertising devices")
|
||||||
control_write(CTRL_ARG_DEVICE, CTRL_CMD_ADD, zero_addr + '\0' + "Follow IRK")
|
control_write(CTRL_ARG_DEVICE, CTRL_CMD_ADD, zero_addr + "\0" + "Follow IRK")
|
||||||
control_write(CTRL_ARG_DEVICE, CTRL_CMD_SET, " ")
|
control_write(CTRL_ARG_DEVICE, CTRL_CMD_SET, " ")
|
||||||
|
|
||||||
|
|
||||||
def handle_control_command(sniffer, arg, typ, payload):
|
def handle_control_command(sniffer, arg, typ, payload):
|
||||||
"""Handle command from control channel"""
|
"""Handle command from control channel"""
|
||||||
global last_used_key_type
|
global last_used_key_type
|
||||||
|
|
||||||
if arg == CTRL_ARG_DEVICE:
|
if arg == CTRL_ARG_DEVICE:
|
||||||
if payload == b' ':
|
if payload == b" ":
|
||||||
scan_for_devices(sniffer)
|
scan_for_devices(sniffer)
|
||||||
else:
|
else:
|
||||||
values = payload
|
values = payload
|
||||||
values = values.replace(b'[', b'')
|
values = values.replace(b"[", b"")
|
||||||
values = values.replace(b']', b'')
|
values = values.replace(b"]", b"")
|
||||||
device_address = values.split(b',')
|
device_address = values.split(b",")
|
||||||
|
|
||||||
logging.info('follow_device: {}'.format(device_address))
|
logging.info("follow_device: {}".format(device_address))
|
||||||
for i in range(6):
|
for i in range(6):
|
||||||
device_address[i] = int(device_address[i])
|
device_address[i] = int(device_address[i])
|
||||||
|
|
||||||
device_address[6] = 1 if device_address[6] == b' 1' else 0
|
device_address[6] = 1 if device_address[6] == b" 1" else 0
|
||||||
|
|
||||||
device = Devices.Device(address=device_address, name='""', RSSI=0)
|
device = Devices.Device(address=device_address, name='""', RSSI=0)
|
||||||
|
|
||||||
|
|
@ -333,7 +420,7 @@ def handle_control_command(sniffer, arg, typ, payload):
|
||||||
elif arg == CTRL_ARG_DEVICE_CLEAR:
|
elif arg == CTRL_ARG_DEVICE_CLEAR:
|
||||||
clear_devices(sniffer)
|
clear_devices(sniffer)
|
||||||
elif arg == CTRL_ARG_KEY_TYPE:
|
elif arg == CTRL_ARG_KEY_TYPE:
|
||||||
last_used_key_type = int(payload.decode('utf-8'))
|
last_used_key_type = int(payload.decode("utf-8"))
|
||||||
elif arg == CTRL_ARG_KEY_VAL:
|
elif arg == CTRL_ARG_KEY_VAL:
|
||||||
set_key_value(sniffer, payload)
|
set_key_value(sniffer, payload)
|
||||||
elif arg == CTRL_ARG_ADVHOP:
|
elif arg == CTRL_ARG_ADVHOP:
|
||||||
|
|
@ -384,8 +471,10 @@ def follow_device(sniffer, device):
|
||||||
"""Follow the selected device"""
|
"""Follow the selected device"""
|
||||||
global write_new_packets, in_follow_mode
|
global write_new_packets, in_follow_mode
|
||||||
|
|
||||||
sniffer.follow(device, capture_only_advertising, capture_only_legacy_advertising, capture_coded)
|
sniffer.follow(
|
||||||
time.sleep(.1)
|
device, capture_only_advertising, capture_only_legacy_advertising, capture_coded
|
||||||
|
)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
in_follow_mode = True
|
in_follow_mode = True
|
||||||
logging.info("Following " + string_address(device.address))
|
logging.info("Following " + string_address(device.address))
|
||||||
|
|
@ -395,59 +484,65 @@ def set_key_value(sniffer, payload):
|
||||||
"""Send key value to device"""
|
"""Send key value to device"""
|
||||||
global last_used_key_val
|
global last_used_key_val
|
||||||
|
|
||||||
payload = payload.decode('utf-8')
|
payload = payload.decode("utf-8")
|
||||||
last_used_key_val = payload
|
last_used_key_val = payload
|
||||||
|
|
||||||
if (last_used_key_type == CTRL_KEY_TYPE_PASSKEY):
|
if last_used_key_type == CTRL_KEY_TYPE_PASSKEY:
|
||||||
if re.match("^[0-9]{6}$", payload):
|
if re.match("^[0-9]{6}$", payload):
|
||||||
set_passkey(sniffer, payload)
|
set_passkey(sniffer, payload)
|
||||||
else:
|
else:
|
||||||
logging.info("Invalid key value: " + str(payload))
|
logging.info("Invalid key value: " + str(payload))
|
||||||
elif (last_used_key_type == CTRL_KEY_TYPE_OOB):
|
elif last_used_key_type == CTRL_KEY_TYPE_OOB:
|
||||||
if re.match("^0[xX][0-9A-Za-z]{1,32}$", payload):
|
if re.match("^0[xX][0-9A-Za-z]{1,32}$", payload):
|
||||||
set_OOB(sniffer, payload[2:])
|
set_OOB(sniffer, payload[2:])
|
||||||
else:
|
else:
|
||||||
logging.info("Invalid key value: " + str(payload))
|
logging.info("Invalid key value: " + str(payload))
|
||||||
elif (last_used_key_type == CTRL_KEY_TYPE_DH_PRIVATE_KEY):
|
elif last_used_key_type == CTRL_KEY_TYPE_DH_PRIVATE_KEY:
|
||||||
if (re.match("^0[xX][0-9A-Za-z]{1,64}$", payload)):
|
if re.match("^0[xX][0-9A-Za-z]{1,64}$", payload):
|
||||||
set_dh_private_key(sniffer, payload[2:])
|
set_dh_private_key(sniffer, payload[2:])
|
||||||
else:
|
else:
|
||||||
logging.info("Invalid key value: " + str(payload))
|
logging.info("Invalid key value: " + str(payload))
|
||||||
elif (last_used_key_type == CTRL_KEY_TYPE_LEGACY_LTK):
|
elif last_used_key_type == CTRL_KEY_TYPE_LEGACY_LTK:
|
||||||
if (re.match("^0[xX][0-9A-Za-z]{1,32}$", payload)):
|
if re.match("^0[xX][0-9A-Za-z]{1,32}$", payload):
|
||||||
set_legacy_ltk(sniffer, payload[2:])
|
set_legacy_ltk(sniffer, payload[2:])
|
||||||
else:
|
else:
|
||||||
logging.info("Invalid key value: " + str(payload))
|
logging.info("Invalid key value: " + str(payload))
|
||||||
elif (last_used_key_type == CTRL_KEY_TYPE_SC_LTK):
|
elif last_used_key_type == CTRL_KEY_TYPE_SC_LTK:
|
||||||
if (re.match("^0[xX][0-9A-Za-z]{1,32}$", payload)):
|
if re.match("^0[xX][0-9A-Za-z]{1,32}$", payload):
|
||||||
set_sc_ltk(sniffer, payload[2:])
|
set_sc_ltk(sniffer, payload[2:])
|
||||||
else:
|
else:
|
||||||
logging.info("Invalid key value: " + str(payload))
|
logging.info("Invalid key value: " + str(payload))
|
||||||
elif (last_used_key_type == CTRL_KEY_TYPE_IRK):
|
elif last_used_key_type == CTRL_KEY_TYPE_IRK:
|
||||||
if (re.match("^0[xX][0-9A-Za-z]{1,32}$", payload)):
|
if re.match("^0[xX][0-9A-Za-z]{1,32}$", payload):
|
||||||
set_irk(sniffer, payload[2:])
|
set_irk(sniffer, payload[2:])
|
||||||
else:
|
else:
|
||||||
logging.info("Invalid key value: " + str(payload))
|
logging.info("Invalid key value: " + str(payload))
|
||||||
elif (last_used_key_type == CTRL_KEY_TYPE_ADD_ADDR):
|
elif last_used_key_type == CTRL_KEY_TYPE_ADD_ADDR:
|
||||||
if (re.match("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2}) (public|random)$", payload)):
|
if re.match(
|
||||||
|
"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2}) (public|random)$", payload
|
||||||
|
):
|
||||||
add_address(sniffer, payload)
|
add_address(sniffer, payload)
|
||||||
else:
|
else:
|
||||||
logging.info("Invalid key value: " + str(payload))
|
logging.info("Invalid key value: " + str(payload))
|
||||||
elif (last_used_key_type == CTRL_KEY_TYPE_FOLLOW_ADDR):
|
elif last_used_key_type == CTRL_KEY_TYPE_FOLLOW_ADDR:
|
||||||
if (re.match("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2}) (public|random)$", payload)):
|
if re.match(
|
||||||
|
"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2}) (public|random)$", payload
|
||||||
|
):
|
||||||
follow_address(sniffer, payload)
|
follow_address(sniffer, payload)
|
||||||
else:
|
else:
|
||||||
logging.info("Invalid key value: " + str(payload))
|
logging.info("Invalid key value: " + str(payload))
|
||||||
else:
|
else:
|
||||||
logging.info("Invalid key type: " + str(last_used_key_type))
|
logging.info("Invalid key type: " + str(last_used_key_type))
|
||||||
|
|
||||||
|
|
||||||
def parse_hex(value):
|
def parse_hex(value):
|
||||||
if len(value) % 2 != 0:
|
if len(value) % 2 != 0:
|
||||||
value = '0' + value
|
value = "0" + value
|
||||||
|
|
||||||
a = list(value)
|
a = list(value)
|
||||||
return [int(x + y, 16) for x, y in zip(a[::2], a[1::2])]
|
return [int(x + y, 16) for x, y in zip(a[::2], a[1::2])]
|
||||||
|
|
||||||
|
|
||||||
def set_passkey(sniffer, payload):
|
def set_passkey(sniffer, payload):
|
||||||
"""Send passkey to device"""
|
"""Send passkey to device"""
|
||||||
passkey = []
|
passkey = []
|
||||||
|
|
@ -461,36 +556,42 @@ def set_passkey(sniffer, payload):
|
||||||
|
|
||||||
sniffer.sendTK(passkey)
|
sniffer.sendTK(passkey)
|
||||||
|
|
||||||
|
|
||||||
def set_OOB(sniffer, payload):
|
def set_OOB(sniffer, payload):
|
||||||
"""Send OOB to device"""
|
"""Send OOB to device"""
|
||||||
logging.info("Setting OOB data: " + payload)
|
logging.info("Setting OOB data: " + payload)
|
||||||
sniffer.sendTK(parse_hex(payload))
|
sniffer.sendTK(parse_hex(payload))
|
||||||
|
|
||||||
|
|
||||||
def set_dh_private_key(sniffer, payload):
|
def set_dh_private_key(sniffer, payload):
|
||||||
"""Send Diffie-Hellman private key to device"""
|
"""Send Diffie-Hellman private key to device"""
|
||||||
logging.info("Setting DH private key: " + payload)
|
logging.info("Setting DH private key: " + payload)
|
||||||
sniffer.sendPrivateKey(parse_hex(payload))
|
sniffer.sendPrivateKey(parse_hex(payload))
|
||||||
|
|
||||||
|
|
||||||
def set_legacy_ltk(sniffer, payload):
|
def set_legacy_ltk(sniffer, payload):
|
||||||
"""Send Legacy Long Term Key (LTK) to device"""
|
"""Send Legacy Long Term Key (LTK) to device"""
|
||||||
logging.info("Setting Legacy LTK: " + payload)
|
logging.info("Setting Legacy LTK: " + payload)
|
||||||
sniffer.sendLegacyLTK(parse_hex(payload))
|
sniffer.sendLegacyLTK(parse_hex(payload))
|
||||||
|
|
||||||
|
|
||||||
def set_sc_ltk(sniffer, payload):
|
def set_sc_ltk(sniffer, payload):
|
||||||
"""Send LE secure connections Long Term Key (LTK) to device"""
|
"""Send LE secure connections Long Term Key (LTK) to device"""
|
||||||
logging.info("Setting SC LTK: " + payload)
|
logging.info("Setting SC LTK: " + payload)
|
||||||
sniffer.sendSCLTK(parse_hex(payload))
|
sniffer.sendSCLTK(parse_hex(payload))
|
||||||
|
|
||||||
|
|
||||||
def set_irk(sniffer, payload):
|
def set_irk(sniffer, payload):
|
||||||
"""Send Identity Resolving Key (IRK) to device"""
|
"""Send Identity Resolving Key (IRK) to device"""
|
||||||
logging.info("Setting IRK: " + payload)
|
logging.info("Setting IRK: " + payload)
|
||||||
sniffer.sendIRK(parse_hex(payload))
|
sniffer.sendIRK(parse_hex(payload))
|
||||||
|
|
||||||
|
|
||||||
def add_address(sniffer, payload):
|
def add_address(sniffer, payload):
|
||||||
"""Add LE address to device list"""
|
"""Add LE address to device list"""
|
||||||
logging.info("Adding LE address: " + payload)
|
logging.info("Adding LE address: " + payload)
|
||||||
|
|
||||||
(addr,addr_type) = payload.split(' ')
|
(addr, addr_type) = payload.split(" ")
|
||||||
device = [int(a, 16) for a in addr.split(":")]
|
device = [int(a, 16) for a in addr.split(":")]
|
||||||
|
|
||||||
device.append(1 if addr_type == "random" else 0)
|
device.append(1 if addr_type == "random" else 0)
|
||||||
|
|
@ -498,11 +599,12 @@ def add_address(sniffer, payload):
|
||||||
new_device = Devices.Device(address=device, name='""', RSSI=0)
|
new_device = Devices.Device(address=device, name='""', RSSI=0)
|
||||||
sniffer.addDevice(new_device)
|
sniffer.addDevice(new_device)
|
||||||
|
|
||||||
|
|
||||||
def follow_address(sniffer, payload):
|
def follow_address(sniffer, payload):
|
||||||
"""Add LE address to device list"""
|
"""Add LE address to device list"""
|
||||||
logging.info("Adding LE address: " + payload)
|
logging.info("Adding LE address: " + payload)
|
||||||
|
|
||||||
(addr,addr_type) = payload.split(' ')
|
(addr, addr_type) = payload.split(" ")
|
||||||
device = [int(a, 16) for a in addr.split(":")]
|
device = [int(a, 16) for a in addr.split(":")]
|
||||||
|
|
||||||
device.append(1 if addr_type == "random" else 0)
|
device.append(1 if addr_type == "random" else 0)
|
||||||
|
|
@ -513,15 +615,16 @@ def follow_address(sniffer, payload):
|
||||||
control_write(CTRL_ARG_DEVICE, CTRL_CMD_SET, f"{new_device.address}")
|
control_write(CTRL_ARG_DEVICE, CTRL_CMD_SET, f"{new_device.address}")
|
||||||
follow_device(sniffer, new_device)
|
follow_device(sniffer, new_device)
|
||||||
|
|
||||||
|
|
||||||
def set_advhop(sniffer, payload):
|
def set_advhop(sniffer, payload):
|
||||||
"""Set advertising channel hop sequence"""
|
"""Set advertising channel hop sequence"""
|
||||||
global last_used_advhop
|
global last_used_advhop
|
||||||
|
|
||||||
payload = payload.decode('utf-8')
|
payload = payload.decode("utf-8")
|
||||||
|
|
||||||
last_used_advhop = payload
|
last_used_advhop = payload
|
||||||
|
|
||||||
hops = [int(channel) for channel in payload.split(',')]
|
hops = [int(channel) for channel in payload.split(",")]
|
||||||
|
|
||||||
sniffer.setAdvHopSequence(hops)
|
sniffer.setAdvHopSequence(hops)
|
||||||
|
|
||||||
|
|
@ -546,7 +649,7 @@ def error_interface_not_found(interface, fifo):
|
||||||
|
|
||||||
def validate_interface(interface, fifo):
|
def validate_interface(interface, fifo):
|
||||||
"""Check if interface exists"""
|
"""Check if interface exists"""
|
||||||
if sys.platform != 'win32' and not os.path.exists(interface):
|
if sys.platform != "win32" and not os.path.exists(interface):
|
||||||
error_interface_not_found(interface, fifo)
|
error_interface_not_found(interface, fifo)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -557,12 +660,13 @@ def get_default_baudrate(interface, fifo):
|
||||||
error_interface_not_found(interface, fifo)
|
error_interface_not_found(interface, fifo)
|
||||||
return rates["default"]
|
return rates["default"]
|
||||||
|
|
||||||
|
|
||||||
def get_supported_protocol_version(extcap_version):
|
def get_supported_protocol_version(extcap_version):
|
||||||
"""Return the maximum supported Packet Protocol Version"""
|
"""Return the maximum supported Packet Protocol Version"""
|
||||||
if extcap_version == 'None':
|
if extcap_version == "None":
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
(major, minor) = extcap_version.split('.')
|
(major, minor) = extcap_version.split(".")
|
||||||
|
|
||||||
major = int(major)
|
major = int(major)
|
||||||
minor = int(minor)
|
minor = int(minor)
|
||||||
|
|
@ -572,6 +676,7 @@ def get_supported_protocol_version(extcap_version):
|
||||||
else:
|
else:
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
|
|
||||||
def setup_extcap_log_handler():
|
def setup_extcap_log_handler():
|
||||||
"""Add the a handler that emits log messages through the extcap control out channel"""
|
"""Add the a handler that emits log messages through the extcap control out channel"""
|
||||||
global extcap_log_handler
|
global extcap_log_handler
|
||||||
|
|
@ -593,18 +698,18 @@ def sniffer_capture(interface, baudrate, fifo, control_in, control_out):
|
||||||
global fn_capture, fn_ctrl_in, fn_ctrl_out, write_new_packets, extcap_log_handler
|
global fn_capture, fn_ctrl_in, fn_ctrl_out, write_new_packets, extcap_log_handler
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fn_capture = open(fifo, 'wb', 0)
|
fn_capture = open(fifo, "wb", 0)
|
||||||
|
|
||||||
if control_out is not None:
|
if control_out is not None:
|
||||||
fn_ctrl_out = open(control_out, 'wb', 0)
|
fn_ctrl_out = open(control_out, "wb", 0)
|
||||||
setup_extcap_log_handler()
|
setup_extcap_log_handler()
|
||||||
|
|
||||||
if control_in is not None:
|
if control_in is not None:
|
||||||
fn_ctrl_in = open(control_in, 'rb', 0)
|
fn_ctrl_in = open(control_in, "rb", 0)
|
||||||
|
|
||||||
logging.info("Log started at %s", time.strftime("%c"))
|
logging.info("Log started at %s", time.strftime("%c"))
|
||||||
|
|
||||||
interface, extcap_version = interface.split('-')
|
interface, extcap_version = interface.split("-")
|
||||||
logging.info("Extcap version %s", str(extcap_version))
|
logging.info("Extcap version %s", str(extcap_version))
|
||||||
|
|
||||||
capture_write(Pcap.get_global_header())
|
capture_write(Pcap.get_global_header())
|
||||||
|
|
@ -619,7 +724,9 @@ def sniffer_capture(interface, baudrate, fifo, control_in, control_out):
|
||||||
sniffer.subscribe("DEVICE_REMOVED", device_removed)
|
sniffer.subscribe("DEVICE_REMOVED", device_removed)
|
||||||
sniffer.subscribe("DEVICES_CLEARED", devices_cleared)
|
sniffer.subscribe("DEVICES_CLEARED", devices_cleared)
|
||||||
sniffer.setAdvHopSequence([37, 38, 39])
|
sniffer.setAdvHopSequence([37, 38, 39])
|
||||||
sniffer.setSupportedProtocolVersion(get_supported_protocol_version(extcap_version))
|
sniffer.setSupportedProtocolVersion(
|
||||||
|
get_supported_protocol_version(extcap_version)
|
||||||
|
)
|
||||||
logging.info("Sniffer created")
|
logging.info("Sniffer created")
|
||||||
|
|
||||||
logging.info("Software version: %s" % sniffer.swversion)
|
logging.info("Software version: %s" % sniffer.swversion)
|
||||||
|
|
@ -655,7 +762,7 @@ def sniffer_capture(interface, baudrate, fifo, control_in, control_out):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
except Exceptions.LockedException as e:
|
except Exceptions.LockedException as e:
|
||||||
logging.info('{}'.format(e.message))
|
logging.info("{}".format(e.message))
|
||||||
|
|
||||||
except OSError:
|
except OSError:
|
||||||
# We'll get OSError=22 when/if wireshark kills the pipe(s) on capture
|
# We'll get OSError=22 when/if wireshark kills the pipe(s) on capture
|
||||||
|
|
@ -696,7 +803,7 @@ def extcap_close_fifo(fifo):
|
||||||
|
|
||||||
# This is apparently needed to workaround an issue on Windows/macOS
|
# This is apparently needed to workaround an issue on Windows/macOS
|
||||||
# where the message cannot be read. (really?)
|
# where the message cannot be read. (really?)
|
||||||
fh = open(fifo, 'wb', 0)
|
fh = open(fifo, "wb", 0)
|
||||||
fh.close()
|
fh.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -705,7 +812,7 @@ class ExtcapLoggerHandler(logging.Handler):
|
||||||
|
|
||||||
def emit(self, record):
|
def emit(self, record):
|
||||||
"""Send log message to extcap"""
|
"""Send log message to extcap"""
|
||||||
message = record.message.replace('\0', '\\0')
|
message = record.message.replace("\0", "\\0")
|
||||||
log_message = f"{record.levelname}: {message}\n"
|
log_message = f"{record.levelname}: {message}\n"
|
||||||
control_write(CTRL_ARG_LOG, CTRL_CMD_ADD, log_message)
|
control_write(CTRL_ARG_LOG, CTRL_CMD_ADD, log_message)
|
||||||
|
|
||||||
|
|
@ -720,66 +827,89 @@ def parse_capture_filter(capture_filter):
|
||||||
print("Illegal RSSI value, must be between -10 and -256")
|
print("Illegal RSSI value, must be between -10 and -256")
|
||||||
# Handle >= by modifying the threshold, since comparisons are always done with
|
# Handle >= by modifying the threshold, since comparisons are always done with
|
||||||
# the > operator
|
# the > operator
|
||||||
if m.group(1) == '>=':
|
if m.group(1) == ">=":
|
||||||
rssi_filter = rssi_filter - 1
|
rssi_filter = rssi_filter - 1
|
||||||
else:
|
else:
|
||||||
print("Filter syntax: \"RSSI >= -value\"")
|
print('Filter syntax: "RSSI >= -value"')
|
||||||
|
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
|
|
||||||
|
|
||||||
@atexit.register
|
@atexit.register
|
||||||
def goodbye():
|
def goodbye():
|
||||||
logging.info("Exiting PID {}".format(os.getpid()))
|
logging.info("Exiting PID {}".format(os.getpid()))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
|
|
||||||
# Capture options
|
# Capture options
|
||||||
parser = argparse.ArgumentParser(description="Nordic Semiconductor nRF Sniffer for Bluetooth LE extcap plugin")
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Nordic Semiconductor nRF Sniffer for Bluetooth LE extcap plugin"
|
||||||
|
)
|
||||||
|
|
||||||
# Extcap Arguments
|
# Extcap Arguments
|
||||||
parser.add_argument("--capture",
|
parser.add_argument("--capture", help="Start the capture", action="store_true")
|
||||||
help="Start the capture",
|
|
||||||
action="store_true")
|
|
||||||
|
|
||||||
parser.add_argument("--extcap-interfaces",
|
parser.add_argument(
|
||||||
|
"--extcap-interfaces",
|
||||||
help="List available interfaces to capture from",
|
help="List available interfaces to capture from",
|
||||||
action="store_true")
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument("--extcap-interface",
|
parser.add_argument("--extcap-interface", help="The interface to capture from")
|
||||||
help="The interface to capture from")
|
|
||||||
|
|
||||||
parser.add_argument("--extcap-dlts",
|
parser.add_argument(
|
||||||
help="List DLTs for the given interface",
|
"--extcap-dlts", help="List DLTs for the given interface", action="store_true"
|
||||||
action="store_true")
|
)
|
||||||
|
|
||||||
parser.add_argument("--extcap-config",
|
parser.add_argument(
|
||||||
|
"--extcap-config",
|
||||||
help="List configurations for the given interface",
|
help="List configurations for the given interface",
|
||||||
action="store_true")
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument("--extcap-capture-filter",
|
parser.add_argument(
|
||||||
help="Used together with capture to provide a capture filter")
|
"--extcap-capture-filter",
|
||||||
|
help="Used together with capture to provide a capture filter",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument("--fifo",
|
parser.add_argument(
|
||||||
help="Use together with capture to provide the fifo to dump data to")
|
"--fifo", help="Use together with capture to provide the fifo to dump data to"
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument("--extcap-control-in",
|
parser.add_argument(
|
||||||
help="Used together with capture to get control messages from toolbar")
|
"--extcap-control-in",
|
||||||
|
help="Used together with capture to get control messages from toolbar",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument("--extcap-control-out",
|
parser.add_argument(
|
||||||
help="Used together with capture to send control messages to toolbar")
|
"--extcap-control-out",
|
||||||
|
help="Used together with capture to send control messages to toolbar",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument("--extcap-version",
|
parser.add_argument("--extcap-version", help="Set extcap supported version")
|
||||||
help="Set extcap supported version")
|
|
||||||
|
|
||||||
# Interface Arguments
|
# Interface Arguments
|
||||||
parser.add_argument("--device", help="Device", default="")
|
parser.add_argument("--device", help="Device", default="")
|
||||||
parser.add_argument("--baudrate", type=int, help="The sniffer baud rate")
|
parser.add_argument("--baudrate", type=int, help="The sniffer baud rate")
|
||||||
parser.add_argument("--only-advertising", help="Only advertising packets", action="store_true")
|
parser.add_argument(
|
||||||
parser.add_argument("--only-legacy-advertising", help="Only legacy advertising packets", action="store_true")
|
"--only-advertising", help="Only advertising packets", action="store_true"
|
||||||
parser.add_argument("--scan-follow-rsp", help="Find scan response data ", action="store_true")
|
)
|
||||||
parser.add_argument("--scan-follow-aux", help="Find auxiliary pointer data", action="store_true")
|
parser.add_argument(
|
||||||
parser.add_argument("--coded", help="Scan and follow on LE Coded PHY", action="store_true")
|
"--only-legacy-advertising",
|
||||||
|
help="Only legacy advertising packets",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--scan-follow-rsp", help="Find scan response data ", action="store_true"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--scan-follow-aux", help="Find auxiliary pointer data", action="store_true"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--coded", help="Scan and follow on LE Coded PHY", action="store_true"
|
||||||
|
)
|
||||||
|
|
||||||
logging.info("Started PID {}".format(os.getpid()))
|
logging.info("Started PID {}".format(os.getpid()))
|
||||||
|
|
||||||
|
|
@ -839,16 +969,23 @@ if __name__ == '__main__':
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit(ERROR_FIFO)
|
sys.exit(ERROR_FIFO)
|
||||||
try:
|
try:
|
||||||
logging.info('sniffer capture')
|
logging.info("sniffer capture")
|
||||||
sniffer_capture(interface, args.baudrate, args.fifo, args.extcap_control_in, args.extcap_control_out)
|
sniffer_capture(
|
||||||
|
interface,
|
||||||
|
args.baudrate,
|
||||||
|
args.fifo,
|
||||||
|
args.extcap_control_in,
|
||||||
|
args.extcap_control_out,
|
||||||
|
)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
logging.info(traceback.format_exc())
|
logging.info(traceback.format_exc())
|
||||||
logging.info('internal error: {}'.format(repr(e)))
|
logging.info("internal error: {}".format(repr(e)))
|
||||||
sys.exit(ERROR_INTERNAL)
|
sys.exit(ERROR_INTERNAL)
|
||||||
else:
|
else:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit(ERROR_USAGE)
|
sys.exit(ERROR_USAGE)
|
||||||
logging.info('main exit PID {}'.format(os.getpid()))
|
logging.info("main exit PID {}".format(os.getpid()))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue