blob: 0a9be867f14535c79ac2f19f96c4496a87ae6a5c [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
Harald Welte52255572021-04-03 09:56:32 +020032def h2b(s:Hexstr) -> bytearray:
Harald Welte4f6ca432021-02-01 17:51:56 +010033 """convert from a string of hex nibbles to a sequence of bytes"""
34 return bytearray.fromhex(s)
Sylvain Munaut76504e02010-12-07 00:24:32 +010035
Harald Welte52255572021-04-03 09:56:32 +020036def b2h(b:bytearray) -> Hexstr:
Harald Welte4f6ca432021-02-01 17:51:56 +010037 """convert from a sequence of bytes to a string of hex nibbles"""
38 return ''.join(['%02x'%(x) for x in b])
Sylvain Munaut76504e02010-12-07 00:24:32 +010039
Harald Welte52255572021-04-03 09:56:32 +020040def h2i(s:Hexstr) -> List[int]:
Harald Welteee3501f2021-04-02 13:00:18 +020041 """convert from a string of hex nibbles to a list of integers"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010042 return [(int(x,16)<<4)+int(y,16) for x,y in zip(s[0::2], s[1::2])]
43
Harald Welte52255572021-04-03 09:56:32 +020044def i2h(s:List[int]) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020045 """convert from a list of integers to a string of hex nibbles"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010046 return ''.join(['%02x'%(x) for x in s])
47
Harald Welte52255572021-04-03 09:56:32 +020048def h2s(s:Hexstr) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +020049 """convert from a string of hex nibbles to an ASCII string"""
Vadim Yanitskiyeb06b452020-05-10 02:32:46 +070050 return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])
51 if int(x + y, 16) != 0xff])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030052
Harald Welte52255572021-04-03 09:56:32 +020053def s2h(s:str) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020054 """convert from an ASCII string to a string of hex nibbles"""
Harald Welte4f6ca432021-02-01 17:51:56 +010055 b = bytearray()
56 b.extend(map(ord, s))
57 return b2h(b)
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030058
Harald Welte52255572021-04-03 09:56:32 +020059def i2s(s:List[int]) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +020060 """convert from a list of integers to an ASCII string"""
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020061 return ''.join([chr(x) for x in s])
62
Harald Welte52255572021-04-03 09:56:32 +020063def swap_nibbles(s:Hexstr) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020064 """swap the nibbles in a hex string"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010065 return ''.join([x+y for x,y in zip(s[1::2], s[0::2])])
66
Harald Welteee3501f2021-04-02 13:00:18 +020067def rpad(s:str, l:int, c='f') -> str:
68 """pad string on the right side.
69 Args:
70 s : string to pad
71 l : total length to pad to
72 c : padding character
73 Returns:
74 String 's' padded with as many 'c' as needed to reach total length of 'l'
75 """
Sylvain Munaut76504e02010-12-07 00:24:32 +010076 return s + c * (l - len(s))
77
Harald Welteee3501f2021-04-02 13:00:18 +020078def lpad(s:str, l:int, c='f') -> str:
79 """pad string on the left 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 """
Sylvain Munaut76504e02010-12-07 00:24:32 +010087 return c * (l - len(s)) + s
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040088
Harald Welteee3501f2021-04-02 13:00:18 +020089def half_round_up(n:int) -> int:
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020090 return (n + 1)//2
91
Philipp Maier796ca3d2021-11-01 17:13:57 +010092def str_sanitize(s:str) -> str:
93 """replace all non printable chars, line breaks and whitespaces, with ' ', make sure that
94 there are no whitespaces at the end and at the beginning of the string.
95
96 Args:
97 s : string to sanitize
98 Returns:
99 filtered result of string 's'
100 """
101
102 chars_to_keep = string.digits + string.ascii_letters + string.punctuation
103 res = ''.join([c if c in chars_to_keep else ' ' for c in s])
104 return res.strip()
105
Harald Welte917d98c2021-04-21 11:51:25 +0200106#########################################################################
Harald Welte9f3b44d2021-05-04 18:18:09 +0200107# poor man's COMPREHENSION-TLV decoder.
108#########################################################################
109
Harald Welte6912b1b2021-05-24 23:16:44 +0200110def comprehensiontlv_parse_tag_raw(binary:bytes) -> Tuple[int, bytes]:
111 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
112 if binary[0] in [0x00, 0x80, 0xff]:
113 raise ValueError("Found illegal value 0x%02x in %s" % (binary[0], binary))
114 if binary[0] == 0x7f:
115 # three-byte tag
116 tag = binary[0] << 16 | binary[1] << 8 | binary[2]
117 return (tag, binary[3:])
118 elif binary[0] == 0xff:
119 return None, binary
120 else:
121 # single byte tag
122 tag = binary[0]
123 return (tag, binary[1:])
124
Harald Welte9f3b44d2021-05-04 18:18:09 +0200125def comprehensiontlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
126 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
127 if binary[0] in [0x00, 0x80, 0xff]:
128 raise ValueError("Found illegal value 0x%02x in %s" % (binary[0], binary))
129 if binary[0] == 0x7f:
130 # three-byte tag
131 tag = (binary[1] & 0x7f) << 8
132 tag |= binary[2]
133 compr = True if binary[1] & 0x80 else False
134 return ({'comprehension': compr, 'tag': tag}, binary[3:])
135 else:
136 # single byte tag
137 tag = binary[0] & 0x7f
138 compr = True if binary[0] & 0x80 else False
139 return ({'comprehension': compr, 'tag': tag}, binary[1:])
140
141def comprehensiontlv_encode_tag(tag) -> bytes:
142 """Encode a single Tag according to ETSI TS 101 220 Section 7.1.1"""
143 # permit caller to specify tag also as integer value
144 if isinstance(tag, int):
145 compr = True if tag < 0xff and tag & 0x80 else False
146 tag = {'tag': tag, 'comprehension': compr}
147 compr = tag.get('comprehension', False)
148 if tag['tag'] in [0x00, 0x80, 0xff] or tag['tag'] > 0xff:
149 # 3-byte format
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300150 byte3 = tag['tag'] & 0xff
Harald Welte9f3b44d2021-05-04 18:18:09 +0200151 byte2 = (tag['tag'] >> 8) & 0x7f
152 if compr:
153 byte2 |= 0x80
154 return b'\x7f' + byte2.to_bytes(1, 'big') + byte3.to_bytes(1, 'big')
155 else:
156 # 1-byte format
157 ret = tag['tag']
158 if compr:
159 ret |= 0x80
160 return ret.to_bytes(1, 'big')
161
162# length value coding is equal to BER-TLV
163
Harald Welte6912b1b2021-05-24 23:16:44 +0200164def comprehensiontlv_parse_one(binary:bytes) -> (dict, int, bytes, bytes):
165 """Parse a single TLV IE at the start of the given binary data.
166 Args:
167 binary : binary input data of BER-TLV length field
168 Returns:
169 Tuple of (tag:dict, len:int, remainder:bytes)
170 """
171 (tagdict, remainder) = comprehensiontlv_parse_tag(binary)
172 (length, remainder) = bertlv_parse_len(remainder)
173 value = remainder[:length]
174 remainder = remainder[length:]
175 return (tagdict, length, value, remainder)
176
177
Harald Welte9f3b44d2021-05-04 18:18:09 +0200178
179#########################################################################
Harald Welte917d98c2021-04-21 11:51:25 +0200180# poor man's BER-TLV decoder. To be a more sophisticated OO library later
181#########################################################################
182
Harald Welte6912b1b2021-05-24 23:16:44 +0200183def bertlv_parse_tag_raw(binary:bytes) -> Tuple[int, bytes]:
184 """Get a single raw Tag from start of input according to ITU-T X.690 8.1.2
185 Args:
186 binary : binary input data of BER-TLV length field
187 Returns:
188 Tuple of (tag:int, remainder:bytes)
189 """
Harald Welte9a754102021-10-21 13:46:59 +0200190 # check for FF padding at the end, as customary in SIM card files
191 if binary[0] == 0xff and len(binary) == 1 or binary[0] == 0xff and binary[1] == 0xff:
Harald Welte6912b1b2021-05-24 23:16:44 +0200192 return None, binary
193 tag = binary[0] & 0x1f
194 if tag <= 30:
195 return binary[0], binary[1:]
196 else: # multi-byte tag
197 tag = binary[0]
198 i = 1
199 last = False
200 while not last:
201 last = False if binary[i] & 0x80 else True
202 tag <<= 8
203 tag |= binary[i]
204 i += 1
205 return tag, binary[i:]
206
Harald Welte917d98c2021-04-21 11:51:25 +0200207def bertlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
208 """Parse a single Tag value according to ITU-T X.690 8.1.2
209 Args:
210 binary : binary input data of BER-TLV length field
211 Returns:
212 Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes)
213 """
214 cls = binary[0] >> 6
215 constructed = True if binary[0] & 0x20 else False
216 tag = binary[0] & 0x1f
217 if tag <= 30:
218 return ({'class':cls, 'constructed':constructed, 'tag': tag}, binary[1:])
219 else: # multi-byte tag
220 tag = 0
221 i = 1
222 last = False
223 while not last:
224 last = False if binary[i] & 0x80 else True
225 tag <<= 7
226 tag |= binary[i] & 0x7f
227 i += 1
228 return ({'class':cls, 'constructed':constructed, 'tag':tag}, binary[i:])
229
Harald Weltef0885b12021-05-24 23:18:59 +0200230def bertlv_encode_tag(t) -> bytes:
231 """Encode a single Tag value according to ITU-T X.690 8.1.2
232 """
233 def get_top7_bits(inp:int) -> Tuple[int, int]:
234 """Get top 7 bits of integer. Returns those 7 bits as integer and the remaining LSBs."""
235 remain_bits = inp.bit_length()
236 if remain_bits >= 7:
237 bitcnt = 7
238 else:
239 bitcnt = remain_bits
240 outp = inp >> (remain_bits - bitcnt)
241 remainder = inp & ~ (inp << (remain_bits - bitcnt))
242 return outp, remainder
243
244 if isinstance(t, int):
245 # FIXME: multiple byte tags
246 tag = t & 0x1f
247 constructed = True if t & 0x20 else False
248 cls = t >> 6
249 else:
250 tag = t['tag']
251 constructed = t['constructed']
252 cls = t['class']
253 if tag <= 30:
254 t = tag & 0x1f
255 if constructed:
256 t |= 0x20
257 t |= (cls & 3) << 6
258 return bytes([t])
259 else: # multi-byte tag
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300260 t = 0x1f
Harald Weltef0885b12021-05-24 23:18:59 +0200261 if constructed:
262 t |= 0x20
263 t |= (cls & 3) << 6
264 tag_bytes = bytes([t])
265 remain = tag
266 while True:
267 t, remain = get_top7_bits(remain)
268 if remain:
269 t |= 0x80
270 tag_bytes += bytes([t])
271 if not remain:
272 break
273 return tag_bytes
274
Harald Welte917d98c2021-04-21 11:51:25 +0200275def bertlv_parse_len(binary:bytes) -> Tuple[int, bytes]:
276 """Parse a single Length value according to ITU-T X.690 8.1.3;
277 only the definite form is supported here.
278 Args:
279 binary : binary input data of BER-TLV length field
280 Returns:
281 Tuple of (length, remainder)
282 """
283 if binary[0] < 0x80:
284 return (binary[0], binary[1:])
285 else:
286 num_len_oct = binary[0] & 0x7f
287 length = 0
288 for i in range(1, 1+num_len_oct):
289 length <<= 8
290 length |= binary[i]
Harald Weltede027182021-05-04 20:06:45 +0200291 return (length, binary[1+num_len_oct:])
Harald Welte917d98c2021-04-21 11:51:25 +0200292
293def bertlv_encode_len(length:int) -> bytes:
294 """Encode a single Length value according to ITU-T X.690 8.1.3;
295 only the definite form is supported here.
296 Args:
297 length : length value to be encoded
298 Returns:
299 binary output data of BER-TLV length field
300 """
301 if length < 0x80:
302 return length.to_bytes(1, 'big')
303 elif length <= 0xff:
304 return b'\x81' + length.to_bytes(1, 'big')
305 elif length <= 0xffff:
306 return b'\x82' + length.to_bytes(2, 'big')
307 elif length <= 0xffffff:
308 return b'\x83' + length.to_bytes(3, 'big')
309 elif length <= 0xffffffff:
310 return b'\x84' + length.to_bytes(4, 'big')
311 else:
312 raise ValueError("Length > 32bits not supported")
313
Harald Weltec1475302021-05-21 21:47:55 +0200314def bertlv_parse_one(binary:bytes) -> (dict, int, bytes, bytes):
Harald Welte917d98c2021-04-21 11:51:25 +0200315 """Parse a single TLV IE at the start of the given binary data.
316 Args:
317 binary : binary input data of BER-TLV length field
318 Returns:
319 Tuple of (tag:dict, len:int, remainder:bytes)
320 """
321 (tagdict, remainder) = bertlv_parse_tag(binary)
322 (length, remainder) = bertlv_parse_len(remainder)
Harald Weltec1475302021-05-21 21:47:55 +0200323 value = remainder[:length]
324 remainder = remainder[length:]
325 return (tagdict, length, value, remainder)
Harald Welte917d98c2021-04-21 11:51:25 +0200326
327
328
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200329# IMSI encoded format:
330# For IMSI 0123456789ABCDE:
331#
332# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
333# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
334#
335# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
336#
337# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
338# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
339# encode itself.
340#
341# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
342# even length IMSI only uses half of the last byte.
343
Harald Welteee3501f2021-04-02 13:00:18 +0200344def enc_imsi(imsi:str):
345 """Converts a string IMSI into the encoded value of the EF"""
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200346 l = half_round_up(len(imsi) + 1) # Required bytes - include space for odd/even indicator
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400347 oe = len(imsi) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200348 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15)))
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400349 return ei
350
Harald Welte52255572021-04-03 09:56:32 +0200351def dec_imsi(ef:Hexstr) -> Optional[str]:
Harald Weltec9cdce32021-04-11 10:28:28 +0200352 """Converts an EF value to the IMSI string representation"""
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400353 if len(ef) < 4:
354 return None
Pau Espin Pedrol665bd222017-12-29 20:30:35 +0100355 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200356 l = l - 1 # Encoded length byte includes oe nibble
357 swapped = swap_nibbles(ef[2:]).rstrip('f')
Philipp Maiercd3d6262020-05-11 21:41:56 +0200358 if len(swapped) < 1:
359 return None
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400360 oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200361 if not oe:
362 # if even, only half of last byte was used
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400363 l = l-1
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200364 if l != len(swapped) - 1:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400365 return None
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200366 imsi = swapped[1:]
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400367 return imsi
368
Harald Welte52255572021-04-03 09:56:32 +0200369def dec_iccid(ef:Hexstr) -> str:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400370 return swap_nibbles(ef).strip('f')
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400371
Harald Welte52255572021-04-03 09:56:32 +0200372def enc_iccid(iccid:str) -> Hexstr:
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400373 return swap_nibbles(rpad(iccid, 20))
374
Philipp Maiere6f8d682021-04-23 21:14:41 +0200375def enc_plmn(mcc:Hexstr, mnc:Hexstr) -> Hexstr:
Alexander Chemerisdddbf522017-07-18 16:49:59 +0300376 """Converts integer MCC/MNC into 3 bytes for EF"""
Philipp Maier6c5cd802021-04-23 21:19:36 +0200377
378 # Make sure there are no excess whitespaces in the input
379 # parameters
380 mcc = mcc.strip()
381 mnc = mnc.strip()
382
383 # Make sure that MCC/MNC are correctly padded with leading
384 # zeros or 'F', depending on the length.
385 if len(mnc) == 0:
386 mnc = "FFF"
387 elif len(mnc) == 1:
388 mnc = "F0" + mnc
389 elif len(mnc) == 2:
390 mnc += "F"
391
392 if len(mcc) == 0:
393 mcc = "FFF"
394 elif len(mcc) == 1:
395 mcc = "00" + mcc
396 elif len(mcc) == 2:
397 mcc = "0" + mcc
398
Vadim Yanitskiyc8458e22021-03-12 00:34:10 +0100399 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300400
Harald Welted7a7e172021-04-07 00:30:10 +0200401def dec_plmn(threehexbytes:Hexstr) -> dict:
Philipp Maier6c5cd802021-04-23 21:19:36 +0200402 res = {'mcc': "0", 'mnc': "0" }
403 dec_mcc_from_plmn_str(threehexbytes)
404 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
405 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
Harald Welted7a7e172021-04-07 00:30:10 +0200406 return res
407
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300408def dec_spn(ef):
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200409 """Obsolete, kept for API compatibility"""
410 from ts_51_011 import EF_SPN
411 abstract_data = EF_SPN().decode_hex(ef)
412 show_in_hplmn = abstract_data['show_in_hplmn']
413 hide_in_oplmn = abstract_data['hide_in_oplmn']
414 name = abstract_data['spn']
415 return (name, show_in_hplmn, hide_in_oplmn)
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300416
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200417def enc_spn(name:str, show_in_hplmn=False, hide_in_oplmn=False):
418 """Obsolete, kept for API compatibility"""
419 from ts_51_011 import EF_SPN
420 abstract_data = {
421 'hide_in_oplmn' : hide_in_oplmn,
422 'show_in_hplmn' : show_in_hplmn,
423 'spn' : name,
424 }
425 return EF_SPN().encode_hex(abstract_data)
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900426
Supreeth Herlef3948532020-03-24 12:23:51 +0100427def hexstr_to_Nbytearr(s, nbytes):
428 return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]
429
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100430# Accepts hex string representing three bytes
Harald Welte52255572021-04-03 09:56:32 +0200431def dec_mcc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100432 ia = h2i(plmn)
433 digit1 = ia[0] & 0x0F # 1st byte, LSB
434 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
435 digit3 = ia[1] & 0x0F # 2nd byte, LSB
436 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
437 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100438 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100439
Philipp Maier6c5cd802021-04-23 21:19:36 +0200440def dec_mcc_from_plmn_str(plmn:Hexstr) -> str:
441 digit1 = plmn[1] # 1st byte, LSB
442 digit2 = plmn[0] # 1st byte, MSB
443 digit3 = plmn[3] # 2nd byte, LSB
444 res = digit1 + digit2 + digit3
445 return res.upper().strip("F")
446
Harald Welte52255572021-04-03 09:56:32 +0200447def dec_mnc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100448 ia = h2i(plmn)
Vadim Yanitskiyb271be32021-03-11 23:56:58 +0100449 digit1 = ia[2] & 0x0F # 3rd byte, LSB
450 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
451 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100452 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
453 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100454 return derive_mnc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100455
Philipp Maier6c5cd802021-04-23 21:19:36 +0200456def dec_mnc_from_plmn_str(plmn:Hexstr) -> str:
457 digit1 = plmn[5] # 3rd byte, LSB
458 digit2 = plmn[4] # 3rd byte, MSB
459 digit3 = plmn[2] # 2nd byte, MSB
460 res = digit1 + digit2 + digit3
461 return res.upper().strip("F")
462
Harald Welte52255572021-04-03 09:56:32 +0200463def dec_act(twohexbytes:Hexstr) -> List[str]:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100464 act_list = [
465 {'bit': 15, 'name': "UTRAN"},
466 {'bit': 14, 'name': "E-UTRAN"},
Bjoern Riemerffee89a2022-01-20 22:05:22 +0100467 {'bit': 11, 'name': "NG-RAN"},
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100468 {'bit': 7, 'name': "GSM"},
469 {'bit': 6, 'name': "GSM COMPACT"},
470 {'bit': 5, 'name': "cdma2000 HRPD"},
471 {'bit': 4, 'name': "cdma2000 1xRTT"},
472 ]
473 ia = h2i(twohexbytes)
474 u16t = (ia[0] << 8)|ia[1]
475 sel = []
476 for a in act_list:
477 if u16t & (1 << a['bit']):
Philipp Maiere7d41792021-04-29 16:20:07 +0200478 if a['name'] == "E-UTRAN":
479 # The Access technology identifier of E-UTRAN
480 # allows a more detailed specification:
481 if u16t & (1 << 13) and u16t & (1 << 12):
482 sel.append("E-UTRAN WB-S1")
483 sel.append("E-UTRAN NB-S1")
484 elif u16t & (1 << 13):
485 sel.append("E-UTRAN WB-S1")
486 elif u16t & (1 << 12):
487 sel.append("E-UTRAN NB-S1")
488 else:
489 sel.append("E-UTRAN")
490 else:
491 sel.append(a['name'])
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100492 return sel
493
Harald Welte52255572021-04-03 09:56:32 +0200494def dec_xplmn_w_act(fivehexbytes:Hexstr) -> Dict[str,Any]:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200495 res = {'mcc': "0", 'mnc': "0", 'act': []}
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100496 plmn_chars = 6
497 act_chars = 4
498 plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
499 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
Philipp Maierb919f8b2021-04-27 18:28:27 +0200500 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
501 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100502 res['act'] = dec_act(act_str)
503 return res
504
505def format_xplmn_w_act(hexstr):
506 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200507 for rec_data in hexstr_to_Nbytearr(hexstr, 5):
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100508 rec_info = dec_xplmn_w_act(rec_data)
Philipp Maierb919f8b2021-04-27 18:28:27 +0200509 if rec_info['mcc'] == "" and rec_info['mnc'] == "":
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100510 rec_str = "unused"
511 else:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200512 rec_str = "MCC: %s MNC: %s AcT: %s" % (rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act']))
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100513 s += "\t%s # %s\n" % (rec_data, rec_str)
514 return s
515
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100516def dec_loci(hexstr):
517 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
518 res['tmsi'] = hexstr[:8]
519 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
520 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
521 res['lac'] = hexstr[14:18]
522 res['status'] = h2i(hexstr[20:22])
523 return res
524
525def dec_psloci(hexstr):
526 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
527 res['p-tmsi'] = hexstr[:8]
528 res['p-tmsi-sig'] = hexstr[8:14]
529 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
530 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
531 res['lac'] = hexstr[20:24]
532 res['rac'] = hexstr[24:26]
533 res['status'] = h2i(hexstr[26:28])
534 return res
535
536def dec_epsloci(hexstr):
537 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
538 res['guti'] = hexstr[:24]
539 res['tai'] = hexstr[24:34]
540 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
541 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
542 res['tac'] = hexstr[30:34]
543 res['status'] = h2i(hexstr[34:36])
544 return res
545
Harald Welte52255572021-04-03 09:56:32 +0200546def dec_xplmn(threehexbytes:Hexstr) -> dict:
Harald Welteca673942020-06-03 15:19:40 +0200547 res = {'mcc': 0, 'mnc': 0, 'act': []}
548 plmn_chars = 6
549 plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
550 res['mcc'] = dec_mcc_from_plmn(plmn_str)
551 res['mnc'] = dec_mnc_from_plmn(plmn_str)
552 return res
553
Harald Welte52255572021-04-03 09:56:32 +0200554def format_xplmn(hexstr:Hexstr) -> str:
Harald Welteca673942020-06-03 15:19:40 +0200555 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200556 for rec_data in hexstr_to_Nbytearr(hexstr, 3):
Harald Welteca673942020-06-03 15:19:40 +0200557 rec_info = dec_xplmn(rec_data)
558 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
559 rec_str = "unused"
560 else:
561 rec_str = "MCC: %03d MNC: %03d" % (rec_info['mcc'], rec_info['mnc'])
562 s += "\t%s # %s\n" % (rec_data, rec_str)
563 return s
564
Harald Welte52255572021-04-03 09:56:32 +0200565def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900566 """
567 Run the milenage algorithm to calculate OPC from Ki and OP
568 """
569 from Crypto.Cipher import AES
Harald Weltebf82ceb2022-02-09 16:36:53 +0100570 # pylint: disable=no-name-in-module
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900571 from Crypto.Util.strxor import strxor
572 from pySim.utils import b2h
573
574 # We pass in hex string and now need to work on bytes
Harald Welteeab8d2a2021-03-05 18:30:23 +0100575 ki_bytes = bytes(h2b(ki_hex))
576 op_bytes = bytes(h2b(op_hex))
Harald Welteab34fa82021-03-05 18:39:59 +0100577 aes = AES.new(ki_bytes, AES.MODE_ECB)
Harald Welteeab8d2a2021-03-05 18:30:23 +0100578 opc_bytes = aes.encrypt(op_bytes)
579 return b2h(strxor(opc_bytes, op_bytes))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900580
Harald Welte52255572021-04-03 09:56:32 +0200581def calculate_luhn(cc) -> int:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900582 """
583 Calculate Luhn checksum used in e.g. ICCID and IMEI
584 """
Daniel Willmanndd014ea2020-10-19 10:35:11 +0200585 num = list(map(int, str(cc)))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900586 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
587 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200588
Harald Welte52255572021-04-03 09:56:32 +0200589def mcc_from_imsi(imsi:str) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200590 """
591 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
592 """
593 if imsi == None:
594 return None
595
596 if len(imsi) > 3:
597 return imsi[:3]
598 else:
599 return None
600
Harald Welte52255572021-04-03 09:56:32 +0200601def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200602 """
603 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
604 """
605 if imsi == None:
606 return None
607
608 if len(imsi) > 3:
609 if long:
610 return imsi[3:6]
611 else:
612 return imsi[3:5]
613 else:
614 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100615
Harald Welte52255572021-04-03 09:56:32 +0200616def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100617 """
618 Derive decimal representation of the MCC (Mobile Country Code)
619 from three given digits.
620 """
621
622 mcc = 0
623
624 if digit1 != 0x0f:
625 mcc += digit1 * 100
626 if digit2 != 0x0f:
627 mcc += digit2 * 10
628 if digit3 != 0x0f:
629 mcc += digit3
630
631 return mcc
632
Harald Welte52255572021-04-03 09:56:32 +0200633def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100634 """
635 Derive decimal representation of the MNC (Mobile Network Code)
636 from two or (optionally) three given digits.
637 """
638
639 mnc = 0
640
641 # 3-rd digit is optional for the MNC. If present
642 # the algorythm is the same as for the MCC.
643 if digit3 != 0x0f:
644 return derive_mcc(digit1, digit2, digit3)
645
646 if digit1 != 0x0f:
647 mnc += digit1 * 10
648 if digit2 != 0x0f:
649 mnc += digit2
650
651 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100652
Harald Welte52255572021-04-03 09:56:32 +0200653def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100654 """
655 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
656 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
657 """
658
659 # Convert from str to (kind of) 'bytes'
660 ef_msisdn = h2b(ef_msisdn)
661
662 # Make sure mandatory fields are present
663 if len(ef_msisdn) < 14:
664 raise ValueError("EF.MSISDN is too short")
665
666 # Skip optional Alpha Identifier
667 xlen = len(ef_msisdn) - 14
668 msisdn_lhv = ef_msisdn[xlen:]
669
670 # Parse the length (in bytes) of the BCD encoded number
Harald Welte4f6ca432021-02-01 17:51:56 +0100671 bcd_len = msisdn_lhv[0]
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100672 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
673 if bcd_len == 0xff:
674 return None
675 elif bcd_len > 11 or bcd_len < 1:
676 raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len)
677
678 # Parse ToN / NPI
Harald Welte4f6ca432021-02-01 17:51:56 +0100679 ton = (msisdn_lhv[1] >> 4) & 0x07
680 npi = msisdn_lhv[1] & 0x0f
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100681 bcd_len -= 1
682
683 # No MSISDN?
684 if not bcd_len:
685 return (npi, ton, None)
686
687 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
688 # International number 10.5.118/3GPP TS 24.008
Vadim Yanitskiy7ba24282020-02-27 00:04:13 +0700689 if ton == 0x01:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100690 msisdn = '+' + msisdn
691
692 return (npi, ton, msisdn)
Supreeth Herle5a541012019-12-22 08:59:16 +0100693
Harald Welte52255572021-04-03 09:56:32 +0200694def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr:
Supreeth Herle5a541012019-12-22 08:59:16 +0100695 """
696 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200697 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
698 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100699
700 Default NPI / ToN values:
701 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
702 - ToN: network specific or international number (if starts with '+').
703 """
704
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200705 # If no MSISDN is supplied then encode the file contents as all "ff"
706 if msisdn == "" or msisdn == "+":
707 return "ff" * 14
708
Supreeth Herle5a541012019-12-22 08:59:16 +0100709 # Leading '+' indicates International Number
710 if msisdn[0] == '+':
711 msisdn = msisdn[1:]
712 ton = 0x01
713
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200714 # An MSISDN must not exceed 20 digits
715 if len(msisdn) > 20:
716 raise ValueError("msisdn must not be longer than 20 digits")
717
Supreeth Herle5a541012019-12-22 08:59:16 +0100718 # Append 'f' padding if number of digits is odd
719 if len(msisdn) % 2 > 0:
720 msisdn += 'f'
721
722 # BCD length also includes NPI/ToN header
723 bcd_len = len(msisdn) // 2 + 1
724 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
725 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
726
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200727 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
728
Supreeth Herle441c4a72020-03-24 10:19:15 +0100729
Harald Welte52255572021-04-03 09:56:32 +0200730def dec_st(st, table="sim") -> str:
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200731 """
732 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
733 """
734
Supreeth Herledf330372020-04-20 14:48:55 +0200735 if table == "isim":
736 from pySim.ts_31_103 import EF_IST_map
737 lookup_map = EF_IST_map
738 elif table == "usim":
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200739 from pySim.ts_31_102 import EF_UST_map
740 lookup_map = EF_UST_map
741 else:
742 from pySim.ts_51_011 import EF_SST_map
743 lookup_map = EF_SST_map
744
745 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
746
747 avail_st = ""
748 # Get each byte and check for available services
749 for i in range(0, len(st_bytes)):
750 # Byte i contains info about Services num (8i+1) to num (8i+8)
751 byte = int(st_bytes[i], 16)
752 # Services in each byte are in order MSB to LSB
753 # MSB - Service (8i+8)
754 # LSB - Service (8i+1)
755 for j in range(1, 9):
756 if byte&0x01 == 0x01 and ((8*i) + j in lookup_map):
757 # Byte X contains info about Services num (8X-7) to num (8X)
758 # bit = 1: service available
759 # bit = 0: service not available
760 avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j])
761 byte = byte >> 1
762 return avail_st
Supreeth Herle98370552020-05-11 09:04:41 +0200763
764def first_TLV_parser(bytelist):
765 '''
766 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
767
768 parses first TLV format record in a list of bytelist
769 returns a 3-Tuple: Tag, Length, Value
770 Value is a list of bytes
771 parsing of length is ETSI'style 101.220
772 '''
773 Tag = bytelist[0]
774 if bytelist[1] == 0xFF:
775 Len = bytelist[2]*256 + bytelist[3]
776 Val = bytelist[4:4+Len]
777 else:
778 Len = bytelist[1]
779 Val = bytelist[2:2+Len]
780 return (Tag, Len, Val)
781
782def TLV_parser(bytelist):
783 '''
784 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
785
786 loops on the input list of bytes with the "first_TLV_parser()" function
787 returns a list of 3-Tuples
788 '''
789 ret = []
790 while len(bytelist) > 0:
791 T, L, V = first_TLV_parser(bytelist)
792 if T == 0xFF:
793 # padding bytes
794 break
795 ret.append( (T, L, V) )
796 # need to manage length of L
797 if L > 0xFE:
798 bytelist = bytelist[ L+4 : ]
799 else:
800 bytelist = bytelist[ L+2 : ]
801 return ret
Supreeth Herled572ede2020-03-22 09:55:04 +0100802
Supreeth Herled84daa12020-03-24 12:20:40 +0100803def enc_st(st, service, state=1):
804 """
805 Encodes the EF S/U/IST/EST and returns the updated Service Table
806
807 Parameters:
808 st - Current value of SIM/USIM/ISIM Service Table
809 service - Service Number to encode as activated/de-activated
810 state - 1 mean activate, 0 means de-activate
811
812 Returns:
813 s - Modified value of SIM/USIM/ISIM Service Table
814
815 Default values:
816 - state: 1 - Sets the particular Service bit to 1
817 """
818 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
819
820 s = ""
821 # Check whether the requested service is present in each byte
822 for i in range(0, len(st_bytes)):
823 # Byte i contains info about Services num (8i+1) to num (8i+8)
824 if service in range((8*i) + 1, (8*i) + 9):
825 byte = int(st_bytes[i], 16)
826 # Services in each byte are in order MSB to LSB
827 # MSB - Service (8i+8)
828 # LSB - Service (8i+1)
829 mod_byte = 0x00
830 # Copy bit by bit contents of byte to mod_byte with modified bit
831 # for requested service
832 for j in range(1, 9):
833 mod_byte = mod_byte >> 1
834 if service == (8*i) + j:
835 mod_byte = state == 1 and mod_byte|0x80 or mod_byte&0x7f
836 else:
837 mod_byte = byte&0x01 == 0x01 and mod_byte|0x80 or mod_byte&0x7f
838 byte = byte >> 1
839
840 s += ('%02x' % (mod_byte))
841 else:
842 s += st_bytes[i]
843
844 return s
845
Supreeth Herle3b342c22020-03-24 16:15:02 +0100846def dec_addr_tlv(hexstr):
Supreeth Herled572ede2020-03-22 09:55:04 +0100847 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100848 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
849 See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104.
Supreeth Herled572ede2020-03-22 09:55:04 +0100850 """
851
852 # Convert from hex str to int bytes list
Supreeth Herle3b342c22020-03-24 16:15:02 +0100853 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100854
Supreeth Herled572ede2020-03-22 09:55:04 +0100855 # Get list of tuples containing parsed TLVs
Supreeth Herle3b342c22020-03-24 16:15:02 +0100856 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100857
858 for tlv in tlvs:
859 # tlv = (T, L, [V])
860 # T = Tag
861 # L = Length
862 # [V] = List of value
863
864 # Invalid Tag value scenario
865 if tlv[0] != 0x80:
866 continue
867
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200868 # Empty field - Zero length
869 if tlv[1] == 0:
870 continue
871
Supreeth Herled572ede2020-03-22 09:55:04 +0100872 # First byte in the value has the address type
873 addr_type = tlv[2][0]
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100874 # TODO: Support parsing of IPv6
Supreeth Herle3b342c22020-03-24 16:15:02 +0100875 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
Supreeth Herled572ede2020-03-22 09:55:04 +0100876 if addr_type == 0x00: #FQDN
877 # Skip address tye byte i.e. first byte in value list
878 content = tlv[2][1:]
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200879 return (i2s(content), '00')
880
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100881 elif addr_type == 0x01: #IPv4
882 # Skip address tye byte i.e. first byte in value list
883 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
884 ipv4 = tlv[2][2:]
885 content = '.'.join(str(x) for x in ipv4)
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200886 return (content, '01')
887 else:
888 raise ValueError("Invalid address type")
Supreeth Herled572ede2020-03-22 09:55:04 +0100889
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200890 return (None, None)
Philipp Maierff84c232020-05-12 17:24:18 +0200891
Supreeth Herle3b342c22020-03-24 16:15:02 +0100892def enc_addr_tlv(addr, addr_type='00'):
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100893 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100894 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
895 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 +0100896
897 Default values:
Supreeth Herle3b342c22020-03-24 16:15:02 +0100898 - addr_type: 00 - FQDN format of Address
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100899 """
900
901 s = ""
902
Supreeth Herle654eca72020-03-25 14:25:38 +0100903 # TODO: Encoding of IPv6 address
904 if addr_type == '00': #FQDN
Supreeth Herle3b342c22020-03-24 16:15:02 +0100905 hex_str = s2h(addr)
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100906 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
Supreeth Herle654eca72020-03-25 14:25:38 +0100907 elif addr_type == '01': #IPv4
908 ipv4_list = addr.split('.')
909 ipv4_str = ""
910 for i in ipv4_list:
911 ipv4_str += ('%02x' % (int(i)))
912
913 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
914 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
915 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100916
917 return s
918
Harald Welte52255572021-04-03 09:56:32 +0200919def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
Philipp Maier47236502021-03-09 21:28:25 +0100920 """
921 Check if a string is a valid hexstring
922 """
923
924 # Filter obviously bad strings
925 if not string:
926 return False
927 if len(string) < minlen or minlen < 2:
928 return False
929 if len(string) % 2:
930 return False
931 if maxlen and len(string) > maxlen:
932 return False
933
934 # Try actual encoding to be sure
935 try:
936 try_encode = h2b(string)
937 return True
938 except:
939 return False
940
Harald Welte52255572021-04-03 09:56:32 +0200941def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
Philipp Maiere8536c02020-05-11 21:35:01 +0200942 """
943 The ADM pin can be supplied either in its hexadecimal form or as
944 ascii string. This function checks the supplied opts parameter and
Harald Welte236a65f2021-04-03 10:20:11 +0200945 returns the pin_adm as hex encoded string, regardless in which form
Philipp Maiere8536c02020-05-11 21:35:01 +0200946 it was originally supplied by the user
947 """
948
Harald Welte79b5ba42021-01-08 21:22:38 +0100949 if pin_adm is not None:
950 if len(pin_adm) <= 8:
951 pin_adm = ''.join(['%02x'%(ord(x)) for x in pin_adm])
Philipp Maiere8536c02020-05-11 21:35:01 +0200952 pin_adm = rpad(pin_adm, 16)
953
954 else:
955 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
956
Harald Welte79b5ba42021-01-08 21:22:38 +0100957 if pin_adm_hex is not None:
958 if len(pin_adm_hex) == 16:
959 pin_adm = pin_adm_hex
Philipp Maiere8536c02020-05-11 21:35:01 +0200960 # Ensure that it's hex-encoded
961 try:
962 try_encode = h2b(pin_adm)
963 except ValueError:
964 raise ValueError("PIN-ADM needs to be hex encoded using this option")
965 else:
966 raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)")
967
968 return pin_adm
969
Supreeth Herle95ec7722020-03-24 13:09:03 +0100970def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
971 """
972 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
973 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
974
975 Default values:
976 - epdg_priority: '0001' - 1st Priority
977 - epdg_fqdn_format: '00' - Operator Identifier FQDN
978 """
979
980 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
981 # TODO: Handle encoding of Length field for length more than 127 Bytes
982 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
983 content = rpad(content, len(hexstr))
984 return content
985
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100986def dec_ePDGSelection(sixhexbytes):
987 """
988 Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
989 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
990 """
991
992 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
993 plmn_chars = 6
994 epdg_priority_chars = 4
995 epdg_fqdn_format_chars = 2
996 # first three bytes (six ascii hex chars)
997 plmn_str = sixhexbytes[:plmn_chars]
998 # two bytes after first three bytes
999 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars]
1000 # one byte after first five bytes
1001 epdg_fqdn_format_str = sixhexbytes[plmn_chars + epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
1002 res['mcc'] = dec_mcc_from_plmn(plmn_str)
1003 res['mnc'] = dec_mnc_from_plmn(plmn_str)
1004 res['epdg_priority'] = epdg_priority_str
1005 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
1006 return res
1007
1008def format_ePDGSelection(hexstr):
1009 ePDGSelection_info_tag_chars = 2
1010 ePDGSelection_info_tag_str = hexstr[:2]
herlesupreeth3a261d32021-01-05 09:20:11 +01001011 s = ""
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001012 # Minimum length
1013 len_chars = 2
1014 # TODO: Need to determine length properly - definite length support only
1015 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
1016 # As per spec, length is 5n, n - number of PLMNs
1017 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
1018 # Totalling to 6 Bytes, maybe length should be 6n
1019 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +01001020
1021 # Not programmed scenario
1022 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
1023 len_chars = 0
1024 ePDGSelection_info_tag_chars = 0
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001025 if len_str[0] == '8':
1026 # The bits 7 to 1 denotes the number of length octets if length > 127
1027 if int(len_str[1]) > 0:
1028 # Update number of length octets
1029 len_chars = len_chars * int(len_str[1])
1030 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
1031
1032 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
1033 # Right pad to prevent index out of range - multiple of 6 bytes
1034 content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12)))
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001035 for rec_data in hexstr_to_Nbytearr(content_str, 6):
1036 rec_info = dec_ePDGSelection(rec_data)
1037 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
1038 rec_str = "unused"
1039 else:
1040 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
1041 (rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
1042 s += "\t%s # %s\n" % (rec_data, rec_str)
1043 return s
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001044
1045def get_addr_type(addr):
1046 """
1047 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
1048 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
1049
1050 TODO: Handle IPv6
1051 """
1052
1053 # Empty address string
1054 if not len(addr):
1055 return None
1056
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001057 addr_list = addr.split('.')
1058
1059 # Check for IPv4/IPv6
1060 try:
1061 import ipaddress
1062 # Throws ValueError if addr is not correct
Denis 'GNUtoo' Carikli79f5b602020-02-15 04:02:57 +07001063 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001064
1065 if ipa.version == 4:
1066 return 0x01
1067 elif ipa.version == 6:
1068 return 0x02
1069 except Exception as e:
1070 invalid_ipv4 = True
1071 for i in addr_list:
1072 # Invalid IPv4 may qualify for a valid FQDN, so make check here
1073 # e.g. 172.24.15.300
1074 import re
1075 if not re.match('^[0-9_]+$', i):
1076 invalid_ipv4 = False
1077 break
1078
1079 if invalid_ipv4:
1080 return None
1081
1082 fqdn_flag = True
1083 for i in addr_list:
1084 # Only Alpha-numeric characters and hyphen - RFC 1035
1085 import re
1086 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
1087 fqdn_flag = False
1088 break
1089
1090 # FQDN
1091 if fqdn_flag:
1092 return 0x00
1093
1094 return None
Harald Welte67d551a2021-01-21 14:50:01 +01001095
Harald Welte1e456572021-04-02 17:16:30 +02001096def sw_match(sw:str, pattern:str) -> bool:
Harald Welte67d551a2021-01-21 14:50:01 +01001097 """Match given SW against given pattern."""
1098 # Create a masked version of the returned status word
1099 sw_lower = sw.lower()
1100 sw_masked = ""
1101 for i in range(0, 4):
Philipp Maier78e32f22021-03-22 20:39:24 +01001102 if pattern[i] == '?':
Harald Welte67d551a2021-01-21 14:50:01 +01001103 sw_masked = sw_masked + '?'
Philipp Maier78e32f22021-03-22 20:39:24 +01001104 elif pattern[i] == 'x':
Harald Welte67d551a2021-01-21 14:50:01 +01001105 sw_masked = sw_masked + 'x'
1106 else:
1107 sw_masked = sw_masked + sw_lower[i]
Philipp Maier78e32f22021-03-22 20:39:24 +01001108 # Compare the masked version against the pattern
Harald Welte67d551a2021-01-21 14:50:01 +01001109 return sw_masked == pattern
Philipp Maier5d3e2592021-02-22 17:22:16 +01001110
Harald Welteee3501f2021-04-02 13:00:18 +02001111def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
Harald Welte52255572021-04-03 09:56:32 +02001112 align_left:bool = True) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +02001113 """Pretty print a list of strings into a tabulated form.
1114
1115 Args:
1116 width : total width in characters per line
1117 space : horizontal space between cells
1118 lspace : number of spaces before row
1119 align_lef : Align text to the left side
1120 Returns:
1121 multi-line string containing formatted table
1122 """
Philipp Maier5d3e2592021-02-22 17:22:16 +01001123 if str_list == None:
1124 return ""
1125 if len(str_list) <= 0:
1126 return ""
1127 longest_str = max(str_list, key=len)
1128 cellwith = len(longest_str) + hspace
1129 cols = width // cellwith
1130 rows = (len(str_list) - 1) // cols + 1
1131 table = []
1132 for i in iter(range(rows)):
1133 str_list_row = str_list[i::rows]
1134 if (align_left):
1135 format_str_cell = '%%-%ds'
1136 else:
1137 format_str_cell = '%%%ds'
1138 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
1139 format_str_row = (" " * lspace) + format_str_row
1140 table.append(format_str_row % tuple(str_list_row))
1141 return '\n'.join(table)
Harald Welte5e749a72021-04-10 17:18:17 +02001142
Harald Welte917d98c2021-04-21 11:51:25 +02001143def auto_int(x):
1144 """Helper function for argparse to accept hexadecimal integers."""
1145 return int(x, 0)
1146
Harald Welte5e749a72021-04-10 17:18:17 +02001147class JsonEncoder(json.JSONEncoder):
1148 """Extend the standard library JSONEncoder with support for more types."""
1149 def default(self, o):
1150 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
1151 return b2h(o)
1152 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +02001153
1154def boxed_heading_str(heading, width=80):
1155 """Generate a string that contains a boxed heading."""
1156 # Auto-enlarge box if heading exceeds length
1157 if len(heading) > width - 4:
1158 width = len(heading) + 4
1159
1160 res = "#" * width
1161 fstr = "\n# %-" + str(width - 4) + "s #\n"
1162 res += fstr % (heading)
1163 res += "#" * width
1164 return res
Harald Welte3de6ca22021-05-02 20:05:56 +02001165
1166
1167
1168class DataObject(abc.ABC):
1169 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
1170 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
1171 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
1172 tags at specific points in a stream. This class tries to represent this."""
1173 def __init__(self, name, desc = None, tag = None):
1174 """
1175 Args:
1176 name: A brief, all-lowercase, underscore separated string identifier
1177 desc: A human-readable description of what this DO represents
1178 tag : The tag associated with this DO
1179 """
1180 self.name = name
1181 self.desc = desc
1182 self.tag = tag
1183 self.decoded = None
1184 self.encoded = None
1185
1186 def __str__(self):
1187 return self.name
1188
1189 def __repr__(self):
1190 return '%s(%s)' % (self.__class__, self.name)
1191
1192 def __or__(self, other):
1193 """OR-ing DataObjects together renders a DataObjectChoice."""
1194 if isinstance(other, DataObject):
1195 # DataObject | DataObject = DataObjectChoice
1196 return DataObjectChoice(None, members=[self, other])
1197 else:
1198 raise TypeError
1199
1200 def __add__(self, other):
1201 """ADD-ing DataObjects together renders a DataObjectCollection."""
1202 if isinstance(other, DataObject):
1203 # DataObject + DataObject = DataObjectCollectin
1204 return DataObjectCollection(None, members=[self, other])
1205
1206 def _compute_tag(self):
1207 """Compute the tag (sometimes the tag encodes part of the value)."""
1208 return self.tag
1209
1210 def to_dict(self):
1211 """Return a dict in form "name: decoded_value" """
1212 return {self.name: self.decoded}
1213
1214 @abc.abstractmethod
1215 def from_bytes(self, do:bytes):
1216 """Parse the value part of the DO into the internal state of this instance.
1217 Args:
1218 do : binary encoded bytes
1219 """
1220
1221 @abc.abstractmethod
1222 def to_bytes(self):
1223 """Encode the internal state of this instance into the TLV value part.
1224 Returns:
1225 binary bytes encoding the internal state
1226 """
1227
1228 def from_tlv(self, do:bytes):
1229 """Parse binary TLV representation into internal state. The resulting decoded
1230 representation is _not_ returned, but just internalized in the object instance!
1231 Args:
1232 do : input bytes containing TLV-encoded representation
1233 Returns:
1234 bytes remaining at end of 'do' after parsing one TLV/DO.
1235 """
1236 if do[0] != self.tag:
1237 raise ValueError('%s: Can only decode tag 0x%02x' % (self, self.tag))
1238 length = do[1]
1239 val = do[2:2+length]
1240 self.from_bytes(val)
1241 # return remaining bytes
1242 return do[2+length:]
1243
1244 def to_tlv(self):
1245 """Encode internal representation to binary TLV.
1246 Returns:
1247 bytes encoded in TLV format.
1248 """
1249 val = self.to_bytes()
1250 return bytes(self._compute_tag()) + bytes(len(val)) + val
1251
1252 # 'codec' interface
1253 def decode(self, binary:bytes):
1254 """Decode a single DOs from the input data.
1255 Args:
1256 binary : binary bytes of encoded data
1257 Returns:
1258 tuple of (decoded_result, binary_remainder)
1259 """
1260 tag = binary[0]
1261 if tag != self.tag:
1262 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1263 (self, tag, binary, self.tag))
1264 remainder = self.from_tlv(binary)
1265 return (self.to_dict(), remainder)
1266
1267 # 'codec' interface
1268 def encode(self):
1269 return self.to_tlv()
1270
1271class TL0_DataObject(DataObject):
1272 """Data Object that has Tag, Len=0 and no Value part."""
1273 def __init__(self, name, desc, tag, val=None):
1274 super().__init__(name, desc, tag)
1275 self.val = val
1276
1277 def from_bytes(self, binary:bytes):
1278 if len(binary) != 0:
1279 raise ValueError
1280 self.decoded = self.val
1281
1282 def to_bytes(self):
1283 return b''
1284
1285
1286class DataObjectCollection:
1287 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1288 A given encoded DO may contain any of them in any order, and may contain multiple instances
1289 of each DO."""
1290 def __init__(self, name, desc = None, members=None):
1291 self.name = name
1292 self.desc = desc
1293 self.members = members or []
1294 self.members_by_tag = {}
1295 self.members_by_name = {}
1296 self.members_by_tag = { m.tag:m for m in members }
1297 self.members_by_name = { m.name:m for m in members }
1298
1299 def __str__(self):
1300 member_strs = [str(x) for x in self.members]
1301 return '%s(%s)' % (self.name, ','.join(member_strs))
1302
1303 def __repr__(self):
1304 member_strs = [repr(x) for x in self.members]
1305 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1306
1307 def __add__(self, other):
1308 """Extending DataCollections with other DataCollections or DataObjects."""
1309 if isinstance(other, DataObjectCollection):
1310 # adding one collection to another
1311 members = self.members + other.members
1312 return DataObjectCollection(self.name, self.desc, members)
1313 elif isinstance(other, DataObject):
1314 # adding a member to a collection
1315 return DataObjectCollection(self.name, self.desc, self.members + [other])
1316 else:
1317 raise TypeError
1318
1319 # 'codec' interface
1320 def decode(self, binary:bytes):
1321 """Decode any number of DOs from the collection until the end of the input data,
1322 or uninitialized memory (0xFF) is found.
1323 Args:
1324 binary : binary bytes of encoded data
1325 Returns:
1326 tuple of (decoded_result, binary_remainder)
1327 """
1328 res = []
1329 remainder = binary
1330 # iterate until no binary trailer is left
1331 while len(remainder):
1332 tag = remainder[0]
1333 if tag == 0xff: # uninitialized memory at the end?
1334 return (res, remainder)
1335 if not tag in self.members_by_tag:
1336 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1337 (self, tag, remainder, self.members_by_tag.keys()))
1338 obj = self.members_by_tag[tag]
1339 # DO from_tlv returns remainder of binary
1340 remainder = obj.from_tlv(remainder)
1341 # collect our results
1342 res.append(obj.to_dict())
1343 return (res, remainder)
1344
1345 # 'codec' interface
1346 def encode(self, decoded):
1347 res = bytearray()
1348 for i in decoded:
1349 obj = self.members_by_name(i[0])
1350 res.append(obj.to_tlv())
1351 return res
1352
1353class DataObjectChoice(DataObjectCollection):
1354 """One Data Object from within a choice, identified by its tag.
1355 This means that exactly one member of the choice must occur, and which one occurs depends
1356 on the tag."""
1357 def __add__(self, other):
1358 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1359 raise TypeError
1360
1361 def __or__(self, other):
1362 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1363 if isinstance(other, DataObjectChoice):
1364 # adding one collection to another
1365 members = self.members + other.members
1366 return DataObjectChoice(self.name, self.desc, members)
1367 elif isinstance(other, DataObject):
1368 # adding a member to a collection
1369 return DataObjectChoice(self.name, self.desc, self.members + [other])
1370 else:
1371 raise TypeError
1372
1373 # 'codec' interface
1374 def decode(self, binary:bytes):
1375 """Decode a single DOs from the choice based on the tag.
1376 Args:
1377 binary : binary bytes of encoded data
1378 Returns:
1379 tuple of (decoded_result, binary_remainder)
1380 """
1381 tag = binary[0]
1382 if tag == 0xff:
1383 return (None, binary)
1384 if not tag in self.members_by_tag:
1385 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1386 (self, tag, binary, self.members_by_tag.keys()))
1387 obj = self.members_by_tag[tag]
1388 remainder = obj.from_tlv(binary)
1389 return (obj.to_dict(), remainder)
1390
1391 # 'codec' interface
1392 def encode(self, decoded):
1393 obj = self.members_by_name(decoded[0])
1394 return obj.to_tlv()
1395
1396class DataObjectSequence:
1397 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1398 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1399 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1400 for encoding or decoding an entire sequence."""
1401 def __init__(self, name, desc=None, sequence=None):
1402 self.sequence = sequence or []
1403 self.name = name
1404 self.desc = desc
1405
1406 def __str__(self):
1407 member_strs = [str(x) for x in self.sequence]
1408 return '%s(%s)' % (self.name, ','.join(member_strs))
1409
1410 def __repr__(self):
1411 member_strs = [repr(x) for x in self.sequence]
1412 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1413
1414 def __add__(self, other):
1415 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1416 if isinstance(other, 'DataObject'):
1417 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1418 elif isinstance(other, 'DataObjectChoice'):
1419 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1420 elif isinstance(other, 'DataObjectSequence'):
1421 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
1422
1423 # 'codec' interface
1424 def decode(self, binary:bytes):
1425 """Decode a sequence by calling the decoder of each element in the sequence.
1426 Args:
1427 binary : binary bytes of encoded data
1428 Returns:
1429 tuple of (decoded_result, binary_remainder)
1430 """
1431 remainder = binary
1432 res = []
1433 for e in self.sequence:
1434 (r, remainder) = e.decode(remainder)
1435 if r:
1436 res.append(r)
1437 return (res, remainder)
1438
1439 # 'codec' interface
1440 def decode_multi(self, do:bytes):
1441 """Decode multiple occurrences of the sequence from the binary input data.
1442 Args:
1443 do : binary input data to be decoded
1444 Returns:
1445 list of results of the decoder of this sequences
1446 """
1447 remainder = do
1448 res = []
1449 while len(remainder):
1450 (r, remainder2) = self.decode(remainder)
1451 if r:
1452 res.append(r)
1453 if len(remainder2) < len(remainder):
1454 remainder = remainder2
1455 else:
1456 remainder = remainder2
1457 break
1458 return (res, remainder)
1459
1460 # 'codec' interface
1461 def encode(self, decoded):
1462 """Encode a sequence by calling the encoder of each element in the sequence."""
1463 encoded = bytearray()
1464 i = 0
1465 for e in self.sequence:
1466 encoded += e.encode(decoded[i])
1467 i += 1
1468 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001469
1470class CardCommand:
1471 """A single card command / instruction."""
1472 def __init__(self, name, ins, cla_list=None, desc=None):
1473 self.name = name
1474 self.ins = ins
1475 self.cla_list = cla_list or []
1476 self.cla_list = [x.lower() for x in self.cla_list]
1477 self.desc = desc
1478
1479 def __str__(self):
1480 return self.name
1481
1482 def __repr__(self):
1483 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1484
1485 def match_cla(self, cla):
1486 """Does the given CLA match the CLA list of the command?."""
1487 if not isinstance(cla, str):
1488 cla = '%02u' % cla
1489 cla = cla.lower()
1490 for cla_match in self.cla_list:
1491 cla_masked = ""
1492 for i in range(0, 2):
1493 if cla_match[i] == 'x':
1494 cla_masked += 'x'
1495 else:
1496 cla_masked += cla[i]
1497 if cla_masked == cla_match:
1498 return True
1499 return False
1500
1501
1502class CardCommandSet:
1503 """A set of card instructions, typically specified within one spec."""
1504 def __init__(self, name, cmds=[]):
1505 self.name = name
1506 self.cmds = { c.ins : c for c in cmds }
1507
1508 def __str__(self):
1509 return self.name
1510
1511 def __getitem__(self, idx):
1512 return self.cmds[idx]
1513
1514 def __add__(self, other):
1515 if isinstance(other, CardCommand):
1516 if other.ins in self.cmds:
1517 raise ValueError('%s: INS 0x%02x already defined: %s' %
1518 (self, other.ins, self.cmds[other.ins]))
1519 self.cmds[other.ins] = other
1520 elif isinstance(other, CardCommandSet):
1521 for c in other.cmds.keys():
1522 self.cmds[c] = other.cmds[c]
1523 else:
1524 raise ValueError('%s: Unsupported type to add operator: %s' % (self, other))
1525
1526 def lookup(self, ins, cla=None):
1527 """look-up the command within the CommandSet."""
1528 ins = int(ins)
1529 if not ins in self.cmds:
1530 return None
1531 cmd = self.cmds[ins]
1532 if cla and not cmd.match_cla(cla):
1533 return None
1534 return cmd
Philipp Maiera028c7d2021-11-08 16:12:03 +01001535
1536def all_subclasses(cls) -> set:
1537 """Recursively get all subclasses of a specified class"""
1538 return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])