Introduce APDU/TPDU trace decoder
This introduces a new pySim.apdu module hierarchy, which contains
classes that represent TPDU/APDUs as exchanged between
SIM/UICC/USIM/ISIM card and UE.
It contains instruction level decoders for SELECT, READ BINARY and
friends, and then uses the pySim.filesystem.Runtime{Lchan,State} classes
to keep track of the currently selected EF/DF/ADF for each logical
channel, and uses the file-specific decoder classes of pySim to decode
the actual file content that is being read or written.
This provides a much more meaningful decode of protocol traces than
wireshark will ever be able to give us.
Furthermore, there's the new pySim.apdu_source set of classes which
provides "input plugins" for obtaining APDU traces in a variety of
formats. So far, GSMTAP UDP live capture and pyshark based RSPRO
live and pcap file reading are imlpemented.
Change-Id: I862d93163d495a294364168f7818641e47b18c0a
Closes: OS#5126
diff --git a/pySim/gsmtap.py b/pySim/gsmtap.py
new file mode 100644
index 0000000..48a7af7
--- /dev/null
+++ b/pySim/gsmtap.py
@@ -0,0 +1,214 @@
+# -*- coding: utf-8 -*-
+
+""" Osmocom GSMTAP python implementation.
+GSMTAP is a packet format used for conveying a number of different
+telecom-related protocol traces over UDP.
+"""
+
+#
+# Copyright (C) 2022 Harald Welte <laforge@gnumonks.org>
+#
+# 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.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import socket
+from typing import List, Dict, Optional
+from construct import Optional as COptional
+from construct import *
+from pySim.construct import *
+
+# The root definition of GSMTAP can be found at
+# https://cgit.osmocom.org/cgit/libosmocore/tree/include/osmocom/core/gsmtap.h
+
+GSMTAP_UDP_PORT = 4729
+
+# GSMTAP_TYPE_*
+gsmtap_type_construct = Enum(Int8ub,
+ gsm_um = 0x01,
+ gsm_abis = 0x02,
+ gsm_um_burst = 0x03,
+ sim = 0x04,
+ tetra_i1 = 0x05,
+ tetra_i1_burst = 0x06,
+ wimax_burst = 0x07,
+ gprs_gb_llc = 0x08,
+ gprs_gb_sndcp = 0x09,
+ gmr1_um = 0x0a,
+ umts_rlc_mac = 0x0b,
+ umts_rrc = 0x0c,
+ lte_rrc = 0x0d,
+ lte_mac = 0x0e,
+ lte_mac_framed = 0x0f,
+ osmocore_log = 0x10,
+ qc_diag = 0x11,
+ lte_nas = 0x12,
+ e1_t1 = 0x13)
+
+
+# TYPE_UM_BURST
+gsmtap_subtype_burst_construct = Enum(Int8ub,
+ unknown = 0x00,
+ fcch = 0x01,
+ partial_sch = 0x02,
+ sch = 0x03,
+ cts_sch = 0x04,
+ compact_sch = 0x05,
+ normal = 0x06,
+ dummy = 0x07,
+ access = 0x08,
+ none = 0x09)
+
+gsmtap_subtype_wimax_burst_construct = Enum(Int8ub,
+ cdma_code = 0x10,
+ fch = 0x11,
+ ffb = 0x12,
+ pdu = 0x13,
+ hack = 0x14,
+ phy_attributes = 0x15)
+
+# GSMTAP_CHANNEL_*
+gsmtap_subtype_um_construct = Enum(Int8ub,
+ unknown = 0x00,
+ bcch = 0x01,
+ ccch = 0x02,
+ rach = 0x03,
+ agch = 0x04,
+ pch = 0x05,
+ sdcch = 0x06,
+ sdcch4 = 0x07,
+ sdcch8 = 0x08,
+ facch_f = 0x09,
+ facch_h = 0x0a,
+ pacch = 0x0b,
+ cbch52 = 0x0c,
+ pdtch = 0x0d,
+ ptcch = 0x0e,
+ cbch51 = 0x0f,
+ voice_f = 0x10,
+ voice_h = 0x11)
+
+
+# GSMTAP_SIM_*
+gsmtap_subtype_sim_construct = Enum(Int8ub,
+ apdu = 0x00,
+ atr = 0x01,
+ pps_req = 0x02,
+ pps_rsp = 0x03,
+ tpdu_hdr = 0x04,
+ tpdu_cmd = 0x05,
+ tpdu_rsp = 0x06,
+ tpdu_sw = 0x07)
+
+gsmtap_subtype_tetra_construct = Enum(Int8ub,
+ bsch = 0x01,
+ aach = 0x02,
+ sch_hu = 0x03,
+ sch_hd = 0x04,
+ sch_f = 0x05,
+ bnch = 0x06,
+ stch = 0x07,
+ tch_f = 0x08,
+ dmo_sch_s = 0x09,
+ dmo_sch_h = 0x0a,
+ dmo_sch_f = 0x0b,
+ dmo_stch = 0x0c,
+ dmo_tch = 0x0d)
+
+gsmtap_subtype_gmr1_construct = Enum(Int8ub,
+ unknown = 0x00,
+ bcch = 0x01,
+ ccch = 0x02,
+ pch = 0x03,
+ agch = 0x04,
+ bach = 0x05,
+ rach = 0x06,
+ cbch = 0x07,
+ sdcch = 0x08,
+ tachh = 0x09,
+ gbch = 0x0a,
+ tch3 = 0x10,
+ tch6 = 0x14,
+ tch9 = 0x18)
+
+gsmtap_subtype_e1t1_construct = Enum(Int8ub,
+ lapd = 0x01,
+ fr = 0x02,
+ raw = 0x03,
+ trau16 = 0x04,
+ trau8 = 0x05)
+
+gsmtap_arfcn_construct = BitStruct('pcs'/Flag, 'uplink'/Flag, 'arfcn'/BitsInteger(14))
+
+gsmtap_hdr_construct = Struct('version'/Int8ub,
+ 'hdr_len'/Int8ub,
+ 'type'/gsmtap_type_construct,
+ 'timeslot'/Int8ub,
+ 'arfcn'/gsmtap_arfcn_construct,
+ 'signal_dbm'/Int8sb,
+ 'snr_db'/Int8sb,
+ 'frame_nr'/Int32ub,
+ 'sub_type'/Switch(this.type, {
+ 'gsm_um': gsmtap_subtype_um_construct,
+ 'gsm_um_burst': gsmtap_subtype_burst_construct,
+ 'sim': gsmtap_subtype_sim_construct,
+ 'tetra_i1': gsmtap_subtype_tetra_construct,
+ 'tetra_i1_burst': gsmtap_subtype_tetra_construct,
+ 'wimax_burst': gsmtap_subtype_wimax_burst_construct,
+ 'gmr1_um': gsmtap_subtype_gmr1_construct,
+ 'e1_t1': gsmtap_subtype_e1t1_construct,
+ }),
+ 'antenna_nr'/Int8ub,
+ 'sub_slot'/Int8ub,
+ 'res'/Int8ub,
+ 'body'/GreedyBytes)
+
+osmocore_log_ts_construct = Struct('sec'/Int32ub, 'usec'/Int32ub)
+osmocore_log_level_construct = Enum(Int8ub, debug=1, info=3, notice=5, error=7, fatal=8)
+gsmtap_osmocore_log_hdr_construct = Struct('ts'/osmocore_log_ts_construct,
+ 'proc_name'/PaddedString(16, 'ascii'),
+ 'pid'/Int32ub,
+ 'level'/osmocore_log_level_construct,
+ Bytes(3),
+ 'subsys'/PaddedString(16, 'ascii'),
+ 'src_file'/Struct('name'/PaddedString(32, 'ascii'), 'line_nr'/Int32ub))
+
+
+class GsmtapMessage:
+ """Class whose objects represent a single GSMTAP message. Can encode and decode messages."""
+ def __init__(self, encoded = None):
+ self.encoded = encoded
+ self.decoded = None
+
+ def decode(self):
+ self.decoded = parse_construct(gsmtap_hdr_construct, self.encoded)
+ return self.decoded
+
+ def encode(self, decoded):
+ self.encoded = gsmtap_hdr_construct.build(decoded)
+ return self.encoded
+
+class GsmtapSource:
+ def __init__(self, bind_ip:str='127.0.0.1', bind_port:int=4729):
+ self.bind_ip = bind_ip
+ self.bind_port = bind_port
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ self.sock.bind((self.bind_ip, self.bind_port))
+
+ def read_packet(self) -> GsmtapMessage:
+ data, addr = self.sock.recvfrom(1024)
+ gsmtap_msg = GsmtapMessage(data)
+ gsmtap_msg.decode()
+ if gsmtap_msg.decoded['version'] != 0x02:
+ raise ValueError('Unknown GSMTAP version 0x%02x' % gsmtap_msg.decoded['version'])
+ return gsmtap_msg.decoded, addr