Add --format option: timestamp and atr-time TPDU output modes
dump.py: tpdu_timestamp_prefix(), tpdu_atr_time_prefix(), format_message() accepts tpdu_prefix parameter, FileDumper supports fmt + ATR time tracking __main__.py: --format/-f flag (hex|timestamp|atr-time), atr_time_start tracking in message loop sniff-start.sh: pass through --format/-f and FORMAT env var
This commit is contained in:
1 parent
73b0277ef7
commit
ec129c4463
4 files changed
+63
-10
No files matched your search
@@ -17,6 +17,8 @@ Or use the bundled startup script:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
./sniff-start.sh # hex dump to stdout
|
./sniff-start.sh # hex dump to stdout
|
||||||
|
./sniff-start.sh --format timestamp # TPDU lines with local time
|
||||||
|
./sniff-start.sh --format atr-time # TPDU lines relative to ATR
|
||||||
./sniff-start.sh --gsmtap 127.0.0.1:4729 # + Wireshark GSMTAP
|
./sniff-start.sh --gsmtap 127.0.0.1:4729 # + Wireshark GSMTAP
|
||||||
./sniff-start.sh --pcap trace.pcap # save PCAP for later
|
./sniff-start.sh --pcap trace.pcap # save PCAP for later
|
||||||
./sniff-start.sh --inactivity-timeout 30 # reconnect on silence
|
./sniff-start.sh --inactivity-timeout 30 # reconnect on silence
|
||||||
@@ -58,6 +60,10 @@ After that, `python -m simtrace2_pysniff` works identically to Linux.
|
|||||||
```
|
```
|
||||||
python -m simtrace2_pysniff [OPTIONS]
|
python -m simtrace2_pysniff [OPTIONS]
|
||||||
|
|
||||||
|
--format, -f FORMAT Output format (default: hex)
|
||||||
|
hex — plain hex dump
|
||||||
|
timestamp — TPDU lines prefixed with [HH:MM:SS.mmm]
|
||||||
|
atr-time — TPDU lines prefixed with [SSSS.mmm] from last ATR
|
||||||
--gsmtap HOST[:PORT] Send ATR/TPDU as GSMTAP over UDP (default port 4729)
|
--gsmtap HOST[:PORT] Send ATR/TPDU as GSMTAP over UDP (default port 4729)
|
||||||
--pcap FILE Write sniffed data to PCAP file for Wireshark
|
--pcap FILE Write sniffed data to PCAP file for Wireshark
|
||||||
--output, -o FILE Write hex dump to file
|
--output, -o FILE Write hex dump to file
|
||||||
@@ -80,9 +86,14 @@ for msg in session.iter_messages():
|
|||||||
print(f"{msg.type}: {msg.data.hex()}")
|
print(f"{msg.type}: {msg.data.hex()}")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Output
|
## Output Formats
|
||||||
|
|
||||||
|
- **`hex`** (default): plain hex dump — `ATR: 3b 9e ...`, `TPDU: a0 a4 00 00 02 3f 00`
|
||||||
|
- **`timestamp`**: TPDU lines prefixed with local time — `[14:32:05.123] TPDU: a0 a4 00 00 02 3f 00`
|
||||||
|
- **`atr-time`**: TPDU lines prefixed with seconds since last ATR — `[0001.234] TPDU: a0 a4 00 00 02 3f 00`
|
||||||
|
|
||||||
|
Non-TPDU messages (ATR, PPS, card state changes, Fi/Di) print identically in all formats.
|
||||||
|
|
||||||
- **stdout**: `ATR: 3b 9e ...`, `TPDU: a0 a4 00 00 02 3f 00`, `Card state change: reset de-asserted`
|
|
||||||
- **GSMTAP**: ATR (subtype 1) and TPDU (subtype 2) over UDP to Wireshark
|
- **GSMTAP**: ATR (subtype 1) and TPDU (subtype 2) over UDP to Wireshark
|
||||||
- **PCAP**: all messages as GSMTAP-encapsulated packets (LINKTYPE 155), openable in Wireshark
|
- **PCAP**: all messages as GSMTAP-encapsulated packets (LINKTYPE 155), openable in Wireshark
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from . import (
|
|||||||
format_message,
|
format_message,
|
||||||
__version__,
|
__version__,
|
||||||
)
|
)
|
||||||
|
from .dump import tpdu_timestamp_prefix, tpdu_atr_time_prefix
|
||||||
from .gsmtap import build_gsmtap_packet, GSMTAP_SIM_ATR, GSMTAP_SIM_APDU
|
from .gsmtap import build_gsmtap_packet, GSMTAP_SIM_ATR, GSMTAP_SIM_APDU
|
||||||
|
|
||||||
|
|
||||||
@@ -48,6 +49,9 @@ def build_parser():
|
|||||||
help='Write hex dump to file')
|
help='Write hex dump to file')
|
||||||
p.add_argument('--no-stdout', action='store_true',
|
p.add_argument('--no-stdout', action='store_true',
|
||||||
help='Suppress hex dump to stdout')
|
help='Suppress hex dump to stdout')
|
||||||
|
p.add_argument('--format', '-f', choices=['hex', 'timestamp', 'atr-time'],
|
||||||
|
default='hex',
|
||||||
|
help='Output format for TPDU lines (default: hex)')
|
||||||
p.add_argument('--vendor', type=lambda x: int(x, 16),
|
p.add_argument('--vendor', type=lambda x: int(x, 16),
|
||||||
default=0x1d50, metavar='HEX',
|
default=0x1d50, metavar='HEX',
|
||||||
help='USB vendor ID (default: 0x1d50)')
|
help='USB vendor ID (default: 0x1d50)')
|
||||||
@@ -99,7 +103,7 @@ def main():
|
|||||||
|
|
||||||
file_dumper = None
|
file_dumper = None
|
||||||
if args.output:
|
if args.output:
|
||||||
file_dumper = FileDumper(args.output)
|
file_dumper = FileDumper(args.output, fmt=args.format)
|
||||||
print(f'File output: {args.output}', file=sys.stderr)
|
print(f'File output: {args.output}', file=sys.stderr)
|
||||||
|
|
||||||
session = SniffSession(
|
session = SniffSession(
|
||||||
@@ -130,10 +134,22 @@ def main():
|
|||||||
signal.signal(signal.SIGINT, on_signal)
|
signal.signal(signal.SIGINT, on_signal)
|
||||||
signal.signal(signal.SIGTERM, on_signal)
|
signal.signal(signal.SIGTERM, on_signal)
|
||||||
|
|
||||||
|
atr_time_start = time.monotonic()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for msg in session.iter_messages():
|
for msg in session.iter_messages():
|
||||||
|
if msg.type == 'atr':
|
||||||
|
atr_time_start = msg.timestamp
|
||||||
|
|
||||||
|
tpdu_prefix = ''
|
||||||
|
if args.format == 'timestamp' and msg.type == 'tpdu':
|
||||||
|
tpdu_prefix = tpdu_timestamp_prefix(msg)
|
||||||
|
elif args.format == 'atr-time' and msg.type == 'tpdu':
|
||||||
|
tpdu_prefix = tpdu_atr_time_prefix(
|
||||||
|
msg.timestamp - atr_time_start)
|
||||||
|
|
||||||
if not args.no_stdout:
|
if not args.no_stdout:
|
||||||
print(format_message(msg))
|
print(format_message(msg, tpdu_prefix=tpdu_prefix))
|
||||||
|
|
||||||
if file_dumper is not None:
|
if file_dumper is not None:
|
||||||
file_dumper.write(msg)
|
file_dumper.write(msg)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""Hex dump formatters for simtrace2 sniffing output."""
|
"""Hex dump formatters for simtrace2 sniffing output."""
|
||||||
|
|
||||||
import sys
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
def hexdump(data, sep=' '):
|
def hexdump(data, sep=' '):
|
||||||
@@ -25,7 +26,20 @@ def _flag_str(flags, flag_names):
|
|||||||
return ', '.join(names)
|
return ', '.join(names)
|
||||||
|
|
||||||
|
|
||||||
def format_message(msg, *, show_flags=True):
|
def tpdu_timestamp_prefix(msg):
|
||||||
|
"""Return ``[HH:MM:SS.mmm]`` for a message timestamp (local time)."""
|
||||||
|
dt = datetime.fromtimestamp(msg.timestamp)
|
||||||
|
return f"[{dt.strftime('%H:%M:%S')}.{dt.microsecond // 1000:03d}]"
|
||||||
|
|
||||||
|
|
||||||
|
def tpdu_atr_time_prefix(elapsed_sec):
|
||||||
|
"""Return ``[SSSS.mmm]`` — zero-filled seconds + milliseconds."""
|
||||||
|
s = int(elapsed_sec)
|
||||||
|
ms = int((elapsed_sec - s) * 1000)
|
||||||
|
return f"[{s:04d}.{ms:03d}]"
|
||||||
|
|
||||||
|
|
||||||
|
def format_message(msg, *, show_flags=True, tpdu_prefix=''):
|
||||||
"""Format a SniffMessage as a human-readable line.
|
"""Format a SniffMessage as a human-readable line.
|
||||||
|
|
||||||
Returns a str suitable for stdout or file output.
|
Returns a str suitable for stdout or file output.
|
||||||
@@ -70,7 +84,7 @@ def format_message(msg, *, show_flags=True):
|
|||||||
flags_str = ''
|
flags_str = ''
|
||||||
if show_flags and msg.flags:
|
if show_flags and msg.flags:
|
||||||
flags_str = f' ({_flag_str(msg.flags, data_flag_names)})'
|
flags_str = f' ({_flag_str(msg.flags, data_flag_names)})'
|
||||||
line = f'{type_label}{flags_str}: {hexdump(msg.data)}'
|
line = f'{tpdu_prefix}{type_label}{flags_str}: {hexdump(msg.data)}'
|
||||||
|
|
||||||
return line
|
return line
|
||||||
|
|
||||||
@@ -78,11 +92,20 @@ def format_message(msg, *, show_flags=True):
|
|||||||
class FileDumper:
|
class FileDumper:
|
||||||
"""Write formatted sniff messages to a file."""
|
"""Write formatted sniff messages to a file."""
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path, fmt='hex'):
|
||||||
|
self._fmt = fmt
|
||||||
self._f = open(path, 'a', buffering=1)
|
self._f = open(path, 'a', buffering=1)
|
||||||
|
self._atr_start = time.monotonic()
|
||||||
|
|
||||||
def write(self, msg):
|
def write(self, msg):
|
||||||
line = format_message(msg)
|
tpdu_prefix = ''
|
||||||
|
if self._fmt == 'timestamp' and msg.type == 'tpdu':
|
||||||
|
tpdu_prefix = tpdu_timestamp_prefix(msg)
|
||||||
|
elif self._fmt == 'atr-time' and msg.type == 'tpdu':
|
||||||
|
tpdu_prefix = tpdu_atr_time_prefix(msg.timestamp - self._atr_start)
|
||||||
|
if msg.type == 'atr':
|
||||||
|
self._atr_start = msg.timestamp
|
||||||
|
line = format_message(msg, tpdu_prefix=tpdu_prefix)
|
||||||
self._f.write(line + '\n')
|
self._f.write(line + '\n')
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
|||||||
+4
-1
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# simtrace2-pysniff startup script
|
# simtrace2-pysniff startup script
|
||||||
#
|
#
|
||||||
# Usage: ./sniff-start.sh [--gsmtap HOST[:PORT]] [--pcap FILE] [--output FILE] [--inactivity-timeout SECONDS]
|
# Usage: ./sniff-start.sh [--format FORMAT] [--gsmtap HOST[:PORT]] [--pcap FILE] [--output FILE] [--inactivity-timeout SECONDS]
|
||||||
#
|
#
|
||||||
# Options set via environment variables act as defaults; CLI flags override them.
|
# Options set via environment variables act as defaults; CLI flags override them.
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|||||||
GSMTAP="${GSMTAP:-}"
|
GSMTAP="${GSMTAP:-}"
|
||||||
PCAP="${PCAP:-}"
|
PCAP="${PCAP:-}"
|
||||||
OUTPUT="${OUTPUT:-}"
|
OUTPUT="${OUTPUT:-}"
|
||||||
|
FORMAT="${FORMAT:-}"
|
||||||
INACTIVITY_TIMEOUT="${INACTIVITY_TIMEOUT:-0}"
|
INACTIVITY_TIMEOUT="${INACTIVITY_TIMEOUT:-0}"
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
@@ -20,6 +21,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--gsmtap) GSMTAP="$2"; shift 2 ;;
|
--gsmtap) GSMTAP="$2"; shift 2 ;;
|
||||||
--pcap) PCAP="$2"; shift 2 ;;
|
--pcap) PCAP="$2"; shift 2 ;;
|
||||||
--output|-o) OUTPUT="$2"; shift 2 ;;
|
--output|-o) OUTPUT="$2"; shift 2 ;;
|
||||||
|
--format|-f) FORMAT="$2"; shift 2 ;;
|
||||||
--inactivity-timeout) INACTIVITY_TIMEOUT="$2"; shift 2 ;;
|
--inactivity-timeout) INACTIVITY_TIMEOUT="$2"; shift 2 ;;
|
||||||
--keep-running|--no-reconnect)
|
--keep-running|--no-reconnect)
|
||||||
EXTRA_ARGS+=("$1"); shift ;;
|
EXTRA_ARGS+=("$1"); shift ;;
|
||||||
@@ -76,6 +78,7 @@ ARGS=()
|
|||||||
[[ -n "$GSMTAP" ]] && ARGS+=(--gsmtap "$GSMTAP")
|
[[ -n "$GSMTAP" ]] && ARGS+=(--gsmtap "$GSMTAP")
|
||||||
[[ -n "$PCAP" ]] && ARGS+=(--pcap "$PCAP")
|
[[ -n "$PCAP" ]] && ARGS+=(--pcap "$PCAP")
|
||||||
[[ -n "$OUTPUT" ]] && ARGS+=(--output "$OUTPUT")
|
[[ -n "$OUTPUT" ]] && ARGS+=(--output "$OUTPUT")
|
||||||
|
[[ -n "$FORMAT" ]] && ARGS+=(--format "$FORMAT")
|
||||||
[[ "$INACTIVITY_TIMEOUT" != "0" ]] && ARGS+=(--inactivity-timeout "$INACTIVITY_TIMEOUT")
|
[[ "$INACTIVITY_TIMEOUT" != "0" ]] && ARGS+=(--inactivity-timeout "$INACTIVITY_TIMEOUT")
|
||||||
ARGS+=("${EXTRA_ARGS[@]}")
|
ARGS+=("${EXTRA_ARGS[@]}")
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user