## ## GSMTAP live streaming for the ISO 7816 decoder. ## ## Sends decoded events as GSMTAP-SIM UDP packets, compatible with ## simtrace2-sniff, simtrace2-pysniff and the GSMTAP dissector in ## Wireshark. ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## import socket import struct GSMTAP_VERSION = 0x02 GSMTAP_HDR_LEN = 4 # in 32-bit words (16 bytes) GSMTAP_TYPE_SIM = 0x04 # sub_type values. 0x00-0x03 are standard GSMTAP-SIM types with standard # payloads (libosmocore gsmtap.h): the PPS request and response are emitted # as separate packets (PPS_REQ 0x02 / PPS_RSP 0x03). 0x10/0x11 are custom # extensions (not in the spec) for RST/VCC line events. GSMTAP_SIM_APDU = 0x00 GSMTAP_SIM_ATR = 0x01 GSMTAP_SIM_PPS_REQ = 0x02 # raw PPS request bytes GSMTAP_SIM_PPS_RSP = 0x03 # raw PPS response bytes GSMTAP_SIM_RST_EVENT = 0x10 # payload: [direction, level, reserved] GSMTAP_SIM_VCC_EVENT = 0x11 # payload: [direction, level, reserved] # RST/VCC event payload bytes: # byte 0 (direction): # RST: 1 = reset asserted (high->low), 0 = reset deasserted (low->high) # VCC: 1 = power applied (low->high), 0 = power removed (high->low) # byte 1 (level): line level after the transition (0 = low, 1 = high) # byte 2: reserved (0x00) GSMTAP_UDP_PORT = 4729 # Per-packet flags are carried in the header `res` byte, which libosmocore's # gsmtap.h defines as reserved (RFU). This carriage is our own extension -- # upstream simtrace2-sniff/cardem never set `res` -- although the flag # semantics mirror simtrace2's per-message USB data flags (the wire problem # is reported out-of-band instead of being encoded in the APDU bytes). GSMTAP_FLAG_BAD_FCS = 0x01 # "Any data checksum is wrong" -- reused here to # mark decoder-desynced TPDUs (see README.md, # "GSMTAP events"). _HDR_FMT = '!BBBBHBBIBBBB' # 16 bytes, big-endian def build_packet(sub_type, data, flags=0): '''Build a complete GSMTAP packet (header + payload) as bytes. `flags` is written into the header `res` byte (GSMTAP_FLAG_*).''' hdr = struct.pack( _HDR_FMT, GSMTAP_VERSION, # version GSMTAP_HDR_LEN, # hdr_len (in 32-bit words) GSMTAP_TYPE_SIM, # type 0, # timeslot 0, # arfcn 0, # signal_dbm (libosmocore gsmtap.h field order) 0, # snr_db 0, # frame_number sub_type, # sub_type 0, # antenna_nr 0, # sub_slot flags, # res (GSMTAP_FLAG_*) ) return hdr + bytes(data) class GsmtapStreamSender: '''Fire-and-forget UDP sender. Never raises on send: with no listener present, datagrams are silently dropped by the OS (same behavior as simtrace2-sniff). ''' def __init__(self, host='127.0.0.1', port=GSMTAP_UDP_PORT): self._addr = (host, int(port)) self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP to localhost sendto is non-blocking anyway; make it explicit so # a full socket buffer can never stall the decode loop. self._sock.setblocking(False) def send(self, sub_type, data, flags=0): try: self._sock.sendto(build_packet(sub_type, data, flags), self._addr) except OSError: pass def close(self): try: self._sock.close() except OSError: pass