forked from auracaster/bumble_mirror
show timestamps from snoop logs
This commit is contained in:
75
apps/show.py
75
apps/show.py
@@ -15,7 +15,11 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Imports
|
# Imports
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from bumble.colors import color
|
from bumble.colors import color
|
||||||
@@ -24,6 +28,14 @@ from bumble.transport.common import PacketReader
|
|||||||
from bumble.helpers import PacketTracer
|
from bumble.helpers import PacketTracer
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Logging
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Classes
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class SnoopPacketReader:
|
class SnoopPacketReader:
|
||||||
'''
|
'''
|
||||||
@@ -36,12 +48,18 @@ class SnoopPacketReader:
|
|||||||
DATALINK_BSCP = 1003
|
DATALINK_BSCP = 1003
|
||||||
DATALINK_H5 = 1004
|
DATALINK_H5 = 1004
|
||||||
|
|
||||||
|
IDENTIFICATION_PATTERN = b'btsnoop\0'
|
||||||
|
TIMESTAMP_ANCHOR = datetime.datetime(2000, 1, 1)
|
||||||
|
TIMESTAMP_DELTA = 0x00E03AB44A676000
|
||||||
|
ONE_MICROSECOND = datetime.timedelta(microseconds=1)
|
||||||
|
|
||||||
def __init__(self, source):
|
def __init__(self, source):
|
||||||
self.source = source
|
self.source = source
|
||||||
|
self.at_end = False
|
||||||
|
|
||||||
# Read the header
|
# Read the header
|
||||||
identification_pattern = source.read(8)
|
identification_pattern = source.read(8)
|
||||||
if identification_pattern.hex().lower() != '6274736e6f6f7000':
|
if identification_pattern != self.IDENTIFICATION_PATTERN:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'not a valid snoop file, unexpected identification pattern'
|
'not a valid snoop file, unexpected identification pattern'
|
||||||
)
|
)
|
||||||
@@ -55,19 +73,32 @@ class SnoopPacketReader:
|
|||||||
# Read the record header
|
# Read the record header
|
||||||
header = self.source.read(24)
|
header = self.source.read(24)
|
||||||
if len(header) < 24:
|
if len(header) < 24:
|
||||||
return (0, None)
|
self.at_end = True
|
||||||
|
return (None, 0, None)
|
||||||
|
|
||||||
|
# Parse the header
|
||||||
(
|
(
|
||||||
original_length,
|
original_length,
|
||||||
included_length,
|
included_length,
|
||||||
packet_flags,
|
packet_flags,
|
||||||
_cumulative_drops,
|
_cumulative_drops,
|
||||||
_timestamp_seconds,
|
timestamp,
|
||||||
_timestamp_microsecond,
|
) = struct.unpack('>IIIIQ', header)
|
||||||
) = struct.unpack('>IIIIII', header)
|
|
||||||
|
|
||||||
# Abort on truncated packets
|
# Skip truncated packets
|
||||||
if original_length != included_length:
|
if original_length != included_length:
|
||||||
return (0, None)
|
print(
|
||||||
|
color(
|
||||||
|
f"!!! truncated packet ({included_length}/{original_length})", "red"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.source.read(included_length)
|
||||||
|
return (None, 0, None)
|
||||||
|
|
||||||
|
# Convert the timestamp to a datetime object.
|
||||||
|
ts_dt = self.TIMESTAMP_ANCHOR + datetime.timedelta(
|
||||||
|
microseconds=timestamp - self.TIMESTAMP_DELTA
|
||||||
|
)
|
||||||
|
|
||||||
if self.data_link_type == self.DATALINK_H1:
|
if self.data_link_type == self.DATALINK_H1:
|
||||||
# The packet is un-encapsulated, look at the flags to figure out its type
|
# The packet is un-encapsulated, look at the flags to figure out its type
|
||||||
@@ -89,7 +120,17 @@ class SnoopPacketReader:
|
|||||||
bytes([packet_type]) + self.source.read(included_length),
|
bytes([packet_type]) + self.source.read(included_length),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (packet_flags & 1, self.source.read(included_length))
|
return (ts_dt, packet_flags & 1, self.source.read(included_length))
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
class Printer:
|
||||||
|
def __init__(self):
|
||||||
|
self.index = 0
|
||||||
|
|
||||||
|
def print(self, message: str) -> None:
|
||||||
|
self.index += 1
|
||||||
|
print(f"[{self.index}]{message}")
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -122,24 +163,28 @@ def main(format, vendors, filename):
|
|||||||
packet_reader = PacketReader(input)
|
packet_reader = PacketReader(input)
|
||||||
|
|
||||||
def read_next_packet():
|
def read_next_packet():
|
||||||
return (0, packet_reader.next_packet())
|
return (None, 0, packet_reader.next_packet())
|
||||||
|
|
||||||
else:
|
else:
|
||||||
packet_reader = SnoopPacketReader(input)
|
packet_reader = SnoopPacketReader(input)
|
||||||
read_next_packet = packet_reader.next_packet
|
read_next_packet = packet_reader.next_packet
|
||||||
|
|
||||||
tracer = PacketTracer(emit_message=print)
|
printer = Printer()
|
||||||
|
tracer = PacketTracer(emit_message=printer.print)
|
||||||
|
|
||||||
while True:
|
while not packet_reader.at_end:
|
||||||
try:
|
try:
|
||||||
(direction, packet) = read_next_packet()
|
(timestamp, direction, packet) = read_next_packet()
|
||||||
if packet is None:
|
if packet:
|
||||||
break
|
tracer.trace(hci.HCI_Packet.from_bytes(packet), direction, timestamp)
|
||||||
tracer.trace(hci.HCI_Packet.from_bytes(packet), direction)
|
else:
|
||||||
|
printer.print(color("[TRUNCATED]", "red"))
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
|
logger.exception()
|
||||||
print(color(f'!!! {error}', 'red'))
|
print(color(f'!!! {error}', 'red'))
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'WARNING').upper())
|
||||||
main() # pylint: disable=no-value-for-parameter
|
main() # pylint: disable=no-value-for-parameter
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable, MutableMapping
|
from collections.abc import Callable, MutableMapping
|
||||||
|
import datetime
|
||||||
from typing import cast, Any, Optional
|
from typing import cast, Any, Optional
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -66,12 +67,13 @@ PSM_NAMES = {
|
|||||||
rfcomm.RFCOMM_PSM: 'RFCOMM',
|
rfcomm.RFCOMM_PSM: 'RFCOMM',
|
||||||
sdp.SDP_PSM: 'SDP',
|
sdp.SDP_PSM: 'SDP',
|
||||||
avdtp.AVDTP_PSM: 'AVDTP',
|
avdtp.AVDTP_PSM: 'AVDTP',
|
||||||
avctp.AVCTP_PSM: 'AVCTP'
|
avctp.AVCTP_PSM: 'AVCTP',
|
||||||
# TODO: add more PSM values
|
# TODO: add more PSM values
|
||||||
}
|
}
|
||||||
|
|
||||||
AVCTP_PID_NAMES = {avrcp.AVRCP_PID: 'AVRCP'}
|
AVCTP_PID_NAMES = {avrcp.AVRCP_PID: 'AVRCP'}
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class PacketTracer:
|
class PacketTracer:
|
||||||
class AclStream:
|
class AclStream:
|
||||||
@@ -207,6 +209,7 @@ class PacketTracer:
|
|||||||
self.label = label
|
self.label = label
|
||||||
self.emit_message = emit_message
|
self.emit_message = emit_message
|
||||||
self.acl_streams = {} # ACL streams, by connection handle
|
self.acl_streams = {} # ACL streams, by connection handle
|
||||||
|
self.packet_timestamp: Optional[datetime.datetime] = None
|
||||||
|
|
||||||
def start_acl_stream(self, connection_handle: int) -> PacketTracer.AclStream:
|
def start_acl_stream(self, connection_handle: int) -> PacketTracer.AclStream:
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -234,7 +237,10 @@ class PacketTracer:
|
|||||||
# Let the other forwarder know so it can cleanup its stream as well
|
# Let the other forwarder know so it can cleanup its stream as well
|
||||||
self.peer.end_acl_stream(connection_handle)
|
self.peer.end_acl_stream(connection_handle)
|
||||||
|
|
||||||
def on_packet(self, packet: HCI_Packet) -> None:
|
def on_packet(
|
||||||
|
self, timestamp: Optional[datetime.datetime], packet: HCI_Packet
|
||||||
|
) -> None:
|
||||||
|
self.packet_timestamp = timestamp
|
||||||
self.emit(packet)
|
self.emit(packet)
|
||||||
|
|
||||||
if packet.hci_packet_type == HCI_ACL_DATA_PACKET:
|
if packet.hci_packet_type == HCI_ACL_DATA_PACKET:
|
||||||
@@ -254,13 +260,22 @@ class PacketTracer:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def emit(self, message: Any) -> None:
|
def emit(self, message: Any) -> None:
|
||||||
self.emit_message(f'[{self.label}] {message}')
|
if self.packet_timestamp:
|
||||||
|
prefix = f"[{self.packet_timestamp.strftime('%Y-%m-%d %H:%M:%S.%f')}]"
|
||||||
|
else:
|
||||||
|
prefix = ""
|
||||||
|
self.emit_message(f'{prefix}[{self.label}] {message}')
|
||||||
|
|
||||||
def trace(self, packet: HCI_Packet, direction: int = 0) -> None:
|
def trace(
|
||||||
|
self,
|
||||||
|
packet: HCI_Packet,
|
||||||
|
direction: int = 0,
|
||||||
|
timestamp: Optional[datetime.datetime] = None,
|
||||||
|
) -> None:
|
||||||
if direction == 0:
|
if direction == 0:
|
||||||
self.host_to_controller_analyzer.on_packet(packet)
|
self.host_to_controller_analyzer.on_packet(timestamp, packet)
|
||||||
else:
|
else:
|
||||||
self.controller_to_host_analyzer.on_packet(packet)
|
self.controller_to_host_analyzer.on_packet(timestamp, packet)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -168,11 +168,13 @@ class PacketReader:
|
|||||||
|
|
||||||
def __init__(self, source: io.BufferedReader) -> None:
|
def __init__(self, source: io.BufferedReader) -> None:
|
||||||
self.source = source
|
self.source = source
|
||||||
|
self.at_end = False
|
||||||
|
|
||||||
def next_packet(self) -> Optional[bytes]:
|
def next_packet(self) -> Optional[bytes]:
|
||||||
# Get the packet type
|
# Get the packet type
|
||||||
packet_type = self.source.read(1)
|
packet_type = self.source.read(1)
|
||||||
if len(packet_type) != 1:
|
if len(packet_type) != 1:
|
||||||
|
self.at_end = True
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Get the packet info based on its type
|
# Get the packet info based on its type
|
||||||
|
|||||||
Reference in New Issue
Block a user