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
130 lines
3.9 KiB
Python
130 lines
3.9 KiB
Python
"""Hex dump formatters for simtrace2 sniffing output."""
|
|
|
|
import time
|
|
from datetime import datetime
|
|
|
|
|
|
def hexdump(data, sep=' '):
|
|
"""Return hex string representation of bytes.
|
|
|
|
>>> hexdump(b'\\x3b\\x9e\\x12\\x34')
|
|
'3b 9e 12 34'
|
|
"""
|
|
return data.hex(sep)
|
|
|
|
|
|
def _flag_str(flags, flag_names):
|
|
if not flags:
|
|
return ''
|
|
names = []
|
|
for bit, name in sorted(flag_names.items()):
|
|
if flags & bit:
|
|
names.append(name)
|
|
flags &= ~bit
|
|
if flags:
|
|
names.append(f'unknown(0x{flags:08x})')
|
|
return ', '.join(names)
|
|
|
|
|
|
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.
|
|
|
|
Returns a str suitable for stdout or file output.
|
|
"""
|
|
from .protocol import (
|
|
CHANGE_FLAG_CARD_INSERT, CHANGE_FLAG_CARD_EJECT,
|
|
CHANGE_FLAG_RESET_ASSERT, CHANGE_FLAG_RESET_DEASSERT,
|
|
CHANGE_FLAG_TIMEOUT_WT,
|
|
DATA_FLAG_ERROR_INCOMPLETE, DATA_FLAG_ERROR_MALFORMED,
|
|
DATA_FLAG_ERROR_CHECKSUM, DATA_FLAG_ERROR_OVERRUN,
|
|
DATA_FLAG_ERROR_FRAMING, DATA_FLAG_ERROR_PARITY,
|
|
)
|
|
|
|
change_flag_names = {
|
|
CHANGE_FLAG_CARD_INSERT: 'card inserted',
|
|
CHANGE_FLAG_CARD_EJECT: 'card ejected',
|
|
CHANGE_FLAG_RESET_ASSERT: 'reset asserted',
|
|
CHANGE_FLAG_RESET_DEASSERT: 'reset de-asserted',
|
|
CHANGE_FLAG_TIMEOUT_WT: 'waiting time timeout',
|
|
}
|
|
|
|
data_flag_names = {
|
|
DATA_FLAG_ERROR_INCOMPLETE: 'incomplete',
|
|
DATA_FLAG_ERROR_MALFORMED: 'malformed',
|
|
DATA_FLAG_ERROR_CHECKSUM: 'checksum error',
|
|
DATA_FLAG_ERROR_OVERRUN: 'overrun',
|
|
DATA_FLAG_ERROR_FRAMING: 'framing error',
|
|
DATA_FLAG_ERROR_PARITY: 'parity error',
|
|
}
|
|
|
|
type_label = msg.type.upper()
|
|
|
|
if msg.type == 'change':
|
|
flags_str = _flag_str(msg.flags, change_flag_names)
|
|
line = f'Card state change: {flags_str}' if flags_str else 'Card state change: no changes'
|
|
elif msg.type == 'fidi':
|
|
fidi = msg.data[0] if len(msg.data) >= 1 else 0
|
|
fi = fidi >> 4
|
|
di = fidi & 0x0f
|
|
line = f'Fi/Di switched to {_fi_table.get(fi, fi)}/{_di_table.get(di, di)}'
|
|
else:
|
|
flags_str = ''
|
|
if show_flags and msg.flags:
|
|
flags_str = f' ({_flag_str(msg.flags, data_flag_names)})'
|
|
line = f'{tpdu_prefix}{type_label}{flags_str}: {hexdump(msg.data)}'
|
|
|
|
return line
|
|
|
|
|
|
class FileDumper:
|
|
"""Write formatted sniff messages to a file."""
|
|
|
|
def __init__(self, path, fmt='hex'):
|
|
self._fmt = fmt
|
|
self._f = open(path, 'a', buffering=1)
|
|
self._atr_start = time.monotonic()
|
|
|
|
def write(self, 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')
|
|
|
|
def close(self):
|
|
self._f.close()
|
|
|
|
|
|
# ISO 7816-3:2006 Table 7
|
|
_fi_table = {
|
|
0: 372, 1: 372, 2: 558, 3: 744,
|
|
4: 1116, 5: 1488, 6: 1860, 7: 0,
|
|
8: 0, 9: 512, 10: 768, 11: 1024,
|
|
12: 1536, 13: 2048, 14: 0, 15: 0,
|
|
}
|
|
|
|
# ISO 7816-3:2006 Table 8
|
|
_di_table = {
|
|
0: 0, 1: 1, 2: 2, 3: 4,
|
|
4: 8, 5: 16, 6: 32, 7: 64,
|
|
8: 12, 9: 20, 10: 2, 11: 4,
|
|
12: 8, 13: 16, 14: 32, 15: 64,
|
|
}
|