blob: 9defaf3f78e3b657426d46b4569a96fa96599946 [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)
Harald Weltefdb187d2023-07-09 17:03:17 +020033ResTuple = Tuple[Hexstr, SwHexstr]
Sylvain Munaut76504e02010-12-07 00:24:32 +010034
Harald Weltec91085e2022-02-10 18:05:45 +010035def h2b(s: Hexstr) -> bytearray:
36 """convert from a string of hex nibbles to a sequence of bytes"""
37 return bytearray.fromhex(s)
Sylvain Munaut76504e02010-12-07 00:24:32 +010038
Sylvain Munaut76504e02010-12-07 00:24:32 +010039
Harald Weltec91085e2022-02-10 18:05:45 +010040def b2h(b: bytearray) -> Hexstr:
41 """convert from a sequence of bytes to a string of hex nibbles"""
42 return ''.join(['%02x' % (x) for x in b])
Sylvain Munaut76504e02010-12-07 00:24:32 +010043
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030044
Harald Weltec91085e2022-02-10 18:05:45 +010045def h2i(s: Hexstr) -> List[int]:
46 """convert from a string of hex nibbles to a list of integers"""
47 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 +030048
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020049
Harald Weltec91085e2022-02-10 18:05:45 +010050def i2h(s: List[int]) -> Hexstr:
51 """convert from a list of integers to a string of hex nibbles"""
52 return ''.join(['%02x' % (x) for x in s])
Sylvain Munaut76504e02010-12-07 00:24:32 +010053
Sylvain Munaut76504e02010-12-07 00:24:32 +010054
Harald Weltec91085e2022-02-10 18:05:45 +010055def h2s(s: Hexstr) -> str:
56 """convert from a string of hex nibbles to an ASCII string"""
57 return ''.join([chr((int(x, 16) << 4)+int(y, 16)) for x, y in zip(s[0::2], s[1::2])
58 if int(x + y, 16) != 0xff])
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040059
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020060
Harald Weltec91085e2022-02-10 18:05:45 +010061def s2h(s: str) -> Hexstr:
62 """convert from an ASCII string to a string of hex nibbles"""
63 b = bytearray()
64 b.extend(map(ord, s))
65 return b2h(b)
Philipp Maier796ca3d2021-11-01 17:13:57 +010066
Philipp Maier796ca3d2021-11-01 17:13:57 +010067
Harald Weltec91085e2022-02-10 18:05:45 +010068def i2s(s: List[int]) -> str:
69 """convert from a list of integers to an ASCII string"""
70 return ''.join([chr(x) for x in s])
71
72
73def swap_nibbles(s: Hexstr) -> Hexstr:
74 """swap the nibbles in a hex string"""
75 return ''.join([x+y for x, y in zip(s[1::2], s[0::2])])
76
77
78def rpad(s: str, l: int, c='f') -> str:
79 """pad string on the right side.
80 Args:
81 s : string to pad
82 l : total length to pad to
83 c : padding character
84 Returns:
85 String 's' padded with as many 'c' as needed to reach total length of 'l'
86 """
87 return s + c * (l - len(s))
88
89
90def lpad(s: str, l: int, c='f') -> str:
91 """pad string on the left side.
92 Args:
93 s : string to pad
94 l : total length to pad to
95 c : padding character
96 Returns:
97 String 's' padded with as many 'c' as needed to reach total length of 'l'
98 """
99 return c * (l - len(s)) + s
100
101
102def half_round_up(n: int) -> int:
103 return (n + 1)//2
104
105
106def str_sanitize(s: str) -> str:
107 """replace all non printable chars, line breaks and whitespaces, with ' ', make sure that
108 there are no whitespaces at the end and at the beginning of the string.
109
110 Args:
111 s : string to sanitize
112 Returns:
113 filtered result of string 's'
114 """
115
116 chars_to_keep = string.digits + string.ascii_letters + string.punctuation
117 res = ''.join([c if c in chars_to_keep else ' ' for c in s])
118 return res.strip()
Philipp Maier796ca3d2021-11-01 17:13:57 +0100119
Harald Welte917d98c2021-04-21 11:51:25 +0200120#########################################################################
Harald Welte9f3b44d2021-05-04 18:18:09 +0200121# poor man's COMPREHENSION-TLV decoder.
122#########################################################################
123
Harald Weltec91085e2022-02-10 18:05:45 +0100124
125def comprehensiontlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
Harald Welte6912b1b2021-05-24 23:16:44 +0200126 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
127 if binary[0] in [0x00, 0x80, 0xff]:
Harald Weltec91085e2022-02-10 18:05:45 +0100128 raise ValueError("Found illegal value 0x%02x in %s" %
129 (binary[0], binary))
Harald Welte6912b1b2021-05-24 23:16:44 +0200130 if binary[0] == 0x7f:
131 # three-byte tag
132 tag = binary[0] << 16 | binary[1] << 8 | binary[2]
133 return (tag, binary[3:])
134 elif binary[0] == 0xff:
135 return None, binary
136 else:
137 # single byte tag
138 tag = binary[0]
139 return (tag, binary[1:])
140
Harald Weltec91085e2022-02-10 18:05:45 +0100141
142def comprehensiontlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
Harald Welte9f3b44d2021-05-04 18:18:09 +0200143 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
144 if binary[0] in [0x00, 0x80, 0xff]:
Harald Weltec91085e2022-02-10 18:05:45 +0100145 raise ValueError("Found illegal value 0x%02x in %s" %
146 (binary[0], binary))
Harald Welte9f3b44d2021-05-04 18:18:09 +0200147 if binary[0] == 0x7f:
148 # three-byte tag
149 tag = (binary[1] & 0x7f) << 8
150 tag |= binary[2]
151 compr = True if binary[1] & 0x80 else False
152 return ({'comprehension': compr, 'tag': tag}, binary[3:])
153 else:
154 # single byte tag
155 tag = binary[0] & 0x7f
156 compr = True if binary[0] & 0x80 else False
157 return ({'comprehension': compr, 'tag': tag}, binary[1:])
158
Harald Weltec91085e2022-02-10 18:05:45 +0100159
Harald Welte9f3b44d2021-05-04 18:18:09 +0200160def comprehensiontlv_encode_tag(tag) -> bytes:
161 """Encode a single Tag according to ETSI TS 101 220 Section 7.1.1"""
162 # permit caller to specify tag also as integer value
163 if isinstance(tag, int):
164 compr = True if tag < 0xff and tag & 0x80 else False
165 tag = {'tag': tag, 'comprehension': compr}
166 compr = tag.get('comprehension', False)
167 if tag['tag'] in [0x00, 0x80, 0xff] or tag['tag'] > 0xff:
168 # 3-byte format
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300169 byte3 = tag['tag'] & 0xff
Harald Welte9f3b44d2021-05-04 18:18:09 +0200170 byte2 = (tag['tag'] >> 8) & 0x7f
171 if compr:
172 byte2 |= 0x80
173 return b'\x7f' + byte2.to_bytes(1, 'big') + byte3.to_bytes(1, 'big')
174 else:
175 # 1-byte format
176 ret = tag['tag']
177 if compr:
178 ret |= 0x80
179 return ret.to_bytes(1, 'big')
180
181# length value coding is equal to BER-TLV
182
Harald Welte6912b1b2021-05-24 23:16:44 +0200183
Harald Weltec91085e2022-02-10 18:05:45 +0100184def comprehensiontlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
185 """Parse a single TLV IE at the start of the given binary data.
186 Args:
187 binary : binary input data of BER-TLV length field
188 Returns:
189 Tuple of (tag:dict, len:int, remainder:bytes)
190 """
191 (tagdict, remainder) = comprehensiontlv_parse_tag(binary)
192 (length, remainder) = bertlv_parse_len(remainder)
193 value = remainder[:length]
194 remainder = remainder[length:]
195 return (tagdict, length, value, remainder)
Harald Welte6912b1b2021-05-24 23:16:44 +0200196
Harald Welte9f3b44d2021-05-04 18:18:09 +0200197
198#########################################################################
Harald Welte917d98c2021-04-21 11:51:25 +0200199# poor man's BER-TLV decoder. To be a more sophisticated OO library later
200#########################################################################
201
Harald Weltec91085e2022-02-10 18:05:45 +0100202def bertlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
203 """Get a single raw Tag from start of input according to ITU-T X.690 8.1.2
204 Args:
205 binary : binary input data of BER-TLV length field
206 Returns:
207 Tuple of (tag:int, remainder:bytes)
208 """
209 # check for FF padding at the end, as customary in SIM card files
210 if binary[0] == 0xff and len(binary) == 1 or binary[0] == 0xff and binary[1] == 0xff:
211 return None, binary
212 tag = binary[0] & 0x1f
213 if tag <= 30:
214 return binary[0], binary[1:]
215 else: # multi-byte tag
216 tag = binary[0]
217 i = 1
218 last = False
219 while not last:
220 last = False if binary[i] & 0x80 else True
221 tag <<= 8
222 tag |= binary[i]
223 i += 1
224 return tag, binary[i:]
Harald Welte6912b1b2021-05-24 23:16:44 +0200225
Harald Weltec91085e2022-02-10 18:05:45 +0100226
227def bertlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
228 """Parse a single Tag value according to ITU-T X.690 8.1.2
229 Args:
230 binary : binary input data of BER-TLV length field
231 Returns:
232 Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes)
233 """
234 cls = binary[0] >> 6
235 constructed = True if binary[0] & 0x20 else False
236 tag = binary[0] & 0x1f
237 if tag <= 30:
238 return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[1:])
239 else: # multi-byte tag
240 tag = 0
241 i = 1
242 last = False
243 while not last:
244 last = False if binary[i] & 0x80 else True
245 tag <<= 7
246 tag |= binary[i] & 0x7f
247 i += 1
248 return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[i:])
249
Harald Welte917d98c2021-04-21 11:51:25 +0200250
Harald Weltef0885b12021-05-24 23:18:59 +0200251def bertlv_encode_tag(t) -> bytes:
252 """Encode a single Tag value according to ITU-T X.690 8.1.2
253 """
Harald Weltec91085e2022-02-10 18:05:45 +0100254 def get_top7_bits(inp: int) -> Tuple[int, int]:
Harald Weltef0885b12021-05-24 23:18:59 +0200255 """Get top 7 bits of integer. Returns those 7 bits as integer and the remaining LSBs."""
256 remain_bits = inp.bit_length()
257 if remain_bits >= 7:
258 bitcnt = 7
259 else:
260 bitcnt = remain_bits
261 outp = inp >> (remain_bits - bitcnt)
262 remainder = inp & ~ (inp << (remain_bits - bitcnt))
263 return outp, remainder
264
265 if isinstance(t, int):
266 # FIXME: multiple byte tags
267 tag = t & 0x1f
268 constructed = True if t & 0x20 else False
269 cls = t >> 6
270 else:
271 tag = t['tag']
272 constructed = t['constructed']
273 cls = t['class']
274 if tag <= 30:
275 t = tag & 0x1f
276 if constructed:
277 t |= 0x20
278 t |= (cls & 3) << 6
279 return bytes([t])
Harald Weltec91085e2022-02-10 18:05:45 +0100280 else: # multi-byte tag
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300281 t = 0x1f
Harald Weltef0885b12021-05-24 23:18:59 +0200282 if constructed:
283 t |= 0x20
284 t |= (cls & 3) << 6
285 tag_bytes = bytes([t])
286 remain = tag
287 while True:
288 t, remain = get_top7_bits(remain)
289 if remain:
290 t |= 0x80
291 tag_bytes += bytes([t])
292 if not remain:
293 break
294 return tag_bytes
295
Harald Welte917d98c2021-04-21 11:51:25 +0200296
Harald Weltec91085e2022-02-10 18:05:45 +0100297def bertlv_parse_len(binary: bytes) -> Tuple[int, bytes]:
298 """Parse a single Length value according to ITU-T X.690 8.1.3;
299 only the definite form is supported here.
300 Args:
301 binary : binary input data of BER-TLV length field
302 Returns:
303 Tuple of (length, remainder)
304 """
305 if binary[0] < 0x80:
306 return (binary[0], binary[1:])
307 else:
308 num_len_oct = binary[0] & 0x7f
309 length = 0
310 for i in range(1, 1+num_len_oct):
311 length <<= 8
312 length |= binary[i]
313 return (length, binary[1+num_len_oct:])
Harald Welte917d98c2021-04-21 11:51:25 +0200314
Harald Welte917d98c2021-04-21 11:51:25 +0200315
Harald Weltec91085e2022-02-10 18:05:45 +0100316def bertlv_encode_len(length: int) -> bytes:
317 """Encode a single Length value according to ITU-T X.690 8.1.3;
318 only the definite form is supported here.
319 Args:
320 length : length value to be encoded
321 Returns:
322 binary output data of BER-TLV length field
323 """
324 if length < 0x80:
325 return length.to_bytes(1, 'big')
326 elif length <= 0xff:
327 return b'\x81' + length.to_bytes(1, 'big')
328 elif length <= 0xffff:
329 return b'\x82' + length.to_bytes(2, 'big')
330 elif length <= 0xffffff:
331 return b'\x83' + length.to_bytes(3, 'big')
332 elif length <= 0xffffffff:
333 return b'\x84' + length.to_bytes(4, 'big')
334 else:
335 raise ValueError("Length > 32bits not supported")
336
337
338def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
339 """Parse a single TLV IE at the start of the given binary data.
340 Args:
341 binary : binary input data of BER-TLV length field
342 Returns:
343 Tuple of (tag:dict, len:int, remainder:bytes)
344 """
345 (tagdict, remainder) = bertlv_parse_tag(binary)
346 (length, remainder) = bertlv_parse_len(remainder)
347 value = remainder[:length]
348 remainder = remainder[length:]
349 return (tagdict, length, value, remainder)
Harald Welte917d98c2021-04-21 11:51:25 +0200350
351
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200352# IMSI encoded format:
353# For IMSI 0123456789ABCDE:
354#
355# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
356# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
357#
358# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
359#
360# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
361# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
362# encode itself.
363#
364# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
365# even length IMSI only uses half of the last byte.
366
Harald Weltec91085e2022-02-10 18:05:45 +0100367def enc_imsi(imsi: str):
368 """Converts a string IMSI into the encoded value of the EF"""
369 l = half_round_up(
370 len(imsi) + 1) # Required bytes - include space for odd/even indicator
371 oe = len(imsi) & 1 # Odd (1) / Even (0)
372 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe << 3) | 1, rpad(imsi, 15)))
373 return ei
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400374
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400375
Harald Weltec91085e2022-02-10 18:05:45 +0100376def dec_imsi(ef: Hexstr) -> Optional[str]:
377 """Converts an EF value to the IMSI string representation"""
378 if len(ef) < 4:
379 return None
380 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
381 l = l - 1 # Encoded length byte includes oe nibble
382 swapped = swap_nibbles(ef[2:]).rstrip('f')
383 if len(swapped) < 1:
384 return None
385 oe = (int(swapped[0]) >> 3) & 1 # Odd (1) / Even (0)
386 if not oe:
387 # if even, only half of last byte was used
388 l = l-1
389 if l != len(swapped) - 1:
390 return None
391 imsi = swapped[1:]
392 return imsi
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400393
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400394
Harald Weltec91085e2022-02-10 18:05:45 +0100395def dec_iccid(ef: Hexstr) -> str:
396 return swap_nibbles(ef).strip('f')
Philipp Maier6c5cd802021-04-23 21:19:36 +0200397
Philipp Maier6c5cd802021-04-23 21:19:36 +0200398
Harald Weltec91085e2022-02-10 18:05:45 +0100399def enc_iccid(iccid: str) -> Hexstr:
400 return swap_nibbles(rpad(iccid, 20))
Philipp Maier6c5cd802021-04-23 21:19:36 +0200401
Philipp Maier6c5cd802021-04-23 21:19:36 +0200402
Harald Weltec91085e2022-02-10 18:05:45 +0100403def enc_plmn(mcc: Hexstr, mnc: Hexstr) -> Hexstr:
404 """Converts integer MCC/MNC into 3 bytes for EF"""
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300405
Harald Weltec91085e2022-02-10 18:05:45 +0100406 # Make sure there are no excess whitespaces in the input
407 # parameters
408 mcc = mcc.strip()
409 mnc = mnc.strip()
410
411 # Make sure that MCC/MNC are correctly padded with leading
412 # zeros or 'F', depending on the length.
413 if len(mnc) == 0:
414 mnc = "FFF"
415 elif len(mnc) == 1:
416 mnc = "F0" + mnc
417 elif len(mnc) == 2:
418 mnc += "F"
419
420 if len(mcc) == 0:
421 mcc = "FFF"
422 elif len(mcc) == 1:
423 mcc = "00" + mcc
424 elif len(mcc) == 2:
425 mcc = "0" + mcc
426
427 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
428
429
430def dec_plmn(threehexbytes: Hexstr) -> dict:
431 res = {'mcc': "0", 'mnc': "0"}
432 dec_mcc_from_plmn_str(threehexbytes)
433 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
434 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
435 return res
436
Harald Welted7a7e172021-04-07 00:30:10 +0200437
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300438def dec_spn(ef):
Harald Weltec91085e2022-02-10 18:05:45 +0100439 """Obsolete, kept for API compatibility"""
440 from ts_51_011 import EF_SPN
441 abstract_data = EF_SPN().decode_hex(ef)
442 show_in_hplmn = abstract_data['show_in_hplmn']
443 hide_in_oplmn = abstract_data['hide_in_oplmn']
444 name = abstract_data['spn']
445 return (name, show_in_hplmn, hide_in_oplmn)
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300446
Harald Weltec91085e2022-02-10 18:05:45 +0100447
448def enc_spn(name: str, show_in_hplmn=False, hide_in_oplmn=False):
449 """Obsolete, kept for API compatibility"""
450 from ts_51_011 import EF_SPN
451 abstract_data = {
452 'hide_in_oplmn': hide_in_oplmn,
453 'show_in_hplmn': show_in_hplmn,
454 'spn': name,
455 }
456 return EF_SPN().encode_hex(abstract_data)
457
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900458
Supreeth Herlef3948532020-03-24 12:23:51 +0100459def hexstr_to_Nbytearr(s, nbytes):
Harald Weltec91085e2022-02-10 18:05:45 +0100460 return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2))]
Supreeth Herlef3948532020-03-24 12:23:51 +0100461
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100462# Accepts hex string representing three bytes
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100463
Philipp Maier6c5cd802021-04-23 21:19:36 +0200464
Harald Weltec91085e2022-02-10 18:05:45 +0100465def dec_mcc_from_plmn(plmn: Hexstr) -> int:
466 ia = h2i(plmn)
467 digit1 = ia[0] & 0x0F # 1st byte, LSB
468 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
469 digit3 = ia[1] & 0x0F # 2nd byte, LSB
470 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
471 return 0xFFF # 4095
472 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100473
Philipp Maier6c5cd802021-04-23 21:19:36 +0200474
Harald Weltec91085e2022-02-10 18:05:45 +0100475def dec_mcc_from_plmn_str(plmn: Hexstr) -> str:
476 digit1 = plmn[1] # 1st byte, LSB
477 digit2 = plmn[0] # 1st byte, MSB
478 digit3 = plmn[3] # 2nd byte, LSB
479 res = digit1 + digit2 + digit3
480 return res.upper().strip("F")
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100481
Harald Weltec91085e2022-02-10 18:05:45 +0100482
483def dec_mnc_from_plmn(plmn: Hexstr) -> int:
484 ia = h2i(plmn)
485 digit1 = ia[2] & 0x0F # 3rd byte, LSB
486 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
487 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
488 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
489 return 0xFFF # 4095
490 return derive_mnc(digit1, digit2, digit3)
491
492
493def dec_mnc_from_plmn_str(plmn: Hexstr) -> str:
494 digit1 = plmn[5] # 3rd byte, LSB
495 digit2 = plmn[4] # 3rd byte, MSB
496 digit3 = plmn[2] # 2nd byte, MSB
497 res = digit1 + digit2 + digit3
498 return res.upper().strip("F")
499
500
501def dec_act(twohexbytes: Hexstr) -> List[str]:
502 act_list = [
503 {'bit': 15, 'name': "UTRAN"},
504 {'bit': 14, 'name': "E-UTRAN"},
505 {'bit': 11, 'name': "NG-RAN"},
506 {'bit': 7, 'name': "GSM"},
507 {'bit': 6, 'name': "GSM COMPACT"},
508 {'bit': 5, 'name': "cdma2000 HRPD"},
509 {'bit': 4, 'name': "cdma2000 1xRTT"},
510 ]
511 ia = h2i(twohexbytes)
512 u16t = (ia[0] << 8) | ia[1]
513 sel = []
514 for a in act_list:
515 if u16t & (1 << a['bit']):
516 if a['name'] == "E-UTRAN":
517 # The Access technology identifier of E-UTRAN
518 # allows a more detailed specification:
519 if u16t & (1 << 13) and u16t & (1 << 12):
520 sel.append("E-UTRAN WB-S1")
521 sel.append("E-UTRAN NB-S1")
522 elif u16t & (1 << 13):
523 sel.append("E-UTRAN WB-S1")
524 elif u16t & (1 << 12):
525 sel.append("E-UTRAN NB-S1")
526 else:
527 sel.append("E-UTRAN")
528 else:
529 sel.append(a['name'])
530 return sel
531
532
533def dec_xplmn_w_act(fivehexbytes: Hexstr) -> Dict[str, Any]:
534 res = {'mcc': "0", 'mnc': "0", 'act': []}
535 plmn_chars = 6
536 act_chars = 4
537 # first three bytes (six ascii hex chars)
538 plmn_str = fivehexbytes[:plmn_chars]
539 # two bytes after first three bytes
540 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars]
541 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
542 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
543 res['act'] = dec_act(act_str)
544 return res
545
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100546
547def format_xplmn_w_act(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100548 s = ""
549 for rec_data in hexstr_to_Nbytearr(hexstr, 5):
550 rec_info = dec_xplmn_w_act(rec_data)
551 if rec_info['mcc'] == "" and rec_info['mnc'] == "":
552 rec_str = "unused"
553 else:
554 rec_str = "MCC: %s MNC: %s AcT: %s" % (
555 rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act']))
556 s += "\t%s # %s\n" % (rec_data, rec_str)
557 return s
558
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100559
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100560def dec_loci(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100561 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
562 res['tmsi'] = hexstr[:8]
563 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
564 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
565 res['lac'] = hexstr[14:18]
566 res['status'] = h2i(hexstr[20:22])
567 return res
568
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100569
570def dec_psloci(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100571 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0,
572 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
573 res['p-tmsi'] = hexstr[:8]
574 res['p-tmsi-sig'] = hexstr[8:14]
575 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
576 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
577 res['lac'] = hexstr[20:24]
578 res['rac'] = hexstr[24:26]
579 res['status'] = h2i(hexstr[26:28])
580 return res
581
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100582
583def dec_epsloci(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100584 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
585 res['guti'] = hexstr[:24]
586 res['tai'] = hexstr[24:34]
587 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
588 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
589 res['tac'] = hexstr[30:34]
590 res['status'] = h2i(hexstr[34:36])
591 return res
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100592
Harald Welteca673942020-06-03 15:19:40 +0200593
Harald Weltec91085e2022-02-10 18:05:45 +0100594def dec_xplmn(threehexbytes: Hexstr) -> dict:
595 res = {'mcc': 0, 'mnc': 0, 'act': []}
596 plmn_chars = 6
597 # first three bytes (six ascii hex chars)
598 plmn_str = threehexbytes[:plmn_chars]
Matan Perelman60951b02023-06-01 17:39:04 +0300599 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
600 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Harald Weltec91085e2022-02-10 18:05:45 +0100601 return res
Harald Welteca673942020-06-03 15:19:40 +0200602
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900603
Harald Weltec91085e2022-02-10 18:05:45 +0100604def format_xplmn(hexstr: Hexstr) -> str:
605 s = ""
606 for rec_data in hexstr_to_Nbytearr(hexstr, 3):
607 rec_info = dec_xplmn(rec_data)
Matan Perelman60951b02023-06-01 17:39:04 +0300608 if not rec_info['mcc'] and not rec_info['mnc']:
Harald Weltec91085e2022-02-10 18:05:45 +0100609 rec_str = "unused"
610 else:
Matan Perelman60951b02023-06-01 17:39:04 +0300611 rec_str = "MCC: %s MNC: %s" % (rec_info['mcc'], rec_info['mnc'])
Harald Weltec91085e2022-02-10 18:05:45 +0100612 s += "\t%s # %s\n" % (rec_data, rec_str)
613 return s
614
615
616def derive_milenage_opc(ki_hex: Hexstr, op_hex: Hexstr) -> Hexstr:
617 """
618 Run the milenage algorithm to calculate OPC from Ki and OP
619 """
Harald Welted75fa3f2023-05-31 20:47:55 +0200620 from Cryptodome.Cipher import AES
Harald Weltec91085e2022-02-10 18:05:45 +0100621 # pylint: disable=no-name-in-module
Harald Welted75fa3f2023-05-31 20:47:55 +0200622 from Cryptodome.Util.strxor import strxor
Harald Weltec91085e2022-02-10 18:05:45 +0100623 from pySim.utils import b2h
624
625 # We pass in hex string and now need to work on bytes
626 ki_bytes = bytes(h2b(ki_hex))
627 op_bytes = bytes(h2b(op_hex))
628 aes = AES.new(ki_bytes, AES.MODE_ECB)
629 opc_bytes = aes.encrypt(op_bytes)
630 return b2h(strxor(opc_bytes, op_bytes))
631
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900632
Harald Welte52255572021-04-03 09:56:32 +0200633def calculate_luhn(cc) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100634 """
635 Calculate Luhn checksum used in e.g. ICCID and IMEI
636 """
637 num = list(map(int, str(cc)))
638 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10))
639 for d in num[::-2]]) % 10
640 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200641
Philipp Maier7592eee2019-09-12 13:03:23 +0200642
Harald Weltec91085e2022-02-10 18:05:45 +0100643def mcc_from_imsi(imsi: str) -> Optional[str]:
644 """
645 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
646 """
647 if imsi == None:
648 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200649
Harald Weltec91085e2022-02-10 18:05:45 +0100650 if len(imsi) > 3:
651 return imsi[:3]
652 else:
653 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200654
Supreeth Herled24f1632019-11-30 10:37:09 +0100655
Harald Weltec91085e2022-02-10 18:05:45 +0100656def mnc_from_imsi(imsi: str, long: bool = False) -> Optional[str]:
657 """
658 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
659 """
660 if imsi == None:
661 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100662
Harald Weltec91085e2022-02-10 18:05:45 +0100663 if len(imsi) > 3:
664 if long:
665 return imsi[3:6]
666 else:
667 return imsi[3:5]
668 else:
669 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100670
Supreeth Herled24f1632019-11-30 10:37:09 +0100671
Harald Weltec91085e2022-02-10 18:05:45 +0100672def derive_mcc(digit1: int, digit2: int, digit3: int) -> int:
673 """
674 Derive decimal representation of the MCC (Mobile Country Code)
675 from three given digits.
676 """
Supreeth Herled24f1632019-11-30 10:37:09 +0100677
Harald Weltec91085e2022-02-10 18:05:45 +0100678 mcc = 0
Supreeth Herled24f1632019-11-30 10:37:09 +0100679
Harald Weltec91085e2022-02-10 18:05:45 +0100680 if digit1 != 0x0f:
681 mcc += digit1 * 100
682 if digit2 != 0x0f:
683 mcc += digit2 * 10
684 if digit3 != 0x0f:
685 mcc += digit3
Supreeth Herled24f1632019-11-30 10:37:09 +0100686
Harald Weltec91085e2022-02-10 18:05:45 +0100687 return mcc
Supreeth Herled24f1632019-11-30 10:37:09 +0100688
Supreeth Herled24f1632019-11-30 10:37:09 +0100689
Harald Weltec91085e2022-02-10 18:05:45 +0100690def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int:
691 """
692 Derive decimal representation of the MNC (Mobile Network Code)
693 from two or (optionally) three given digits.
694 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100695
Harald Weltec91085e2022-02-10 18:05:45 +0100696 mnc = 0
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100697
Harald Weltec91085e2022-02-10 18:05:45 +0100698 # 3-rd digit is optional for the MNC. If present
699 # the algorythm is the same as for the MCC.
700 if digit3 != 0x0f:
701 return derive_mcc(digit1, digit2, digit3)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100702
Harald Weltec91085e2022-02-10 18:05:45 +0100703 if digit1 != 0x0f:
704 mnc += digit1 * 10
705 if digit2 != 0x0f:
706 mnc += digit2
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100707
Harald Weltec91085e2022-02-10 18:05:45 +0100708 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100709
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100710
Harald Weltec91085e2022-02-10 18:05:45 +0100711def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]:
712 """
713 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
714 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
715 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100716
Harald Weltec91085e2022-02-10 18:05:45 +0100717 # Convert from str to (kind of) 'bytes'
718 ef_msisdn = h2b(ef_msisdn)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100719
Harald Weltec91085e2022-02-10 18:05:45 +0100720 # Make sure mandatory fields are present
721 if len(ef_msisdn) < 14:
722 raise ValueError("EF.MSISDN is too short")
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100723
Harald Weltec91085e2022-02-10 18:05:45 +0100724 # Skip optional Alpha Identifier
725 xlen = len(ef_msisdn) - 14
726 msisdn_lhv = ef_msisdn[xlen:]
Supreeth Herle5a541012019-12-22 08:59:16 +0100727
Harald Weltec91085e2022-02-10 18:05:45 +0100728 # Parse the length (in bytes) of the BCD encoded number
729 bcd_len = msisdn_lhv[0]
730 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
731 if bcd_len == 0xff:
732 return None
733 elif bcd_len > 11 or bcd_len < 1:
734 raise ValueError(
735 "Length of MSISDN (%d bytes) is out of range" % bcd_len)
Supreeth Herle5a541012019-12-22 08:59:16 +0100736
Harald Weltec91085e2022-02-10 18:05:45 +0100737 # Parse ToN / NPI
738 ton = (msisdn_lhv[1] >> 4) & 0x07
739 npi = msisdn_lhv[1] & 0x0f
740 bcd_len -= 1
Supreeth Herle5a541012019-12-22 08:59:16 +0100741
Harald Weltec91085e2022-02-10 18:05:45 +0100742 # No MSISDN?
743 if not bcd_len:
744 return (npi, ton, None)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200745
Harald Weltec91085e2022-02-10 18:05:45 +0100746 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
747 # International number 10.5.118/3GPP TS 24.008
748 if ton == 0x01:
749 msisdn = '+' + msisdn
Supreeth Herle5a541012019-12-22 08:59:16 +0100750
Harald Weltec91085e2022-02-10 18:05:45 +0100751 return (npi, ton, msisdn)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200752
Supreeth Herle5a541012019-12-22 08:59:16 +0100753
Harald Weltec91085e2022-02-10 18:05:45 +0100754def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
755 """
756 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
757 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
758 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100759
Harald Weltec91085e2022-02-10 18:05:45 +0100760 Default NPI / ToN values:
761 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
762 - ToN: network specific or international number (if starts with '+').
763 """
764
765 # If no MSISDN is supplied then encode the file contents as all "ff"
766 if msisdn == "" or msisdn == "+":
767 return "ff" * 14
768
769 # Leading '+' indicates International Number
770 if msisdn[0] == '+':
771 msisdn = msisdn[1:]
772 ton = 0x01
773
774 # An MSISDN must not exceed 20 digits
775 if len(msisdn) > 20:
776 raise ValueError("msisdn must not be longer than 20 digits")
777
778 # Append 'f' padding if number of digits is odd
779 if len(msisdn) % 2 > 0:
780 msisdn += 'f'
781
782 # BCD length also includes NPI/ToN header
783 bcd_len = len(msisdn) // 2 + 1
784 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
785 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
786
787 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200788
Supreeth Herle441c4a72020-03-24 10:19:15 +0100789
Harald Welte52255572021-04-03 09:56:32 +0200790def dec_st(st, table="sim") -> str:
Harald Weltec91085e2022-02-10 18:05:45 +0100791 """
792 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
793 """
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200794
Harald Weltec91085e2022-02-10 18:05:45 +0100795 if table == "isim":
796 from pySim.ts_31_103 import EF_IST_map
797 lookup_map = EF_IST_map
798 elif table == "usim":
799 from pySim.ts_31_102 import EF_UST_map
800 lookup_map = EF_UST_map
801 else:
802 from pySim.ts_51_011 import EF_SST_map
803 lookup_map = EF_SST_map
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200804
Harald Weltec91085e2022-02-10 18:05:45 +0100805 st_bytes = [st[i:i+2] for i in range(0, len(st), 2)]
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200806
Harald Weltec91085e2022-02-10 18:05:45 +0100807 avail_st = ""
808 # Get each byte and check for available services
809 for i in range(0, len(st_bytes)):
810 # Byte i contains info about Services num (8i+1) to num (8i+8)
811 byte = int(st_bytes[i], 16)
812 # Services in each byte are in order MSB to LSB
813 # MSB - Service (8i+8)
814 # LSB - Service (8i+1)
815 for j in range(1, 9):
816 if byte & 0x01 == 0x01 and ((8*i) + j in lookup_map):
817 # Byte X contains info about Services num (8X-7) to num (8X)
818 # bit = 1: service available
819 # bit = 0: service not available
820 avail_st += '\tService %d - %s\n' % (
821 (8*i) + j, lookup_map[(8*i) + j])
822 byte = byte >> 1
823 return avail_st
824
Supreeth Herle98370552020-05-11 09:04:41 +0200825
826def first_TLV_parser(bytelist):
Harald Weltec91085e2022-02-10 18:05:45 +0100827 '''
828 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
Supreeth Herle98370552020-05-11 09:04:41 +0200829
Harald Weltec91085e2022-02-10 18:05:45 +0100830 parses first TLV format record in a list of bytelist
831 returns a 3-Tuple: Tag, Length, Value
832 Value is a list of bytes
833 parsing of length is ETSI'style 101.220
834 '''
835 Tag = bytelist[0]
836 if bytelist[1] == 0xFF:
837 Len = bytelist[2]*256 + bytelist[3]
838 Val = bytelist[4:4+Len]
839 else:
840 Len = bytelist[1]
841 Val = bytelist[2:2+Len]
842 return (Tag, Len, Val)
843
Supreeth Herle98370552020-05-11 09:04:41 +0200844
845def TLV_parser(bytelist):
Harald Weltec91085e2022-02-10 18:05:45 +0100846 '''
847 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
Supreeth Herle98370552020-05-11 09:04:41 +0200848
Harald Weltec91085e2022-02-10 18:05:45 +0100849 loops on the input list of bytes with the "first_TLV_parser()" function
850 returns a list of 3-Tuples
851 '''
852 ret = []
853 while len(bytelist) > 0:
854 T, L, V = first_TLV_parser(bytelist)
855 if T == 0xFF:
856 # padding bytes
857 break
858 ret.append((T, L, V))
859 # need to manage length of L
860 if L > 0xFE:
861 bytelist = bytelist[L+4:]
862 else:
863 bytelist = bytelist[L+2:]
864 return ret
865
Supreeth Herled572ede2020-03-22 09:55:04 +0100866
Supreeth Herled84daa12020-03-24 12:20:40 +0100867def enc_st(st, service, state=1):
Harald Weltec91085e2022-02-10 18:05:45 +0100868 """
869 Encodes the EF S/U/IST/EST and returns the updated Service Table
Supreeth Herled84daa12020-03-24 12:20:40 +0100870
Harald Weltec91085e2022-02-10 18:05:45 +0100871 Parameters:
872 st - Current value of SIM/USIM/ISIM Service Table
873 service - Service Number to encode as activated/de-activated
874 state - 1 mean activate, 0 means de-activate
Supreeth Herled84daa12020-03-24 12:20:40 +0100875
Harald Weltec91085e2022-02-10 18:05:45 +0100876 Returns:
877 s - Modified value of SIM/USIM/ISIM Service Table
Supreeth Herled84daa12020-03-24 12:20:40 +0100878
Harald Weltec91085e2022-02-10 18:05:45 +0100879 Default values:
880 - state: 1 - Sets the particular Service bit to 1
881 """
882 st_bytes = [st[i:i+2] for i in range(0, len(st), 2)]
Supreeth Herled84daa12020-03-24 12:20:40 +0100883
Harald Weltec91085e2022-02-10 18:05:45 +0100884 s = ""
885 # Check whether the requested service is present in each byte
886 for i in range(0, len(st_bytes)):
887 # Byte i contains info about Services num (8i+1) to num (8i+8)
888 if service in range((8*i) + 1, (8*i) + 9):
889 byte = int(st_bytes[i], 16)
890 # Services in each byte are in order MSB to LSB
891 # MSB - Service (8i+8)
892 # LSB - Service (8i+1)
893 mod_byte = 0x00
894 # Copy bit by bit contents of byte to mod_byte with modified bit
895 # for requested service
896 for j in range(1, 9):
897 mod_byte = mod_byte >> 1
898 if service == (8*i) + j:
899 mod_byte = state == 1 and mod_byte | 0x80 or mod_byte & 0x7f
900 else:
901 mod_byte = byte & 0x01 == 0x01 and mod_byte | 0x80 or mod_byte & 0x7f
902 byte = byte >> 1
Supreeth Herled84daa12020-03-24 12:20:40 +0100903
Harald Weltec91085e2022-02-10 18:05:45 +0100904 s += ('%02x' % (mod_byte))
905 else:
906 s += st_bytes[i]
Supreeth Herled84daa12020-03-24 12:20:40 +0100907
Harald Weltec91085e2022-02-10 18:05:45 +0100908 return s
909
Supreeth Herled84daa12020-03-24 12:20:40 +0100910
Supreeth Herle3b342c22020-03-24 16:15:02 +0100911def dec_addr_tlv(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100912 """
913 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
914 See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104.
915 """
Supreeth Herled572ede2020-03-22 09:55:04 +0100916
Harald Weltec91085e2022-02-10 18:05:45 +0100917 # Convert from hex str to int bytes list
918 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100919
Harald Weltec91085e2022-02-10 18:05:45 +0100920 # Get list of tuples containing parsed TLVs
921 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100922
Harald Weltec91085e2022-02-10 18:05:45 +0100923 for tlv in tlvs:
924 # tlv = (T, L, [V])
925 # T = Tag
926 # L = Length
927 # [V] = List of value
Supreeth Herled572ede2020-03-22 09:55:04 +0100928
Harald Weltec91085e2022-02-10 18:05:45 +0100929 # Invalid Tag value scenario
930 if tlv[0] != 0x80:
931 continue
Supreeth Herled572ede2020-03-22 09:55:04 +0100932
Harald Weltec91085e2022-02-10 18:05:45 +0100933 # Empty field - Zero length
934 if tlv[1] == 0:
935 continue
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200936
Harald Weltec91085e2022-02-10 18:05:45 +0100937 # First byte in the value has the address type
938 addr_type = tlv[2][0]
939 # TODO: Support parsing of IPv6
940 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
941 if addr_type == 0x00: # FQDN
942 # Skip address tye byte i.e. first byte in value list
943 content = tlv[2][1:]
944 return (i2s(content), '00')
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200945
Harald Weltec91085e2022-02-10 18:05:45 +0100946 elif addr_type == 0x01: # IPv4
947 # Skip address tye byte i.e. first byte in value list
948 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
949 ipv4 = tlv[2][2:]
950 content = '.'.join(str(x) for x in ipv4)
951 return (content, '01')
952 else:
953 raise ValueError("Invalid address type")
Supreeth Herled572ede2020-03-22 09:55:04 +0100954
Harald Weltec91085e2022-02-10 18:05:45 +0100955 return (None, None)
956
Philipp Maierff84c232020-05-12 17:24:18 +0200957
Supreeth Herle3b342c22020-03-24 16:15:02 +0100958def enc_addr_tlv(addr, addr_type='00'):
Harald Weltec91085e2022-02-10 18:05:45 +0100959 """
960 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
961 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 +0100962
Harald Weltec91085e2022-02-10 18:05:45 +0100963 Default values:
964 - addr_type: 00 - FQDN format of Address
965 """
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100966
Harald Weltec91085e2022-02-10 18:05:45 +0100967 s = ""
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100968
Harald Weltec91085e2022-02-10 18:05:45 +0100969 # TODO: Encoding of IPv6 address
970 if addr_type == '00': # FQDN
971 hex_str = s2h(addr)
972 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
973 elif addr_type == '01': # IPv4
974 ipv4_list = addr.split('.')
975 ipv4_str = ""
976 for i in ipv4_list:
977 ipv4_str += ('%02x' % (int(i)))
Supreeth Herle654eca72020-03-25 14:25:38 +0100978
Harald Weltec91085e2022-02-10 18:05:45 +0100979 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
980 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
981 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100982
Harald Weltec91085e2022-02-10 18:05:45 +0100983 return s
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100984
Philipp Maier47236502021-03-09 21:28:25 +0100985
Harald Weltec91085e2022-02-10 18:05:45 +0100986def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool:
987 """
988 Check if a string is a valid hexstring
989 """
Philipp Maier47236502021-03-09 21:28:25 +0100990
Harald Weltec91085e2022-02-10 18:05:45 +0100991 # Filter obviously bad strings
992 if not string:
993 return False
994 if len(string) < minlen or minlen < 2:
995 return False
996 if len(string) % 2:
997 return False
998 if maxlen and len(string) > maxlen:
999 return False
Philipp Maier47236502021-03-09 21:28:25 +01001000
Harald Weltec91085e2022-02-10 18:05:45 +01001001 # Try actual encoding to be sure
1002 try:
1003 try_encode = h2b(string)
1004 return True
1005 except:
1006 return False
Philipp Maiere8536c02020-05-11 21:35:01 +02001007
Philipp Maiere8536c02020-05-11 21:35:01 +02001008
Harald Weltec91085e2022-02-10 18:05:45 +01001009def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr:
1010 """
1011 The ADM pin can be supplied either in its hexadecimal form or as
1012 ascii string. This function checks the supplied opts parameter and
1013 returns the pin_adm as hex encoded string, regardless in which form
1014 it was originally supplied by the user
1015 """
Philipp Maiere8536c02020-05-11 21:35:01 +02001016
Harald Weltec91085e2022-02-10 18:05:45 +01001017 if pin_adm is not None:
1018 if len(pin_adm) <= 8:
1019 pin_adm = ''.join(['%02x' % (ord(x)) for x in pin_adm])
1020 pin_adm = rpad(pin_adm, 16)
Philipp Maiere8536c02020-05-11 21:35:01 +02001021
Harald Weltec91085e2022-02-10 18:05:45 +01001022 else:
1023 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
1024
1025 if pin_adm_hex is not None:
1026 if len(pin_adm_hex) == 16:
1027 pin_adm = pin_adm_hex
1028 # Ensure that it's hex-encoded
1029 try:
1030 try_encode = h2b(pin_adm)
1031 except ValueError:
1032 raise ValueError(
1033 "PIN-ADM needs to be hex encoded using this option")
1034 else:
1035 raise ValueError(
1036 "PIN-ADM needs to be exactly 16 digits (hex encoded)")
1037
1038 return pin_adm
1039
Philipp Maiere8536c02020-05-11 21:35:01 +02001040
Supreeth Herle95ec7722020-03-24 13:09:03 +01001041def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
Harald Weltec91085e2022-02-10 18:05:45 +01001042 """
1043 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
1044 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 +01001045
Harald Weltec91085e2022-02-10 18:05:45 +01001046 Default values:
1047 - epdg_priority: '0001' - 1st Priority
1048 - epdg_fqdn_format: '00' - Operator Identifier FQDN
1049 """
Supreeth Herle95ec7722020-03-24 13:09:03 +01001050
Harald Weltec91085e2022-02-10 18:05:45 +01001051 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
1052 # TODO: Handle encoding of Length field for length more than 127 Bytes
1053 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
1054 content = rpad(content, len(hexstr))
1055 return content
1056
Supreeth Herle95ec7722020-03-24 13:09:03 +01001057
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001058def dec_ePDGSelection(sixhexbytes):
Harald Weltec91085e2022-02-10 18:05:45 +01001059 """
1060 Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
1061 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
1062 """
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001063
Harald Weltec91085e2022-02-10 18:05:45 +01001064 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
1065 plmn_chars = 6
1066 epdg_priority_chars = 4
1067 epdg_fqdn_format_chars = 2
1068 # first three bytes (six ascii hex chars)
1069 plmn_str = sixhexbytes[:plmn_chars]
1070 # two bytes after first three bytes
1071 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars +
1072 epdg_priority_chars]
1073 # one byte after first five bytes
1074 epdg_fqdn_format_str = sixhexbytes[plmn_chars +
1075 epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
1076 res['mcc'] = dec_mcc_from_plmn(plmn_str)
1077 res['mnc'] = dec_mnc_from_plmn(plmn_str)
1078 res['epdg_priority'] = epdg_priority_str
1079 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
1080 return res
1081
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001082
1083def format_ePDGSelection(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +01001084 ePDGSelection_info_tag_chars = 2
1085 ePDGSelection_info_tag_str = hexstr[:2]
1086 s = ""
1087 # Minimum length
1088 len_chars = 2
1089 # TODO: Need to determine length properly - definite length support only
1090 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
1091 # As per spec, length is 5n, n - number of PLMNs
1092 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
1093 # Totalling to 6 Bytes, maybe length should be 6n
1094 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +01001095
Harald Weltec91085e2022-02-10 18:05:45 +01001096 # Not programmed scenario
1097 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
1098 len_chars = 0
1099 ePDGSelection_info_tag_chars = 0
1100 if len_str[0] == '8':
1101 # The bits 7 to 1 denotes the number of length octets if length > 127
1102 if int(len_str[1]) > 0:
1103 # Update number of length octets
1104 len_chars = len_chars * int(len_str[1])
1105 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001106
Harald Weltec91085e2022-02-10 18:05:45 +01001107 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
1108 # Right pad to prevent index out of range - multiple of 6 bytes
1109 content_str = rpad(content_str, len(content_str) +
1110 (12 - (len(content_str) % 12)))
1111 for rec_data in hexstr_to_Nbytearr(content_str, 6):
1112 rec_info = dec_ePDGSelection(rec_data)
1113 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
1114 rec_str = "unused"
1115 else:
1116 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
1117 (rec_info['mcc'], rec_info['mnc'],
1118 rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
1119 s += "\t%s # %s\n" % (rec_data, rec_str)
1120 return s
1121
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001122
1123def get_addr_type(addr):
Harald Weltec91085e2022-02-10 18:05:45 +01001124 """
1125 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
1126 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001127
Harald Weltec91085e2022-02-10 18:05:45 +01001128 TODO: Handle IPv6
1129 """
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001130
Harald Weltec91085e2022-02-10 18:05:45 +01001131 # Empty address string
1132 if not len(addr):
1133 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001134
Harald Weltec91085e2022-02-10 18:05:45 +01001135 addr_list = addr.split('.')
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001136
Harald Weltec91085e2022-02-10 18:05:45 +01001137 # Check for IPv4/IPv6
1138 try:
1139 import ipaddress
1140 # Throws ValueError if addr is not correct
1141 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001142
Harald Weltec91085e2022-02-10 18:05:45 +01001143 if ipa.version == 4:
1144 return 0x01
1145 elif ipa.version == 6:
1146 return 0x02
1147 except Exception as e:
1148 invalid_ipv4 = True
1149 for i in addr_list:
1150 # Invalid IPv4 may qualify for a valid FQDN, so make check here
1151 # e.g. 172.24.15.300
1152 import re
1153 if not re.match('^[0-9_]+$', i):
1154 invalid_ipv4 = False
1155 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001156
Harald Weltec91085e2022-02-10 18:05:45 +01001157 if invalid_ipv4:
1158 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001159
Harald Weltec91085e2022-02-10 18:05:45 +01001160 fqdn_flag = True
1161 for i in addr_list:
1162 # Only Alpha-numeric characters and hyphen - RFC 1035
1163 import re
1164 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
1165 fqdn_flag = False
1166 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001167
Harald Weltec91085e2022-02-10 18:05:45 +01001168 # FQDN
1169 if fqdn_flag:
1170 return 0x00
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001171
Harald Weltec91085e2022-02-10 18:05:45 +01001172 return None
Harald Welte67d551a2021-01-21 14:50:01 +01001173
Philipp Maier5d3e2592021-02-22 17:22:16 +01001174
Harald Weltec91085e2022-02-10 18:05:45 +01001175def sw_match(sw: str, pattern: str) -> bool:
1176 """Match given SW against given pattern."""
1177 # Create a masked version of the returned status word
1178 sw_lower = sw.lower()
1179 sw_masked = ""
1180 for i in range(0, 4):
1181 if pattern[i] == '?':
1182 sw_masked = sw_masked + '?'
1183 elif pattern[i] == 'x':
1184 sw_masked = sw_masked + 'x'
1185 else:
1186 sw_masked = sw_masked + sw_lower[i]
1187 # Compare the masked version against the pattern
1188 return sw_masked == pattern
Harald Welteee3501f2021-04-02 13:00:18 +02001189
Harald Weltec91085e2022-02-10 18:05:45 +01001190
1191def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = 1,
1192 align_left: bool = True) -> str:
1193 """Pretty print a list of strings into a tabulated form.
1194
1195 Args:
1196 width : total width in characters per line
1197 space : horizontal space between cells
1198 lspace : number of spaces before row
1199 align_lef : Align text to the left side
1200 Returns:
1201 multi-line string containing formatted table
1202 """
1203 if str_list == None:
1204 return ""
1205 if len(str_list) <= 0:
1206 return ""
1207 longest_str = max(str_list, key=len)
1208 cellwith = len(longest_str) + hspace
1209 cols = width // cellwith
1210 rows = (len(str_list) - 1) // cols + 1
1211 table = []
1212 for i in iter(range(rows)):
1213 str_list_row = str_list[i::rows]
1214 if (align_left):
1215 format_str_cell = '%%-%ds'
1216 else:
1217 format_str_cell = '%%%ds'
1218 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
1219 format_str_row = (" " * lspace) + format_str_row
1220 table.append(format_str_row % tuple(str_list_row))
1221 return '\n'.join(table)
1222
Harald Welte5e749a72021-04-10 17:18:17 +02001223
Harald Welte917d98c2021-04-21 11:51:25 +02001224def auto_int(x):
1225 """Helper function for argparse to accept hexadecimal integers."""
1226 return int(x, 0)
1227
Harald Weltec91085e2022-02-10 18:05:45 +01001228
Philipp Maier40ea4a42022-06-02 14:45:41 +02001229def expand_hex(hexstring, length):
1230 """Expand a given hexstring to a specified length by replacing "." or ".."
1231 with a filler that is derived from the neighboring nibbles respective
1232 bytes. Usually this will be the nibble respective byte before "." or
1233 "..", execpt when the string begins with "." or "..", then the nibble
1234 respective byte after "." or ".." is used.". In case the string cannot
1235 be expanded for some reason, the input string is returned unmodified.
1236
1237 Args:
1238 hexstring : hexstring to expand
1239 length : desired length of the resulting hexstring.
1240 Returns:
1241 expanded hexstring
1242 """
1243
1244 # expand digit aligned
1245 if hexstring.count(".") == 1:
1246 pos = hexstring.index(".")
1247 if pos > 0:
1248 filler = hexstring[pos - 1]
1249 else:
1250 filler = hexstring[pos + 1]
1251
1252 missing = length * 2 - (len(hexstring) - 1)
1253 if missing <= 0:
1254 return hexstring
1255
1256 return hexstring.replace(".", filler * missing)
1257
1258 # expand byte aligned
1259 elif hexstring.count("..") == 1:
1260 if len(hexstring) % 2:
1261 return hexstring
1262
1263 pos = hexstring.index("..")
1264
1265 if pos % 2:
1266 return hexstring
1267
1268 if pos > 1:
1269 filler = hexstring[pos - 2:pos]
1270 else:
1271 filler = hexstring[pos + 2:pos+4]
1272
1273 missing = length * 2 - (len(hexstring) - 2)
1274 if missing <= 0:
1275 return hexstring
1276
1277 return hexstring.replace("..", filler * (missing // 2))
1278
1279 # no change
1280 return hexstring
1281
1282
Harald Welte5e749a72021-04-10 17:18:17 +02001283class JsonEncoder(json.JSONEncoder):
1284 """Extend the standard library JSONEncoder with support for more types."""
Harald Weltec91085e2022-02-10 18:05:45 +01001285
Harald Welte5e749a72021-04-10 17:18:17 +02001286 def default(self, o):
1287 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
1288 return b2h(o)
1289 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +02001290
Harald Weltec91085e2022-02-10 18:05:45 +01001291
Philipp Maier80ce71f2021-04-19 21:24:23 +02001292def boxed_heading_str(heading, width=80):
Harald Weltec91085e2022-02-10 18:05:45 +01001293 """Generate a string that contains a boxed heading."""
1294 # Auto-enlarge box if heading exceeds length
1295 if len(heading) > width - 4:
1296 width = len(heading) + 4
Philipp Maier80ce71f2021-04-19 21:24:23 +02001297
Harald Weltec91085e2022-02-10 18:05:45 +01001298 res = "#" * width
1299 fstr = "\n# %-" + str(width - 4) + "s #\n"
1300 res += fstr % (heading)
1301 res += "#" * width
1302 return res
Harald Welte3de6ca22021-05-02 20:05:56 +02001303
1304
1305class DataObject(abc.ABC):
1306 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
1307 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
1308 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
1309 tags at specific points in a stream. This class tries to represent this."""
Harald Weltec91085e2022-02-10 18:05:45 +01001310
Harald Welte425038f2022-02-10 19:32:04 +01001311 def __init__(self, name: str, desc: Optional[str] = None, tag: Optional[int] = None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001312 """
1313 Args:
1314 name: A brief, all-lowercase, underscore separated string identifier
1315 desc: A human-readable description of what this DO represents
1316 tag : The tag associated with this DO
1317 """
1318 self.name = name
1319 self.desc = desc
1320 self.tag = tag
1321 self.decoded = None
1322 self.encoded = None
1323
1324 def __str__(self):
1325 return self.name
1326
Harald Welte50368772022-02-10 15:22:22 +01001327 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001328 return '%s(%s)' % (self.__class__, self.name)
1329
Harald Welte50368772022-02-10 15:22:22 +01001330 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001331 """OR-ing DataObjects together renders a DataObjectChoice."""
1332 if isinstance(other, DataObject):
1333 # DataObject | DataObject = DataObjectChoice
1334 return DataObjectChoice(None, members=[self, other])
1335 else:
1336 raise TypeError
1337
Harald Welte50368772022-02-10 15:22:22 +01001338 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001339 """ADD-ing DataObjects together renders a DataObjectCollection."""
1340 if isinstance(other, DataObject):
1341 # DataObject + DataObject = DataObjectCollectin
1342 return DataObjectCollection(None, members=[self, other])
Harald Welte50368772022-02-10 15:22:22 +01001343 else:
1344 raise TypeError
Harald Welte3de6ca22021-05-02 20:05:56 +02001345
Harald Welte50368772022-02-10 15:22:22 +01001346 def _compute_tag(self) -> int:
Harald Welte3de6ca22021-05-02 20:05:56 +02001347 """Compute the tag (sometimes the tag encodes part of the value)."""
1348 return self.tag
1349
Harald Welte50368772022-02-10 15:22:22 +01001350 def to_dict(self) -> dict:
Harald Welte3de6ca22021-05-02 20:05:56 +02001351 """Return a dict in form "name: decoded_value" """
1352 return {self.name: self.decoded}
1353
1354 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001355 def from_bytes(self, do: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001356 """Parse the value part of the DO into the internal state of this instance.
1357 Args:
1358 do : binary encoded bytes
1359 """
1360
1361 @abc.abstractmethod
Harald Welte50368772022-02-10 15:22:22 +01001362 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001363 """Encode the internal state of this instance into the TLV value part.
1364 Returns:
1365 binary bytes encoding the internal state
1366 """
1367
Harald Weltec91085e2022-02-10 18:05:45 +01001368 def from_tlv(self, do: bytes) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001369 """Parse binary TLV representation into internal state. The resulting decoded
1370 representation is _not_ returned, but just internalized in the object instance!
1371 Args:
1372 do : input bytes containing TLV-encoded representation
1373 Returns:
1374 bytes remaining at end of 'do' after parsing one TLV/DO.
1375 """
1376 if do[0] != self.tag:
Harald Weltec91085e2022-02-10 18:05:45 +01001377 raise ValueError('%s: Can only decode tag 0x%02x' %
1378 (self, self.tag))
Harald Welte3de6ca22021-05-02 20:05:56 +02001379 length = do[1]
1380 val = do[2:2+length]
1381 self.from_bytes(val)
1382 # return remaining bytes
1383 return do[2+length:]
1384
Harald Welte50368772022-02-10 15:22:22 +01001385 def to_tlv(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001386 """Encode internal representation to binary TLV.
1387 Returns:
1388 bytes encoded in TLV format.
1389 """
1390 val = self.to_bytes()
Harald Welte785d4842022-04-05 14:24:22 +02001391 return bertlv_encode_tag(self._compute_tag()) + bertlv_encode_len(len(val)) + val
Harald Welte3de6ca22021-05-02 20:05:56 +02001392
1393 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001394 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001395 """Decode a single DOs from the input data.
1396 Args:
1397 binary : binary bytes of encoded data
1398 Returns:
1399 tuple of (decoded_result, binary_remainder)
1400 """
1401 tag = binary[0]
1402 if tag != self.tag:
1403 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1404 (self, tag, binary, self.tag))
1405 remainder = self.from_tlv(binary)
1406 return (self.to_dict(), remainder)
1407
1408 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001409 def encode(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001410 return self.to_tlv()
1411
Harald Weltec91085e2022-02-10 18:05:45 +01001412
Harald Welte3de6ca22021-05-02 20:05:56 +02001413class TL0_DataObject(DataObject):
1414 """Data Object that has Tag, Len=0 and no Value part."""
Harald Weltec91085e2022-02-10 18:05:45 +01001415
1416 def __init__(self, name: str, desc: str, tag: int, val=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001417 super().__init__(name, desc, tag)
1418 self.val = val
1419
Harald Weltec91085e2022-02-10 18:05:45 +01001420 def from_bytes(self, binary: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001421 if len(binary) != 0:
1422 raise ValueError
1423 self.decoded = self.val
1424
Harald Welte50368772022-02-10 15:22:22 +01001425 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001426 return b''
1427
1428
1429class DataObjectCollection:
1430 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1431 A given encoded DO may contain any of them in any order, and may contain multiple instances
1432 of each DO."""
Harald Weltec91085e2022-02-10 18:05:45 +01001433
Harald Welte425038f2022-02-10 19:32:04 +01001434 def __init__(self, name: str, desc: Optional[str] = None, members=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001435 self.name = name
1436 self.desc = desc
1437 self.members = members or []
1438 self.members_by_tag = {}
1439 self.members_by_name = {}
Harald Weltec91085e2022-02-10 18:05:45 +01001440 self.members_by_tag = {m.tag: m for m in members}
1441 self.members_by_name = {m.name: m for m in members}
Harald Welte3de6ca22021-05-02 20:05:56 +02001442
Harald Welte50368772022-02-10 15:22:22 +01001443 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001444 member_strs = [str(x) for x in self.members]
1445 return '%s(%s)' % (self.name, ','.join(member_strs))
1446
Harald Welte50368772022-02-10 15:22:22 +01001447 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001448 member_strs = [repr(x) for x in self.members]
1449 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1450
Harald Welte50368772022-02-10 15:22:22 +01001451 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001452 """Extending DataCollections with other DataCollections or DataObjects."""
1453 if isinstance(other, DataObjectCollection):
1454 # adding one collection to another
1455 members = self.members + other.members
1456 return DataObjectCollection(self.name, self.desc, members)
1457 elif isinstance(other, DataObject):
1458 # adding a member to a collection
1459 return DataObjectCollection(self.name, self.desc, self.members + [other])
1460 else:
1461 raise TypeError
1462
1463 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001464 def decode(self, binary: bytes) -> Tuple[List, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001465 """Decode any number of DOs from the collection until the end of the input data,
1466 or uninitialized memory (0xFF) is found.
1467 Args:
1468 binary : binary bytes of encoded data
1469 Returns:
1470 tuple of (decoded_result, binary_remainder)
1471 """
1472 res = []
1473 remainder = binary
1474 # iterate until no binary trailer is left
1475 while len(remainder):
1476 tag = remainder[0]
Harald Weltec91085e2022-02-10 18:05:45 +01001477 if tag == 0xff: # uninitialized memory at the end?
Harald Welte3de6ca22021-05-02 20:05:56 +02001478 return (res, remainder)
1479 if not tag in self.members_by_tag:
1480 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1481 (self, tag, remainder, self.members_by_tag.keys()))
1482 obj = self.members_by_tag[tag]
1483 # DO from_tlv returns remainder of binary
1484 remainder = obj.from_tlv(remainder)
1485 # collect our results
1486 res.append(obj.to_dict())
1487 return (res, remainder)
1488
1489 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001490 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001491 res = bytearray()
1492 for i in decoded:
1493 obj = self.members_by_name(i[0])
1494 res.append(obj.to_tlv())
1495 return res
1496
Harald Weltec91085e2022-02-10 18:05:45 +01001497
Harald Welte3de6ca22021-05-02 20:05:56 +02001498class DataObjectChoice(DataObjectCollection):
1499 """One Data Object from within a choice, identified by its tag.
1500 This means that exactly one member of the choice must occur, and which one occurs depends
1501 on the tag."""
Harald Weltec91085e2022-02-10 18:05:45 +01001502
Harald Welte3de6ca22021-05-02 20:05:56 +02001503 def __add__(self, other):
1504 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1505 raise TypeError
1506
Harald Welte50368772022-02-10 15:22:22 +01001507 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001508 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1509 if isinstance(other, DataObjectChoice):
1510 # adding one collection to another
1511 members = self.members + other.members
1512 return DataObjectChoice(self.name, self.desc, members)
1513 elif isinstance(other, DataObject):
1514 # adding a member to a collection
1515 return DataObjectChoice(self.name, self.desc, self.members + [other])
1516 else:
1517 raise TypeError
1518
1519 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001520 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001521 """Decode a single DOs from the choice based on the tag.
1522 Args:
1523 binary : binary bytes of encoded data
1524 Returns:
1525 tuple of (decoded_result, binary_remainder)
1526 """
1527 tag = binary[0]
1528 if tag == 0xff:
1529 return (None, binary)
1530 if not tag in self.members_by_tag:
1531 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1532 (self, tag, binary, self.members_by_tag.keys()))
1533 obj = self.members_by_tag[tag]
1534 remainder = obj.from_tlv(binary)
1535 return (obj.to_dict(), remainder)
1536
1537 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001538 def encode(self, decoded) -> bytes:
Harald Welte785d4842022-04-05 14:24:22 +02001539 obj = self.members_by_name[list(decoded)[0]]
1540 obj.decoded = list(decoded.values())[0]
Harald Welte3de6ca22021-05-02 20:05:56 +02001541 return obj.to_tlv()
1542
Harald Weltec91085e2022-02-10 18:05:45 +01001543
Harald Welte3de6ca22021-05-02 20:05:56 +02001544class DataObjectSequence:
1545 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1546 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1547 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1548 for encoding or decoding an entire sequence."""
Harald Weltec91085e2022-02-10 18:05:45 +01001549
Harald Welte425038f2022-02-10 19:32:04 +01001550 def __init__(self, name: str, desc: Optional[str] = None, sequence=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001551 self.sequence = sequence or []
1552 self.name = name
1553 self.desc = desc
1554
Harald Welte50368772022-02-10 15:22:22 +01001555 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001556 member_strs = [str(x) for x in self.sequence]
1557 return '%s(%s)' % (self.name, ','.join(member_strs))
1558
Harald Welte50368772022-02-10 15:22:22 +01001559 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001560 member_strs = [repr(x) for x in self.sequence]
1561 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1562
Harald Welte50368772022-02-10 15:22:22 +01001563 def __add__(self, other) -> 'DataObjectSequence':
Harald Welte3de6ca22021-05-02 20:05:56 +02001564 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1565 if isinstance(other, 'DataObject'):
Harald Weltec91085e2022-02-10 18:05:45 +01001566 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001567 elif isinstance(other, 'DataObjectChoice'):
Harald Weltec91085e2022-02-10 18:05:45 +01001568 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001569 elif isinstance(other, 'DataObjectSequence'):
Harald Weltec91085e2022-02-10 18:05:45 +01001570 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
Harald Welte3de6ca22021-05-02 20:05:56 +02001571
1572 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001573 def decode(self, binary: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001574 """Decode a sequence by calling the decoder of each element in the sequence.
1575 Args:
1576 binary : binary bytes of encoded data
1577 Returns:
1578 tuple of (decoded_result, binary_remainder)
1579 """
1580 remainder = binary
1581 res = []
1582 for e in self.sequence:
1583 (r, remainder) = e.decode(remainder)
1584 if r:
1585 res.append(r)
1586 return (res, remainder)
1587
1588 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001589 def decode_multi(self, do: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001590 """Decode multiple occurrences of the sequence from the binary input data.
1591 Args:
1592 do : binary input data to be decoded
1593 Returns:
1594 list of results of the decoder of this sequences
1595 """
1596 remainder = do
1597 res = []
1598 while len(remainder):
1599 (r, remainder2) = self.decode(remainder)
1600 if r:
1601 res.append(r)
1602 if len(remainder2) < len(remainder):
1603 remainder = remainder2
1604 else:
1605 remainder = remainder2
1606 break
1607 return (res, remainder)
1608
1609 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001610 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001611 """Encode a sequence by calling the encoder of each element in the sequence."""
1612 encoded = bytearray()
1613 i = 0
1614 for e in self.sequence:
1615 encoded += e.encode(decoded[i])
1616 i += 1
1617 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001618
Harald Welte0dcdfbf2022-04-05 14:42:48 +02001619 def encode_multi(self, decoded) -> bytes:
1620 """Encode multiple occurrences of the sequence from the decoded input data.
1621 Args:
1622 decoded : list of json-serializable input data; one sequence per list item
1623 Returns:
1624 binary encoded output data
1625 """
1626 encoded = bytearray()
1627 for d in decoded:
1628 encoded += self.encode(d)
1629 return encoded
1630
Harald Weltec91085e2022-02-10 18:05:45 +01001631
Harald Welte90441432021-05-02 21:28:12 +02001632class CardCommand:
1633 """A single card command / instruction."""
Harald Weltec91085e2022-02-10 18:05:45 +01001634
Harald Welte90441432021-05-02 21:28:12 +02001635 def __init__(self, name, ins, cla_list=None, desc=None):
1636 self.name = name
1637 self.ins = ins
1638 self.cla_list = cla_list or []
1639 self.cla_list = [x.lower() for x in self.cla_list]
1640 self.desc = desc
1641
1642 def __str__(self):
1643 return self.name
1644
1645 def __repr__(self):
1646 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1647
1648 def match_cla(self, cla):
1649 """Does the given CLA match the CLA list of the command?."""
1650 if not isinstance(cla, str):
1651 cla = '%02u' % cla
1652 cla = cla.lower()
1653 for cla_match in self.cla_list:
1654 cla_masked = ""
1655 for i in range(0, 2):
1656 if cla_match[i] == 'x':
1657 cla_masked += 'x'
1658 else:
1659 cla_masked += cla[i]
1660 if cla_masked == cla_match:
1661 return True
1662 return False
1663
1664
1665class CardCommandSet:
1666 """A set of card instructions, typically specified within one spec."""
Harald Weltec91085e2022-02-10 18:05:45 +01001667
Harald Welte90441432021-05-02 21:28:12 +02001668 def __init__(self, name, cmds=[]):
1669 self.name = name
Harald Weltec91085e2022-02-10 18:05:45 +01001670 self.cmds = {c.ins: c for c in cmds}
Harald Welte90441432021-05-02 21:28:12 +02001671
1672 def __str__(self):
1673 return self.name
1674
1675 def __getitem__(self, idx):
1676 return self.cmds[idx]
1677
1678 def __add__(self, other):
1679 if isinstance(other, CardCommand):
1680 if other.ins in self.cmds:
1681 raise ValueError('%s: INS 0x%02x already defined: %s' %
1682 (self, other.ins, self.cmds[other.ins]))
1683 self.cmds[other.ins] = other
1684 elif isinstance(other, CardCommandSet):
1685 for c in other.cmds.keys():
1686 self.cmds[c] = other.cmds[c]
1687 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001688 raise ValueError(
1689 '%s: Unsupported type to add operator: %s' % (self, other))
Harald Welte90441432021-05-02 21:28:12 +02001690
1691 def lookup(self, ins, cla=None):
1692 """look-up the command within the CommandSet."""
1693 ins = int(ins)
1694 if not ins in self.cmds:
1695 return None
1696 cmd = self.cmds[ins]
1697 if cla and not cmd.match_cla(cla):
1698 return None
1699 return cmd
Philipp Maiera028c7d2021-11-08 16:12:03 +01001700
Harald Weltec91085e2022-02-10 18:05:45 +01001701
Philipp Maiera028c7d2021-11-08 16:12:03 +01001702def all_subclasses(cls) -> set:
Harald Weltec91085e2022-02-10 18:05:45 +01001703 """Recursively get all subclasses of a specified class"""
1704 return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])