blob: e30c970ec91fa1c8567faacce7ddf88d97ec1865 [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"},
467 {'bit': 7, 'name': "GSM"},
468 {'bit': 6, 'name': "GSM COMPACT"},
469 {'bit': 5, 'name': "cdma2000 HRPD"},
470 {'bit': 4, 'name': "cdma2000 1xRTT"},
471 ]
472 ia = h2i(twohexbytes)
473 u16t = (ia[0] << 8)|ia[1]
474 sel = []
475 for a in act_list:
476 if u16t & (1 << a['bit']):
Philipp Maiere7d41792021-04-29 16:20:07 +0200477 if a['name'] == "E-UTRAN":
478 # The Access technology identifier of E-UTRAN
479 # allows a more detailed specification:
480 if u16t & (1 << 13) and u16t & (1 << 12):
481 sel.append("E-UTRAN WB-S1")
482 sel.append("E-UTRAN NB-S1")
483 elif u16t & (1 << 13):
484 sel.append("E-UTRAN WB-S1")
485 elif u16t & (1 << 12):
486 sel.append("E-UTRAN NB-S1")
487 else:
488 sel.append("E-UTRAN")
489 else:
490 sel.append(a['name'])
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100491 return sel
492
Harald Welte52255572021-04-03 09:56:32 +0200493def dec_xplmn_w_act(fivehexbytes:Hexstr) -> Dict[str,Any]:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200494 res = {'mcc': "0", 'mnc': "0", 'act': []}
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100495 plmn_chars = 6
496 act_chars = 4
497 plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
498 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
Philipp Maierb919f8b2021-04-27 18:28:27 +0200499 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
500 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100501 res['act'] = dec_act(act_str)
502 return res
503
504def format_xplmn_w_act(hexstr):
505 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200506 for rec_data in hexstr_to_Nbytearr(hexstr, 5):
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100507 rec_info = dec_xplmn_w_act(rec_data)
Philipp Maierb919f8b2021-04-27 18:28:27 +0200508 if rec_info['mcc'] == "" and rec_info['mnc'] == "":
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100509 rec_str = "unused"
510 else:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200511 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 +0100512 s += "\t%s # %s\n" % (rec_data, rec_str)
513 return s
514
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100515def dec_loci(hexstr):
516 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
517 res['tmsi'] = hexstr[:8]
518 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
519 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
520 res['lac'] = hexstr[14:18]
521 res['status'] = h2i(hexstr[20:22])
522 return res
523
524def dec_psloci(hexstr):
525 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
526 res['p-tmsi'] = hexstr[:8]
527 res['p-tmsi-sig'] = hexstr[8:14]
528 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
529 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
530 res['lac'] = hexstr[20:24]
531 res['rac'] = hexstr[24:26]
532 res['status'] = h2i(hexstr[26:28])
533 return res
534
535def dec_epsloci(hexstr):
536 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
537 res['guti'] = hexstr[:24]
538 res['tai'] = hexstr[24:34]
539 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
540 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
541 res['tac'] = hexstr[30:34]
542 res['status'] = h2i(hexstr[34:36])
543 return res
544
Harald Welte52255572021-04-03 09:56:32 +0200545def dec_xplmn(threehexbytes:Hexstr) -> dict:
Harald Welteca673942020-06-03 15:19:40 +0200546 res = {'mcc': 0, 'mnc': 0, 'act': []}
547 plmn_chars = 6
548 plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
549 res['mcc'] = dec_mcc_from_plmn(plmn_str)
550 res['mnc'] = dec_mnc_from_plmn(plmn_str)
551 return res
552
Harald Welte52255572021-04-03 09:56:32 +0200553def format_xplmn(hexstr:Hexstr) -> str:
Harald Welteca673942020-06-03 15:19:40 +0200554 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200555 for rec_data in hexstr_to_Nbytearr(hexstr, 3):
Harald Welteca673942020-06-03 15:19:40 +0200556 rec_info = dec_xplmn(rec_data)
557 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
558 rec_str = "unused"
559 else:
560 rec_str = "MCC: %03d MNC: %03d" % (rec_info['mcc'], rec_info['mnc'])
561 s += "\t%s # %s\n" % (rec_data, rec_str)
562 return s
563
Harald Welte52255572021-04-03 09:56:32 +0200564def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900565 """
566 Run the milenage algorithm to calculate OPC from Ki and OP
567 """
568 from Crypto.Cipher import AES
569 from Crypto.Util.strxor import strxor
570 from pySim.utils import b2h
571
572 # We pass in hex string and now need to work on bytes
Harald Welteeab8d2a2021-03-05 18:30:23 +0100573 ki_bytes = bytes(h2b(ki_hex))
574 op_bytes = bytes(h2b(op_hex))
Harald Welteab34fa82021-03-05 18:39:59 +0100575 aes = AES.new(ki_bytes, AES.MODE_ECB)
Harald Welteeab8d2a2021-03-05 18:30:23 +0100576 opc_bytes = aes.encrypt(op_bytes)
577 return b2h(strxor(opc_bytes, op_bytes))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900578
Harald Welte52255572021-04-03 09:56:32 +0200579def calculate_luhn(cc) -> int:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900580 """
581 Calculate Luhn checksum used in e.g. ICCID and IMEI
582 """
Daniel Willmanndd014ea2020-10-19 10:35:11 +0200583 num = list(map(int, str(cc)))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900584 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
585 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200586
Harald Welte52255572021-04-03 09:56:32 +0200587def mcc_from_imsi(imsi:str) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200588 """
589 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
590 """
591 if imsi == None:
592 return None
593
594 if len(imsi) > 3:
595 return imsi[:3]
596 else:
597 return None
598
Harald Welte52255572021-04-03 09:56:32 +0200599def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200600 """
601 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
602 """
603 if imsi == None:
604 return None
605
606 if len(imsi) > 3:
607 if long:
608 return imsi[3:6]
609 else:
610 return imsi[3:5]
611 else:
612 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100613
Harald Welte52255572021-04-03 09:56:32 +0200614def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100615 """
616 Derive decimal representation of the MCC (Mobile Country Code)
617 from three given digits.
618 """
619
620 mcc = 0
621
622 if digit1 != 0x0f:
623 mcc += digit1 * 100
624 if digit2 != 0x0f:
625 mcc += digit2 * 10
626 if digit3 != 0x0f:
627 mcc += digit3
628
629 return mcc
630
Harald Welte52255572021-04-03 09:56:32 +0200631def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100632 """
633 Derive decimal representation of the MNC (Mobile Network Code)
634 from two or (optionally) three given digits.
635 """
636
637 mnc = 0
638
639 # 3-rd digit is optional for the MNC. If present
640 # the algorythm is the same as for the MCC.
641 if digit3 != 0x0f:
642 return derive_mcc(digit1, digit2, digit3)
643
644 if digit1 != 0x0f:
645 mnc += digit1 * 10
646 if digit2 != 0x0f:
647 mnc += digit2
648
649 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100650
Harald Welte52255572021-04-03 09:56:32 +0200651def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100652 """
653 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
654 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
655 """
656
657 # Convert from str to (kind of) 'bytes'
658 ef_msisdn = h2b(ef_msisdn)
659
660 # Make sure mandatory fields are present
661 if len(ef_msisdn) < 14:
662 raise ValueError("EF.MSISDN is too short")
663
664 # Skip optional Alpha Identifier
665 xlen = len(ef_msisdn) - 14
666 msisdn_lhv = ef_msisdn[xlen:]
667
668 # Parse the length (in bytes) of the BCD encoded number
Harald Welte4f6ca432021-02-01 17:51:56 +0100669 bcd_len = msisdn_lhv[0]
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100670 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
671 if bcd_len == 0xff:
672 return None
673 elif bcd_len > 11 or bcd_len < 1:
674 raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len)
675
676 # Parse ToN / NPI
Harald Welte4f6ca432021-02-01 17:51:56 +0100677 ton = (msisdn_lhv[1] >> 4) & 0x07
678 npi = msisdn_lhv[1] & 0x0f
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100679 bcd_len -= 1
680
681 # No MSISDN?
682 if not bcd_len:
683 return (npi, ton, None)
684
685 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
686 # International number 10.5.118/3GPP TS 24.008
Vadim Yanitskiy7ba24282020-02-27 00:04:13 +0700687 if ton == 0x01:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100688 msisdn = '+' + msisdn
689
690 return (npi, ton, msisdn)
Supreeth Herle5a541012019-12-22 08:59:16 +0100691
Harald Welte52255572021-04-03 09:56:32 +0200692def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr:
Supreeth Herle5a541012019-12-22 08:59:16 +0100693 """
694 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200695 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
696 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100697
698 Default NPI / ToN values:
699 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
700 - ToN: network specific or international number (if starts with '+').
701 """
702
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200703 # If no MSISDN is supplied then encode the file contents as all "ff"
704 if msisdn == "" or msisdn == "+":
705 return "ff" * 14
706
Supreeth Herle5a541012019-12-22 08:59:16 +0100707 # Leading '+' indicates International Number
708 if msisdn[0] == '+':
709 msisdn = msisdn[1:]
710 ton = 0x01
711
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200712 # An MSISDN must not exceed 20 digits
713 if len(msisdn) > 20:
714 raise ValueError("msisdn must not be longer than 20 digits")
715
Supreeth Herle5a541012019-12-22 08:59:16 +0100716 # Append 'f' padding if number of digits is odd
717 if len(msisdn) % 2 > 0:
718 msisdn += 'f'
719
720 # BCD length also includes NPI/ToN header
721 bcd_len = len(msisdn) // 2 + 1
722 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
723 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
724
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200725 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
726
Supreeth Herle441c4a72020-03-24 10:19:15 +0100727
Harald Welte52255572021-04-03 09:56:32 +0200728def dec_st(st, table="sim") -> str:
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200729 """
730 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
731 """
732
Supreeth Herledf330372020-04-20 14:48:55 +0200733 if table == "isim":
734 from pySim.ts_31_103 import EF_IST_map
735 lookup_map = EF_IST_map
736 elif table == "usim":
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200737 from pySim.ts_31_102 import EF_UST_map
738 lookup_map = EF_UST_map
739 else:
740 from pySim.ts_51_011 import EF_SST_map
741 lookup_map = EF_SST_map
742
743 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
744
745 avail_st = ""
746 # Get each byte and check for available services
747 for i in range(0, len(st_bytes)):
748 # Byte i contains info about Services num (8i+1) to num (8i+8)
749 byte = int(st_bytes[i], 16)
750 # Services in each byte are in order MSB to LSB
751 # MSB - Service (8i+8)
752 # LSB - Service (8i+1)
753 for j in range(1, 9):
754 if byte&0x01 == 0x01 and ((8*i) + j in lookup_map):
755 # Byte X contains info about Services num (8X-7) to num (8X)
756 # bit = 1: service available
757 # bit = 0: service not available
758 avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j])
759 byte = byte >> 1
760 return avail_st
Supreeth Herle98370552020-05-11 09:04:41 +0200761
762def first_TLV_parser(bytelist):
763 '''
764 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
765
766 parses first TLV format record in a list of bytelist
767 returns a 3-Tuple: Tag, Length, Value
768 Value is a list of bytes
769 parsing of length is ETSI'style 101.220
770 '''
771 Tag = bytelist[0]
772 if bytelist[1] == 0xFF:
773 Len = bytelist[2]*256 + bytelist[3]
774 Val = bytelist[4:4+Len]
775 else:
776 Len = bytelist[1]
777 Val = bytelist[2:2+Len]
778 return (Tag, Len, Val)
779
780def TLV_parser(bytelist):
781 '''
782 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
783
784 loops on the input list of bytes with the "first_TLV_parser()" function
785 returns a list of 3-Tuples
786 '''
787 ret = []
788 while len(bytelist) > 0:
789 T, L, V = first_TLV_parser(bytelist)
790 if T == 0xFF:
791 # padding bytes
792 break
793 ret.append( (T, L, V) )
794 # need to manage length of L
795 if L > 0xFE:
796 bytelist = bytelist[ L+4 : ]
797 else:
798 bytelist = bytelist[ L+2 : ]
799 return ret
Supreeth Herled572ede2020-03-22 09:55:04 +0100800
Supreeth Herled84daa12020-03-24 12:20:40 +0100801def enc_st(st, service, state=1):
802 """
803 Encodes the EF S/U/IST/EST and returns the updated Service Table
804
805 Parameters:
806 st - Current value of SIM/USIM/ISIM Service Table
807 service - Service Number to encode as activated/de-activated
808 state - 1 mean activate, 0 means de-activate
809
810 Returns:
811 s - Modified value of SIM/USIM/ISIM Service Table
812
813 Default values:
814 - state: 1 - Sets the particular Service bit to 1
815 """
816 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
817
818 s = ""
819 # Check whether the requested service is present in each byte
820 for i in range(0, len(st_bytes)):
821 # Byte i contains info about Services num (8i+1) to num (8i+8)
822 if service in range((8*i) + 1, (8*i) + 9):
823 byte = int(st_bytes[i], 16)
824 # Services in each byte are in order MSB to LSB
825 # MSB - Service (8i+8)
826 # LSB - Service (8i+1)
827 mod_byte = 0x00
828 # Copy bit by bit contents of byte to mod_byte with modified bit
829 # for requested service
830 for j in range(1, 9):
831 mod_byte = mod_byte >> 1
832 if service == (8*i) + j:
833 mod_byte = state == 1 and mod_byte|0x80 or mod_byte&0x7f
834 else:
835 mod_byte = byte&0x01 == 0x01 and mod_byte|0x80 or mod_byte&0x7f
836 byte = byte >> 1
837
838 s += ('%02x' % (mod_byte))
839 else:
840 s += st_bytes[i]
841
842 return s
843
Supreeth Herle3b342c22020-03-24 16:15:02 +0100844def dec_addr_tlv(hexstr):
Supreeth Herled572ede2020-03-22 09:55:04 +0100845 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100846 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
847 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 +0100848 """
849
850 # Convert from hex str to int bytes list
Supreeth Herle3b342c22020-03-24 16:15:02 +0100851 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100852
Supreeth Herled572ede2020-03-22 09:55:04 +0100853 # Get list of tuples containing parsed TLVs
Supreeth Herle3b342c22020-03-24 16:15:02 +0100854 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100855
856 for tlv in tlvs:
857 # tlv = (T, L, [V])
858 # T = Tag
859 # L = Length
860 # [V] = List of value
861
862 # Invalid Tag value scenario
863 if tlv[0] != 0x80:
864 continue
865
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200866 # Empty field - Zero length
867 if tlv[1] == 0:
868 continue
869
Supreeth Herled572ede2020-03-22 09:55:04 +0100870 # First byte in the value has the address type
871 addr_type = tlv[2][0]
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100872 # TODO: Support parsing of IPv6
Supreeth Herle3b342c22020-03-24 16:15:02 +0100873 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
Supreeth Herled572ede2020-03-22 09:55:04 +0100874 if addr_type == 0x00: #FQDN
875 # Skip address tye byte i.e. first byte in value list
876 content = tlv[2][1:]
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200877 return (i2s(content), '00')
878
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100879 elif addr_type == 0x01: #IPv4
880 # Skip address tye byte i.e. first byte in value list
881 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
882 ipv4 = tlv[2][2:]
883 content = '.'.join(str(x) for x in ipv4)
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200884 return (content, '01')
885 else:
886 raise ValueError("Invalid address type")
Supreeth Herled572ede2020-03-22 09:55:04 +0100887
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200888 return (None, None)
Philipp Maierff84c232020-05-12 17:24:18 +0200889
Supreeth Herle3b342c22020-03-24 16:15:02 +0100890def enc_addr_tlv(addr, addr_type='00'):
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100891 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100892 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
893 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 +0100894
895 Default values:
Supreeth Herle3b342c22020-03-24 16:15:02 +0100896 - addr_type: 00 - FQDN format of Address
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100897 """
898
899 s = ""
900
Supreeth Herle654eca72020-03-25 14:25:38 +0100901 # TODO: Encoding of IPv6 address
902 if addr_type == '00': #FQDN
Supreeth Herle3b342c22020-03-24 16:15:02 +0100903 hex_str = s2h(addr)
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100904 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
Supreeth Herle654eca72020-03-25 14:25:38 +0100905 elif addr_type == '01': #IPv4
906 ipv4_list = addr.split('.')
907 ipv4_str = ""
908 for i in ipv4_list:
909 ipv4_str += ('%02x' % (int(i)))
910
911 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
912 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
913 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100914
915 return s
916
Harald Welte52255572021-04-03 09:56:32 +0200917def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
Philipp Maier47236502021-03-09 21:28:25 +0100918 """
919 Check if a string is a valid hexstring
920 """
921
922 # Filter obviously bad strings
923 if not string:
924 return False
925 if len(string) < minlen or minlen < 2:
926 return False
927 if len(string) % 2:
928 return False
929 if maxlen and len(string) > maxlen:
930 return False
931
932 # Try actual encoding to be sure
933 try:
934 try_encode = h2b(string)
935 return True
936 except:
937 return False
938
Harald Welte52255572021-04-03 09:56:32 +0200939def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
Philipp Maiere8536c02020-05-11 21:35:01 +0200940 """
941 The ADM pin can be supplied either in its hexadecimal form or as
942 ascii string. This function checks the supplied opts parameter and
Harald Welte236a65f2021-04-03 10:20:11 +0200943 returns the pin_adm as hex encoded string, regardless in which form
Philipp Maiere8536c02020-05-11 21:35:01 +0200944 it was originally supplied by the user
945 """
946
Harald Welte79b5ba42021-01-08 21:22:38 +0100947 if pin_adm is not None:
948 if len(pin_adm) <= 8:
949 pin_adm = ''.join(['%02x'%(ord(x)) for x in pin_adm])
Philipp Maiere8536c02020-05-11 21:35:01 +0200950 pin_adm = rpad(pin_adm, 16)
951
952 else:
953 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
954
Harald Welte79b5ba42021-01-08 21:22:38 +0100955 if pin_adm_hex is not None:
956 if len(pin_adm_hex) == 16:
957 pin_adm = pin_adm_hex
Philipp Maiere8536c02020-05-11 21:35:01 +0200958 # Ensure that it's hex-encoded
959 try:
960 try_encode = h2b(pin_adm)
961 except ValueError:
962 raise ValueError("PIN-ADM needs to be hex encoded using this option")
963 else:
964 raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)")
965
966 return pin_adm
967
Supreeth Herle95ec7722020-03-24 13:09:03 +0100968def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
969 """
970 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
971 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
972
973 Default values:
974 - epdg_priority: '0001' - 1st Priority
975 - epdg_fqdn_format: '00' - Operator Identifier FQDN
976 """
977
978 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
979 # TODO: Handle encoding of Length field for length more than 127 Bytes
980 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
981 content = rpad(content, len(hexstr))
982 return content
983
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100984def dec_ePDGSelection(sixhexbytes):
985 """
986 Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
987 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
988 """
989
990 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
991 plmn_chars = 6
992 epdg_priority_chars = 4
993 epdg_fqdn_format_chars = 2
994 # first three bytes (six ascii hex chars)
995 plmn_str = sixhexbytes[:plmn_chars]
996 # two bytes after first three bytes
997 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars]
998 # one byte after first five bytes
999 epdg_fqdn_format_str = sixhexbytes[plmn_chars + epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
1000 res['mcc'] = dec_mcc_from_plmn(plmn_str)
1001 res['mnc'] = dec_mnc_from_plmn(plmn_str)
1002 res['epdg_priority'] = epdg_priority_str
1003 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
1004 return res
1005
1006def format_ePDGSelection(hexstr):
1007 ePDGSelection_info_tag_chars = 2
1008 ePDGSelection_info_tag_str = hexstr[:2]
herlesupreeth3a261d32021-01-05 09:20:11 +01001009 s = ""
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001010 # Minimum length
1011 len_chars = 2
1012 # TODO: Need to determine length properly - definite length support only
1013 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
1014 # As per spec, length is 5n, n - number of PLMNs
1015 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
1016 # Totalling to 6 Bytes, maybe length should be 6n
1017 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +01001018
1019 # Not programmed scenario
1020 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
1021 len_chars = 0
1022 ePDGSelection_info_tag_chars = 0
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001023 if len_str[0] == '8':
1024 # The bits 7 to 1 denotes the number of length octets if length > 127
1025 if int(len_str[1]) > 0:
1026 # Update number of length octets
1027 len_chars = len_chars * int(len_str[1])
1028 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
1029
1030 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
1031 # Right pad to prevent index out of range - multiple of 6 bytes
1032 content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12)))
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001033 for rec_data in hexstr_to_Nbytearr(content_str, 6):
1034 rec_info = dec_ePDGSelection(rec_data)
1035 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
1036 rec_str = "unused"
1037 else:
1038 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
1039 (rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
1040 s += "\t%s # %s\n" % (rec_data, rec_str)
1041 return s
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001042
1043def get_addr_type(addr):
1044 """
1045 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
1046 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
1047
1048 TODO: Handle IPv6
1049 """
1050
1051 # Empty address string
1052 if not len(addr):
1053 return None
1054
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001055 addr_list = addr.split('.')
1056
1057 # Check for IPv4/IPv6
1058 try:
1059 import ipaddress
1060 # Throws ValueError if addr is not correct
Denis 'GNUtoo' Carikli79f5b602020-02-15 04:02:57 +07001061 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001062
1063 if ipa.version == 4:
1064 return 0x01
1065 elif ipa.version == 6:
1066 return 0x02
1067 except Exception as e:
1068 invalid_ipv4 = True
1069 for i in addr_list:
1070 # Invalid IPv4 may qualify for a valid FQDN, so make check here
1071 # e.g. 172.24.15.300
1072 import re
1073 if not re.match('^[0-9_]+$', i):
1074 invalid_ipv4 = False
1075 break
1076
1077 if invalid_ipv4:
1078 return None
1079
1080 fqdn_flag = True
1081 for i in addr_list:
1082 # Only Alpha-numeric characters and hyphen - RFC 1035
1083 import re
1084 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
1085 fqdn_flag = False
1086 break
1087
1088 # FQDN
1089 if fqdn_flag:
1090 return 0x00
1091
1092 return None
Harald Welte67d551a2021-01-21 14:50:01 +01001093
Harald Welte1e456572021-04-02 17:16:30 +02001094def sw_match(sw:str, pattern:str) -> bool:
Harald Welte67d551a2021-01-21 14:50:01 +01001095 """Match given SW against given pattern."""
1096 # Create a masked version of the returned status word
1097 sw_lower = sw.lower()
1098 sw_masked = ""
1099 for i in range(0, 4):
Philipp Maier78e32f22021-03-22 20:39:24 +01001100 if pattern[i] == '?':
Harald Welte67d551a2021-01-21 14:50:01 +01001101 sw_masked = sw_masked + '?'
Philipp Maier78e32f22021-03-22 20:39:24 +01001102 elif pattern[i] == 'x':
Harald Welte67d551a2021-01-21 14:50:01 +01001103 sw_masked = sw_masked + 'x'
1104 else:
1105 sw_masked = sw_masked + sw_lower[i]
Philipp Maier78e32f22021-03-22 20:39:24 +01001106 # Compare the masked version against the pattern
Harald Welte67d551a2021-01-21 14:50:01 +01001107 return sw_masked == pattern
Philipp Maier5d3e2592021-02-22 17:22:16 +01001108
Harald Welteee3501f2021-04-02 13:00:18 +02001109def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
Harald Welte52255572021-04-03 09:56:32 +02001110 align_left:bool = True) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +02001111 """Pretty print a list of strings into a tabulated form.
1112
1113 Args:
1114 width : total width in characters per line
1115 space : horizontal space between cells
1116 lspace : number of spaces before row
1117 align_lef : Align text to the left side
1118 Returns:
1119 multi-line string containing formatted table
1120 """
Philipp Maier5d3e2592021-02-22 17:22:16 +01001121 if str_list == None:
1122 return ""
1123 if len(str_list) <= 0:
1124 return ""
1125 longest_str = max(str_list, key=len)
1126 cellwith = len(longest_str) + hspace
1127 cols = width // cellwith
1128 rows = (len(str_list) - 1) // cols + 1
1129 table = []
1130 for i in iter(range(rows)):
1131 str_list_row = str_list[i::rows]
1132 if (align_left):
1133 format_str_cell = '%%-%ds'
1134 else:
1135 format_str_cell = '%%%ds'
1136 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
1137 format_str_row = (" " * lspace) + format_str_row
1138 table.append(format_str_row % tuple(str_list_row))
1139 return '\n'.join(table)
Harald Welte5e749a72021-04-10 17:18:17 +02001140
Harald Welte917d98c2021-04-21 11:51:25 +02001141def auto_int(x):
1142 """Helper function for argparse to accept hexadecimal integers."""
1143 return int(x, 0)
1144
Harald Welte5e749a72021-04-10 17:18:17 +02001145class JsonEncoder(json.JSONEncoder):
1146 """Extend the standard library JSONEncoder with support for more types."""
1147 def default(self, o):
1148 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
1149 return b2h(o)
1150 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +02001151
1152def boxed_heading_str(heading, width=80):
1153 """Generate a string that contains a boxed heading."""
1154 # Auto-enlarge box if heading exceeds length
1155 if len(heading) > width - 4:
1156 width = len(heading) + 4
1157
1158 res = "#" * width
1159 fstr = "\n# %-" + str(width - 4) + "s #\n"
1160 res += fstr % (heading)
1161 res += "#" * width
1162 return res
Harald Welte3de6ca22021-05-02 20:05:56 +02001163
1164
1165
1166class DataObject(abc.ABC):
1167 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
1168 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
1169 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
1170 tags at specific points in a stream. This class tries to represent this."""
1171 def __init__(self, name, desc = None, tag = None):
1172 """
1173 Args:
1174 name: A brief, all-lowercase, underscore separated string identifier
1175 desc: A human-readable description of what this DO represents
1176 tag : The tag associated with this DO
1177 """
1178 self.name = name
1179 self.desc = desc
1180 self.tag = tag
1181 self.decoded = None
1182 self.encoded = None
1183
1184 def __str__(self):
1185 return self.name
1186
1187 def __repr__(self):
1188 return '%s(%s)' % (self.__class__, self.name)
1189
1190 def __or__(self, other):
1191 """OR-ing DataObjects together renders a DataObjectChoice."""
1192 if isinstance(other, DataObject):
1193 # DataObject | DataObject = DataObjectChoice
1194 return DataObjectChoice(None, members=[self, other])
1195 else:
1196 raise TypeError
1197
1198 def __add__(self, other):
1199 """ADD-ing DataObjects together renders a DataObjectCollection."""
1200 if isinstance(other, DataObject):
1201 # DataObject + DataObject = DataObjectCollectin
1202 return DataObjectCollection(None, members=[self, other])
1203
1204 def _compute_tag(self):
1205 """Compute the tag (sometimes the tag encodes part of the value)."""
1206 return self.tag
1207
1208 def to_dict(self):
1209 """Return a dict in form "name: decoded_value" """
1210 return {self.name: self.decoded}
1211
1212 @abc.abstractmethod
1213 def from_bytes(self, do:bytes):
1214 """Parse the value part of the DO into the internal state of this instance.
1215 Args:
1216 do : binary encoded bytes
1217 """
1218
1219 @abc.abstractmethod
1220 def to_bytes(self):
1221 """Encode the internal state of this instance into the TLV value part.
1222 Returns:
1223 binary bytes encoding the internal state
1224 """
1225
1226 def from_tlv(self, do:bytes):
1227 """Parse binary TLV representation into internal state. The resulting decoded
1228 representation is _not_ returned, but just internalized in the object instance!
1229 Args:
1230 do : input bytes containing TLV-encoded representation
1231 Returns:
1232 bytes remaining at end of 'do' after parsing one TLV/DO.
1233 """
1234 if do[0] != self.tag:
1235 raise ValueError('%s: Can only decode tag 0x%02x' % (self, self.tag))
1236 length = do[1]
1237 val = do[2:2+length]
1238 self.from_bytes(val)
1239 # return remaining bytes
1240 return do[2+length:]
1241
1242 def to_tlv(self):
1243 """Encode internal representation to binary TLV.
1244 Returns:
1245 bytes encoded in TLV format.
1246 """
1247 val = self.to_bytes()
1248 return bytes(self._compute_tag()) + bytes(len(val)) + val
1249
1250 # 'codec' interface
1251 def decode(self, binary:bytes):
1252 """Decode a single DOs from the input data.
1253 Args:
1254 binary : binary bytes of encoded data
1255 Returns:
1256 tuple of (decoded_result, binary_remainder)
1257 """
1258 tag = binary[0]
1259 if tag != self.tag:
1260 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1261 (self, tag, binary, self.tag))
1262 remainder = self.from_tlv(binary)
1263 return (self.to_dict(), remainder)
1264
1265 # 'codec' interface
1266 def encode(self):
1267 return self.to_tlv()
1268
1269class TL0_DataObject(DataObject):
1270 """Data Object that has Tag, Len=0 and no Value part."""
1271 def __init__(self, name, desc, tag, val=None):
1272 super().__init__(name, desc, tag)
1273 self.val = val
1274
1275 def from_bytes(self, binary:bytes):
1276 if len(binary) != 0:
1277 raise ValueError
1278 self.decoded = self.val
1279
1280 def to_bytes(self):
1281 return b''
1282
1283
1284class DataObjectCollection:
1285 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1286 A given encoded DO may contain any of them in any order, and may contain multiple instances
1287 of each DO."""
1288 def __init__(self, name, desc = None, members=None):
1289 self.name = name
1290 self.desc = desc
1291 self.members = members or []
1292 self.members_by_tag = {}
1293 self.members_by_name = {}
1294 self.members_by_tag = { m.tag:m for m in members }
1295 self.members_by_name = { m.name:m for m in members }
1296
1297 def __str__(self):
1298 member_strs = [str(x) for x in self.members]
1299 return '%s(%s)' % (self.name, ','.join(member_strs))
1300
1301 def __repr__(self):
1302 member_strs = [repr(x) for x in self.members]
1303 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1304
1305 def __add__(self, other):
1306 """Extending DataCollections with other DataCollections or DataObjects."""
1307 if isinstance(other, DataObjectCollection):
1308 # adding one collection to another
1309 members = self.members + other.members
1310 return DataObjectCollection(self.name, self.desc, members)
1311 elif isinstance(other, DataObject):
1312 # adding a member to a collection
1313 return DataObjectCollection(self.name, self.desc, self.members + [other])
1314 else:
1315 raise TypeError
1316
1317 # 'codec' interface
1318 def decode(self, binary:bytes):
1319 """Decode any number of DOs from the collection until the end of the input data,
1320 or uninitialized memory (0xFF) is found.
1321 Args:
1322 binary : binary bytes of encoded data
1323 Returns:
1324 tuple of (decoded_result, binary_remainder)
1325 """
1326 res = []
1327 remainder = binary
1328 # iterate until no binary trailer is left
1329 while len(remainder):
1330 tag = remainder[0]
1331 if tag == 0xff: # uninitialized memory at the end?
1332 return (res, remainder)
1333 if not tag in self.members_by_tag:
1334 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1335 (self, tag, remainder, self.members_by_tag.keys()))
1336 obj = self.members_by_tag[tag]
1337 # DO from_tlv returns remainder of binary
1338 remainder = obj.from_tlv(remainder)
1339 # collect our results
1340 res.append(obj.to_dict())
1341 return (res, remainder)
1342
1343 # 'codec' interface
1344 def encode(self, decoded):
1345 res = bytearray()
1346 for i in decoded:
1347 obj = self.members_by_name(i[0])
1348 res.append(obj.to_tlv())
1349 return res
1350
1351class DataObjectChoice(DataObjectCollection):
1352 """One Data Object from within a choice, identified by its tag.
1353 This means that exactly one member of the choice must occur, and which one occurs depends
1354 on the tag."""
1355 def __add__(self, other):
1356 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1357 raise TypeError
1358
1359 def __or__(self, other):
1360 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1361 if isinstance(other, DataObjectChoice):
1362 # adding one collection to another
1363 members = self.members + other.members
1364 return DataObjectChoice(self.name, self.desc, members)
1365 elif isinstance(other, DataObject):
1366 # adding a member to a collection
1367 return DataObjectChoice(self.name, self.desc, self.members + [other])
1368 else:
1369 raise TypeError
1370
1371 # 'codec' interface
1372 def decode(self, binary:bytes):
1373 """Decode a single DOs from the choice based on the tag.
1374 Args:
1375 binary : binary bytes of encoded data
1376 Returns:
1377 tuple of (decoded_result, binary_remainder)
1378 """
1379 tag = binary[0]
1380 if tag == 0xff:
1381 return (None, binary)
1382 if not tag in self.members_by_tag:
1383 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1384 (self, tag, binary, self.members_by_tag.keys()))
1385 obj = self.members_by_tag[tag]
1386 remainder = obj.from_tlv(binary)
1387 return (obj.to_dict(), remainder)
1388
1389 # 'codec' interface
1390 def encode(self, decoded):
1391 obj = self.members_by_name(decoded[0])
1392 return obj.to_tlv()
1393
1394class DataObjectSequence:
1395 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1396 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1397 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1398 for encoding or decoding an entire sequence."""
1399 def __init__(self, name, desc=None, sequence=None):
1400 self.sequence = sequence or []
1401 self.name = name
1402 self.desc = desc
1403
1404 def __str__(self):
1405 member_strs = [str(x) for x in self.sequence]
1406 return '%s(%s)' % (self.name, ','.join(member_strs))
1407
1408 def __repr__(self):
1409 member_strs = [repr(x) for x in self.sequence]
1410 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1411
1412 def __add__(self, other):
1413 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1414 if isinstance(other, 'DataObject'):
1415 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1416 elif isinstance(other, 'DataObjectChoice'):
1417 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1418 elif isinstance(other, 'DataObjectSequence'):
1419 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
1420
1421 # 'codec' interface
1422 def decode(self, binary:bytes):
1423 """Decode a sequence by calling the decoder of each element in the sequence.
1424 Args:
1425 binary : binary bytes of encoded data
1426 Returns:
1427 tuple of (decoded_result, binary_remainder)
1428 """
1429 remainder = binary
1430 res = []
1431 for e in self.sequence:
1432 (r, remainder) = e.decode(remainder)
1433 if r:
1434 res.append(r)
1435 return (res, remainder)
1436
1437 # 'codec' interface
1438 def decode_multi(self, do:bytes):
1439 """Decode multiple occurrences of the sequence from the binary input data.
1440 Args:
1441 do : binary input data to be decoded
1442 Returns:
1443 list of results of the decoder of this sequences
1444 """
1445 remainder = do
1446 res = []
1447 while len(remainder):
1448 (r, remainder2) = self.decode(remainder)
1449 if r:
1450 res.append(r)
1451 if len(remainder2) < len(remainder):
1452 remainder = remainder2
1453 else:
1454 remainder = remainder2
1455 break
1456 return (res, remainder)
1457
1458 # 'codec' interface
1459 def encode(self, decoded):
1460 """Encode a sequence by calling the encoder of each element in the sequence."""
1461 encoded = bytearray()
1462 i = 0
1463 for e in self.sequence:
1464 encoded += e.encode(decoded[i])
1465 i += 1
1466 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001467
1468class CardCommand:
1469 """A single card command / instruction."""
1470 def __init__(self, name, ins, cla_list=None, desc=None):
1471 self.name = name
1472 self.ins = ins
1473 self.cla_list = cla_list or []
1474 self.cla_list = [x.lower() for x in self.cla_list]
1475 self.desc = desc
1476
1477 def __str__(self):
1478 return self.name
1479
1480 def __repr__(self):
1481 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1482
1483 def match_cla(self, cla):
1484 """Does the given CLA match the CLA list of the command?."""
1485 if not isinstance(cla, str):
1486 cla = '%02u' % cla
1487 cla = cla.lower()
1488 for cla_match in self.cla_list:
1489 cla_masked = ""
1490 for i in range(0, 2):
1491 if cla_match[i] == 'x':
1492 cla_masked += 'x'
1493 else:
1494 cla_masked += cla[i]
1495 if cla_masked == cla_match:
1496 return True
1497 return False
1498
1499
1500class CardCommandSet:
1501 """A set of card instructions, typically specified within one spec."""
1502 def __init__(self, name, cmds=[]):
1503 self.name = name
1504 self.cmds = { c.ins : c for c in cmds }
1505
1506 def __str__(self):
1507 return self.name
1508
1509 def __getitem__(self, idx):
1510 return self.cmds[idx]
1511
1512 def __add__(self, other):
1513 if isinstance(other, CardCommand):
1514 if other.ins in self.cmds:
1515 raise ValueError('%s: INS 0x%02x already defined: %s' %
1516 (self, other.ins, self.cmds[other.ins]))
1517 self.cmds[other.ins] = other
1518 elif isinstance(other, CardCommandSet):
1519 for c in other.cmds.keys():
1520 self.cmds[c] = other.cmds[c]
1521 else:
1522 raise ValueError('%s: Unsupported type to add operator: %s' % (self, other))
1523
1524 def lookup(self, ins, cla=None):
1525 """look-up the command within the CommandSet."""
1526 ins = int(ins)
1527 if not ins in self.cmds:
1528 return None
1529 cmd = self.cmds[ins]
1530 if cla and not cmd.match_cla(cla):
1531 return None
1532 return cmd
Philipp Maiera028c7d2021-11-08 16:12:03 +01001533
1534def all_subclasses(cls) -> set:
1535 """Recursively get all subclasses of a specified class"""
1536 return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])