feat: GSMTAP-SIM APDU streaming for Wireshark / SIMtrace Analyser (v3.1.0)
Streams every APDU the server sends or receives as GSMTAP-SIM UDP packets, so a live capture can be followed in Wireshark or the SIMtrace Analyser without a hardware sniffer. CLI-only: --gsmtap [HOST[:PORT]], default target 127.0.0.1:4729; no UI or API. - pysim_simple_server/gsmtap.py: 16-byte big-endian GSMTAP-SIM header (type 0x04, sub_type 0x00 = APDU / 0x01 = ATR) + raw APDU bytes, a fire-and-forget non-blocking sender that never raises into card I/O, an ApduTracer (a response is sent as data + SW1SW2, the wire form) and a fan-out tracer. The packet layout is byte-identical to sigrok-iso7816-stream / simtrace2-sniff (verified against the sigrok module) and is what the analyser's GSMTAP receiver expects. - __main__.py: --gsmtap option, tracer installed on the shared transport before the first APDU, combined with --apdu-trace via the fan-out and re-attached across equips (pySim nulls the tracer on every equip); one ATR packet per equip from _apply_equipped_card/_send_gsmtap_atr. - start.sh/start.bat: forward their extra arguments to the server, so ./start.sh --gsmtap works. - tests: packet layout, loopback UDP delivery, tracer mapping, target parsing, fan-out; docs (READMEs, help EN/RU, AGENTS); version 3.1.0, sw.js simple-v235.
This commit is contained in:
@@ -28,7 +28,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '3.0.1'
|
||||
VERSION = '3.1.0'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
@@ -2532,10 +2532,34 @@ def _handle_card_disconnect():
|
||||
_reset_proactive_log()
|
||||
|
||||
|
||||
def _send_gsmtap_atr(server):
|
||||
"""Stream the card's ATR as a GSMTAP-SIM packet (--gsmtap only).
|
||||
|
||||
Sent at every equip so a GSMTAP receiver (SIMtrace Analyser, Wireshark)
|
||||
has the session context before the first APDU of the new session."""
|
||||
sender = getattr(server, 'gsmtap', None)
|
||||
if sender is None:
|
||||
return
|
||||
rs = getattr(server, 'rs', None)
|
||||
atr = None
|
||||
if rs is not None:
|
||||
try:
|
||||
atr = (rs.identity or {}).get('ATR')
|
||||
except Exception:
|
||||
atr = None
|
||||
if not atr:
|
||||
return
|
||||
try:
|
||||
sender.send_atr(bytes.fromhex(atr))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
|
||||
def _apply_equipped_card(server):
|
||||
"""Common post-equip state refresh + TERMINAL PROFILE, shared by the
|
||||
/api/command equip branch and the auto-equip worker."""
|
||||
global _CARD_CONNECTED
|
||||
_send_gsmtap_atr(server)
|
||||
server.stk_pending = None
|
||||
server.menu_active = False
|
||||
_cancel_menu_timeout()
|
||||
|
||||
Reference in New Issue
Block a user