laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/31055 )
Change subject: Prepare for decoding/encoding records differently based on record number ......................................................................
Prepare for decoding/encoding records differently based on record number
In their infinite wisdom, the authors of the EIRENE FFFIS for GSM-R SIM cards invented yet a new way of encoding data in SIM card files: The first record of a file may be encoded differently than further records of files.
Let's add the required infrastructure to pySim so that the encode and decode methods for record-oriented files get passed in the current record number.
Change-Id: I02d6942016dd0631b21d1fd301711c13cb27962b Related: OS#5784 --- M pySim/apdu/ts_102_221.py M pySim/filesystem.py M pySim/sysmocom_sja2.py M pySim/ts_102_221.py M pySim/ts_31_102.py M pySim/ts_31_103.py M pySim/ts_51_011.py 7 files changed, 46 insertions(+), 42 deletions(-)
Approvals: Jenkins Builder: Verified fixeria: Looks good to me, but someone else must approve laforge: Looks good to me, approved
diff --git a/pySim/apdu/ts_102_221.py b/pySim/apdu/ts_102_221.py index ea45602..cd246b1 100644 --- a/pySim/apdu/ts_102_221.py +++ b/pySim/apdu/ts_102_221.py @@ -201,7 +201,7 @@ return b2h(self.rsp_data) method = getattr(self.file, 'decode_record_bin', None) if self.successful and callable(method): - return method(self.rsp_data) + return method(self.rsp_data, self.cmd_dict['record_number'])
# TS 102 221 Section 11.1.6 class UpdateRecord(ApduCommand, n='UPDATE RECORD', ins=0xDC, cla=['0X', '4X', '6X']): @@ -217,7 +217,7 @@ return b2h(self.cmd_data) method = getattr(self.file, 'decode_record_bin', None) if self.successful and callable(method): - return method(self.cmd_data) + return method(self.cmd_data, self.cmd_dict['record_number'])
# TS 102 221 Section 11.1.7 class SearchRecord(ApduCommand, n='SEARCH RECORD', ins=0xA2, cla=['0X', '4X', '6X']): diff --git a/pySim/filesystem.py b/pySim/filesystem.py index a3d1122..6dd1db7 100644 --- a/pySim/filesystem.py +++ b/pySim/filesystem.py @@ -945,7 +945,7 @@ self._construct = None self._tlv = None
- def decode_record_hex(self, raw_hex_data: str) -> dict: + def decode_record_hex(self, raw_hex_data: str, record_nr: int) -> dict: """Decode raw (hex string) data into abstract representation.
A derived class would typically provide a _decode_record_bin() or _decode_record_hex() @@ -954,16 +954,17 @@
Args: raw_hex_data : hex-encoded data + record_nr : record number (1 for first record, ...) Returns: abstract_data; dict representing the decoded data """ method = getattr(self, '_decode_record_hex', None) if callable(method): - return method(raw_hex_data) + return method(raw_hex_data, record_nr=record_nr) raw_bin_data = h2b(raw_hex_data) method = getattr(self, '_decode_record_bin', None) if callable(method): - return method(raw_bin_data) + return method(raw_bin_data, record_nr=record_nr) if self._construct: return parse_construct(self._construct, raw_bin_data) elif self._tlv: @@ -972,7 +973,7 @@ return t.to_dict() return {'raw': raw_bin_data.hex()}
- def decode_record_bin(self, raw_bin_data: bytearray) -> dict: + def decode_record_bin(self, raw_bin_data: bytearray, record_nr: int) -> dict: """Decode raw (binary) data into abstract representation.
A derived class would typically provide a _decode_record_bin() or _decode_record_hex() @@ -981,16 +982,17 @@
Args: raw_bin_data : binary encoded data + record_nr : record number (1 for first record, ...) Returns: abstract_data; dict representing the decoded data """ method = getattr(self, '_decode_record_bin', None) if callable(method): - return method(raw_bin_data) + return method(raw_bin_data, record_nr=record_nr) raw_hex_data = b2h(raw_bin_data) method = getattr(self, '_decode_record_hex', None) if callable(method): - return method(raw_hex_data) + return method(raw_hex_data, record_nr=record_nr) if self._construct: return parse_construct(self._construct, raw_bin_data) elif self._tlv: @@ -999,7 +1001,7 @@ return t.to_dict() return {'raw': raw_hex_data}
- def encode_record_hex(self, abstract_data: dict) -> str: + def encode_record_hex(self, abstract_data: dict, record_nr: int) -> str: """Encode abstract representation into raw (hex string) data.
A derived class would typically provide an _encode_record_bin() or _encode_record_hex() @@ -1008,15 +1010,16 @@
Args: abstract_data : dict representing the decoded data + record_nr : record number (1 for first record, ...) Returns: hex string encoded data """ method = getattr(self, '_encode_record_hex', None) if callable(method): - return method(abstract_data) + return method(abstract_data, record_nr=record_nr) method = getattr(self, '_encode_record_bin', None) if callable(method): - raw_bin_data = method(abstract_data) + raw_bin_data = method(abstract_data, record_nr=record_nr) return b2h(raw_bin_data) if self._construct: return b2h(self._construct.build(abstract_data)) @@ -1027,7 +1030,7 @@ raise NotImplementedError( "%s encoder not yet implemented. Patches welcome." % self)
- def encode_record_bin(self, abstract_data: dict) -> bytearray: + def encode_record_bin(self, abstract_data: dict, record_nr : int) -> bytearray: """Encode abstract representation into raw (binary) data.
A derived class would typically provide an _encode_record_bin() or _encode_record_hex() @@ -1036,15 +1039,16 @@
Args: abstract_data : dict representing the decoded data + record_nr : record number (1 for first record, ...) Returns: binary encoded data """ method = getattr(self, '_encode_record_bin', None) if callable(method): - return method(abstract_data) + return method(abstract_data, record_nr=record_nr) method = getattr(self, '_encode_record_hex', None) if callable(method): - return h2b(method(abstract_data)) + return h2b(method(abstract_data, record_nr=record_nr)) if self._construct: return self._construct.build(abstract_data) elif self._tlv: @@ -1681,7 +1685,7 @@ abstract data contained in record """ (data, sw) = self.read_record(rec_nr) - return (self.selected_file.decode_record_hex(data), sw) + return (self.selected_file.decode_record_hex(data, rec_nr), sw)
def update_record(self, rec_nr: int, data_hex: str): """Update a record with given binary data @@ -1702,7 +1706,7 @@ rec_nr : Record number to read data_hex : Abstract data to be written """ - data_hex = self.selected_file.encode_record_hex(data) + data_hex = self.selected_file.encode_record_hex(data, rec_nr) return self.update_record(rec_nr, data_hex)
def retrieve_data(self, tag: int = 0): diff --git a/pySim/sysmocom_sja2.py b/pySim/sysmocom_sja2.py index a78318e..4967701 100644 --- a/pySim/sysmocom_sja2.py +++ b/pySim/sysmocom_sja2.py @@ -87,7 +87,7 @@ def __init__(self, fid='6f22', name='EF.0348_KEY', desc='TS 03.48 OTA Keys'): super().__init__(fid, name=name, desc=desc, rec_len=(27, 35))
- def _decode_record_bin(self, raw_bin_data): + def _decode_record_bin(self, raw_bin_data, **kwargs): u = unpack('!BBB', raw_bin_data[0:3]) key_algo = (u[2] >> 6) & 1 key_length = ((u[2] >> 3) & 3) * 8 @@ -105,7 +105,7 @@ def __init__(self, fid='6f23', name='EF.0348_COUNT', desc='TS 03.48 OTA Counters'): super().__init__(fid, name=name, desc=desc, rec_len=(7, 7))
- def _decode_record_bin(self, raw_bin_data): + def _decode_record_bin(self, raw_bin_data, **kwargs): u = unpack('!BB5s', raw_bin_data) return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2]}
@@ -120,7 +120,7 @@ def __init__(self, fid='6f26', name='EF.GP_COUNT', desc='GP SCP02 Counters'): super().__init__(fid, name=name, desc=desc, rec_len=(5, 5))
- def _decode_record_bin(self, raw_bin_data): + def _decode_record_bin(self, raw_bin_data, **kwargs): u = unpack('!BBHB', raw_bin_data) return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2], 'rfu': u[3]}
@@ -129,7 +129,7 @@ def __init__(self, fid='6f27', name='EF.GP_DIV_DATA', desc='GP SCP02 key diversification data'): super().__init__(fid, name=name, desc=desc, rec_len=(12, 12))
- def _decode_record_bin(self, raw_bin_data): + def _decode_record_bin(self, raw_bin_data, **kwargs): u = unpack('!BB8s', raw_bin_data) return {'sec_domain': u[0], 'key_set_version': u[1], 'key_div_data': u[2].hex()}
diff --git a/pySim/ts_102_221.py b/pySim/ts_102_221.py index 83ddb85..e180b28 100644 --- a/pySim/ts_102_221.py +++ b/pySim/ts_102_221.py @@ -608,13 +608,13 @@ super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=2, size=(2, None))
- def _decode_record_bin(self, bin_data): + def _decode_record_bin(self, bin_data, **kwargs): if bin_data == b'\xff\xff': return None else: return bin_data.decode('ascii')
- def _encode_record_bin(self, in_json): + def _encode_record_bin(self, in_json, **kwargs): if in_json is None: return b'\xff\xff' else: @@ -665,7 +665,7 @@ raise ValueError return by_mode
- def _decode_record_bin(self, raw_bin_data): + def _decode_record_bin(self, raw_bin_data, **kwargs): # we can only guess if we should decode for EF or DF here :( arr_seq = DataObjectSequence('arr', sequence=[AM_DO_EF, SC_DO]) dec = arr_seq.decode_multi(raw_bin_data) @@ -673,7 +673,7 @@ # 'un-flattening' decoder, and hence would be unable to encode :( return dec[0]
- def _encode_record_bin(self, in_json): + def _encode_record_bin(self, in_json, **kwargs): # we can only guess if we should decode for EF or DF here :( arr_seq = DataObjectSequence('arr', sequence=[AM_DO_EF, SC_DO]) return arr_seq.encode_multi(in_json) diff --git a/pySim/ts_31_102.py b/pySim/ts_31_102.py index 65a2afe..e68866c 100644 --- a/pySim/ts_31_102.py +++ b/pySim/ts_31_102.py @@ -515,14 +515,14 @@ desc='Language Indication'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len)
- def _decode_record_bin(self, in_bin): + def _decode_record_bin(self, in_bin, **kwargs): if in_bin == b'\xff\xff': return None else: # officially this is 7-bit GSM alphabet with one padding bit in each byte return in_bin.decode('ascii')
- def _encode_record_bin(self, in_json): + def _encode_record_bin(self, in_json, **kwargs): if in_json == None: return b'\xff\xff' else: @@ -604,7 +604,7 @@ desc='Emergency Call Codes'): super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=(4, 20))
- def _decode_record_bin(self, in_bin): + def _decode_record_bin(self, in_bin, **kwargs): # mandatory parts code = in_bin[:3] if code == b'\xff\xff\xff': @@ -618,7 +618,7 @@ ret['alpha_id'] = parse_construct(EF_ECC.alpha_construct, alpha_id) return ret
- def _encode_record_bin(self, in_json): + def _encode_record_bin(self, in_json, **kwargs): if in_json is None: return b'\xff\xff\xff\xff' code = EF_ECC.cc_construct.build(in_json['call_code']) @@ -753,7 +753,7 @@ def __init__(self, fid='6f65', sfid=None, name='EF.RPLMNAcTD', size=(2, 4), rec_len=2, desc='RPLMN Last used Access Technology', **kwargs): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len, **kwargs) - def _decode_record_hex(self, in_hex): + def _decode_record_hex(self, in_hex, **kwargs): return dec_act(in_hex) # TODO: Encode
diff --git a/pySim/ts_31_103.py b/pySim/ts_31_103.py index 30df0b7..ee50ddb 100644 --- a/pySim/ts_31_103.py +++ b/pySim/ts_31_103.py @@ -142,11 +142,11 @@ def __init__(self, fid='6f09', sfid=None, name='EF.P-CSCF', desc='P-CSCF Address', **kwargs): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, **kwargs)
- def _decode_record_hex(self, raw_hex): + def _decode_record_hex(self, raw_hex, **kwargs): addr, addr_type = dec_addr_tlv(raw_hex) return {"addr": addr, "addr_type": addr_type}
- def _encode_record_hex(self, json_in): + def _encode_record_hex(self, json_in, **kwargs): addr = json_in['addr'] addr_type = json_in['addr_type'] return enc_addr_tlv(addr, addr_type) diff --git a/pySim/ts_51_011.py b/pySim/ts_51_011.py index d16ea72..bb49f13 100644 --- a/pySim/ts_51_011.py +++ b/pySim/ts_51_011.py @@ -356,7 +356,7 @@ def __init__(self, fid='6f3c', sfid=None, name='EF.SMS', desc='Short messages', **kwargs): super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=(176, 176), **kwargs)
- def _decode_record_bin(self, raw_bin_data): + def _decode_record_bin(self, raw_bin_data, **kwargs): def decode_status(status): if status & 0x01 == 0x00: return (None, 'free_space') @@ -387,10 +387,10 @@ def __init__(self, fid='6f40', sfid=None, name='EF.MSISDN', desc='MSISDN', **kwargs): super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=(15, 34), **kwargs)
- def _decode_record_hex(self, raw_hex_data): + def _decode_record_hex(self, raw_hex_data, **kwargs): return {'msisdn': dec_msisdn(raw_hex_data)}
- def _encode_record_hex(self, abstract): + def _encode_record_hex(self, abstract, **kwargs): msisdn = abstract['msisdn'] if type(msisdn) == str: encoded_msisdn = enc_msisdn(msisdn) @@ -516,10 +516,10 @@ desc='Language Preference'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len)
- def _decode_record_bin(self, in_bin): + def _decode_record_bin(self, in_bin, **kwargs): return b2h(in_bin)
- def _encode_record_bin(self, in_json): + def _encode_record_bin(self, in_json, **kwargs): return h2b(in_json)
# TS 51.011 Section 10.3.2 @@ -566,13 +566,13 @@ size=(24, None), rec_len=3, **kwargs): super().__init__(fid, name=name, sfid=sfid, desc=desc, size=size, rec_len=rec_len, **kwargs)
- def _decode_record_hex(self, in_hex): + def _decode_record_hex(self, in_hex, **kwargs): if in_hex[:6] == "ffffff": return None else: return dec_plmn(in_hex)
- def _encode_record_hex(self, in_json): + def _encode_record_hex(self, in_json, **kwargs): if in_json == None: return "ffffff" else: @@ -772,7 +772,7 @@ desc='Co-operative Network List', **kwargs): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len, **kwargs)
- def _decode_record_hex(self, in_hex): + def _decode_record_hex(self, in_hex, **kwargs): (in_plmn, sub, svp, corp) = unpack('!3sBBB', h2b(in_hex)) res = dec_plmn(b2h(in_plmn)) res['network_subset'] = sub @@ -780,7 +780,7 @@ res['corporate_id'] = corp return res
- def _encode_record_hex(self, in_json): + def _encode_record_hex(self, in_json, **kwargs): plmn = enc_plmn(in_json['mcc'], in_json['mnc']) return b2h(pack('!3sBBB', h2b(plmn), @@ -815,13 +815,13 @@ def __init__(self, fid, sfid=None, name=None, desc=None, size=(40, None), rec_len=5, **kwargs): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len, **kwargs)
- def _decode_record_hex(self, in_hex): + def _decode_record_hex(self, in_hex, **kwargs): if in_hex[:6] == "ffffff": return None else: return dec_xplmn_w_act(in_hex)
- def _encode_record_hex(self, in_json): + def _encode_record_hex(self, in_json, **kwargs): if in_json == None: return "ffffff0000" else: