blob: c6362fabfe82d9e215b537f130b1b556e80cdaff [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 Welte52255572021-04-03 09:56:32 +020010from typing import Optional, List, Dict, Any, Tuple
11
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
30Hexstr = str
Sylvain Munaut76504e02010-12-07 00:24:32 +010031
Sylvain Munaut76504e02010-12-07 00:24:32 +010032
Harald Weltec91085e2022-02-10 18:05:45 +010033def h2b(s: Hexstr) -> bytearray:
34 """convert from a string of hex nibbles to a sequence of bytes"""
35 return bytearray.fromhex(s)
Sylvain Munaut76504e02010-12-07 00:24:32 +010036
Sylvain Munaut76504e02010-12-07 00:24:32 +010037
Harald Weltec91085e2022-02-10 18:05:45 +010038def b2h(b: bytearray) -> Hexstr:
39 """convert from a sequence of bytes to a string of hex nibbles"""
40 return ''.join(['%02x' % (x) for x in b])
Sylvain Munaut76504e02010-12-07 00:24:32 +010041
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030042
Harald Weltec91085e2022-02-10 18:05:45 +010043def h2i(s: Hexstr) -> List[int]:
44 """convert from a string of hex nibbles to a list of integers"""
45 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 +030046
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020047
Harald Weltec91085e2022-02-10 18:05:45 +010048def i2h(s: List[int]) -> Hexstr:
49 """convert from a list of integers to a string of hex nibbles"""
50 return ''.join(['%02x' % (x) for x in s])
Sylvain Munaut76504e02010-12-07 00:24:32 +010051
Sylvain Munaut76504e02010-12-07 00:24:32 +010052
Harald Weltec91085e2022-02-10 18:05:45 +010053def h2s(s: Hexstr) -> str:
54 """convert from a string of hex nibbles to an ASCII string"""
55 return ''.join([chr((int(x, 16) << 4)+int(y, 16)) for x, y in zip(s[0::2], s[1::2])
56 if int(x + y, 16) != 0xff])
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040057
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020058
Harald Weltec91085e2022-02-10 18:05:45 +010059def s2h(s: str) -> Hexstr:
60 """convert from an ASCII string to a string of hex nibbles"""
61 b = bytearray()
62 b.extend(map(ord, s))
63 return b2h(b)
Philipp Maier796ca3d2021-11-01 17:13:57 +010064
Philipp Maier796ca3d2021-11-01 17:13:57 +010065
Harald Weltec91085e2022-02-10 18:05:45 +010066def i2s(s: List[int]) -> str:
67 """convert from a list of integers to an ASCII string"""
68 return ''.join([chr(x) for x in s])
69
70
71def swap_nibbles(s: Hexstr) -> Hexstr:
72 """swap the nibbles in a hex string"""
73 return ''.join([x+y for x, y in zip(s[1::2], s[0::2])])
74
75
76def rpad(s: str, l: int, c='f') -> str:
77 """pad string on the right side.
78 Args:
79 s : string to pad
80 l : total length to pad to
81 c : padding character
82 Returns:
83 String 's' padded with as many 'c' as needed to reach total length of 'l'
84 """
85 return s + c * (l - len(s))
86
87
88def lpad(s: str, l: int, c='f') -> str:
89 """pad string on the left side.
90 Args:
91 s : string to pad
92 l : total length to pad to
93 c : padding character
94 Returns:
95 String 's' padded with as many 'c' as needed to reach total length of 'l'
96 """
97 return c * (l - len(s)) + s
98
99
100def half_round_up(n: int) -> int:
101 return (n + 1)//2
102
103
104def str_sanitize(s: str) -> str:
105 """replace all non printable chars, line breaks and whitespaces, with ' ', make sure that
106 there are no whitespaces at the end and at the beginning of the string.
107
108 Args:
109 s : string to sanitize
110 Returns:
111 filtered result of string 's'
112 """
113
114 chars_to_keep = string.digits + string.ascii_letters + string.punctuation
115 res = ''.join([c if c in chars_to_keep else ' ' for c in s])
116 return res.strip()
Philipp Maier796ca3d2021-11-01 17:13:57 +0100117
Harald Welte917d98c2021-04-21 11:51:25 +0200118#########################################################################
Harald Welte9f3b44d2021-05-04 18:18:09 +0200119# poor man's COMPREHENSION-TLV decoder.
120#########################################################################
121
Harald Weltec91085e2022-02-10 18:05:45 +0100122
123def comprehensiontlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
Harald Welte6912b1b2021-05-24 23:16:44 +0200124 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
125 if binary[0] in [0x00, 0x80, 0xff]:
Harald Weltec91085e2022-02-10 18:05:45 +0100126 raise ValueError("Found illegal value 0x%02x in %s" %
127 (binary[0], binary))
Harald Welte6912b1b2021-05-24 23:16:44 +0200128 if binary[0] == 0x7f:
129 # three-byte tag
130 tag = binary[0] << 16 | binary[1] << 8 | binary[2]
131 return (tag, binary[3:])
132 elif binary[0] == 0xff:
133 return None, binary
134 else:
135 # single byte tag
136 tag = binary[0]
137 return (tag, binary[1:])
138
Harald Weltec91085e2022-02-10 18:05:45 +0100139
140def comprehensiontlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
Harald Welte9f3b44d2021-05-04 18:18:09 +0200141 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
142 if binary[0] in [0x00, 0x80, 0xff]:
Harald Weltec91085e2022-02-10 18:05:45 +0100143 raise ValueError("Found illegal value 0x%02x in %s" %
144 (binary[0], binary))
Harald Welte9f3b44d2021-05-04 18:18:09 +0200145 if binary[0] == 0x7f:
146 # three-byte tag
147 tag = (binary[1] & 0x7f) << 8
148 tag |= binary[2]
149 compr = True if binary[1] & 0x80 else False
150 return ({'comprehension': compr, 'tag': tag}, binary[3:])
151 else:
152 # single byte tag
153 tag = binary[0] & 0x7f
154 compr = True if binary[0] & 0x80 else False
155 return ({'comprehension': compr, 'tag': tag}, binary[1:])
156
Harald Weltec91085e2022-02-10 18:05:45 +0100157
Harald Welte9f3b44d2021-05-04 18:18:09 +0200158def comprehensiontlv_encode_tag(tag) -> bytes:
159 """Encode a single Tag according to ETSI TS 101 220 Section 7.1.1"""
160 # permit caller to specify tag also as integer value
161 if isinstance(tag, int):
162 compr = True if tag < 0xff and tag & 0x80 else False
163 tag = {'tag': tag, 'comprehension': compr}
164 compr = tag.get('comprehension', False)
165 if tag['tag'] in [0x00, 0x80, 0xff] or tag['tag'] > 0xff:
166 # 3-byte format
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300167 byte3 = tag['tag'] & 0xff
Harald Welte9f3b44d2021-05-04 18:18:09 +0200168 byte2 = (tag['tag'] >> 8) & 0x7f
169 if compr:
170 byte2 |= 0x80
171 return b'\x7f' + byte2.to_bytes(1, 'big') + byte3.to_bytes(1, 'big')
172 else:
173 # 1-byte format
174 ret = tag['tag']
175 if compr:
176 ret |= 0x80
177 return ret.to_bytes(1, 'big')
178
179# length value coding is equal to BER-TLV
180
Harald Welte6912b1b2021-05-24 23:16:44 +0200181
Harald Weltec91085e2022-02-10 18:05:45 +0100182def comprehensiontlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
183 """Parse a single TLV IE at the start of the given binary data.
184 Args:
185 binary : binary input data of BER-TLV length field
186 Returns:
187 Tuple of (tag:dict, len:int, remainder:bytes)
188 """
189 (tagdict, remainder) = comprehensiontlv_parse_tag(binary)
190 (length, remainder) = bertlv_parse_len(remainder)
191 value = remainder[:length]
192 remainder = remainder[length:]
193 return (tagdict, length, value, remainder)
Harald Welte6912b1b2021-05-24 23:16:44 +0200194
Harald Welte9f3b44d2021-05-04 18:18:09 +0200195
196#########################################################################
Harald Welte917d98c2021-04-21 11:51:25 +0200197# poor man's BER-TLV decoder. To be a more sophisticated OO library later
198#########################################################################
199
Harald Weltec91085e2022-02-10 18:05:45 +0100200def bertlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
201 """Get a single raw Tag from start of input according to ITU-T X.690 8.1.2
202 Args:
203 binary : binary input data of BER-TLV length field
204 Returns:
205 Tuple of (tag:int, remainder:bytes)
206 """
207 # check for FF padding at the end, as customary in SIM card files
208 if binary[0] == 0xff and len(binary) == 1 or binary[0] == 0xff and binary[1] == 0xff:
209 return None, binary
210 tag = binary[0] & 0x1f
211 if tag <= 30:
212 return binary[0], binary[1:]
213 else: # multi-byte tag
214 tag = binary[0]
215 i = 1
216 last = False
217 while not last:
218 last = False if binary[i] & 0x80 else True
219 tag <<= 8
220 tag |= binary[i]
221 i += 1
222 return tag, binary[i:]
Harald Welte6912b1b2021-05-24 23:16:44 +0200223
Harald Weltec91085e2022-02-10 18:05:45 +0100224
225def bertlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
226 """Parse a single Tag value according to ITU-T X.690 8.1.2
227 Args:
228 binary : binary input data of BER-TLV length field
229 Returns:
230 Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes)
231 """
232 cls = binary[0] >> 6
233 constructed = True if binary[0] & 0x20 else False
234 tag = binary[0] & 0x1f
235 if tag <= 30:
236 return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[1:])
237 else: # multi-byte tag
238 tag = 0
239 i = 1
240 last = False
241 while not last:
242 last = False if binary[i] & 0x80 else True
243 tag <<= 7
244 tag |= binary[i] & 0x7f
245 i += 1
246 return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[i:])
247
Harald Welte917d98c2021-04-21 11:51:25 +0200248
Harald Weltef0885b12021-05-24 23:18:59 +0200249def bertlv_encode_tag(t) -> bytes:
250 """Encode a single Tag value according to ITU-T X.690 8.1.2
251 """
Harald Weltec91085e2022-02-10 18:05:45 +0100252 def get_top7_bits(inp: int) -> Tuple[int, int]:
Harald Weltef0885b12021-05-24 23:18:59 +0200253 """Get top 7 bits of integer. Returns those 7 bits as integer and the remaining LSBs."""
254 remain_bits = inp.bit_length()
255 if remain_bits >= 7:
256 bitcnt = 7
257 else:
258 bitcnt = remain_bits
259 outp = inp >> (remain_bits - bitcnt)
260 remainder = inp & ~ (inp << (remain_bits - bitcnt))
261 return outp, remainder
262
263 if isinstance(t, int):
264 # FIXME: multiple byte tags
265 tag = t & 0x1f
266 constructed = True if t & 0x20 else False
267 cls = t >> 6
268 else:
269 tag = t['tag']
270 constructed = t['constructed']
271 cls = t['class']
272 if tag <= 30:
273 t = tag & 0x1f
274 if constructed:
275 t |= 0x20
276 t |= (cls & 3) << 6
277 return bytes([t])
Harald Weltec91085e2022-02-10 18:05:45 +0100278 else: # multi-byte tag
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300279 t = 0x1f
Harald Weltef0885b12021-05-24 23:18:59 +0200280 if constructed:
281 t |= 0x20
282 t |= (cls & 3) << 6
283 tag_bytes = bytes([t])
284 remain = tag
285 while True:
286 t, remain = get_top7_bits(remain)
287 if remain:
288 t |= 0x80
289 tag_bytes += bytes([t])
290 if not remain:
291 break
292 return tag_bytes
293
Harald Welte917d98c2021-04-21 11:51:25 +0200294
Harald Weltec91085e2022-02-10 18:05:45 +0100295def bertlv_parse_len(binary: bytes) -> Tuple[int, bytes]:
296 """Parse a single Length value according to ITU-T X.690 8.1.3;
297 only the definite form is supported here.
298 Args:
299 binary : binary input data of BER-TLV length field
300 Returns:
301 Tuple of (length, remainder)
302 """
303 if binary[0] < 0x80:
304 return (binary[0], binary[1:])
305 else:
306 num_len_oct = binary[0] & 0x7f
307 length = 0
308 for i in range(1, 1+num_len_oct):
309 length <<= 8
310 length |= binary[i]
311 return (length, binary[1+num_len_oct:])
Harald Welte917d98c2021-04-21 11:51:25 +0200312
Harald Welte917d98c2021-04-21 11:51:25 +0200313
Harald Weltec91085e2022-02-10 18:05:45 +0100314def bertlv_encode_len(length: int) -> bytes:
315 """Encode a single Length value according to ITU-T X.690 8.1.3;
316 only the definite form is supported here.
317 Args:
318 length : length value to be encoded
319 Returns:
320 binary output data of BER-TLV length field
321 """
322 if length < 0x80:
323 return length.to_bytes(1, 'big')
324 elif length <= 0xff:
325 return b'\x81' + length.to_bytes(1, 'big')
326 elif length <= 0xffff:
327 return b'\x82' + length.to_bytes(2, 'big')
328 elif length <= 0xffffff:
329 return b'\x83' + length.to_bytes(3, 'big')
330 elif length <= 0xffffffff:
331 return b'\x84' + length.to_bytes(4, 'big')
332 else:
333 raise ValueError("Length > 32bits not supported")
334
335
336def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
337 """Parse a single TLV IE at the start of the given binary data.
338 Args:
339 binary : binary input data of BER-TLV length field
340 Returns:
341 Tuple of (tag:dict, len:int, remainder:bytes)
342 """
343 (tagdict, remainder) = bertlv_parse_tag(binary)
344 (length, remainder) = bertlv_parse_len(remainder)
345 value = remainder[:length]
346 remainder = remainder[length:]
347 return (tagdict, length, value, remainder)
Harald Welte917d98c2021-04-21 11:51:25 +0200348
349
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200350# IMSI encoded format:
351# For IMSI 0123456789ABCDE:
352#
353# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
354# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
355#
356# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
357#
358# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
359# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
360# encode itself.
361#
362# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
363# even length IMSI only uses half of the last byte.
364
Harald Weltec91085e2022-02-10 18:05:45 +0100365def enc_imsi(imsi: str):
366 """Converts a string IMSI into the encoded value of the EF"""
367 l = half_round_up(
368 len(imsi) + 1) # Required bytes - include space for odd/even indicator
369 oe = len(imsi) & 1 # Odd (1) / Even (0)
370 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe << 3) | 1, rpad(imsi, 15)))
371 return ei
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400372
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400373
Harald Weltec91085e2022-02-10 18:05:45 +0100374def dec_imsi(ef: Hexstr) -> Optional[str]:
375 """Converts an EF value to the IMSI string representation"""
376 if len(ef) < 4:
377 return None
378 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
379 l = l - 1 # Encoded length byte includes oe nibble
380 swapped = swap_nibbles(ef[2:]).rstrip('f')
381 if len(swapped) < 1:
382 return None
383 oe = (int(swapped[0]) >> 3) & 1 # Odd (1) / Even (0)
384 if not oe:
385 # if even, only half of last byte was used
386 l = l-1
387 if l != len(swapped) - 1:
388 return None
389 imsi = swapped[1:]
390 return imsi
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400391
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400392
Harald Weltec91085e2022-02-10 18:05:45 +0100393def dec_iccid(ef: Hexstr) -> str:
394 return swap_nibbles(ef).strip('f')
Philipp Maier6c5cd802021-04-23 21:19:36 +0200395
Philipp Maier6c5cd802021-04-23 21:19:36 +0200396
Harald Weltec91085e2022-02-10 18:05:45 +0100397def enc_iccid(iccid: str) -> Hexstr:
398 return swap_nibbles(rpad(iccid, 20))
Philipp Maier6c5cd802021-04-23 21:19:36 +0200399
Philipp Maier6c5cd802021-04-23 21:19:36 +0200400
Harald Weltec91085e2022-02-10 18:05:45 +0100401def enc_plmn(mcc: Hexstr, mnc: Hexstr) -> Hexstr:
402 """Converts integer MCC/MNC into 3 bytes for EF"""
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300403
Harald Weltec91085e2022-02-10 18:05:45 +0100404 # Make sure there are no excess whitespaces in the input
405 # parameters
406 mcc = mcc.strip()
407 mnc = mnc.strip()
408
409 # Make sure that MCC/MNC are correctly padded with leading
410 # zeros or 'F', depending on the length.
411 if len(mnc) == 0:
412 mnc = "FFF"
413 elif len(mnc) == 1:
414 mnc = "F0" + mnc
415 elif len(mnc) == 2:
416 mnc += "F"
417
418 if len(mcc) == 0:
419 mcc = "FFF"
420 elif len(mcc) == 1:
421 mcc = "00" + mcc
422 elif len(mcc) == 2:
423 mcc = "0" + mcc
424
425 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
426
427
428def dec_plmn(threehexbytes: Hexstr) -> dict:
429 res = {'mcc': "0", 'mnc': "0"}
430 dec_mcc_from_plmn_str(threehexbytes)
431 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
432 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
433 return res
434
Harald Welted7a7e172021-04-07 00:30:10 +0200435
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300436def dec_spn(ef):
Harald Weltec91085e2022-02-10 18:05:45 +0100437 """Obsolete, kept for API compatibility"""
438 from ts_51_011 import EF_SPN
439 abstract_data = EF_SPN().decode_hex(ef)
440 show_in_hplmn = abstract_data['show_in_hplmn']
441 hide_in_oplmn = abstract_data['hide_in_oplmn']
442 name = abstract_data['spn']
443 return (name, show_in_hplmn, hide_in_oplmn)
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300444
Harald Weltec91085e2022-02-10 18:05:45 +0100445
446def enc_spn(name: str, show_in_hplmn=False, hide_in_oplmn=False):
447 """Obsolete, kept for API compatibility"""
448 from ts_51_011 import EF_SPN
449 abstract_data = {
450 'hide_in_oplmn': hide_in_oplmn,
451 'show_in_hplmn': show_in_hplmn,
452 'spn': name,
453 }
454 return EF_SPN().encode_hex(abstract_data)
455
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900456
Supreeth Herlef3948532020-03-24 12:23:51 +0100457def hexstr_to_Nbytearr(s, nbytes):
Harald Weltec91085e2022-02-10 18:05:45 +0100458 return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2))]
Supreeth Herlef3948532020-03-24 12:23:51 +0100459
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100460# Accepts hex string representing three bytes
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100461
Philipp Maier6c5cd802021-04-23 21:19:36 +0200462
Harald Weltec91085e2022-02-10 18:05:45 +0100463def dec_mcc_from_plmn(plmn: Hexstr) -> int:
464 ia = h2i(plmn)
465 digit1 = ia[0] & 0x0F # 1st byte, LSB
466 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
467 digit3 = ia[1] & 0x0F # 2nd byte, LSB
468 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
469 return 0xFFF # 4095
470 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100471
Philipp Maier6c5cd802021-04-23 21:19:36 +0200472
Harald Weltec91085e2022-02-10 18:05:45 +0100473def dec_mcc_from_plmn_str(plmn: Hexstr) -> str:
474 digit1 = plmn[1] # 1st byte, LSB
475 digit2 = plmn[0] # 1st byte, MSB
476 digit3 = plmn[3] # 2nd byte, LSB
477 res = digit1 + digit2 + digit3
478 return res.upper().strip("F")
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100479
Harald Weltec91085e2022-02-10 18:05:45 +0100480
481def dec_mnc_from_plmn(plmn: Hexstr) -> int:
482 ia = h2i(plmn)
483 digit1 = ia[2] & 0x0F # 3rd byte, LSB
484 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
485 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
486 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
487 return 0xFFF # 4095
488 return derive_mnc(digit1, digit2, digit3)
489
490
491def dec_mnc_from_plmn_str(plmn: Hexstr) -> str:
492 digit1 = plmn[5] # 3rd byte, LSB
493 digit2 = plmn[4] # 3rd byte, MSB
494 digit3 = plmn[2] # 2nd byte, MSB
495 res = digit1 + digit2 + digit3
496 return res.upper().strip("F")
497
498
499def dec_act(twohexbytes: Hexstr) -> List[str]:
500 act_list = [
501 {'bit': 15, 'name': "UTRAN"},
502 {'bit': 14, 'name': "E-UTRAN"},
503 {'bit': 11, 'name': "NG-RAN"},
504 {'bit': 7, 'name': "GSM"},
505 {'bit': 6, 'name': "GSM COMPACT"},
506 {'bit': 5, 'name': "cdma2000 HRPD"},
507 {'bit': 4, 'name': "cdma2000 1xRTT"},
508 ]
509 ia = h2i(twohexbytes)
510 u16t = (ia[0] << 8) | ia[1]
511 sel = []
512 for a in act_list:
513 if u16t & (1 << a['bit']):
514 if a['name'] == "E-UTRAN":
515 # The Access technology identifier of E-UTRAN
516 # allows a more detailed specification:
517 if u16t & (1 << 13) and u16t & (1 << 12):
518 sel.append("E-UTRAN WB-S1")
519 sel.append("E-UTRAN NB-S1")
520 elif u16t & (1 << 13):
521 sel.append("E-UTRAN WB-S1")
522 elif u16t & (1 << 12):
523 sel.append("E-UTRAN NB-S1")
524 else:
525 sel.append("E-UTRAN")
526 else:
527 sel.append(a['name'])
528 return sel
529
530
531def dec_xplmn_w_act(fivehexbytes: Hexstr) -> Dict[str, Any]:
532 res = {'mcc': "0", 'mnc': "0", 'act': []}
533 plmn_chars = 6
534 act_chars = 4
535 # first three bytes (six ascii hex chars)
536 plmn_str = fivehexbytes[:plmn_chars]
537 # two bytes after first three bytes
538 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars]
539 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
540 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
541 res['act'] = dec_act(act_str)
542 return res
543
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100544
545def format_xplmn_w_act(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100546 s = ""
547 for rec_data in hexstr_to_Nbytearr(hexstr, 5):
548 rec_info = dec_xplmn_w_act(rec_data)
549 if rec_info['mcc'] == "" and rec_info['mnc'] == "":
550 rec_str = "unused"
551 else:
552 rec_str = "MCC: %s MNC: %s AcT: %s" % (
553 rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act']))
554 s += "\t%s # %s\n" % (rec_data, rec_str)
555 return s
556
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100557
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100558def dec_loci(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100559 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
560 res['tmsi'] = hexstr[:8]
561 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
562 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
563 res['lac'] = hexstr[14:18]
564 res['status'] = h2i(hexstr[20:22])
565 return res
566
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100567
568def dec_psloci(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100569 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0,
570 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
571 res['p-tmsi'] = hexstr[:8]
572 res['p-tmsi-sig'] = hexstr[8:14]
573 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
574 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
575 res['lac'] = hexstr[20:24]
576 res['rac'] = hexstr[24:26]
577 res['status'] = h2i(hexstr[26:28])
578 return res
579
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100580
581def dec_epsloci(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100582 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
583 res['guti'] = hexstr[:24]
584 res['tai'] = hexstr[24:34]
585 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
586 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
587 res['tac'] = hexstr[30:34]
588 res['status'] = h2i(hexstr[34:36])
589 return res
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100590
Harald Welteca673942020-06-03 15:19:40 +0200591
Harald Weltec91085e2022-02-10 18:05:45 +0100592def dec_xplmn(threehexbytes: Hexstr) -> dict:
593 res = {'mcc': 0, 'mnc': 0, 'act': []}
594 plmn_chars = 6
595 # first three bytes (six ascii hex chars)
596 plmn_str = threehexbytes[:plmn_chars]
Matan Perelman60951b02023-06-01 17:39:04 +0300597 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
598 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Harald Weltec91085e2022-02-10 18:05:45 +0100599 return res
Harald Welteca673942020-06-03 15:19:40 +0200600
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900601
Harald Weltec91085e2022-02-10 18:05:45 +0100602def format_xplmn(hexstr: Hexstr) -> str:
603 s = ""
604 for rec_data in hexstr_to_Nbytearr(hexstr, 3):
605 rec_info = dec_xplmn(rec_data)
Matan Perelman60951b02023-06-01 17:39:04 +0300606 if not rec_info['mcc'] and not rec_info['mnc']:
Harald Weltec91085e2022-02-10 18:05:45 +0100607 rec_str = "unused"
608 else:
Matan Perelman60951b02023-06-01 17:39:04 +0300609 rec_str = "MCC: %s MNC: %s" % (rec_info['mcc'], rec_info['mnc'])
Harald Weltec91085e2022-02-10 18:05:45 +0100610 s += "\t%s # %s\n" % (rec_data, rec_str)
611 return s
612
613
614def derive_milenage_opc(ki_hex: Hexstr, op_hex: Hexstr) -> Hexstr:
615 """
616 Run the milenage algorithm to calculate OPC from Ki and OP
617 """
Harald Welted75fa3f2023-05-31 20:47:55 +0200618 from Cryptodome.Cipher import AES
Harald Weltec91085e2022-02-10 18:05:45 +0100619 # pylint: disable=no-name-in-module
Harald Welted75fa3f2023-05-31 20:47:55 +0200620 from Cryptodome.Util.strxor import strxor
Harald Weltec91085e2022-02-10 18:05:45 +0100621 from pySim.utils import b2h
622
623 # We pass in hex string and now need to work on bytes
624 ki_bytes = bytes(h2b(ki_hex))
625 op_bytes = bytes(h2b(op_hex))
626 aes = AES.new(ki_bytes, AES.MODE_ECB)
627 opc_bytes = aes.encrypt(op_bytes)
628 return b2h(strxor(opc_bytes, op_bytes))
629
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900630
Harald Welte52255572021-04-03 09:56:32 +0200631def calculate_luhn(cc) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100632 """
633 Calculate Luhn checksum used in e.g. ICCID and IMEI
634 """
635 num = list(map(int, str(cc)))
636 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10))
637 for d in num[::-2]]) % 10
638 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200639
Philipp Maier7592eee2019-09-12 13:03:23 +0200640
Harald Weltec91085e2022-02-10 18:05:45 +0100641def mcc_from_imsi(imsi: str) -> Optional[str]:
642 """
643 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
644 """
645 if imsi == None:
646 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200647
Harald Weltec91085e2022-02-10 18:05:45 +0100648 if len(imsi) > 3:
649 return imsi[:3]
650 else:
651 return None
Philipp Maier7592eee2019-09-12 13:03:23 +0200652
Supreeth Herled24f1632019-11-30 10:37:09 +0100653
Harald Weltec91085e2022-02-10 18:05:45 +0100654def mnc_from_imsi(imsi: str, long: bool = False) -> Optional[str]:
655 """
656 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
657 """
658 if imsi == None:
659 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100660
Harald Weltec91085e2022-02-10 18:05:45 +0100661 if len(imsi) > 3:
662 if long:
663 return imsi[3:6]
664 else:
665 return imsi[3:5]
666 else:
667 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100668
Supreeth Herled24f1632019-11-30 10:37:09 +0100669
Harald Weltec91085e2022-02-10 18:05:45 +0100670def derive_mcc(digit1: int, digit2: int, digit3: int) -> int:
671 """
672 Derive decimal representation of the MCC (Mobile Country Code)
673 from three given digits.
674 """
Supreeth Herled24f1632019-11-30 10:37:09 +0100675
Harald Weltec91085e2022-02-10 18:05:45 +0100676 mcc = 0
Supreeth Herled24f1632019-11-30 10:37:09 +0100677
Harald Weltec91085e2022-02-10 18:05:45 +0100678 if digit1 != 0x0f:
679 mcc += digit1 * 100
680 if digit2 != 0x0f:
681 mcc += digit2 * 10
682 if digit3 != 0x0f:
683 mcc += digit3
Supreeth Herled24f1632019-11-30 10:37:09 +0100684
Harald Weltec91085e2022-02-10 18:05:45 +0100685 return mcc
Supreeth Herled24f1632019-11-30 10:37:09 +0100686
Supreeth Herled24f1632019-11-30 10:37:09 +0100687
Harald Weltec91085e2022-02-10 18:05:45 +0100688def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int:
689 """
690 Derive decimal representation of the MNC (Mobile Network Code)
691 from two or (optionally) three given digits.
692 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100693
Harald Weltec91085e2022-02-10 18:05:45 +0100694 mnc = 0
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100695
Harald Weltec91085e2022-02-10 18:05:45 +0100696 # 3-rd digit is optional for the MNC. If present
697 # the algorythm is the same as for the MCC.
698 if digit3 != 0x0f:
699 return derive_mcc(digit1, digit2, digit3)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100700
Harald Weltec91085e2022-02-10 18:05:45 +0100701 if digit1 != 0x0f:
702 mnc += digit1 * 10
703 if digit2 != 0x0f:
704 mnc += digit2
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100705
Harald Weltec91085e2022-02-10 18:05:45 +0100706 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100707
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100708
Harald Weltec91085e2022-02-10 18:05:45 +0100709def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]:
710 """
711 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
712 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
713 """
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100714
Harald Weltec91085e2022-02-10 18:05:45 +0100715 # Convert from str to (kind of) 'bytes'
716 ef_msisdn = h2b(ef_msisdn)
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100717
Harald Weltec91085e2022-02-10 18:05:45 +0100718 # Make sure mandatory fields are present
719 if len(ef_msisdn) < 14:
720 raise ValueError("EF.MSISDN is too short")
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100721
Harald Weltec91085e2022-02-10 18:05:45 +0100722 # Skip optional Alpha Identifier
723 xlen = len(ef_msisdn) - 14
724 msisdn_lhv = ef_msisdn[xlen:]
Supreeth Herle5a541012019-12-22 08:59:16 +0100725
Harald Weltec91085e2022-02-10 18:05:45 +0100726 # Parse the length (in bytes) of the BCD encoded number
727 bcd_len = msisdn_lhv[0]
728 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
729 if bcd_len == 0xff:
730 return None
731 elif bcd_len > 11 or bcd_len < 1:
732 raise ValueError(
733 "Length of MSISDN (%d bytes) is out of range" % bcd_len)
Supreeth Herle5a541012019-12-22 08:59:16 +0100734
Harald Weltec91085e2022-02-10 18:05:45 +0100735 # Parse ToN / NPI
736 ton = (msisdn_lhv[1] >> 4) & 0x07
737 npi = msisdn_lhv[1] & 0x0f
738 bcd_len -= 1
Supreeth Herle5a541012019-12-22 08:59:16 +0100739
Harald Weltec91085e2022-02-10 18:05:45 +0100740 # No MSISDN?
741 if not bcd_len:
742 return (npi, ton, None)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200743
Harald Weltec91085e2022-02-10 18:05:45 +0100744 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
745 # International number 10.5.118/3GPP TS 24.008
746 if ton == 0x01:
747 msisdn = '+' + msisdn
Supreeth Herle5a541012019-12-22 08:59:16 +0100748
Harald Weltec91085e2022-02-10 18:05:45 +0100749 return (npi, ton, msisdn)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200750
Supreeth Herle5a541012019-12-22 08:59:16 +0100751
Harald Weltec91085e2022-02-10 18:05:45 +0100752def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
753 """
754 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
755 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
756 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100757
Harald Weltec91085e2022-02-10 18:05:45 +0100758 Default NPI / ToN values:
759 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
760 - ToN: network specific or international number (if starts with '+').
761 """
762
763 # If no MSISDN is supplied then encode the file contents as all "ff"
764 if msisdn == "" or msisdn == "+":
765 return "ff" * 14
766
767 # Leading '+' indicates International Number
768 if msisdn[0] == '+':
769 msisdn = msisdn[1:]
770 ton = 0x01
771
772 # An MSISDN must not exceed 20 digits
773 if len(msisdn) > 20:
774 raise ValueError("msisdn must not be longer than 20 digits")
775
776 # Append 'f' padding if number of digits is odd
777 if len(msisdn) % 2 > 0:
778 msisdn += 'f'
779
780 # BCD length also includes NPI/ToN header
781 bcd_len = len(msisdn) // 2 + 1
782 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
783 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
784
785 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200786
Supreeth Herle441c4a72020-03-24 10:19:15 +0100787
Harald Welte52255572021-04-03 09:56:32 +0200788def dec_st(st, table="sim") -> str:
Harald Weltec91085e2022-02-10 18:05:45 +0100789 """
790 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
791 """
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200792
Harald Weltec91085e2022-02-10 18:05:45 +0100793 if table == "isim":
794 from pySim.ts_31_103 import EF_IST_map
795 lookup_map = EF_IST_map
796 elif table == "usim":
797 from pySim.ts_31_102 import EF_UST_map
798 lookup_map = EF_UST_map
799 else:
800 from pySim.ts_51_011 import EF_SST_map
801 lookup_map = EF_SST_map
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200802
Harald Weltec91085e2022-02-10 18:05:45 +0100803 st_bytes = [st[i:i+2] for i in range(0, len(st), 2)]
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200804
Harald Weltec91085e2022-02-10 18:05:45 +0100805 avail_st = ""
806 # Get each byte and check for available services
807 for i in range(0, len(st_bytes)):
808 # Byte i contains info about Services num (8i+1) to num (8i+8)
809 byte = int(st_bytes[i], 16)
810 # Services in each byte are in order MSB to LSB
811 # MSB - Service (8i+8)
812 # LSB - Service (8i+1)
813 for j in range(1, 9):
814 if byte & 0x01 == 0x01 and ((8*i) + j in lookup_map):
815 # Byte X contains info about Services num (8X-7) to num (8X)
816 # bit = 1: service available
817 # bit = 0: service not available
818 avail_st += '\tService %d - %s\n' % (
819 (8*i) + j, lookup_map[(8*i) + j])
820 byte = byte >> 1
821 return avail_st
822
Supreeth Herle98370552020-05-11 09:04:41 +0200823
824def first_TLV_parser(bytelist):
Harald Weltec91085e2022-02-10 18:05:45 +0100825 '''
826 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
Supreeth Herle98370552020-05-11 09:04:41 +0200827
Harald Weltec91085e2022-02-10 18:05:45 +0100828 parses first TLV format record in a list of bytelist
829 returns a 3-Tuple: Tag, Length, Value
830 Value is a list of bytes
831 parsing of length is ETSI'style 101.220
832 '''
833 Tag = bytelist[0]
834 if bytelist[1] == 0xFF:
835 Len = bytelist[2]*256 + bytelist[3]
836 Val = bytelist[4:4+Len]
837 else:
838 Len = bytelist[1]
839 Val = bytelist[2:2+Len]
840 return (Tag, Len, Val)
841
Supreeth Herle98370552020-05-11 09:04:41 +0200842
843def TLV_parser(bytelist):
Harald Weltec91085e2022-02-10 18:05:45 +0100844 '''
845 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
Supreeth Herle98370552020-05-11 09:04:41 +0200846
Harald Weltec91085e2022-02-10 18:05:45 +0100847 loops on the input list of bytes with the "first_TLV_parser()" function
848 returns a list of 3-Tuples
849 '''
850 ret = []
851 while len(bytelist) > 0:
852 T, L, V = first_TLV_parser(bytelist)
853 if T == 0xFF:
854 # padding bytes
855 break
856 ret.append((T, L, V))
857 # need to manage length of L
858 if L > 0xFE:
859 bytelist = bytelist[L+4:]
860 else:
861 bytelist = bytelist[L+2:]
862 return ret
863
Supreeth Herled572ede2020-03-22 09:55:04 +0100864
Supreeth Herled84daa12020-03-24 12:20:40 +0100865def enc_st(st, service, state=1):
Harald Weltec91085e2022-02-10 18:05:45 +0100866 """
867 Encodes the EF S/U/IST/EST and returns the updated Service Table
Supreeth Herled84daa12020-03-24 12:20:40 +0100868
Harald Weltec91085e2022-02-10 18:05:45 +0100869 Parameters:
870 st - Current value of SIM/USIM/ISIM Service Table
871 service - Service Number to encode as activated/de-activated
872 state - 1 mean activate, 0 means de-activate
Supreeth Herled84daa12020-03-24 12:20:40 +0100873
Harald Weltec91085e2022-02-10 18:05:45 +0100874 Returns:
875 s - Modified value of SIM/USIM/ISIM Service Table
Supreeth Herled84daa12020-03-24 12:20:40 +0100876
Harald Weltec91085e2022-02-10 18:05:45 +0100877 Default values:
878 - state: 1 - Sets the particular Service bit to 1
879 """
880 st_bytes = [st[i:i+2] for i in range(0, len(st), 2)]
Supreeth Herled84daa12020-03-24 12:20:40 +0100881
Harald Weltec91085e2022-02-10 18:05:45 +0100882 s = ""
883 # Check whether the requested service is present in each byte
884 for i in range(0, len(st_bytes)):
885 # Byte i contains info about Services num (8i+1) to num (8i+8)
886 if service in range((8*i) + 1, (8*i) + 9):
887 byte = int(st_bytes[i], 16)
888 # Services in each byte are in order MSB to LSB
889 # MSB - Service (8i+8)
890 # LSB - Service (8i+1)
891 mod_byte = 0x00
892 # Copy bit by bit contents of byte to mod_byte with modified bit
893 # for requested service
894 for j in range(1, 9):
895 mod_byte = mod_byte >> 1
896 if service == (8*i) + j:
897 mod_byte = state == 1 and mod_byte | 0x80 or mod_byte & 0x7f
898 else:
899 mod_byte = byte & 0x01 == 0x01 and mod_byte | 0x80 or mod_byte & 0x7f
900 byte = byte >> 1
Supreeth Herled84daa12020-03-24 12:20:40 +0100901
Harald Weltec91085e2022-02-10 18:05:45 +0100902 s += ('%02x' % (mod_byte))
903 else:
904 s += st_bytes[i]
Supreeth Herled84daa12020-03-24 12:20:40 +0100905
Harald Weltec91085e2022-02-10 18:05:45 +0100906 return s
907
Supreeth Herled84daa12020-03-24 12:20:40 +0100908
Supreeth Herle3b342c22020-03-24 16:15:02 +0100909def dec_addr_tlv(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100910 """
911 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
912 See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104.
913 """
Supreeth Herled572ede2020-03-22 09:55:04 +0100914
Harald Weltec91085e2022-02-10 18:05:45 +0100915 # Convert from hex str to int bytes list
916 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100917
Harald Weltec91085e2022-02-10 18:05:45 +0100918 # Get list of tuples containing parsed TLVs
919 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100920
Harald Weltec91085e2022-02-10 18:05:45 +0100921 for tlv in tlvs:
922 # tlv = (T, L, [V])
923 # T = Tag
924 # L = Length
925 # [V] = List of value
Supreeth Herled572ede2020-03-22 09:55:04 +0100926
Harald Weltec91085e2022-02-10 18:05:45 +0100927 # Invalid Tag value scenario
928 if tlv[0] != 0x80:
929 continue
Supreeth Herled572ede2020-03-22 09:55:04 +0100930
Harald Weltec91085e2022-02-10 18:05:45 +0100931 # Empty field - Zero length
932 if tlv[1] == 0:
933 continue
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200934
Harald Weltec91085e2022-02-10 18:05:45 +0100935 # First byte in the value has the address type
936 addr_type = tlv[2][0]
937 # TODO: Support parsing of IPv6
938 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
939 if addr_type == 0x00: # FQDN
940 # Skip address tye byte i.e. first byte in value list
941 content = tlv[2][1:]
942 return (i2s(content), '00')
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200943
Harald Weltec91085e2022-02-10 18:05:45 +0100944 elif addr_type == 0x01: # IPv4
945 # Skip address tye byte i.e. first byte in value list
946 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
947 ipv4 = tlv[2][2:]
948 content = '.'.join(str(x) for x in ipv4)
949 return (content, '01')
950 else:
951 raise ValueError("Invalid address type")
Supreeth Herled572ede2020-03-22 09:55:04 +0100952
Harald Weltec91085e2022-02-10 18:05:45 +0100953 return (None, None)
954
Philipp Maierff84c232020-05-12 17:24:18 +0200955
Supreeth Herle3b342c22020-03-24 16:15:02 +0100956def enc_addr_tlv(addr, addr_type='00'):
Harald Weltec91085e2022-02-10 18:05:45 +0100957 """
958 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
959 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 +0100960
Harald Weltec91085e2022-02-10 18:05:45 +0100961 Default values:
962 - addr_type: 00 - FQDN format of Address
963 """
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100964
Harald Weltec91085e2022-02-10 18:05:45 +0100965 s = ""
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100966
Harald Weltec91085e2022-02-10 18:05:45 +0100967 # TODO: Encoding of IPv6 address
968 if addr_type == '00': # FQDN
969 hex_str = s2h(addr)
970 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
971 elif addr_type == '01': # IPv4
972 ipv4_list = addr.split('.')
973 ipv4_str = ""
974 for i in ipv4_list:
975 ipv4_str += ('%02x' % (int(i)))
Supreeth Herle654eca72020-03-25 14:25:38 +0100976
Harald Weltec91085e2022-02-10 18:05:45 +0100977 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
978 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
979 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100980
Harald Weltec91085e2022-02-10 18:05:45 +0100981 return s
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100982
Philipp Maier47236502021-03-09 21:28:25 +0100983
Harald Weltec91085e2022-02-10 18:05:45 +0100984def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool:
985 """
986 Check if a string is a valid hexstring
987 """
Philipp Maier47236502021-03-09 21:28:25 +0100988
Harald Weltec91085e2022-02-10 18:05:45 +0100989 # Filter obviously bad strings
990 if not string:
991 return False
992 if len(string) < minlen or minlen < 2:
993 return False
994 if len(string) % 2:
995 return False
996 if maxlen and len(string) > maxlen:
997 return False
Philipp Maier47236502021-03-09 21:28:25 +0100998
Harald Weltec91085e2022-02-10 18:05:45 +0100999 # Try actual encoding to be sure
1000 try:
1001 try_encode = h2b(string)
1002 return True
1003 except:
1004 return False
Philipp Maiere8536c02020-05-11 21:35:01 +02001005
Philipp Maiere8536c02020-05-11 21:35:01 +02001006
Harald Weltec91085e2022-02-10 18:05:45 +01001007def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr:
1008 """
1009 The ADM pin can be supplied either in its hexadecimal form or as
1010 ascii string. This function checks the supplied opts parameter and
1011 returns the pin_adm as hex encoded string, regardless in which form
1012 it was originally supplied by the user
1013 """
Philipp Maiere8536c02020-05-11 21:35:01 +02001014
Harald Weltec91085e2022-02-10 18:05:45 +01001015 if pin_adm is not None:
1016 if len(pin_adm) <= 8:
1017 pin_adm = ''.join(['%02x' % (ord(x)) for x in pin_adm])
1018 pin_adm = rpad(pin_adm, 16)
Philipp Maiere8536c02020-05-11 21:35:01 +02001019
Harald Weltec91085e2022-02-10 18:05:45 +01001020 else:
1021 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
1022
1023 if pin_adm_hex is not None:
1024 if len(pin_adm_hex) == 16:
1025 pin_adm = pin_adm_hex
1026 # Ensure that it's hex-encoded
1027 try:
1028 try_encode = h2b(pin_adm)
1029 except ValueError:
1030 raise ValueError(
1031 "PIN-ADM needs to be hex encoded using this option")
1032 else:
1033 raise ValueError(
1034 "PIN-ADM needs to be exactly 16 digits (hex encoded)")
1035
1036 return pin_adm
1037
Philipp Maiere8536c02020-05-11 21:35:01 +02001038
Supreeth Herle95ec7722020-03-24 13:09:03 +01001039def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
Harald Weltec91085e2022-02-10 18:05:45 +01001040 """
1041 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
1042 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 +01001043
Harald Weltec91085e2022-02-10 18:05:45 +01001044 Default values:
1045 - epdg_priority: '0001' - 1st Priority
1046 - epdg_fqdn_format: '00' - Operator Identifier FQDN
1047 """
Supreeth Herle95ec7722020-03-24 13:09:03 +01001048
Harald Weltec91085e2022-02-10 18:05:45 +01001049 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
1050 # TODO: Handle encoding of Length field for length more than 127 Bytes
1051 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
1052 content = rpad(content, len(hexstr))
1053 return content
1054
Supreeth Herle95ec7722020-03-24 13:09:03 +01001055
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001056def dec_ePDGSelection(sixhexbytes):
Harald Weltec91085e2022-02-10 18:05:45 +01001057 """
1058 Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
1059 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
1060 """
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001061
Harald Weltec91085e2022-02-10 18:05:45 +01001062 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
1063 plmn_chars = 6
1064 epdg_priority_chars = 4
1065 epdg_fqdn_format_chars = 2
1066 # first three bytes (six ascii hex chars)
1067 plmn_str = sixhexbytes[:plmn_chars]
1068 # two bytes after first three bytes
1069 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars +
1070 epdg_priority_chars]
1071 # one byte after first five bytes
1072 epdg_fqdn_format_str = sixhexbytes[plmn_chars +
1073 epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
1074 res['mcc'] = dec_mcc_from_plmn(plmn_str)
1075 res['mnc'] = dec_mnc_from_plmn(plmn_str)
1076 res['epdg_priority'] = epdg_priority_str
1077 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
1078 return res
1079
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001080
1081def format_ePDGSelection(hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +01001082 ePDGSelection_info_tag_chars = 2
1083 ePDGSelection_info_tag_str = hexstr[:2]
1084 s = ""
1085 # Minimum length
1086 len_chars = 2
1087 # TODO: Need to determine length properly - definite length support only
1088 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
1089 # As per spec, length is 5n, n - number of PLMNs
1090 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
1091 # Totalling to 6 Bytes, maybe length should be 6n
1092 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +01001093
Harald Weltec91085e2022-02-10 18:05:45 +01001094 # Not programmed scenario
1095 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
1096 len_chars = 0
1097 ePDGSelection_info_tag_chars = 0
1098 if len_str[0] == '8':
1099 # The bits 7 to 1 denotes the number of length octets if length > 127
1100 if int(len_str[1]) > 0:
1101 # Update number of length octets
1102 len_chars = len_chars * int(len_str[1])
1103 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001104
Harald Weltec91085e2022-02-10 18:05:45 +01001105 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
1106 # Right pad to prevent index out of range - multiple of 6 bytes
1107 content_str = rpad(content_str, len(content_str) +
1108 (12 - (len(content_str) % 12)))
1109 for rec_data in hexstr_to_Nbytearr(content_str, 6):
1110 rec_info = dec_ePDGSelection(rec_data)
1111 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
1112 rec_str = "unused"
1113 else:
1114 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
1115 (rec_info['mcc'], rec_info['mnc'],
1116 rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
1117 s += "\t%s # %s\n" % (rec_data, rec_str)
1118 return s
1119
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001120
1121def get_addr_type(addr):
Harald Weltec91085e2022-02-10 18:05:45 +01001122 """
1123 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
1124 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001125
Harald Weltec91085e2022-02-10 18:05:45 +01001126 TODO: Handle IPv6
1127 """
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001128
Harald Weltec91085e2022-02-10 18:05:45 +01001129 # Empty address string
1130 if not len(addr):
1131 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001132
Harald Weltec91085e2022-02-10 18:05:45 +01001133 addr_list = addr.split('.')
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001134
Harald Weltec91085e2022-02-10 18:05:45 +01001135 # Check for IPv4/IPv6
1136 try:
1137 import ipaddress
1138 # Throws ValueError if addr is not correct
1139 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001140
Harald Weltec91085e2022-02-10 18:05:45 +01001141 if ipa.version == 4:
1142 return 0x01
1143 elif ipa.version == 6:
1144 return 0x02
1145 except Exception as e:
1146 invalid_ipv4 = True
1147 for i in addr_list:
1148 # Invalid IPv4 may qualify for a valid FQDN, so make check here
1149 # e.g. 172.24.15.300
1150 import re
1151 if not re.match('^[0-9_]+$', i):
1152 invalid_ipv4 = False
1153 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001154
Harald Weltec91085e2022-02-10 18:05:45 +01001155 if invalid_ipv4:
1156 return None
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001157
Harald Weltec91085e2022-02-10 18:05:45 +01001158 fqdn_flag = True
1159 for i in addr_list:
1160 # Only Alpha-numeric characters and hyphen - RFC 1035
1161 import re
1162 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
1163 fqdn_flag = False
1164 break
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001165
Harald Weltec91085e2022-02-10 18:05:45 +01001166 # FQDN
1167 if fqdn_flag:
1168 return 0x00
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001169
Harald Weltec91085e2022-02-10 18:05:45 +01001170 return None
Harald Welte67d551a2021-01-21 14:50:01 +01001171
Philipp Maier5d3e2592021-02-22 17:22:16 +01001172
Harald Weltec91085e2022-02-10 18:05:45 +01001173def sw_match(sw: str, pattern: str) -> bool:
1174 """Match given SW against given pattern."""
1175 # Create a masked version of the returned status word
1176 sw_lower = sw.lower()
1177 sw_masked = ""
1178 for i in range(0, 4):
1179 if pattern[i] == '?':
1180 sw_masked = sw_masked + '?'
1181 elif pattern[i] == 'x':
1182 sw_masked = sw_masked + 'x'
1183 else:
1184 sw_masked = sw_masked + sw_lower[i]
1185 # Compare the masked version against the pattern
1186 return sw_masked == pattern
Harald Welteee3501f2021-04-02 13:00:18 +02001187
Harald Weltec91085e2022-02-10 18:05:45 +01001188
1189def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = 1,
1190 align_left: bool = True) -> str:
1191 """Pretty print a list of strings into a tabulated form.
1192
1193 Args:
1194 width : total width in characters per line
1195 space : horizontal space between cells
1196 lspace : number of spaces before row
1197 align_lef : Align text to the left side
1198 Returns:
1199 multi-line string containing formatted table
1200 """
1201 if str_list == None:
1202 return ""
1203 if len(str_list) <= 0:
1204 return ""
1205 longest_str = max(str_list, key=len)
1206 cellwith = len(longest_str) + hspace
1207 cols = width // cellwith
1208 rows = (len(str_list) - 1) // cols + 1
1209 table = []
1210 for i in iter(range(rows)):
1211 str_list_row = str_list[i::rows]
1212 if (align_left):
1213 format_str_cell = '%%-%ds'
1214 else:
1215 format_str_cell = '%%%ds'
1216 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
1217 format_str_row = (" " * lspace) + format_str_row
1218 table.append(format_str_row % tuple(str_list_row))
1219 return '\n'.join(table)
1220
Harald Welte5e749a72021-04-10 17:18:17 +02001221
Harald Welte917d98c2021-04-21 11:51:25 +02001222def auto_int(x):
1223 """Helper function for argparse to accept hexadecimal integers."""
1224 return int(x, 0)
1225
Harald Weltec91085e2022-02-10 18:05:45 +01001226
Philipp Maier40ea4a42022-06-02 14:45:41 +02001227def expand_hex(hexstring, length):
1228 """Expand a given hexstring to a specified length by replacing "." or ".."
1229 with a filler that is derived from the neighboring nibbles respective
1230 bytes. Usually this will be the nibble respective byte before "." or
1231 "..", execpt when the string begins with "." or "..", then the nibble
1232 respective byte after "." or ".." is used.". In case the string cannot
1233 be expanded for some reason, the input string is returned unmodified.
1234
1235 Args:
1236 hexstring : hexstring to expand
1237 length : desired length of the resulting hexstring.
1238 Returns:
1239 expanded hexstring
1240 """
1241
1242 # expand digit aligned
1243 if hexstring.count(".") == 1:
1244 pos = hexstring.index(".")
1245 if pos > 0:
1246 filler = hexstring[pos - 1]
1247 else:
1248 filler = hexstring[pos + 1]
1249
1250 missing = length * 2 - (len(hexstring) - 1)
1251 if missing <= 0:
1252 return hexstring
1253
1254 return hexstring.replace(".", filler * missing)
1255
1256 # expand byte aligned
1257 elif hexstring.count("..") == 1:
1258 if len(hexstring) % 2:
1259 return hexstring
1260
1261 pos = hexstring.index("..")
1262
1263 if pos % 2:
1264 return hexstring
1265
1266 if pos > 1:
1267 filler = hexstring[pos - 2:pos]
1268 else:
1269 filler = hexstring[pos + 2:pos+4]
1270
1271 missing = length * 2 - (len(hexstring) - 2)
1272 if missing <= 0:
1273 return hexstring
1274
1275 return hexstring.replace("..", filler * (missing // 2))
1276
1277 # no change
1278 return hexstring
1279
1280
Harald Welte5e749a72021-04-10 17:18:17 +02001281class JsonEncoder(json.JSONEncoder):
1282 """Extend the standard library JSONEncoder with support for more types."""
Harald Weltec91085e2022-02-10 18:05:45 +01001283
Harald Welte5e749a72021-04-10 17:18:17 +02001284 def default(self, o):
1285 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
1286 return b2h(o)
1287 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +02001288
Harald Weltec91085e2022-02-10 18:05:45 +01001289
Philipp Maier80ce71f2021-04-19 21:24:23 +02001290def boxed_heading_str(heading, width=80):
Harald Weltec91085e2022-02-10 18:05:45 +01001291 """Generate a string that contains a boxed heading."""
1292 # Auto-enlarge box if heading exceeds length
1293 if len(heading) > width - 4:
1294 width = len(heading) + 4
Philipp Maier80ce71f2021-04-19 21:24:23 +02001295
Harald Weltec91085e2022-02-10 18:05:45 +01001296 res = "#" * width
1297 fstr = "\n# %-" + str(width - 4) + "s #\n"
1298 res += fstr % (heading)
1299 res += "#" * width
1300 return res
Harald Welte3de6ca22021-05-02 20:05:56 +02001301
1302
1303class DataObject(abc.ABC):
1304 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
1305 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
1306 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
1307 tags at specific points in a stream. This class tries to represent this."""
Harald Weltec91085e2022-02-10 18:05:45 +01001308
Harald Welte425038f2022-02-10 19:32:04 +01001309 def __init__(self, name: str, desc: Optional[str] = None, tag: Optional[int] = None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001310 """
1311 Args:
1312 name: A brief, all-lowercase, underscore separated string identifier
1313 desc: A human-readable description of what this DO represents
1314 tag : The tag associated with this DO
1315 """
1316 self.name = name
1317 self.desc = desc
1318 self.tag = tag
1319 self.decoded = None
1320 self.encoded = None
1321
1322 def __str__(self):
1323 return self.name
1324
Harald Welte50368772022-02-10 15:22:22 +01001325 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001326 return '%s(%s)' % (self.__class__, self.name)
1327
Harald Welte50368772022-02-10 15:22:22 +01001328 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001329 """OR-ing DataObjects together renders a DataObjectChoice."""
1330 if isinstance(other, DataObject):
1331 # DataObject | DataObject = DataObjectChoice
1332 return DataObjectChoice(None, members=[self, other])
1333 else:
1334 raise TypeError
1335
Harald Welte50368772022-02-10 15:22:22 +01001336 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001337 """ADD-ing DataObjects together renders a DataObjectCollection."""
1338 if isinstance(other, DataObject):
1339 # DataObject + DataObject = DataObjectCollectin
1340 return DataObjectCollection(None, members=[self, other])
Harald Welte50368772022-02-10 15:22:22 +01001341 else:
1342 raise TypeError
Harald Welte3de6ca22021-05-02 20:05:56 +02001343
Harald Welte50368772022-02-10 15:22:22 +01001344 def _compute_tag(self) -> int:
Harald Welte3de6ca22021-05-02 20:05:56 +02001345 """Compute the tag (sometimes the tag encodes part of the value)."""
1346 return self.tag
1347
Harald Welte50368772022-02-10 15:22:22 +01001348 def to_dict(self) -> dict:
Harald Welte3de6ca22021-05-02 20:05:56 +02001349 """Return a dict in form "name: decoded_value" """
1350 return {self.name: self.decoded}
1351
1352 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001353 def from_bytes(self, do: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001354 """Parse the value part of the DO into the internal state of this instance.
1355 Args:
1356 do : binary encoded bytes
1357 """
1358
1359 @abc.abstractmethod
Harald Welte50368772022-02-10 15:22:22 +01001360 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001361 """Encode the internal state of this instance into the TLV value part.
1362 Returns:
1363 binary bytes encoding the internal state
1364 """
1365
Harald Weltec91085e2022-02-10 18:05:45 +01001366 def from_tlv(self, do: bytes) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001367 """Parse binary TLV representation into internal state. The resulting decoded
1368 representation is _not_ returned, but just internalized in the object instance!
1369 Args:
1370 do : input bytes containing TLV-encoded representation
1371 Returns:
1372 bytes remaining at end of 'do' after parsing one TLV/DO.
1373 """
1374 if do[0] != self.tag:
Harald Weltec91085e2022-02-10 18:05:45 +01001375 raise ValueError('%s: Can only decode tag 0x%02x' %
1376 (self, self.tag))
Harald Welte3de6ca22021-05-02 20:05:56 +02001377 length = do[1]
1378 val = do[2:2+length]
1379 self.from_bytes(val)
1380 # return remaining bytes
1381 return do[2+length:]
1382
Harald Welte50368772022-02-10 15:22:22 +01001383 def to_tlv(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001384 """Encode internal representation to binary TLV.
1385 Returns:
1386 bytes encoded in TLV format.
1387 """
1388 val = self.to_bytes()
Harald Welte785d4842022-04-05 14:24:22 +02001389 return bertlv_encode_tag(self._compute_tag()) + bertlv_encode_len(len(val)) + val
Harald Welte3de6ca22021-05-02 20:05:56 +02001390
1391 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001392 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001393 """Decode a single DOs from the input data.
1394 Args:
1395 binary : binary bytes of encoded data
1396 Returns:
1397 tuple of (decoded_result, binary_remainder)
1398 """
1399 tag = binary[0]
1400 if tag != self.tag:
1401 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1402 (self, tag, binary, self.tag))
1403 remainder = self.from_tlv(binary)
1404 return (self.to_dict(), remainder)
1405
1406 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001407 def encode(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001408 return self.to_tlv()
1409
Harald Weltec91085e2022-02-10 18:05:45 +01001410
Harald Welte3de6ca22021-05-02 20:05:56 +02001411class TL0_DataObject(DataObject):
1412 """Data Object that has Tag, Len=0 and no Value part."""
Harald Weltec91085e2022-02-10 18:05:45 +01001413
1414 def __init__(self, name: str, desc: str, tag: int, val=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001415 super().__init__(name, desc, tag)
1416 self.val = val
1417
Harald Weltec91085e2022-02-10 18:05:45 +01001418 def from_bytes(self, binary: bytes):
Harald Welte3de6ca22021-05-02 20:05:56 +02001419 if len(binary) != 0:
1420 raise ValueError
1421 self.decoded = self.val
1422
Harald Welte50368772022-02-10 15:22:22 +01001423 def to_bytes(self) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001424 return b''
1425
1426
1427class DataObjectCollection:
1428 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1429 A given encoded DO may contain any of them in any order, and may contain multiple instances
1430 of each DO."""
Harald Weltec91085e2022-02-10 18:05:45 +01001431
Harald Welte425038f2022-02-10 19:32:04 +01001432 def __init__(self, name: str, desc: Optional[str] = None, members=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001433 self.name = name
1434 self.desc = desc
1435 self.members = members or []
1436 self.members_by_tag = {}
1437 self.members_by_name = {}
Harald Weltec91085e2022-02-10 18:05:45 +01001438 self.members_by_tag = {m.tag: m for m in members}
1439 self.members_by_name = {m.name: m for m in members}
Harald Welte3de6ca22021-05-02 20:05:56 +02001440
Harald Welte50368772022-02-10 15:22:22 +01001441 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001442 member_strs = [str(x) for x in self.members]
1443 return '%s(%s)' % (self.name, ','.join(member_strs))
1444
Harald Welte50368772022-02-10 15:22:22 +01001445 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001446 member_strs = [repr(x) for x in self.members]
1447 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1448
Harald Welte50368772022-02-10 15:22:22 +01001449 def __add__(self, other) -> 'DataObjectCollection':
Harald Welte3de6ca22021-05-02 20:05:56 +02001450 """Extending DataCollections with other DataCollections or DataObjects."""
1451 if isinstance(other, DataObjectCollection):
1452 # adding one collection to another
1453 members = self.members + other.members
1454 return DataObjectCollection(self.name, self.desc, members)
1455 elif isinstance(other, DataObject):
1456 # adding a member to a collection
1457 return DataObjectCollection(self.name, self.desc, self.members + [other])
1458 else:
1459 raise TypeError
1460
1461 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001462 def decode(self, binary: bytes) -> Tuple[List, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001463 """Decode any number of DOs from the collection until the end of the input data,
1464 or uninitialized memory (0xFF) is found.
1465 Args:
1466 binary : binary bytes of encoded data
1467 Returns:
1468 tuple of (decoded_result, binary_remainder)
1469 """
1470 res = []
1471 remainder = binary
1472 # iterate until no binary trailer is left
1473 while len(remainder):
1474 tag = remainder[0]
Harald Weltec91085e2022-02-10 18:05:45 +01001475 if tag == 0xff: # uninitialized memory at the end?
Harald Welte3de6ca22021-05-02 20:05:56 +02001476 return (res, remainder)
1477 if not tag in self.members_by_tag:
1478 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1479 (self, tag, remainder, self.members_by_tag.keys()))
1480 obj = self.members_by_tag[tag]
1481 # DO from_tlv returns remainder of binary
1482 remainder = obj.from_tlv(remainder)
1483 # collect our results
1484 res.append(obj.to_dict())
1485 return (res, remainder)
1486
1487 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001488 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001489 res = bytearray()
1490 for i in decoded:
1491 obj = self.members_by_name(i[0])
1492 res.append(obj.to_tlv())
1493 return res
1494
Harald Weltec91085e2022-02-10 18:05:45 +01001495
Harald Welte3de6ca22021-05-02 20:05:56 +02001496class DataObjectChoice(DataObjectCollection):
1497 """One Data Object from within a choice, identified by its tag.
1498 This means that exactly one member of the choice must occur, and which one occurs depends
1499 on the tag."""
Harald Weltec91085e2022-02-10 18:05:45 +01001500
Harald Welte3de6ca22021-05-02 20:05:56 +02001501 def __add__(self, other):
1502 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1503 raise TypeError
1504
Harald Welte50368772022-02-10 15:22:22 +01001505 def __or__(self, other) -> 'DataObjectChoice':
Harald Welte3de6ca22021-05-02 20:05:56 +02001506 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1507 if isinstance(other, DataObjectChoice):
1508 # adding one collection to another
1509 members = self.members + other.members
1510 return DataObjectChoice(self.name, self.desc, members)
1511 elif isinstance(other, DataObject):
1512 # adding a member to a collection
1513 return DataObjectChoice(self.name, self.desc, self.members + [other])
1514 else:
1515 raise TypeError
1516
1517 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001518 def decode(self, binary: bytes) -> Tuple[dict, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001519 """Decode a single DOs from the choice based on the tag.
1520 Args:
1521 binary : binary bytes of encoded data
1522 Returns:
1523 tuple of (decoded_result, binary_remainder)
1524 """
1525 tag = binary[0]
1526 if tag == 0xff:
1527 return (None, binary)
1528 if not tag in self.members_by_tag:
1529 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1530 (self, tag, binary, self.members_by_tag.keys()))
1531 obj = self.members_by_tag[tag]
1532 remainder = obj.from_tlv(binary)
1533 return (obj.to_dict(), remainder)
1534
1535 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001536 def encode(self, decoded) -> bytes:
Harald Welte785d4842022-04-05 14:24:22 +02001537 obj = self.members_by_name[list(decoded)[0]]
1538 obj.decoded = list(decoded.values())[0]
Harald Welte3de6ca22021-05-02 20:05:56 +02001539 return obj.to_tlv()
1540
Harald Weltec91085e2022-02-10 18:05:45 +01001541
Harald Welte3de6ca22021-05-02 20:05:56 +02001542class DataObjectSequence:
1543 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1544 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1545 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1546 for encoding or decoding an entire sequence."""
Harald Weltec91085e2022-02-10 18:05:45 +01001547
Harald Welte425038f2022-02-10 19:32:04 +01001548 def __init__(self, name: str, desc: Optional[str] = None, sequence=None):
Harald Welte3de6ca22021-05-02 20:05:56 +02001549 self.sequence = sequence or []
1550 self.name = name
1551 self.desc = desc
1552
Harald Welte50368772022-02-10 15:22:22 +01001553 def __str__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001554 member_strs = [str(x) for x in self.sequence]
1555 return '%s(%s)' % (self.name, ','.join(member_strs))
1556
Harald Welte50368772022-02-10 15:22:22 +01001557 def __repr__(self) -> str:
Harald Welte3de6ca22021-05-02 20:05:56 +02001558 member_strs = [repr(x) for x in self.sequence]
1559 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1560
Harald Welte50368772022-02-10 15:22:22 +01001561 def __add__(self, other) -> 'DataObjectSequence':
Harald Welte3de6ca22021-05-02 20:05:56 +02001562 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1563 if isinstance(other, 'DataObject'):
Harald Weltec91085e2022-02-10 18:05:45 +01001564 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
Harald Welte3de6ca22021-05-02 20:05:56 +02001565 elif isinstance(other, 'DataObjectChoice'):
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, 'DataObjectSequence'):
Harald Weltec91085e2022-02-10 18:05:45 +01001568 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
Harald Welte3de6ca22021-05-02 20:05:56 +02001569
1570 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001571 def decode(self, binary: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001572 """Decode a sequence by calling the decoder of each element in the sequence.
1573 Args:
1574 binary : binary bytes of encoded data
1575 Returns:
1576 tuple of (decoded_result, binary_remainder)
1577 """
1578 remainder = binary
1579 res = []
1580 for e in self.sequence:
1581 (r, remainder) = e.decode(remainder)
1582 if r:
1583 res.append(r)
1584 return (res, remainder)
1585
1586 # 'codec' interface
Harald Weltec91085e2022-02-10 18:05:45 +01001587 def decode_multi(self, do: bytes) -> Tuple[list, bytes]:
Harald Welte3de6ca22021-05-02 20:05:56 +02001588 """Decode multiple occurrences of the sequence from the binary input data.
1589 Args:
1590 do : binary input data to be decoded
1591 Returns:
1592 list of results of the decoder of this sequences
1593 """
1594 remainder = do
1595 res = []
1596 while len(remainder):
1597 (r, remainder2) = self.decode(remainder)
1598 if r:
1599 res.append(r)
1600 if len(remainder2) < len(remainder):
1601 remainder = remainder2
1602 else:
1603 remainder = remainder2
1604 break
1605 return (res, remainder)
1606
1607 # 'codec' interface
Harald Welte50368772022-02-10 15:22:22 +01001608 def encode(self, decoded) -> bytes:
Harald Welte3de6ca22021-05-02 20:05:56 +02001609 """Encode a sequence by calling the encoder of each element in the sequence."""
1610 encoded = bytearray()
1611 i = 0
1612 for e in self.sequence:
1613 encoded += e.encode(decoded[i])
1614 i += 1
1615 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001616
Harald Welte0dcdfbf2022-04-05 14:42:48 +02001617 def encode_multi(self, decoded) -> bytes:
1618 """Encode multiple occurrences of the sequence from the decoded input data.
1619 Args:
1620 decoded : list of json-serializable input data; one sequence per list item
1621 Returns:
1622 binary encoded output data
1623 """
1624 encoded = bytearray()
1625 for d in decoded:
1626 encoded += self.encode(d)
1627 return encoded
1628
Harald Weltec91085e2022-02-10 18:05:45 +01001629
Harald Welte90441432021-05-02 21:28:12 +02001630class CardCommand:
1631 """A single card command / instruction."""
Harald Weltec91085e2022-02-10 18:05:45 +01001632
Harald Welte90441432021-05-02 21:28:12 +02001633 def __init__(self, name, ins, cla_list=None, desc=None):
1634 self.name = name
1635 self.ins = ins
1636 self.cla_list = cla_list or []
1637 self.cla_list = [x.lower() for x in self.cla_list]
1638 self.desc = desc
1639
1640 def __str__(self):
1641 return self.name
1642
1643 def __repr__(self):
1644 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1645
1646 def match_cla(self, cla):
1647 """Does the given CLA match the CLA list of the command?."""
1648 if not isinstance(cla, str):
1649 cla = '%02u' % cla
1650 cla = cla.lower()
1651 for cla_match in self.cla_list:
1652 cla_masked = ""
1653 for i in range(0, 2):
1654 if cla_match[i] == 'x':
1655 cla_masked += 'x'
1656 else:
1657 cla_masked += cla[i]
1658 if cla_masked == cla_match:
1659 return True
1660 return False
1661
1662
1663class CardCommandSet:
1664 """A set of card instructions, typically specified within one spec."""
Harald Weltec91085e2022-02-10 18:05:45 +01001665
Harald Welte90441432021-05-02 21:28:12 +02001666 def __init__(self, name, cmds=[]):
1667 self.name = name
Harald Weltec91085e2022-02-10 18:05:45 +01001668 self.cmds = {c.ins: c for c in cmds}
Harald Welte90441432021-05-02 21:28:12 +02001669
1670 def __str__(self):
1671 return self.name
1672
1673 def __getitem__(self, idx):
1674 return self.cmds[idx]
1675
1676 def __add__(self, other):
1677 if isinstance(other, CardCommand):
1678 if other.ins in self.cmds:
1679 raise ValueError('%s: INS 0x%02x already defined: %s' %
1680 (self, other.ins, self.cmds[other.ins]))
1681 self.cmds[other.ins] = other
1682 elif isinstance(other, CardCommandSet):
1683 for c in other.cmds.keys():
1684 self.cmds[c] = other.cmds[c]
1685 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001686 raise ValueError(
1687 '%s: Unsupported type to add operator: %s' % (self, other))
Harald Welte90441432021-05-02 21:28:12 +02001688
1689 def lookup(self, ins, cla=None):
1690 """look-up the command within the CommandSet."""
1691 ins = int(ins)
1692 if not ins in self.cmds:
1693 return None
1694 cmd = self.cmds[ins]
1695 if cla and not cmd.match_cla(cla):
1696 return None
1697 return cmd
Philipp Maiera028c7d2021-11-08 16:12:03 +01001698
Harald Weltec91085e2022-02-10 18:05:45 +01001699
Philipp Maiera028c7d2021-11-08 16:12:03 +01001700def all_subclasses(cls) -> set:
Harald Weltec91085e2022-02-10 18:05:45 +01001701 """Recursively get all subclasses of a specified class"""
1702 return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])