blob: 22dcda334966465f14c9eac26dce19489d110e2b [file] [log] [blame]
Sylvain Munaut76504e02010-12-07 00:24:32 +01001# -*- coding: utf-8 -*-
2
3""" pySim: various utilities
4"""
5
Harald Welte5e749a72021-04-10 17:18:17 +02006import json
Harald Welte3de6ca22021-05-02 20:05:56 +02007import abc
Philipp Maier796ca3d2021-11-01 17:13:57 +01008import string
Harald Welte5e749a72021-04-10 17:18:17 +02009from io import BytesIO
Harald Weltef5e26ae2023-07-09 16:11:46 +020010from typing import Optional, List, Dict, Any, Tuple, NewType
Harald Welte52255572021-04-03 09:56:32 +020011
Sylvain Munaut76504e02010-12-07 00:24:32 +010012# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
Harald Welte5e749a72021-04-10 17:18:17 +020013# Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
Sylvain Munaut76504e02010-12-07 00:24:32 +010014#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 2 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
27#
28
Harald Welte52255572021-04-03 09:56:32 +020029# just to differentiate strings of hex nibbles from everything else
Harald Weltef5e26ae2023-07-09 16:11:46 +020030Hexstr = NewType('Hexstr', str)
Harald Welteab6897c2023-07-09 16:21:23 +020031SwHexstr = NewType('SwHexstr', str)
32SwMatchstr = NewType('SwMatchstr', str)
Sylvain Munaut76504e02010-12-07 00:24:32 +010033
Harald Weltec91085e2022-02-10 18:05:45 +010034def h2b(s: Hexstr) -> bytearray:
35 """convert from a string of hex nibbles to a sequence of bytes"""
36 return bytearray.fromhex(s)
Sylvain Munaut76504e02010-12-07 00:24:32 +010037
Sylvain Munaut76504e02010-12-07 00:24:32 +010038
Harald Weltec91085e2022-02-10 18:05:45 +010039def b2h(b: bytearray) -> Hexstr:
40 """convert from a sequence of bytes to a string of hex nibbles"""
41 return ''.join(['%02x' % (x) for x in b])
Sylvain Munaut76504e02010-12-07 00:24:32 +010042
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030043
Harald Weltec91085e2022-02-10 18:05:45 +010044def h2i(s: Hexstr) -> List[int]:
45 """convert from a string of hex nibbles to a list of integers"""
46 return [(int(x, 16) << 4)+int(y, 16) for x, y in zip(s[0::2], s[1::2])]
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030047
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020048
Harald Weltec91085e2022-02-10 18:05:45 +010049def i2h(s: List[int]) -> Hexstr:
50 """convert from a list of integers to a string of hex nibbles"""
51 return ''.join(['%02x' % (x) for x in s])
Sylvain Munaut76504e02010-12-07 00:24:32 +010052
Sylvain Munaut76504e02010-12-07 00:24:32 +010053
Harald Weltec91085e2022-02-10 18:05:45 +010054def h2s(s: Hexstr) -> str:
55 """convert from a string of hex nibbles to an ASCII string"""
56 return ''.join([chr((int(x, 16) << 4)+int(y, 16)) for x, y in zip(s[0::2], s[1::2])
57 if int(x + y, 16) != 0xff])
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040058
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020059
Harald Weltec91085e2022-02-10 18:05:45 +010060def s2h(s: str) -> Hexstr:
61 """convert from an ASCII string to a string of hex nibbles"""
62 b = bytearray()
63 b.extend(map(ord, s))
64 return b2h(b)
Philipp Maier796ca3d2021-11-01 17:13:57 +010065
Philipp Maier796ca3d2021-11-01 17:13:57 +010066
Harald Weltec91085e2022-02-10 18:05:45 +010067def i2s(s: List[int]) -> str:
68 """convert from a list of integers to an ASCII string"""
69 return ''.join([chr(x) for x in s])
70
71
72def swap_nibbles(s: Hexstr) -> Hexstr:
73 """swap the nibbles in a hex string"""
74 return ''.join([x+y for x, y in zip(s[1::2], s[0::2])])
75
76
77def rpad(s: str, l: int, c='f') -> str:
78 """pad string on the right side.
79 Args:
80 s : string to pad
81 l : total length to pad to
82 c : padding character
83 Returns:
84 String 's' padded with as many 'c' as needed to reach total length of 'l'
85 """
86 return s + c * (l - len(s))
87
88
89def lpad(s: str, l: int, c='f') -> str:
90 """pad string on the left side.
91 Args:
92 s : string to pad
93 l : total length to pad to
94 c : padding character
95 Returns:
96 String 's' padded with as many 'c' as needed to reach total length of 'l'
97 """
98 return c * (l - len(s)) + s
99
100
101def half_round_up(n: int) -> int:
102 return (n + 1)//2
103
104
105def str_sanitize(s: str) -> str:
106 """replace all non printable chars, line breaks and whitespaces, with ' ', make sure that
107 there are no whitespaces at the end and at the beginning of the string.
108
109 Args:
110 s : string to sanitize
111 Returns:
112 filtered result of string 's'
113 """
114
115 chars_to_keep = string.digits + string.ascii_letters + string.punctuation
116 res = ''.join([c if c in chars_to_keep else ' ' for c in s])
117 return res.strip()
Philipp Maier796ca3d2021-11-01 17:13:57 +0100118
Harald Welte917d98c2021-04-21 11:51:25 +0200119#########################################################################
Harald Welte9f3b44d2021-05-04 18:18:09 +0200120# poor man's COMPREHENSION-TLV decoder.
121#########################################################################
122
Harald Weltec91085e2022-02-10 18:05:45 +0100123
124def comprehensiontlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
Harald Welte6912b1b2021-05-24 23:16:44 +0200125 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
126 if binary[0] in [0x00, 0x80, 0xff]:
Harald Weltec91085e2022-02-10 18:05:45 +0100127 raise ValueError("Found illegal value 0x%02x in %s" %
128 (binary[0], binary))
Harald Welte6912b1b2021-05-24 23:16:44 +0200129 if binary[0] == 0x7f:
130 # three-byte tag
131 tag = binary[0] << 16 | binary[1] << 8 | binary[2]
132 return (tag, binary[3:])
133 elif binary[0] == 0xff:
134 return None, binary
135 else:
136 # single byte tag
137 tag = binary[0]
138 return (tag, binary[1:])
139
Harald Weltec91085e2022-02-10 18:05:45 +0100140
141def comprehensiontlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
Harald Welte9f3b44d2021-05-04 18:18:09 +0200142 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
143 if binary[0] in [0x00, 0x80, 0xff]:
Harald Weltec91085e2022-02-10 18:05:45 +0100144 raise ValueError("Found illegal value 0x%02x in %s" %
145 (binary[0], binary))
Harald Welte9f3b44d2021-05-04 18:18:09 +0200146 if binary[0] == 0x7f:
147 # three-byte tag
148 tag = (binary[1] & 0x7f) << 8
149 tag |= binary[2]
150 compr = True if binary[1] & 0x80 else False
151 return ({'comprehension': compr, 'tag': tag}, binary[3:])
152 else:
153 # single byte tag
154 tag = binary[0] & 0x7f
155 compr = True if binary[0] & 0x80 else False
156 return ({'comprehension': compr, 'tag': tag}, binary[1:])
157
Harald Weltec91085e2022-02-10 18:05:45 +0100158
Harald Welte9f3b44d2021-05-04 18:18:09 +0200159def comprehensiontlv_encode_tag(tag) -> bytes:
160 """Encode a single Tag according to ETSI TS 101 220 Section 7.1.1"""
161 # permit caller to specify tag also as integer value
162 if isinstance(tag, int):
163 compr = True if tag < 0xff and tag & 0x80 else False
164 tag = {'tag': tag, 'comprehension': compr}
165 compr = tag.get('comprehension', False)
166 if tag['tag'] in [0x00, 0x80, 0xff] or tag['tag'] > 0xff:
167 # 3-byte format
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300168 byte3 = tag['tag'] & 0xff
Harald Welte9f3b44d2021-05-04 18:18:09 +0200169 byte2 = (tag['tag'] >> 8) & 0x7f
170 if compr:
171 byte2 |= 0x80
172 return b'\x7f' + byte2.to_bytes(1, 'big') + byte3.to_bytes(1, 'big')
173 else:
174 # 1-byte format
175 ret = tag['tag']
176 if compr:
177 ret |= 0x80
178 return ret.to_bytes(1, 'big')
179
180# length value coding is equal to BER-TLV
181
Harald Welte6912b1b2021-05-24 23:16:44 +0200182
Harald Weltec91085e2022-02-10 18:05:45 +0100183def comprehensiontlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
184 """Parse a single TLV IE at the start of the given binary data.
185 Args:
186 binary : binary input data of BER-TLV length field
187 Returns:
188 Tuple of (tag:dict, len:int, remainder:bytes)
189 """
190 (tagdict, remainder) = comprehensiontlv_parse_tag(binary)
191 (length, remainder) = bertlv_parse_len(remainder)
192 value = remainder[:length]
193 remainder = remainder[length:]
194 return (tagdict, length, value, remainder)
Harald Welte6912b1b2021-05-24 23:16:44 +0200195
Harald Welte9f3b44d2021-05-04 18:18:09 +0200196
197#########################################################################
Harald Welte917d98c2021-04-21 11:51:25 +0200198# poor man's BER-TLV decoder. To be a more sophisticated OO library later
199#########################################################################
200
Harald Weltec91085e2022-02-10 18:05:45 +0100201def bertlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
202 """Get a single raw Tag from start of input according to ITU-T X.690 8.1.2
203 Args:
204 binary : binary input data of BER-TLV length field
205 Returns:
206 Tuple of (tag:int, remainder:bytes)
207 """
208 # check for FF padding at the end, as customary in SIM card files
209 if binary[0] == 0xff and len(binary) == 1 or binary[0] == 0xff and binary[1] == 0xff:
210 return None, binary
211 tag = binary[0] & 0x1f
212 if tag <= 30:
213 return binary[0], binary[1:]
214 else: # multi-byte tag
215 tag = binary[0]
216 i = 1
217 last = False
218 while not last:
219 last = False if binary[i] & 0x80 else True
220 tag <<= 8
221 tag |= binary[i]
222 i += 1
223 return tag, binary[i:]
Harald Welte6912b1b2021-05-24 23:16:44 +0200224
Harald Weltec91085e2022-02-10 18:05:45 +0100225
226def bertlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
227 """Parse a single Tag value according to ITU-T X.690 8.1.2
228 Args:
229 binary : binary input data of BER-TLV length field
230 Returns:
231 Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes)
232 """
233 cls = binary[0] >> 6
234 constructed = True if binary[0] & 0x20 else False
235 tag = binary[0] & 0x1f
236 if tag <= 30:
237 return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[1:])
238 else: # multi-byte tag
239 tag = 0
240 i = 1
241 last = False
242 while not last:
243 last = False if binary[i] & 0x80 else True
244 tag <<= 7
245 tag |= binary[i] & 0x7f
246 i += 1
247 return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[i:])
248
Harald Welte917d98c2021-04-21 11:51:25 +0200249
Harald Weltef0885b12021-05-24 23:18:59 +0200250def bertlv_encode_tag(t) -> bytes:
251 """Encode a single Tag value according to ITU-T X.690 8.1.2
252 """
Harald Weltec91085e2022-02-10 18:05:45 +0100253 def get_top7_bits(inp: int) -> Tuple[int, int]:
Harald Weltef0885b12021-05-24 23:18:59 +0200254 """Get top 7 bits of integer. Returns those 7 bits as integer and the remaining LSBs."""
255 remain_bits = inp.bit_length()
256 if remain_bits >= 7:
257 bitcnt = 7
258 else:
259 bitcnt = remain_bits
260 outp = inp >> (remain_bits - bitcnt)
261 remainder = inp & ~ (inp << (remain_bits - bitcnt))
262 return outp, remainder
263
264 if isinstance(t, int):
265 # FIXME: multiple byte tags
266 tag = t & 0x1f
267 constructed = True if t & 0x20 else False
268 cls = t >> 6
269 else:
270 tag = t['tag']
271 constructed = t['constructed']
272 cls = t['class']
273 if tag <= 30:
274 t = tag & 0x1f
275 if constructed:
276 t |= 0x20
277 t |= (cls & 3) << 6
278 return bytes([t])
Harald Weltec91085e2022-02-10 18:05:45 +0100279 else: # multi-byte tag
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300280 t = 0x1f
Harald Weltef0885b12021-05-24 23:18:59 +0200281 if constructed:
282 t |= 0x20
283 t |= (cls & 3) << 6
284 tag_bytes = bytes([t])
285 remain = tag
286 while True:
287 t, remain = get_top7_bits(remain)
288 if remain:
289 t |= 0x80
290 tag_bytes += bytes([t])
291 if not remain:
292 break
293 return tag_bytes
294
Harald Welte917d98c2021-04-21 11:51:25 +0200295
Harald Weltec91085e2022-02-10 18:05:45 +0100296def bertlv_parse_len(binary: bytes) -> Tuple[int, bytes]:
297 """Parse a single Length value according to ITU-T X.690 8.1.3;
298 only the definite form is supported here.
299 Args:
300 binary : binary input data of BER-TLV length field
301 Returns:
302 Tuple of (length, remainder)
303 """
304 if binary[0] < 0x80:
305 return (binary[0], binary[1:])
306 else:
307 num_len_oct = binary[0] & 0x7f
308 length = 0
309 for i in range(1, 1+num_len_oct):
310 length <<= 8
311 length |= binary[i]
312 return (length, binary[1+num_len_oct:])
Harald Welte917d98c2021-04-21 11:51:25 +0200313
Harald Welte917d98c2021-04-21 11:51:25 +0200314
Harald Weltec91085e2022-02-10 18:05:45 +0100315def bertlv_encode_len(length: int) -> bytes:
316 """Encode a single Length value according to ITU-T X.690 8.1.3;
317 only the definite form is supported here.
318 Args:
319 length : length value to be encoded
320 Returns:
321 binary output data of BER-TLV length field
322 """
323 if length < 0x80:
324 return length.to_bytes(1, 'big')
325 elif length <= 0xff:
326 return b'\x81' + length.to_bytes(1, 'big')
327 elif length <= 0xffff:
328 return b'\x82' + length.to_bytes(2, 'big')
329 elif length <= 0xffffff:
330 return b'\x83' + length.to_bytes(3, 'big')
331 elif length <= 0xffffffff:
332 return b'\x84' + length.to_bytes(4, 'big')
333 else:
334 raise ValueError("Length > 32bits not supported")
335
336
337def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
338 """Parse a single TLV IE at the start of the given binary data.
339 Args:
340 binary : binary input data of BER-TLV length field
341 Returns:
342 Tuple of (tag:dict, len:int, remainder:bytes)
343 """
344 (tagdict, remainder) = bertlv_parse_tag(binary)
345 (length, remainder) = bertlv_parse_len(remainder)
346 value = remainder[:length]
347 remainder = remainder[length:]
348 return (tagdict, length, value, remainder)
Harald Welte917d98c2021-04-21 11:51:25 +0200349
350
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200351# IMSI encoded format:
352# For IMSI 0123456789ABCDE:
353#
354# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
355# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
356#
357# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
358#
359# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
360# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
361# encode itself.
362#
363# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
364# even length IMSI only uses half of the last byte.
365
Harald Weltec91085e2022-02-10 18:05:45 +0100366def enc_imsi(imsi: str):
367 """Converts a string IMSI into the encoded value of the EF"""
368 l = half_round_up(
369 len(imsi) + 1) # Required bytes - include space for odd/even indicator
370 oe = len(imsi) & 1 # Odd (1) / Even (0)
371 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe << 3) | 1, rpad(imsi, 15)))
372 return ei
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400373
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400374
Harald Weltec91085e2022-02-10 18:05:45 +0100375def dec_imsi(ef: Hexstr) -> Optional[str]:
376 """Converts an EF value to the IMSI string representation"""
377 if len(ef) < 4:
378 return None
379 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
380 l = l - 1 # Encoded length byte includes oe nibble
381 swapped = swap_nibbles(ef[2:]).rstrip('f')
382 if len(swapped) < 1:
383 return None
384 oe = (int(swapped[0]) >> 3) & 1 # Odd (1) / Even (0)
385 if not oe:
386 # if even, only half of last byte was used
387 l = l-1
388 if l != len(swapped) - 1:
389 return None
390 imsi = swapped[1:]
391 return imsi
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400392
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400393
Harald Weltec91085e2022-02-10 18:05:45 +0100394def dec_iccid(ef: Hexstr) -> str:
395 return swap_nibbles(ef).strip('f')
Philipp Maier6c5cd802021-04-23 21:19:36 +0200396
Philipp Maier6c5cd802021-04-23 21:19:36 +0200397
Harald Weltec91085e2022-02-10 18:05:45 +0100398def enc_iccid(iccid: str) -> Hexstr:
399 return swap_nibbles(rpad(iccid, 20))
Philipp Maier6c5cd802021-04-23 21:19:36 +0200400
Philipp Maier6c5cd802021-04-23 21:19:36 +0200401
Harald Weltec91085e2022-02-10 18:05:45 +0100402def enc_plmn(mcc: Hexstr, mnc: Hexstr) -> Hexstr:
403 """Converts integer MCC/MNC into 3 bytes for EF"""
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300404
Harald Weltec91085e2022-02-10 18:05:45 +0100405 # Make sure there are no excess whitespaces in the input
406 # parameters
407 mcc = mcc.strip()
408 mnc = mnc.strip()
409
410 # Make sure that MCC/MNC are correctly padded with leading
411 # zeros or 'F', depending on the length.
412 if len(mnc) == 0:
413 mnc = "FFF"
414 elif len(mnc) == 1:
415 mnc = "F0" + mnc
416 elif len(mnc) == 2:
417 mnc += "F"
418
419 if len(mcc) == 0:
420 mcc = "FFF"
421 elif len(mcc) == 1:
422 mcc = "00" + mcc
423 elif len(mcc) == 2:
424 mcc = "0" + mcc
425
426 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
427
428
429def dec_plmn(threehexbytes: Hexstr) -> dict:
430 res = {'mcc': "0", 'mnc': "0"}
431 dec_mcc_from_plmn_str(threehexbytes)
432 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
433 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
434 return res
435
Harald Welted7a7e172021-04-07 00:30:10 +0200436
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300437def dec_spn(ef):
Harald Weltec91085e2022-02-10 18:05:45 +0100438 """Obsolete, kept for API compatibility"""
439 from ts_51_011 import EF_SPN
440 abstract_data = EF_SPN().decode_hex(ef)
441 show_in_hplmn = abstract_data['show_in_hplmn']
442 hide_in_oplmn = abstract_data['hide_in_oplmn']
443 name = abstract_data['spn']
444 return (name, show_in_hplmn, hide_in_oplmn)
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300445
Harald Weltec91085e2022-02-10 18:05:45 +0100446
447def enc_spn(name: str, show_in_hplmn=False, hide_in_oplmn=False):
448 """Obsolete, kept for API compatibility"""
449 from ts_51_011 import EF_SPN
450 abstract_data = {
451 'hide_in_oplmn': hide_in_oplmn,
452 'show_in_hplmn': show_in_hplmn,
453 'spn': name,
454 }
455 return EF_SPN().encode_hex(abstract_data)
456
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900457
Supreeth Herlef3948532020-03-24 12:23:51 +0100458def hexstr_to_Nbytearr(s, nbytes):
Harald Weltec91085e2022-02-10 18:05:45 +0100459 return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2))]
Supreeth Herlef3948532020-03-24 12:23:51 +0100460
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100461# Accepts hex string representing three bytes
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100462
Philipp Maier6c5cd802021-04-23 21:19:36 +0200463
Harald Weltec91085e2022-02-10 18:05:45 +0100464def dec_mcc_from_plmn(plmn: Hexstr) -> int:
465 ia = h2i(plmn)
466 digit1 = ia[0] & 0x0F # 1st byte, LSB
467 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
468 digit3 = ia[1] & 0x0F # 2nd byte, LSB
469 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
470 return 0xFFF # 4095
471 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100472
Philipp Maier6c5cd802021-04-23 21:19:36 +0200473
Harald Weltec91085e2022-02-10 18:05:45 +0100474def dec_mcc_from_plmn_str(plmn: Hexstr) -> str:
475 digit1 = plmn[1] # 1st byte, LSB
476 digit2 = plmn[0] # 1st byte, MSB
477 digit3 = plmn[3] # 2nd byte, LSB
478 res = digit1 + digit2 + digit3
479 return res.upper().strip("F")
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100480
Harald Weltec91085e2022-02-10 18:05:45 +0100481
482def dec_mnc_from_plmn(plmn: Hexstr) -> int:
483 ia = h2i(plmn)
484 digit1 = ia[2] & 0x0F # 3rd byte, LSB
485 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
486 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
487 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
488 return 0xFFF # 4095
489 return derive_mnc(digit1, digit2, digit3)
490
491
492def dec_mnc_from_plmn_str(plmn: Hexstr) -> str:
493 digit1 = plmn[5] # 3rd byte, LSB
494 digit2 = plmn[4] # 3rd byte, MSB
495 digit3 = plmn[2] # 2nd byte, MSB
496 res = digit1 + digit2 + digit3
497 return res.upper().strip("F")
498
499
500def dec_act(twohexbytes: Hexstr) -> List[str]:
501 act_list = [
502 {'bit': 15, 'name': "UTRAN"},
503 {'bit': 14, 'name': "E-UTRAN"},
504 {'bit': 11, 'name': "NG-RAN"},
505 {'bit': 7, 'name': "GSM"},
506 {'bit': 6, 'name': "GSM COMPACT"},
507 {'bit': 5, 'name': "cdma2000 HRPD"},
508 {'bit': 4, 'name': "cdma2000 1xRTT"},
509 ]
510 ia = h2i(twohexbytes)
511 u16t = (ia[0] << 8) | ia[1]
512 sel = []
513 for a in act_list:
514 if u16t & (1 << a['bit']):
515 if a['name'] == "E-UTRAN":
516 # The Access technology identifier of E-UTRAN
517 # allows a more detailed specification:
518 if u16t & (1 << 13) and u16t & (1 << 12):
519 sel.append("E-UTRAN WB-S1")
520 sel.append("E-UTRAN NB-S1")
521 elif u16t & (1 << 13):
522 sel.append("E-UTRAN WB-S1")
523 elif u16t & (1 << 12):
524 sel.append("E-UTRAN NB-S1")
525 else:
526 sel.append("E-UTRAN")
527 else:
528 sel.append(a['name'])
529 return sel
530
531
532def dec_xplmn_w_act(fivehexbytes: Hexstr) -> Dict[str, Any]:
533 res = {'mcc': "0", 'mnc': "0", 'act': []}
534 plmn_chars = 6
535 act_chars = 4
536 # first three bytes (six ascii hex chars)
537 plmn_str = fivehexbytes[:plmn_chars]
538 # two bytes after first three bytes
539 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars]
540 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
541 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
542 res['act'] = dec_act(act_str)
543 return res
544
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100545
546def format_xplmn_w_act(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100547 s = ""
548 for rec_data in hexstr_to_Nbytearr(hexstr, 5):
549 rec_info = dec_xplmn_w_act(rec_data)
550 if rec_info['mcc'] == "" and rec_info['mnc'] == "":
551 rec_str = "unused"
552 else:
553 rec_str = "MCC: %s MNC: %s AcT: %s" % (
554 rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act']))
555 s += "\t%s # %s\n" % (rec_data, rec_str)
556 return s
557
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100558
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100559def dec_loci(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100560 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
561 res['tmsi'] = hexstr[:8]
562 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
563 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
564 res['lac'] = hexstr[14:18]
565 res['status'] = h2i(hexstr[20:22])
566 return res
567
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100568
569def dec_psloci(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100570 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0,
571 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
572 res['p-tmsi'] = hexstr[:8]
573 res['p-tmsi-sig'] = hexstr[8:14]
574 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
575 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
576 res['lac'] = hexstr[20:24]
577 res['rac'] = hexstr[24:26]
578 res['status'] = h2i(hexstr[26:28])
579 return res
580
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100581
582def dec_epsloci(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100583 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
584 res['guti'] = hexstr[:24]
585 res['tai'] = hexstr[24:34]
586 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
587 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
588 res['tac'] = hexstr[30:34]
589 res['status'] = h2i(hexstr[34:36])
590 return res
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100591
Harald Welteca673942020-06-03 15:19:40 +0200592
Harald Weltec91085e2022-02-10 18:05:45 +0100593def dec_xplmn(threehexbytes: Hexstr) -> dict:
594 res = {'mcc': 0, 'mnc': 0, 'act': []}
595 plmn_chars = 6
596 # first three bytes (six ascii hex chars)
597 plmn_str = threehexbytes[:plmn_chars]
Matan Perelman60951b02023-06-01 17:39:04 +0300598 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
599 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Harald Weltec91085e2022-02-10 18:05:45 +0100600 return res
Harald Welteca673942020-06-03 15:19:40 +0200601
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900602
Harald Weltec91085e2022-02-10 18:05:45 +0100603def format_xplmn(hexstr: Hexstr) -> str:
604 s = ""
605 for rec_data in hexstr_to_Nbytearr(hexstr, 3):
606 rec_info = dec_xplmn(rec_data)
Matan Perelman60951b02023-06-01 17:39:04 +0300607 if not rec_info['mcc'] and not rec_info['mnc']:
Harald Weltec91085e2022-02-10 18:05:45 +0100608 rec_str = "unused"
609 else:
Matan Perelman60951b02023-06-01 17:39:04 +0300610 rec_str = "MCC: %s MNC: %s" % (rec_info['mcc'], rec_info['mnc'])
Harald Weltec91085e2022-02-10 18:05:45 +0100611 s += "\t%s # %s\n" % (rec_data, rec_str)
612 return s
613
614
615def derive_milenage_opc(ki_hex: Hexstr, op_hex: Hexstr) -> Hexstr:
616 """
617 Run the milenage algorithm to calculate OPC from Ki and OP
618 """
Harald Welted75fa3f2023-05-31 20:47:55 +0200619 from Cryptodome.Cipher import AES
Harald Weltec91085e2022-02-10 18:05:45 +0100620 # pylint: disable=no-name-in-module
Harald Welted75fa3f2023-05-31 20:47:55 +0200621 from Cryptodome.Util.strxor import strxor
Harald Weltec91085e2022-02-10 18:05:45 +0100622 from pySim.utils import b2h
623
624 # We pass in hex string and now need to work on bytes
625 ki_bytes = bytes(h2b(ki_hex))
626 op_bytes = bytes(h2b(op_hex))
627 aes = AES.new(ki_bytes, AES.MODE_ECB)
628 opc_bytes = aes.encrypt(op_bytes)
629 return b2h(strxor(opc_bytes, op_bytes))
630
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900631
Harald Welte52255572021-04-03 09:56:32 +0200632def calculate_luhn(cc) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100633 """
634 Calculate Luhn checksum used in e.g. ICCID and IMEI
635 """
636 num = list(map(int, str(cc)))
637 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10))
638 for d in num[::-2]]) % 10
639 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200640
Philipp Maier7592eee2019-09-12 13:03:23 +0200641
Harald Weltec91085e2022-02-10 18:05:45 +0100642def mcc_from_imsi(imsi: str) -> Optional[str]:
643 """
644 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
645 """
646 if imsi == None:
647 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200648
Harald Weltec91085e2022-02-10 18:05:45 +0100649 if len(imsi) > 3:
650 return imsi[:3]
651 else:
652 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200653
Supreeth Herled24f1632019-11-30 10:37:09 +0100654
Harald Weltec91085e2022-02-10 18:05:45 +0100655def mnc_from_imsi(imsi: str, long: bool = False) -> Optional[str]:
656 """
657 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
658 """
659 if imsi == None:
660 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100661
Harald Weltec91085e2022-02-10 18:05:45 +0100662 if len(imsi) > 3:
663 if long:
664 return imsi[3:6]
665 else:
666 return imsi[3:5]
667 else:
668 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100669
Supreeth Herled24f1632019-11-30 10:37:09 +0100670
Harald Weltec91085e2022-02-10 18:05:45 +0100671def derive_mcc(digit1: int, digit2: int, digit3: int) -> int:
672 """
673 Derive decimal representation of the MCC (Mobile Country Code)
674 from three given digits.
675 """
Supreeth Herled24f1632019-11-30 10:37:09 +0100676
Harald Weltec91085e2022-02-10 18:05:45 +0100677 mcc = 0
Supreeth Herled24f1632019-11-30 10:37:09 +0100678
Harald Weltec91085e2022-02-10 18:05:45 +0100679 if digit1 != 0x0f:
680 mcc += digit1 * 100
681 if digit2 != 0x0f:
682 mcc += digit2 * 10
683 if digit3 != 0x0f:
684 mcc += digit3
Supreeth Herled24f1632019-11-30 10:37:09 +0100685
Harald Weltec91085e2022-02-10 18:05:45 +0100686 return mcc
Supreeth Herled24f1632019-11-30 10:37:09 +0100687
Supreeth Herled24f1632019-11-30 10:37:09 +0100688
Harald Weltec91085e2022-02-10 18:05:45 +0100689def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int:
690 """
691 Derive decimal representation of the MNC (Mobile Network Code)
692 from two or (optionally) three given digits.
693 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100694
Harald Weltec91085e2022-02-10 18:05:45 +0100695 mnc = 0
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100696
Harald Weltec91085e2022-02-10 18:05:45 +0100697 # 3-rd digit is optional for the MNC. If present
698 # the algorythm is the same as for the MCC.
699 if digit3 != 0x0f:
700 return derive_mcc(digit1, digit2, digit3)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100701
Harald Weltec91085e2022-02-10 18:05:45 +0100702 if digit1 != 0x0f:
703 mnc += digit1 * 10
704 if digit2 != 0x0f:
705 mnc += digit2
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100706
Harald Weltec91085e2022-02-10 18:05:45 +0100707 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100708
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100709
Harald Weltec91085e2022-02-10 18:05:45 +0100710def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]:
711 """
712 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
713 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
714 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100715
Harald Weltec91085e2022-02-10 18:05:45 +0100716 # Convert from str to (kind of) 'bytes'
717 ef_msisdn = h2b(ef_msisdn)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100718
Harald Weltec91085e2022-02-10 18:05:45 +0100719 # Make sure mandatory fields are present
720 if len(ef_msisdn) < 14:
721 raise ValueError("EF.MSISDN is too short")
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100722
Harald Weltec91085e2022-02-10 18:05:45 +0100723 # Skip optional Alpha Identifier
724 xlen = len(ef_msisdn) - 14
725 msisdn_lhv = ef_msisdn[xlen:]
Supreeth Herle5a541012019-12-22 08:59:16 +0100726
Harald Weltec91085e2022-02-10 18:05:45 +0100727 # Parse the length (in bytes) of the BCD encoded number
728 bcd_len = msisdn_lhv[0]
729 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
730 if bcd_len == 0xff:
731 return None
732 elif bcd_len > 11 or bcd_len < 1:
733 raise ValueError(
734 "Length of MSISDN (%d bytes) is out of range" % bcd_len)
Supreeth Herle5a541012019-12-22 08:59:16 +0100735
Harald Weltec91085e2022-02-10 18:05:45 +0100736 # Parse ToN / NPI
737 ton = (msisdn_lhv[1] >> 4) & 0x07
738 npi = msisdn_lhv[1] & 0x0f
739 bcd_len -= 1
Supreeth Herle5a541012019-12-22 08:59:16 +0100740
Harald Weltec91085e2022-02-10 18:05:45 +0100741 # No MSISDN?
742 if not bcd_len:
743 return (npi, ton, None)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200744
Harald Weltec91085e2022-02-10 18:05:45 +0100745 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
746 # International number 10.5.118/3GPP TS 24.008
747 if ton == 0x01:
748 msisdn = '+' + msisdn
Supreeth Herle5a541012019-12-22 08:59:16 +0100749
Harald Weltec91085e2022-02-10 18:05:45 +0100750 return (npi, ton, msisdn)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200751
Supreeth Herle5a541012019-12-22 08:59:16 +0100752
Harald Weltec91085e2022-02-10 18:05:45 +0100753def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
754 """
755 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
756 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
757 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100758
Harald Weltec91085e2022-02-10 18:05:45 +0100759 Default NPI / ToN values:
760 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
761 - ToN: network specific or international number (if starts with '+').
762 """
763
764 # If no MSISDN is supplied then encode the file contents as all "ff"
765 if msisdn == "" or msisdn == "+":
766 return "ff" * 14
767
768 # Leading '+' indicates International Number
769 if msisdn[0] == '+':
770 msisdn = msisdn[1:]
771 ton = 0x01
772
773 # An MSISDN must not exceed 20 digits
774 if len(msisdn) > 20:
775 raise ValueError("msisdn must not be longer than 20 digits")
776
777 # Append 'f' padding if number of digits is odd
778 if len(msisdn) % 2 > 0:
779 msisdn += 'f'
780
781 # BCD length also includes NPI/ToN header
782 bcd_len = len(msisdn) // 2 + 1
783 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
784 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
785
786 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200787
Supreeth Herle441c4a72020-03-24 10:19:15 +0100788
Harald Welte52255572021-04-03 09:56:32 +0200789def dec_st(st, table="sim") -> str:
Harald Weltec91085e2022-02-10 18:05:45 +0100790 """
791 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
792 """
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200793
Harald Weltec91085e2022-02-10 18:05:45 +0100794 if table == "isim":
795 from pySim.ts_31_103 import EF_IST_map
796 lookup_map = EF_IST_map
797 elif table == "usim":
798 from pySim.ts_31_102 import EF_UST_map
799 lookup_map = EF_UST_map
800 else:
801 from pySim.ts_51_011 import EF_SST_map
802 lookup_map = EF_SST_map
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200803
Harald Weltec91085e2022-02-10 18:05:45 +0100804 st_bytes = [st[i:i+2] for i in range(0, len(st), 2)]
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200805
Harald Weltec91085e2022-02-10 18:05:45 +0100806 avail_st = ""
807 # Get each byte and check for available services
808 for i in range(0, len(st_bytes)):
809 # Byte i contains info about Services num (8i+1) to num (8i+8)
810 byte = int(st_bytes[i], 16)
811 # Services in each byte are in order MSB to LSB
812 # MSB - Service (8i+8)
813 # LSB - Service (8i+1)
814 for j in range(1, 9):
815 if byte & 0x01 == 0x01 and ((8*i) + j in lookup_map):
816 # Byte X contains info about Services num (8X-7) to num (8X)
817 # bit = 1: service available
818 # bit = 0: service not available
819 avail_st += '\tService %d - %s\n' % (
820 (8*i) + j, lookup_map[(8*i) + j])
821 byte = byte >> 1
822 return avail_st
823
Supreeth Herle98370552020-05-11 09:04:41 +0200824
825def first_TLV_parser(bytelist):
Harald Weltec91085e2022-02-10 18:05:45 +0100826 '''
827 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
Supreeth Herle98370552020-05-11 09:04:41 +0200828
Harald Weltec91085e2022-02-10 18:05:45 +0100829 parses first TLV format record in a list of bytelist
830 returns a 3-Tuple: Tag, Length, Value
831 Value is a list of bytes
832 parsing of length is ETSI'style 101.220
833 '''
834 Tag = bytelist[0]
835 if bytelist[1] == 0xFF:
836 Len = bytelist[2]*256 + bytelist[3]
837 Val = bytelist[4:4+Len]
838 else:
839 Len = bytelist[1]
840 Val = bytelist[2:2+Len]
841 return (Tag, Len, Val)
842
Supreeth Herle98370552020-05-11 09:04:41 +0200843
844def TLV_parser(bytelist):
Harald Weltec91085e2022-02-10 18:05:45 +0100845 '''
846 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
Supreeth Herle98370552020-05-11 09:04:41 +0200847
Harald Weltec91085e2022-02-10 18:05:45 +0100848 loops on the input list of bytes with the "first_TLV_parser()" function
849 returns a list of 3-Tuples
850 '''
851 ret = []
852 while len(bytelist) > 0:
853 T, L, V = first_TLV_parser(bytelist)
854 if T == 0xFF:
855 # padding bytes
856 break
857 ret.append((T, L, V))
858 # need to manage length of L
859 if L > 0xFE:
860 bytelist = bytelist[L+4:]
861 else:
862 bytelist = bytelist[L+2:]
863 return ret
864
Supreeth Herled572ede2020-03-22 09:55:04 +0100865
Supreeth Herled84daa12020-03-24 12:20:40 +0100866def enc_st(st, service, state=1):
Harald Weltec91085e2022-02-10 18:05:45 +0100867 """
868 Encodes the EF S/U/IST/EST and returns the updated Service Table
Supreeth Herled84daa12020-03-24 12:20:40 +0100869
Harald Weltec91085e2022-02-10 18:05:45 +0100870 Parameters:
871 st - Current value of SIM/USIM/ISIM Service Table
872 service - Service Number to encode as activated/de-activated
873 state - 1 mean activate, 0 means de-activate
Supreeth Herled84daa12020-03-24 12:20:40 +0100874
Harald Weltec91085e2022-02-10 18:05:45 +0100875 Returns:
876 s - Modified value of SIM/USIM/ISIM Service Table
Supreeth Herled84daa12020-03-24 12:20:40 +0100877
Harald Weltec91085e2022-02-10 18:05:45 +0100878 Default values:
879 - state: 1 - Sets the particular Service bit to 1
880 """
881 st_bytes = [st[i:i+2] for i in range(0, len(st), 2)]
Supreeth Herled84daa12020-03-24 12:20:40 +0100882
Harald Weltec91085e2022-02-10 18:05:45 +0100883 s = ""
884 # Check whether the requested service is present in each byte
885 for i in range(0, len(st_bytes)):
886 # Byte i contains info about Services num (8i+1) to num (8i+8)
887 if service in range((8*i) + 1, (8*i) + 9):
888 byte = int(st_bytes[i], 16)
889 # Services in each byte are in order MSB to LSB
890 # MSB - Service (8i+8)
891 # LSB - Service (8i+1)
892 mod_byte = 0x00
893 # Copy bit by bit contents of byte to mod_byte with modified bit
894 # for requested service
895 for j in range(1, 9):
896 mod_byte = mod_byte >> 1
897 if service == (8*i) + j:
898 mod_byte = state == 1 and mod_byte | 0x80 or mod_byte & 0x7f
899 else:
900 mod_byte = byte & 0x01 == 0x01 and mod_byte | 0x80 or mod_byte & 0x7f
901 byte = byte >> 1
Supreeth Herled84daa12020-03-24 12:20:40 +0100902
Harald Weltec91085e2022-02-10 18:05:45 +0100903 s += ('%02x' % (mod_byte))
904 else:
905 s += st_bytes[i]
Supreeth Herled84daa12020-03-24 12:20:40 +0100906
Harald Weltec91085e2022-02-10 18:05:45 +0100907 return s
908
Supreeth Herled84daa12020-03-24 12:20:40 +0100909
Supreeth Herle3b342c22020-03-24 16:15:02 +0100910def dec_addr_tlv(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100911 """
912 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
913 See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104.
914 """
Supreeth Herled572ede2020-03-22 09:55:04 +0100915
Harald Weltec91085e2022-02-10 18:05:45 +0100916 # Convert from hex str to int bytes list
917 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100918
Harald Weltec91085e2022-02-10 18:05:45 +0100919 # Get list of tuples containing parsed TLVs
920 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100921
Harald Weltec91085e2022-02-10 18:05:45 +0100922 for tlv in tlvs:
923 # tlv = (T, L, [V])
924 # T = Tag
925 # L = Length
926 # [V] = List of value
Supreeth Herled572ede2020-03-22 09:55:04 +0100927
Harald Weltec91085e2022-02-10 18:05:45 +0100928 # Invalid Tag value scenario
929 if tlv[0] != 0x80:
930 continue
Supreeth Herled572ede2020-03-22 09:55:04 +0100931
Harald Weltec91085e2022-02-10 18:05:45 +0100932 # Empty field - Zero length
933 if tlv[1] == 0:
934 continue
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200935
Harald Weltec91085e2022-02-10 18:05:45 +0100936 # First byte in the value has the address type
937 addr_type = tlv[2][0]
938 # TODO: Support parsing of IPv6
939 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
940 if addr_type == 0x00: # FQDN
941 # Skip address tye byte i.e. first byte in value list
942 content = tlv[2][1:]
943 return (i2s(content), '00')
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200944
Harald Weltec91085e2022-02-10 18:05:45 +0100945 elif addr_type == 0x01: # IPv4
946 # Skip address tye byte i.e. first byte in value list
947 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
948 ipv4 = tlv[2][2:]
949 content = '.'.join(str(x) for x in ipv4)
950 return (content, '01')
951 else:
952 raise ValueError("Invalid address type")
Supreeth Herled572ede2020-03-22 09:55:04 +0100953
Harald Weltec91085e2022-02-10 18:05:45 +0100954 return (None, None)
955
Philipp Maierff84c232020-05-12 17:24:18 +0200956
Supreeth Herle3b342c22020-03-24 16:15:02 +0100957def enc_addr_tlv(addr, addr_type='00'):
Harald Weltec91085e2022-02-10 18:05:45 +0100958 """
959 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
960 See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104.
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100961
Harald Weltec91085e2022-02-10 18:05:45 +0100962 Default values:
963 - addr_type: 00 - FQDN format of Address
964 """
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100965
Harald Weltec91085e2022-02-10 18:05:45 +0100966 s = ""
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100967
Harald Weltec91085e2022-02-10 18:05:45 +0100968 # TODO: Encoding of IPv6 address
969 if addr_type == '00': # FQDN
970 hex_str = s2h(addr)
971 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
972 elif addr_type == '01': # IPv4
973 ipv4_list = addr.split('.')
974 ipv4_str = ""
975 for i in ipv4_list:
976 ipv4_str += ('%02x' % (int(i)))
Supreeth Herle654eca72020-03-25 14:25:38 +0100977
Harald Weltec91085e2022-02-10 18:05:45 +0100978 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
979 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
980 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100981
Harald Weltec91085e2022-02-10 18:05:45 +0100982 return s
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100983
Philipp Maier47236502021-03-09 21:28:25 +0100984
Harald Weltec91085e2022-02-10 18:05:45 +0100985def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool:
986 """
987 Check if a string is a valid hexstring
988 """
Philipp Maier47236502021-03-09 21:28:25 +0100989
Harald Weltec91085e2022-02-10 18:05:45 +0100990 # Filter obviously bad strings
991 if not string:
992 return False
993 if len(string) < minlen or minlen < 2:
994 return False
995 if len(string) % 2:
996 return False
997 if maxlen and len(string) > maxlen:
998 return False
Philipp Maier47236502021-03-09 21:28:25 +0100999
Harald Weltec91085e2022-02-10 18:05:45 +01001000 # Try actual encoding to be sure
1001 try:
1002 try_encode = h2b(string)
1003 return True
1004 except:
1005 return False
Philipp Maiere8536c02020-05-11 21:35:01 +02001006
Philipp Maiere8536c02020-05-11 21:35:01 +02001007
Harald Weltec91085e2022-02-10 18:05:45 +01001008def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr:
1009 """
1010 The ADM pin can be supplied either in its hexadecimal form or as
1011 ascii string. This function checks the supplied opts parameter and
1012 returns the pin_adm as hex encoded string, regardless in which form
1013 it was originally supplied by the user
1014 """
Philipp Maiere8536c02020-05-11 21:35:01 +02001015
Harald Weltec91085e2022-02-10 18:05:45 +01001016 if pin_adm is not None:
1017 if len(pin_adm) <= 8:
1018 pin_adm = ''.join(['%02x' % (ord(x)) for x in pin_adm])
1019 pin_adm = rpad(pin_adm, 16)
Philipp Maiere8536c02020-05-11 21:35:01 +02001020
Harald Weltec91085e2022-02-10 18:05:45 +01001021 else:
1022 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
1023
1024 if pin_adm_hex is not None:
1025 if len(pin_adm_hex) == 16:
1026 pin_adm = pin_adm_hex
1027 # Ensure that it's hex-encoded
1028 try:
1029 try_encode = h2b(pin_adm)
1030 except ValueError:
1031 raise ValueError(
1032 "PIN-ADM needs to be hex encoded using this option")
1033 else:
1034 raise ValueError(
1035 "PIN-ADM needs to be exactly 16 digits (hex encoded)")
1036
1037 return pin_adm
1038
Philipp Maiere8536c02020-05-11 21:35:01 +02001039
Supreeth Herle95ec7722020-03-24 13:09:03 +01001040def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
Harald Weltec91085e2022-02-10 18:05:45 +01001041 """
1042 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
1043 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
Supreeth Herle95ec7722020-03-24 13:09:03 +01001044
Harald Weltec91085e2022-02-10 18:05:45 +01001045 Default values:
1046 - epdg_priority: '0001' - 1st Priority
1047 - epdg_fqdn_format: '00' - Operator Identifier FQDN
1048 """
Supreeth Herle95ec7722020-03-24 13:09:03 +01001049
Harald Weltec91085e2022-02-10 18:05:45 +01001050 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
1051 # TODO: Handle encoding of Length field for length more than 127 Bytes
1052 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
1053 content = rpad(content, len(hexstr))
1054 return content
1055
Supreeth Herle95ec7722020-03-24 13:09:03 +01001056
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001057def dec_ePDGSelection(sixhexbytes):
Harald Weltec91085e2022-02-10 18:05:45 +01001058 """
1059 Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
1060 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
1061 """
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001062
Harald Weltec91085e2022-02-10 18:05:45 +01001063 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
1064 plmn_chars = 6
1065 epdg_priority_chars = 4
1066 epdg_fqdn_format_chars = 2
1067 # first three bytes (six ascii hex chars)
1068 plmn_str = sixhexbytes[:plmn_chars]
1069 # two bytes after first three bytes
1070 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars +
1071 epdg_priority_chars]
1072 # one byte after first five bytes
1073 epdg_fqdn_format_str = sixhexbytes[plmn_chars +
1074 epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
1075 res['mcc'] = dec_mcc_from_plmn(plmn_str)
1076 res['mnc'] = dec_mnc_from_plmn(plmn_str)
1077 res['epdg_priority'] = epdg_priority_str
1078 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
1079 return res
1080
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001081
1082def format_ePDGSelection(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +01001083 ePDGSelection_info_tag_chars = 2
1084 ePDGSelection_info_tag_str = hexstr[:2]
1085 s = ""
1086 # Minimum length
1087 len_chars = 2
1088 # TODO: Need to determine length properly - definite length support only
1089 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
1090 # As per spec, length is 5n, n - number of PLMNs
1091 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
1092 # Totalling to 6 Bytes, maybe length should be 6n
1093 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +01001094
Harald Weltec91085e2022-02-10 18:05:45 +01001095 # Not programmed scenario
1096 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
1097 len_chars = 0
1098 ePDGSelection_info_tag_chars = 0
1099 if len_str[0] == '8':
1100 # The bits 7 to 1 denotes the number of length octets if length > 127
1101 if int(len_str[1]) > 0:
1102 # Update number of length octets
1103 len_chars = len_chars * int(len_str[1])
1104 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001105
Harald Weltec91085e2022-02-10 18:05:45 +01001106 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
1107 # Right pad to prevent index out of range - multiple of 6 bytes
1108 content_str = rpad(content_str, len(content_str) +
1109 (12 - (len(content_str) % 12)))
1110 for rec_data in hexstr_to_Nbytearr(content_str, 6):
1111 rec_info = dec_ePDGSelection(rec_data)
1112 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
1113 rec_str = "unused"
1114 else:
1115 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
1116 (rec_info['mcc'], rec_info['mnc'],
1117 rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
1118 s += "\t%s # %s\n" % (rec_data, rec_str)
1119 return s
1120
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001121
1122def get_addr_type(addr):
Harald Weltec91085e2022-02-10 18:05:45 +01001123 """
1124 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
1125 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001126
Harald Weltec91085e2022-02-10 18:05:45 +01001127 TODO: Handle IPv6
1128 """
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001129
Harald Weltec91085e2022-02-10 18:05:45 +01001130 # Empty address string
1131 if not len(addr):
1132 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001133
Harald Weltec91085e2022-02-10 18:05:45 +01001134 addr_list = addr.split('.')
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001135
Harald Weltec91085e2022-02-10 18:05:45 +01001136 # Check for IPv4/IPv6
1137 try:
1138 import ipaddress
1139 # Throws ValueError if addr is not correct
1140 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001141
Harald Weltec91085e2022-02-10 18:05:45 +01001142 if ipa.version == 4:
1143 return 0x01
1144 elif ipa.version == 6:
1145 return 0x02
1146 except Exception as e:
1147 invalid_ipv4 = True
1148 for i in addr_list:
1149 # Invalid IPv4 may qualify for a valid FQDN, so make check here
1150 # e.g. 172.24.15.300
1151 import re
1152 if not re.match('^[0-9_]+$', i):
1153 invalid_ipv4 = False
1154 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001155
Harald Weltec91085e2022-02-10 18:05:45 +01001156 if invalid_ipv4:
1157 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001158
Harald Weltec91085e2022-02-10 18:05:45 +01001159 fqdn_flag = True
1160 for i in addr_list:
1161 # Only Alpha-numeric characters and hyphen - RFC 1035
1162 import re
1163 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
1164 fqdn_flag = False
1165 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001166
Harald Weltec91085e2022-02-10 18:05:45 +01001167 # FQDN
1168 if fqdn_flag:
1169 return 0x00
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001170
Harald Weltec91085e2022-02-10 18:05:45 +01001171 return None
Harald Welte67d551a2021-01-21 14:50:01 +01001172
Philipp Maier5d3e2592021-02-22 17:22:16 +01001173
Harald Weltec91085e2022-02-10 18:05:45 +01001174def sw_match(sw: str, pattern: str) -> bool:
1175 """Match given SW against given pattern."""
1176 # Create a masked version of the returned status word
1177 sw_lower = sw.lower()
1178 sw_masked = ""
1179 for i in range(0, 4):
1180 if pattern[i] == '?':
1181 sw_masked = sw_masked + '?'
1182 elif pattern[i] == 'x':
1183 sw_masked = sw_masked + 'x'
1184 else:
1185 sw_masked = sw_masked + sw_lower[i]
1186 # Compare the masked version against the pattern
1187 return sw_masked == pattern
Harald Welteee3501f2021-04-02 13:00:18 +02001188
Harald Weltec91085e2022-02-10 18:05:45 +01001189
1190def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = 1,
1191 align_left: bool = True) -> str:
1192 """Pretty print a list of strings into a tabulated form.
1193
1194 Args:
1195 width : total width in characters per line
1196 space : horizontal space between cells
1197 lspace : number of spaces before row
1198 align_lef : Align text to the left side
1199 Returns:
1200 multi-line string containing formatted table
1201 """
1202 if str_list == None:
1203 return ""
1204 if len(str_list) <= 0:
1205 return ""
1206 longest_str = max(str_list, key=len)
1207 cellwith = len(longest_str) + hspace
1208 cols = width // cellwith
1209 rows = (len(str_list) - 1) // cols + 1
1210 table = []
1211 for i in iter(range(rows)):
1212 str_list_row = str_list[i::rows]
1213 if (align_left):
1214 format_str_cell = '%%-%ds'
1215 else:
1216 format_str_cell = '%%%ds'
1217 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
1218 format_str_row = (" " * lspace) + format_str_row
1219 table.append(format_str_row % tuple(str_list_row))
1220 return '\n'.join(table)
1221
Harald Welte5e749a72021-04-10 17:18:17 +02001222
Harald Welte917d98c2021-04-21 11:51:25 +02001223def auto_int(x):
1224 """Helper function for argparse to accept hexadecimal integers."""
1225 return int(x, 0)
1226
Harald Weltec91085e2022-02-10 18:05:45 +01001227
Philipp Maier40ea4a42022-06-02 14:45:41 +02001228def expand_hex(hexstring, length):
1229 """Expand a given hexstring to a specified length by replacing "." or ".."
1230 with a filler that is derived from the neighboring nibbles respective
1231 bytes. Usually this will be the nibble respective byte before "." or
1232 "..", execpt when the string begins with "." or "..", then the nibble
1233 respective byte after "." or ".." is used.". In case the string cannot
1234 be expanded for some reason, the input string is returned unmodified.
1235
1236 Args:
1237 hexstring : hexstring to expand
1238 length : desired length of the resulting hexstring.
1239 Returns:
1240 expanded hexstring
1241 """
1242
1243 # expand digit aligned
1244 if hexstring.count(".") == 1:
1245 pos = hexstring.index(".")
1246 if pos > 0:
1247 filler = hexstring[pos - 1]
1248 else:
1249 filler = hexstring[pos + 1]
1250
1251 missing = length * 2 - (len(hexstring) - 1)
1252 if missing <= 0:
1253 return hexstring
1254
1255 return hexstring.replace(".", filler * missing)
1256
1257 # expand byte aligned
1258 elif hexstring.count("..") == 1:
1259 if len(hexstring) % 2:
1260 return hexstring
1261
1262 pos = hexstring.index("..")
1263
1264 if pos % 2:
1265 return hexstring
1266
1267 if pos > 1:
1268 filler = hexstring[pos - 2:pos]
1269 else:
1270 filler = hexstring[pos + 2:pos+4]
1271
1272 missing = length * 2 - (len(hexstring) - 2)
1273 if missing <= 0:
1274 return hexstring
1275
1276 return hexstring.replace("..", filler * (missing // 2))
1277
1278 # no change
1279 return hexstring
1280
1281
Harald Welte5e749a72021-04-10 17:18:17 +02001282class JsonEncoder(json.JSONEncoder):
1283 """Extend the standard library JSONEncoder with support for more types."""
Harald Weltec91085e2022-02-10 18:05:45 +01001284
Harald Welte5e749a72021-04-10 17:18:17 +02001285 def default(self, o):
1286 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
1287 return b2h(o)
1288 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +02001289
Harald Weltec91085e2022-02-10 18:05:45 +01001290
Philipp Maier80ce71f2021-04-19 21:24:23 +02001291def boxed_heading_str(heading, width=80):
Harald Weltec91085e2022-02-10 18:05:45 +01001292 """Generate a string that contains a boxed heading."""
1293 # Auto-enlarge box if heading exceeds length
1294 if len(heading) > width - 4:
1295 width = len(heading) + 4
Philipp Maier80ce71f2021-04-19 21:24:23 +02001296
Harald Weltec91085e2022-02-10 18:05:45 +01001297 res = "#" * width
1298 fstr = "\n# %-" + str(width - 4) + "s #\n"
1299 res += fstr % (heading)
1300 res += "#" * width
1301 return res
Harald Welte3de6ca22021-05-02 20:05:56 +02001302
1303
1304class DataObject(abc.ABC):
1305 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
1306 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
1307 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
1308 tags at specific points in a stream. This class tries to represent this."""
Harald Weltec91085e2022-02-10 18:05:45 +01001309
Harald Welte425038f2022-02-10 19:32:04 +01001310 def __init__(self, name: str, desc: Optional[str] = None, tag: Optional[int] = None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001311 """
1312 Args:
1313 name: A brief, all-lowercase, underscore separated string identifier
1314 desc: A human-readable description of what this DO represents
1315 tag : The tag associated with this DO
1316 """
1317 self.name = name
1318 self.desc = desc
1319 self.tag = tag
1320 self.decoded = None
1321 self.encoded = None
1322
1323 def __str__(self):
1324 return self.name
1325
Harald Welte50368772022-02-10 15:22:22 +01001326 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001327 return '%s(%s)' % (self.__class__, self.name)
1328
Harald Welte50368772022-02-10 15:22:22 +01001329 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001330 """OR-ing DataObjects together renders a DataObjectChoice."""
1331 if isinstance(other, DataObject):
1332 # DataObject | DataObject = DataObjectChoice
1333 return DataObjectChoice(None, members=[self, other])
1334 else:
1335 raise TypeError
1336
Harald Welte50368772022-02-10 15:22:22 +01001337 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001338 """ADD-ing DataObjects together renders a DataObjectCollection."""
1339 if isinstance(other, DataObject):
1340 # DataObject + DataObject = DataObjectCollectin
1341 return DataObjectCollection(None, members=[self, other])
Harald Welte50368772022-02-10 15:22:22 +01001342 else:
1343 raise TypeError
Harald Welte3de6ca22021-05-02 20:05:56 +02001344
Harald Welte50368772022-02-10 15:22:22 +01001345 def _compute_tag(self) -> int:
Harald Welte3de6ca22021-05-02 20:05:56 +02001346 """Compute the tag (sometimes the tag encodes part of the value)."""
1347 return self.tag
1348
Harald Welte50368772022-02-10 15:22:22 +01001349 def to_dict(self) -> dict:
Harald Welte3de6ca22021-05-02 20:05:56 +02001350 """Return a dict in form "name: decoded_value" """
1351 return {self.name: self.decoded}
1352
1353 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001354 def from_bytes(self, do: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001355 """Parse the value part of the DO into the internal state of this instance.
1356 Args:
1357 do : binary encoded bytes
1358 """
1359
1360 @abc.abstractmethod
Harald Welte50368772022-02-10 15:22:22 +01001361 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001362 """Encode the internal state of this instance into the TLV value part.
1363 Returns:
1364 binary bytes encoding the internal state
1365 """
1366
Harald Weltec91085e2022-02-10 18:05:45 +01001367 def from_tlv(self, do: bytes) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001368 """Parse binary TLV representation into internal state. The resulting decoded
1369 representation is _not_ returned, but just internalized in the object instance!
1370 Args:
1371 do : input bytes containing TLV-encoded representation
1372 Returns:
1373 bytes remaining at end of 'do' after parsing one TLV/DO.
1374 """
1375 if do[0] != self.tag:
Harald Weltec91085e2022-02-10 18:05:45 +01001376 raise ValueError('%s: Can only decode tag 0x%02x' %
1377 (self, self.tag))
Harald Welte3de6ca22021-05-02 20:05:56 +02001378 length = do[1]
1379 val = do[2:2+length]
1380 self.from_bytes(val)
1381 # return remaining bytes
1382 return do[2+length:]
1383
Harald Welte50368772022-02-10 15:22:22 +01001384 def to_tlv(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001385 """Encode internal representation to binary TLV.
1386 Returns:
1387 bytes encoded in TLV format.
1388 """
1389 val = self.to_bytes()
Harald Welte785d4842022-04-05 14:24:22 +02001390 return bertlv_encode_tag(self._compute_tag()) + bertlv_encode_len(len(val)) + val
Harald Welte3de6ca22021-05-02 20:05:56 +02001391
1392 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001393 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001394 """Decode a single DOs from the input data.
1395 Args:
1396 binary : binary bytes of encoded data
1397 Returns:
1398 tuple of (decoded_result, binary_remainder)
1399 """
1400 tag = binary[0]
1401 if tag != self.tag:
1402 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1403 (self, tag, binary, self.tag))
1404 remainder = self.from_tlv(binary)
1405 return (self.to_dict(), remainder)
1406
1407 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001408 def encode(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001409 return self.to_tlv()
1410
Harald Weltec91085e2022-02-10 18:05:45 +01001411
Harald Welte3de6ca22021-05-02 20:05:56 +02001412class TL0_DataObject(DataObject):
1413 """Data Object that has Tag, Len=0 and no Value part."""
Harald Weltec91085e2022-02-10 18:05:45 +01001414
1415 def __init__(self, name: str, desc: str, tag: int, val=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001416 super().__init__(name, desc, tag)
1417 self.val = val
1418
Harald Weltec91085e2022-02-10 18:05:45 +01001419 def from_bytes(self, binary: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001420 if len(binary) != 0:
1421 raise ValueError
1422 self.decoded = self.val
1423
Harald Welte50368772022-02-10 15:22:22 +01001424 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001425 return b''
1426
1427
1428class DataObjectCollection:
1429 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1430 A given encoded DO may contain any of them in any order, and may contain multiple instances
1431 of each DO."""
Harald Weltec91085e2022-02-10 18:05:45 +01001432
Harald Welte425038f2022-02-10 19:32:04 +01001433 def __init__(self, name: str, desc: Optional[str] = None, members=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001434 self.name = name
1435 self.desc = desc
1436 self.members = members or []
1437 self.members_by_tag = {}
1438 self.members_by_name = {}
Harald Weltec91085e2022-02-10 18:05:45 +01001439 self.members_by_tag = {m.tag: m for m in members}
1440 self.members_by_name = {m.name: m for m in members}
Harald Welte3de6ca22021-05-02 20:05:56 +02001441
Harald Welte50368772022-02-10 15:22:22 +01001442 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001443 member_strs = [str(x) for x in self.members]
1444 return '%s(%s)' % (self.name, ','.join(member_strs))
1445
Harald Welte50368772022-02-10 15:22:22 +01001446 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001447 member_strs = [repr(x) for x in self.members]
1448 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1449
Harald Welte50368772022-02-10 15:22:22 +01001450 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001451 """Extending DataCollections with other DataCollections or DataObjects."""
1452 if isinstance(other, DataObjectCollection):
1453 # adding one collection to another
1454 members = self.members + other.members
1455 return DataObjectCollection(self.name, self.desc, members)
1456 elif isinstance(other, DataObject):
1457 # adding a member to a collection
1458 return DataObjectCollection(self.name, self.desc, self.members + [other])
1459 else:
1460 raise TypeError
1461
1462 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001463 def decode(self, binary: bytes) -> Tuple[List, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001464 """Decode any number of DOs from the collection until the end of the input data,
1465 or uninitialized memory (0xFF) is found.
1466 Args:
1467 binary : binary bytes of encoded data
1468 Returns:
1469 tuple of (decoded_result, binary_remainder)
1470 """
1471 res = []
1472 remainder = binary
1473 # iterate until no binary trailer is left
1474 while len(remainder):
1475 tag = remainder[0]
Harald Weltec91085e2022-02-10 18:05:45 +01001476 if tag == 0xff: # uninitialized memory at the end?
Harald Welte3de6ca22021-05-02 20:05:56 +02001477 return (res, remainder)
1478 if not tag in self.members_by_tag:
1479 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1480 (self, tag, remainder, self.members_by_tag.keys()))
1481 obj = self.members_by_tag[tag]
1482 # DO from_tlv returns remainder of binary
1483 remainder = obj.from_tlv(remainder)
1484 # collect our results
1485 res.append(obj.to_dict())
1486 return (res, remainder)
1487
1488 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001489 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001490 res = bytearray()
1491 for i in decoded:
1492 obj = self.members_by_name(i[0])
1493 res.append(obj.to_tlv())
1494 return res
1495
Harald Weltec91085e2022-02-10 18:05:45 +01001496
Harald Welte3de6ca22021-05-02 20:05:56 +02001497class DataObjectChoice(DataObjectCollection):
1498 """One Data Object from within a choice, identified by its tag.
1499 This means that exactly one member of the choice must occur, and which one occurs depends
1500 on the tag."""
Harald Weltec91085e2022-02-10 18:05:45 +01001501
Harald Welte3de6ca22021-05-02 20:05:56 +02001502 def __add__(self, other):
1503 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1504 raise TypeError
1505
Harald Welte50368772022-02-10 15:22:22 +01001506 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001507 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1508 if isinstance(other, DataObjectChoice):
1509 # adding one collection to another
1510 members = self.members + other.members
1511 return DataObjectChoice(self.name, self.desc, members)
1512 elif isinstance(other, DataObject):
1513 # adding a member to a collection
1514 return DataObjectChoice(self.name, self.desc, self.members + [other])
1515 else:
1516 raise TypeError
1517
1518 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001519 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001520 """Decode a single DOs from the choice based on the tag.
1521 Args:
1522 binary : binary bytes of encoded data
1523 Returns:
1524 tuple of (decoded_result, binary_remainder)
1525 """
1526 tag = binary[0]
1527 if tag == 0xff:
1528 return (None, binary)
1529 if not tag in self.members_by_tag:
1530 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1531 (self, tag, binary, self.members_by_tag.keys()))
1532 obj = self.members_by_tag[tag]
1533 remainder = obj.from_tlv(binary)
1534 return (obj.to_dict(), remainder)
1535
1536 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001537 def encode(self, decoded) -> bytes:
Harald Welte785d4842022-04-05 14:24:22 +02001538 obj = self.members_by_name[list(decoded)[0]]
1539 obj.decoded = list(decoded.values())[0]
Harald Welte3de6ca22021-05-02 20:05:56 +02001540 return obj.to_tlv()
1541
Harald Weltec91085e2022-02-10 18:05:45 +01001542
Harald Welte3de6ca22021-05-02 20:05:56 +02001543class DataObjectSequence:
1544 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1545 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1546 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1547 for encoding or decoding an entire sequence."""
Harald Weltec91085e2022-02-10 18:05:45 +01001548
Harald Welte425038f2022-02-10 19:32:04 +01001549 def __init__(self, name: str, desc: Optional[str] = None, sequence=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001550 self.sequence = sequence or []
1551 self.name = name
1552 self.desc = desc
1553
Harald Welte50368772022-02-10 15:22:22 +01001554 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001555 member_strs = [str(x) for x in self.sequence]
1556 return '%s(%s)' % (self.name, ','.join(member_strs))
1557
Harald Welte50368772022-02-10 15:22:22 +01001558 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001559 member_strs = [repr(x) for x in self.sequence]
1560 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1561
Harald Welte50368772022-02-10 15:22:22 +01001562 def __add__(self, other) -> 'DataObjectSequence':
Harald Welte3de6ca22021-05-02 20:05:56 +02001563 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1564 if isinstance(other, 'DataObject'):
Harald Weltec91085e2022-02-10 18:05:45 +01001565 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001566 elif isinstance(other, 'DataObjectChoice'):
Harald Weltec91085e2022-02-10 18:05:45 +01001567 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001568 elif isinstance(other, 'DataObjectSequence'):
Harald Weltec91085e2022-02-10 18:05:45 +01001569 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
Harald Welte3de6ca22021-05-02 20:05:56 +02001570
1571 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001572 def decode(self, binary: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001573 """Decode a sequence by calling the decoder of each element in the sequence.
1574 Args:
1575 binary : binary bytes of encoded data
1576 Returns:
1577 tuple of (decoded_result, binary_remainder)
1578 """
1579 remainder = binary
1580 res = []
1581 for e in self.sequence:
1582 (r, remainder) = e.decode(remainder)
1583 if r:
1584 res.append(r)
1585 return (res, remainder)
1586
1587 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001588 def decode_multi(self, do: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001589 """Decode multiple occurrences of the sequence from the binary input data.
1590 Args:
1591 do : binary input data to be decoded
1592 Returns:
1593 list of results of the decoder of this sequences
1594 """
1595 remainder = do
1596 res = []
1597 while len(remainder):
1598 (r, remainder2) = self.decode(remainder)
1599 if r:
1600 res.append(r)
1601 if len(remainder2) < len(remainder):
1602 remainder = remainder2
1603 else:
1604 remainder = remainder2
1605 break
1606 return (res, remainder)
1607
1608 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001609 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001610 """Encode a sequence by calling the encoder of each element in the sequence."""
1611 encoded = bytearray()
1612 i = 0
1613 for e in self.sequence:
1614 encoded += e.encode(decoded[i])
1615 i += 1
1616 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001617
Harald Welte0dcdfbf2022-04-05 14:42:48 +02001618 def encode_multi(self, decoded) -> bytes:
1619 """Encode multiple occurrences of the sequence from the decoded input data.
1620 Args:
1621 decoded : list of json-serializable input data; one sequence per list item
1622 Returns:
1623 binary encoded output data
1624 """
1625 encoded = bytearray()
1626 for d in decoded:
1627 encoded += self.encode(d)
1628 return encoded
1629
Harald Weltec91085e2022-02-10 18:05:45 +01001630
Harald Welte90441432021-05-02 21:28:12 +02001631class CardCommand:
1632 """A single card command / instruction."""
Harald Weltec91085e2022-02-10 18:05:45 +01001633
Harald Welte90441432021-05-02 21:28:12 +02001634 def __init__(self, name, ins, cla_list=None, desc=None):
1635 self.name = name
1636 self.ins = ins
1637 self.cla_list = cla_list or []
1638 self.cla_list = [x.lower() for x in self.cla_list]
1639 self.desc = desc
1640
1641 def __str__(self):
1642 return self.name
1643
1644 def __repr__(self):
1645 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1646
1647 def match_cla(self, cla):
1648 """Does the given CLA match the CLA list of the command?."""
1649 if not isinstance(cla, str):
1650 cla = '%02u' % cla
1651 cla = cla.lower()
1652 for cla_match in self.cla_list:
1653 cla_masked = ""
1654 for i in range(0, 2):
1655 if cla_match[i] == 'x':
1656 cla_masked += 'x'
1657 else:
1658 cla_masked += cla[i]
1659 if cla_masked == cla_match:
1660 return True
1661 return False
1662
1663
1664class CardCommandSet:
1665 """A set of card instructions, typically specified within one spec."""
Harald Weltec91085e2022-02-10 18:05:45 +01001666
Harald Welte90441432021-05-02 21:28:12 +02001667 def __init__(self, name, cmds=[]):
1668 self.name = name
Harald Weltec91085e2022-02-10 18:05:45 +01001669 self.cmds = {c.ins: c for c in cmds}
Harald Welte90441432021-05-02 21:28:12 +02001670
1671 def __str__(self):
1672 return self.name
1673
1674 def __getitem__(self, idx):
1675 return self.cmds[idx]
1676
1677 def __add__(self, other):
1678 if isinstance(other, CardCommand):
1679 if other.ins in self.cmds:
1680 raise ValueError('%s: INS 0x%02x already defined: %s' %
1681 (self, other.ins, self.cmds[other.ins]))
1682 self.cmds[other.ins] = other
1683 elif isinstance(other, CardCommandSet):
1684 for c in other.cmds.keys():
1685 self.cmds[c] = other.cmds[c]
1686 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001687 raise ValueError(
1688 '%s: Unsupported type to add operator: %s' % (self, other))
Harald Welte90441432021-05-02 21:28:12 +02001689
1690 def lookup(self, ins, cla=None):
1691 """look-up the command within the CommandSet."""
1692 ins = int(ins)
1693 if not ins in self.cmds:
1694 return None
1695 cmd = self.cmds[ins]
1696 if cla and not cmd.match_cla(cla):
1697 return None
1698 return cmd
Philipp Maiera028c7d2021-11-08 16:12:03 +01001699
Harald Weltec91085e2022-02-10 18:05:45 +01001700
Philipp Maiera028c7d2021-11-08 16:12:03 +01001701def all_subclasses(cls) -> set:
Harald Weltec91085e2022-02-10 18:05:45 +01001702 """Recursively get all subclasses of a specified class"""
1703 return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])