blob: 8f2b2e9000a1e3b0f662f99fade114927bb43ffa [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
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020059# List of bytes to string
Harald Welte52255572021-04-03 09:56:32 +020060def i2s(s:List[int]) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +020061 """convert from a list of integers to an ASCII string"""
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020062 return ''.join([chr(x) for x in s])
63
Harald Welte52255572021-04-03 09:56:32 +020064def swap_nibbles(s:Hexstr) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020065 """swap the nibbles in a hex string"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010066 return ''.join([x+y for x,y in zip(s[1::2], s[0::2])])
67
Harald Welteee3501f2021-04-02 13:00:18 +020068def rpad(s:str, l:int, c='f') -> str:
69 """pad string on the right side.
70 Args:
71 s : string to pad
72 l : total length to pad to
73 c : padding character
74 Returns:
75 String 's' padded with as many 'c' as needed to reach total length of 'l'
76 """
Sylvain Munaut76504e02010-12-07 00:24:32 +010077 return s + c * (l - len(s))
78
Harald Welteee3501f2021-04-02 13:00:18 +020079def lpad(s:str, l:int, c='f') -> str:
80 """pad string on the left side.
81 Args:
82 s : string to pad
83 l : total length to pad to
84 c : padding character
85 Returns:
86 String 's' padded with as many 'c' as needed to reach total length of 'l'
87 """
Sylvain Munaut76504e02010-12-07 00:24:32 +010088 return c * (l - len(s)) + s
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040089
Harald Welteee3501f2021-04-02 13:00:18 +020090def half_round_up(n:int) -> int:
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020091 return (n + 1)//2
92
Philipp Maier796ca3d2021-11-01 17:13:57 +010093def str_sanitize(s:str) -> str:
94 """replace all non printable chars, line breaks and whitespaces, with ' ', make sure that
95 there are no whitespaces at the end and at the beginning of the string.
96
97 Args:
98 s : string to sanitize
99 Returns:
100 filtered result of string 's'
101 """
102
103 chars_to_keep = string.digits + string.ascii_letters + string.punctuation
104 res = ''.join([c if c in chars_to_keep else ' ' for c in s])
105 return res.strip()
106
Harald Welte917d98c2021-04-21 11:51:25 +0200107#########################################################################
Harald Welte9f3b44d2021-05-04 18:18:09 +0200108# poor man's COMPREHENSION-TLV decoder.
109#########################################################################
110
Harald Welte6912b1b2021-05-24 23:16:44 +0200111def comprehensiontlv_parse_tag_raw(binary:bytes) -> Tuple[int, bytes]:
112 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
113 if binary[0] in [0x00, 0x80, 0xff]:
114 raise ValueError("Found illegal value 0x%02x in %s" % (binary[0], binary))
115 if binary[0] == 0x7f:
116 # three-byte tag
117 tag = binary[0] << 16 | binary[1] << 8 | binary[2]
118 return (tag, binary[3:])
119 elif binary[0] == 0xff:
120 return None, binary
121 else:
122 # single byte tag
123 tag = binary[0]
124 return (tag, binary[1:])
125
Harald Welte9f3b44d2021-05-04 18:18:09 +0200126def comprehensiontlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
127 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
128 if binary[0] in [0x00, 0x80, 0xff]:
129 raise ValueError("Found illegal value 0x%02x in %s" % (binary[0], binary))
130 if binary[0] == 0x7f:
131 # three-byte tag
132 tag = (binary[1] & 0x7f) << 8
133 tag |= binary[2]
134 compr = True if binary[1] & 0x80 else False
135 return ({'comprehension': compr, 'tag': tag}, binary[3:])
136 else:
137 # single byte tag
138 tag = binary[0] & 0x7f
139 compr = True if binary[0] & 0x80 else False
140 return ({'comprehension': compr, 'tag': tag}, binary[1:])
141
142def comprehensiontlv_encode_tag(tag) -> bytes:
143 """Encode a single Tag according to ETSI TS 101 220 Section 7.1.1"""
144 # permit caller to specify tag also as integer value
145 if isinstance(tag, int):
146 compr = True if tag < 0xff and tag & 0x80 else False
147 tag = {'tag': tag, 'comprehension': compr}
148 compr = tag.get('comprehension', False)
149 if tag['tag'] in [0x00, 0x80, 0xff] or tag['tag'] > 0xff:
150 # 3-byte format
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300151 byte3 = tag['tag'] & 0xff
Harald Welte9f3b44d2021-05-04 18:18:09 +0200152 byte2 = (tag['tag'] >> 8) & 0x7f
153 if compr:
154 byte2 |= 0x80
155 return b'\x7f' + byte2.to_bytes(1, 'big') + byte3.to_bytes(1, 'big')
156 else:
157 # 1-byte format
158 ret = tag['tag']
159 if compr:
160 ret |= 0x80
161 return ret.to_bytes(1, 'big')
162
163# length value coding is equal to BER-TLV
164
Harald Welte6912b1b2021-05-24 23:16:44 +0200165def comprehensiontlv_parse_one(binary:bytes) -> (dict, int, bytes, bytes):
166 """Parse a single TLV IE at the start of the given binary data.
167 Args:
168 binary : binary input data of BER-TLV length field
169 Returns:
170 Tuple of (tag:dict, len:int, remainder:bytes)
171 """
172 (tagdict, remainder) = comprehensiontlv_parse_tag(binary)
173 (length, remainder) = bertlv_parse_len(remainder)
174 value = remainder[:length]
175 remainder = remainder[length:]
176 return (tagdict, length, value, remainder)
177
178
Harald Welte9f3b44d2021-05-04 18:18:09 +0200179
180#########################################################################
Harald Welte917d98c2021-04-21 11:51:25 +0200181# poor man's BER-TLV decoder. To be a more sophisticated OO library later
182#########################################################################
183
Harald Welte6912b1b2021-05-24 23:16:44 +0200184def bertlv_parse_tag_raw(binary:bytes) -> Tuple[int, bytes]:
185 """Get a single raw Tag from start of input according to ITU-T X.690 8.1.2
186 Args:
187 binary : binary input data of BER-TLV length field
188 Returns:
189 Tuple of (tag:int, remainder:bytes)
190 """
Harald Welte9a754102021-10-21 13:46:59 +0200191 # check for FF padding at the end, as customary in SIM card files
192 if binary[0] == 0xff and len(binary) == 1 or binary[0] == 0xff and binary[1] == 0xff:
Harald Welte6912b1b2021-05-24 23:16:44 +0200193 return None, binary
194 tag = binary[0] & 0x1f
195 if tag <= 30:
196 return binary[0], binary[1:]
197 else: # multi-byte tag
198 tag = binary[0]
199 i = 1
200 last = False
201 while not last:
202 last = False if binary[i] & 0x80 else True
203 tag <<= 8
204 tag |= binary[i]
205 i += 1
206 return tag, binary[i:]
207
Harald Welte917d98c2021-04-21 11:51:25 +0200208def bertlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
209 """Parse a single Tag value according to ITU-T X.690 8.1.2
210 Args:
211 binary : binary input data of BER-TLV length field
212 Returns:
213 Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes)
214 """
215 cls = binary[0] >> 6
216 constructed = True if binary[0] & 0x20 else False
217 tag = binary[0] & 0x1f
218 if tag <= 30:
219 return ({'class':cls, 'constructed':constructed, 'tag': tag}, binary[1:])
220 else: # multi-byte tag
221 tag = 0
222 i = 1
223 last = False
224 while not last:
225 last = False if binary[i] & 0x80 else True
226 tag <<= 7
227 tag |= binary[i] & 0x7f
228 i += 1
229 return ({'class':cls, 'constructed':constructed, 'tag':tag}, binary[i:])
230
Harald Weltef0885b12021-05-24 23:18:59 +0200231def bertlv_encode_tag(t) -> bytes:
232 """Encode a single Tag value according to ITU-T X.690 8.1.2
233 """
234 def get_top7_bits(inp:int) -> Tuple[int, int]:
235 """Get top 7 bits of integer. Returns those 7 bits as integer and the remaining LSBs."""
236 remain_bits = inp.bit_length()
237 if remain_bits >= 7:
238 bitcnt = 7
239 else:
240 bitcnt = remain_bits
241 outp = inp >> (remain_bits - bitcnt)
242 remainder = inp & ~ (inp << (remain_bits - bitcnt))
243 return outp, remainder
244
245 if isinstance(t, int):
246 # FIXME: multiple byte tags
247 tag = t & 0x1f
248 constructed = True if t & 0x20 else False
249 cls = t >> 6
250 else:
251 tag = t['tag']
252 constructed = t['constructed']
253 cls = t['class']
254 if tag <= 30:
255 t = tag & 0x1f
256 if constructed:
257 t |= 0x20
258 t |= (cls & 3) << 6
259 return bytes([t])
260 else: # multi-byte tag
Vadim Yanitskiydbd5ed62021-11-05 16:20:52 +0300261 t = 0x1f
Harald Weltef0885b12021-05-24 23:18:59 +0200262 if constructed:
263 t |= 0x20
264 t |= (cls & 3) << 6
265 tag_bytes = bytes([t])
266 remain = tag
267 while True:
268 t, remain = get_top7_bits(remain)
269 if remain:
270 t |= 0x80
271 tag_bytes += bytes([t])
272 if not remain:
273 break
274 return tag_bytes
275
Harald Welte917d98c2021-04-21 11:51:25 +0200276def bertlv_parse_len(binary:bytes) -> Tuple[int, bytes]:
277 """Parse a single Length value according to ITU-T X.690 8.1.3;
278 only the definite form is supported here.
279 Args:
280 binary : binary input data of BER-TLV length field
281 Returns:
282 Tuple of (length, remainder)
283 """
284 if binary[0] < 0x80:
285 return (binary[0], binary[1:])
286 else:
287 num_len_oct = binary[0] & 0x7f
288 length = 0
289 for i in range(1, 1+num_len_oct):
290 length <<= 8
291 length |= binary[i]
Harald Weltede027182021-05-04 20:06:45 +0200292 return (length, binary[1+num_len_oct:])
Harald Welte917d98c2021-04-21 11:51:25 +0200293
294def bertlv_encode_len(length:int) -> bytes:
295 """Encode a single Length value according to ITU-T X.690 8.1.3;
296 only the definite form is supported here.
297 Args:
298 length : length value to be encoded
299 Returns:
300 binary output data of BER-TLV length field
301 """
302 if length < 0x80:
303 return length.to_bytes(1, 'big')
304 elif length <= 0xff:
305 return b'\x81' + length.to_bytes(1, 'big')
306 elif length <= 0xffff:
307 return b'\x82' + length.to_bytes(2, 'big')
308 elif length <= 0xffffff:
309 return b'\x83' + length.to_bytes(3, 'big')
310 elif length <= 0xffffffff:
311 return b'\x84' + length.to_bytes(4, 'big')
312 else:
313 raise ValueError("Length > 32bits not supported")
314
Harald Weltec1475302021-05-21 21:47:55 +0200315def bertlv_parse_one(binary:bytes) -> (dict, int, bytes, bytes):
Harald Welte917d98c2021-04-21 11:51:25 +0200316 """Parse a single TLV IE at the start of the given binary data.
317 Args:
318 binary : binary input data of BER-TLV length field
319 Returns:
320 Tuple of (tag:dict, len:int, remainder:bytes)
321 """
322 (tagdict, remainder) = bertlv_parse_tag(binary)
323 (length, remainder) = bertlv_parse_len(remainder)
Harald Weltec1475302021-05-21 21:47:55 +0200324 value = remainder[:length]
325 remainder = remainder[length:]
326 return (tagdict, length, value, remainder)
Harald Welte917d98c2021-04-21 11:51:25 +0200327
328
329
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200330# IMSI encoded format:
331# For IMSI 0123456789ABCDE:
332#
333# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
334# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
335#
336# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
337#
338# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
339# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
340# encode itself.
341#
342# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
343# even length IMSI only uses half of the last byte.
344
Harald Welteee3501f2021-04-02 13:00:18 +0200345def enc_imsi(imsi:str):
346 """Converts a string IMSI into the encoded value of the EF"""
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200347 l = half_round_up(len(imsi) + 1) # Required bytes - include space for odd/even indicator
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400348 oe = len(imsi) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200349 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15)))
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400350 return ei
351
Harald Welte52255572021-04-03 09:56:32 +0200352def dec_imsi(ef:Hexstr) -> Optional[str]:
Harald Weltec9cdce32021-04-11 10:28:28 +0200353 """Converts an EF value to the IMSI string representation"""
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400354 if len(ef) < 4:
355 return None
Pau Espin Pedrol665bd222017-12-29 20:30:35 +0100356 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200357 l = l - 1 # Encoded length byte includes oe nibble
358 swapped = swap_nibbles(ef[2:]).rstrip('f')
Philipp Maiercd3d6262020-05-11 21:41:56 +0200359 if len(swapped) < 1:
360 return None
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400361 oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200362 if not oe:
363 # if even, only half of last byte was used
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400364 l = l-1
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200365 if l != len(swapped) - 1:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400366 return None
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200367 imsi = swapped[1:]
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400368 return imsi
369
Harald Welte52255572021-04-03 09:56:32 +0200370def dec_iccid(ef:Hexstr) -> str:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400371 return swap_nibbles(ef).strip('f')
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400372
Harald Welte52255572021-04-03 09:56:32 +0200373def enc_iccid(iccid:str) -> Hexstr:
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400374 return swap_nibbles(rpad(iccid, 20))
375
Philipp Maiere6f8d682021-04-23 21:14:41 +0200376def enc_plmn(mcc:Hexstr, mnc:Hexstr) -> Hexstr:
Alexander Chemerisdddbf522017-07-18 16:49:59 +0300377 """Converts integer MCC/MNC into 3 bytes for EF"""
Philipp Maier6c5cd802021-04-23 21:19:36 +0200378
379 # Make sure there are no excess whitespaces in the input
380 # parameters
381 mcc = mcc.strip()
382 mnc = mnc.strip()
383
384 # Make sure that MCC/MNC are correctly padded with leading
385 # zeros or 'F', depending on the length.
386 if len(mnc) == 0:
387 mnc = "FFF"
388 elif len(mnc) == 1:
389 mnc = "F0" + mnc
390 elif len(mnc) == 2:
391 mnc += "F"
392
393 if len(mcc) == 0:
394 mcc = "FFF"
395 elif len(mcc) == 1:
396 mcc = "00" + mcc
397 elif len(mcc) == 2:
398 mcc = "0" + mcc
399
Vadim Yanitskiyc8458e22021-03-12 00:34:10 +0100400 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300401
Harald Welted7a7e172021-04-07 00:30:10 +0200402def dec_plmn(threehexbytes:Hexstr) -> dict:
Philipp Maier6c5cd802021-04-23 21:19:36 +0200403 res = {'mcc': "0", 'mnc': "0" }
404 dec_mcc_from_plmn_str(threehexbytes)
405 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
406 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
Harald Welted7a7e172021-04-07 00:30:10 +0200407 return res
408
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300409def dec_spn(ef):
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200410 """Obsolete, kept for API compatibility"""
411 from ts_51_011 import EF_SPN
412 abstract_data = EF_SPN().decode_hex(ef)
413 show_in_hplmn = abstract_data['show_in_hplmn']
414 hide_in_oplmn = abstract_data['hide_in_oplmn']
415 name = abstract_data['spn']
416 return (name, show_in_hplmn, hide_in_oplmn)
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300417
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200418def enc_spn(name:str, show_in_hplmn=False, hide_in_oplmn=False):
419 """Obsolete, kept for API compatibility"""
420 from ts_51_011 import EF_SPN
421 abstract_data = {
422 'hide_in_oplmn' : hide_in_oplmn,
423 'show_in_hplmn' : show_in_hplmn,
424 'spn' : name,
425 }
426 return EF_SPN().encode_hex(abstract_data)
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900427
Supreeth Herlef3948532020-03-24 12:23:51 +0100428def hexstr_to_Nbytearr(s, nbytes):
429 return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]
430
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100431# Accepts hex string representing three bytes
Harald Welte52255572021-04-03 09:56:32 +0200432def dec_mcc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100433 ia = h2i(plmn)
434 digit1 = ia[0] & 0x0F # 1st byte, LSB
435 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
436 digit3 = ia[1] & 0x0F # 2nd byte, LSB
437 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
438 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100439 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100440
Philipp Maier6c5cd802021-04-23 21:19:36 +0200441def dec_mcc_from_plmn_str(plmn:Hexstr) -> str:
442 digit1 = plmn[1] # 1st byte, LSB
443 digit2 = plmn[0] # 1st byte, MSB
444 digit3 = plmn[3] # 2nd byte, LSB
445 res = digit1 + digit2 + digit3
446 return res.upper().strip("F")
447
Harald Welte52255572021-04-03 09:56:32 +0200448def dec_mnc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100449 ia = h2i(plmn)
Vadim Yanitskiyb271be32021-03-11 23:56:58 +0100450 digit1 = ia[2] & 0x0F # 3rd byte, LSB
451 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
452 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100453 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
454 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100455 return derive_mnc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100456
Philipp Maier6c5cd802021-04-23 21:19:36 +0200457def dec_mnc_from_plmn_str(plmn:Hexstr) -> str:
458 digit1 = plmn[5] # 3rd byte, LSB
459 digit2 = plmn[4] # 3rd byte, MSB
460 digit3 = plmn[2] # 2nd byte, MSB
461 res = digit1 + digit2 + digit3
462 return res.upper().strip("F")
463
Harald Welte52255572021-04-03 09:56:32 +0200464def dec_act(twohexbytes:Hexstr) -> List[str]:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100465 act_list = [
466 {'bit': 15, 'name': "UTRAN"},
467 {'bit': 14, 'name': "E-UTRAN"},
468 {'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
570 from Crypto.Util.strxor import strxor
571 from pySim.utils import b2h
572
573 # We pass in hex string and now need to work on bytes
Harald Welteeab8d2a2021-03-05 18:30:23 +0100574 ki_bytes = bytes(h2b(ki_hex))
575 op_bytes = bytes(h2b(op_hex))
Harald Welteab34fa82021-03-05 18:39:59 +0100576 aes = AES.new(ki_bytes, AES.MODE_ECB)
Harald Welteeab8d2a2021-03-05 18:30:23 +0100577 opc_bytes = aes.encrypt(op_bytes)
578 return b2h(strxor(opc_bytes, op_bytes))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900579
Harald Welte52255572021-04-03 09:56:32 +0200580def calculate_luhn(cc) -> int:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900581 """
582 Calculate Luhn checksum used in e.g. ICCID and IMEI
583 """
Daniel Willmanndd014ea2020-10-19 10:35:11 +0200584 num = list(map(int, str(cc)))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900585 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
586 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200587
Harald Welte52255572021-04-03 09:56:32 +0200588def mcc_from_imsi(imsi:str) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200589 """
590 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
591 """
592 if imsi == None:
593 return None
594
595 if len(imsi) > 3:
596 return imsi[:3]
597 else:
598 return None
599
Harald Welte52255572021-04-03 09:56:32 +0200600def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200601 """
602 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
603 """
604 if imsi == None:
605 return None
606
607 if len(imsi) > 3:
608 if long:
609 return imsi[3:6]
610 else:
611 return imsi[3:5]
612 else:
613 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100614
Harald Welte52255572021-04-03 09:56:32 +0200615def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100616 """
617 Derive decimal representation of the MCC (Mobile Country Code)
618 from three given digits.
619 """
620
621 mcc = 0
622
623 if digit1 != 0x0f:
624 mcc += digit1 * 100
625 if digit2 != 0x0f:
626 mcc += digit2 * 10
627 if digit3 != 0x0f:
628 mcc += digit3
629
630 return mcc
631
Harald Welte52255572021-04-03 09:56:32 +0200632def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100633 """
634 Derive decimal representation of the MNC (Mobile Network Code)
635 from two or (optionally) three given digits.
636 """
637
638 mnc = 0
639
640 # 3-rd digit is optional for the MNC. If present
641 # the algorythm is the same as for the MCC.
642 if digit3 != 0x0f:
643 return derive_mcc(digit1, digit2, digit3)
644
645 if digit1 != 0x0f:
646 mnc += digit1 * 10
647 if digit2 != 0x0f:
648 mnc += digit2
649
650 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100651
Harald Welte52255572021-04-03 09:56:32 +0200652def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100653 """
654 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
655 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
656 """
657
658 # Convert from str to (kind of) 'bytes'
659 ef_msisdn = h2b(ef_msisdn)
660
661 # Make sure mandatory fields are present
662 if len(ef_msisdn) < 14:
663 raise ValueError("EF.MSISDN is too short")
664
665 # Skip optional Alpha Identifier
666 xlen = len(ef_msisdn) - 14
667 msisdn_lhv = ef_msisdn[xlen:]
668
669 # Parse the length (in bytes) of the BCD encoded number
Harald Welte4f6ca432021-02-01 17:51:56 +0100670 bcd_len = msisdn_lhv[0]
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100671 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
672 if bcd_len == 0xff:
673 return None
674 elif bcd_len > 11 or bcd_len < 1:
675 raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len)
676
677 # Parse ToN / NPI
Harald Welte4f6ca432021-02-01 17:51:56 +0100678 ton = (msisdn_lhv[1] >> 4) & 0x07
679 npi = msisdn_lhv[1] & 0x0f
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100680 bcd_len -= 1
681
682 # No MSISDN?
683 if not bcd_len:
684 return (npi, ton, None)
685
686 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
687 # International number 10.5.118/3GPP TS 24.008
Vadim Yanitskiy7ba24282020-02-27 00:04:13 +0700688 if ton == 0x01:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100689 msisdn = '+' + msisdn
690
691 return (npi, ton, msisdn)
Supreeth Herle5a541012019-12-22 08:59:16 +0100692
Harald Welte52255572021-04-03 09:56:32 +0200693def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr:
Supreeth Herle5a541012019-12-22 08:59:16 +0100694 """
695 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200696 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
697 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100698
699 Default NPI / ToN values:
700 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
701 - ToN: network specific or international number (if starts with '+').
702 """
703
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200704 # If no MSISDN is supplied then encode the file contents as all "ff"
705 if msisdn == "" or msisdn == "+":
706 return "ff" * 14
707
Supreeth Herle5a541012019-12-22 08:59:16 +0100708 # Leading '+' indicates International Number
709 if msisdn[0] == '+':
710 msisdn = msisdn[1:]
711 ton = 0x01
712
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200713 # An MSISDN must not exceed 20 digits
714 if len(msisdn) > 20:
715 raise ValueError("msisdn must not be longer than 20 digits")
716
Supreeth Herle5a541012019-12-22 08:59:16 +0100717 # Append 'f' padding if number of digits is odd
718 if len(msisdn) % 2 > 0:
719 msisdn += 'f'
720
721 # BCD length also includes NPI/ToN header
722 bcd_len = len(msisdn) // 2 + 1
723 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
724 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
725
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200726 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
727
Supreeth Herle441c4a72020-03-24 10:19:15 +0100728
Harald Welte52255572021-04-03 09:56:32 +0200729def dec_st(st, table="sim") -> str:
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200730 """
731 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
732 """
733
Supreeth Herledf330372020-04-20 14:48:55 +0200734 if table == "isim":
735 from pySim.ts_31_103 import EF_IST_map
736 lookup_map = EF_IST_map
737 elif table == "usim":
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200738 from pySim.ts_31_102 import EF_UST_map
739 lookup_map = EF_UST_map
740 else:
741 from pySim.ts_51_011 import EF_SST_map
742 lookup_map = EF_SST_map
743
744 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
745
746 avail_st = ""
747 # Get each byte and check for available services
748 for i in range(0, len(st_bytes)):
749 # Byte i contains info about Services num (8i+1) to num (8i+8)
750 byte = int(st_bytes[i], 16)
751 # Services in each byte are in order MSB to LSB
752 # MSB - Service (8i+8)
753 # LSB - Service (8i+1)
754 for j in range(1, 9):
755 if byte&0x01 == 0x01 and ((8*i) + j in lookup_map):
756 # Byte X contains info about Services num (8X-7) to num (8X)
757 # bit = 1: service available
758 # bit = 0: service not available
759 avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j])
760 byte = byte >> 1
761 return avail_st
Supreeth Herle98370552020-05-11 09:04:41 +0200762
763def first_TLV_parser(bytelist):
764 '''
765 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
766
767 parses first TLV format record in a list of bytelist
768 returns a 3-Tuple: Tag, Length, Value
769 Value is a list of bytes
770 parsing of length is ETSI'style 101.220
771 '''
772 Tag = bytelist[0]
773 if bytelist[1] == 0xFF:
774 Len = bytelist[2]*256 + bytelist[3]
775 Val = bytelist[4:4+Len]
776 else:
777 Len = bytelist[1]
778 Val = bytelist[2:2+Len]
779 return (Tag, Len, Val)
780
781def TLV_parser(bytelist):
782 '''
783 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
784
785 loops on the input list of bytes with the "first_TLV_parser()" function
786 returns a list of 3-Tuples
787 '''
788 ret = []
789 while len(bytelist) > 0:
790 T, L, V = first_TLV_parser(bytelist)
791 if T == 0xFF:
792 # padding bytes
793 break
794 ret.append( (T, L, V) )
795 # need to manage length of L
796 if L > 0xFE:
797 bytelist = bytelist[ L+4 : ]
798 else:
799 bytelist = bytelist[ L+2 : ]
800 return ret
Supreeth Herled572ede2020-03-22 09:55:04 +0100801
Supreeth Herled84daa12020-03-24 12:20:40 +0100802def enc_st(st, service, state=1):
803 """
804 Encodes the EF S/U/IST/EST and returns the updated Service Table
805
806 Parameters:
807 st - Current value of SIM/USIM/ISIM Service Table
808 service - Service Number to encode as activated/de-activated
809 state - 1 mean activate, 0 means de-activate
810
811 Returns:
812 s - Modified value of SIM/USIM/ISIM Service Table
813
814 Default values:
815 - state: 1 - Sets the particular Service bit to 1
816 """
817 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
818
819 s = ""
820 # Check whether the requested service is present in each byte
821 for i in range(0, len(st_bytes)):
822 # Byte i contains info about Services num (8i+1) to num (8i+8)
823 if service in range((8*i) + 1, (8*i) + 9):
824 byte = int(st_bytes[i], 16)
825 # Services in each byte are in order MSB to LSB
826 # MSB - Service (8i+8)
827 # LSB - Service (8i+1)
828 mod_byte = 0x00
829 # Copy bit by bit contents of byte to mod_byte with modified bit
830 # for requested service
831 for j in range(1, 9):
832 mod_byte = mod_byte >> 1
833 if service == (8*i) + j:
834 mod_byte = state == 1 and mod_byte|0x80 or mod_byte&0x7f
835 else:
836 mod_byte = byte&0x01 == 0x01 and mod_byte|0x80 or mod_byte&0x7f
837 byte = byte >> 1
838
839 s += ('%02x' % (mod_byte))
840 else:
841 s += st_bytes[i]
842
843 return s
844
Supreeth Herle3b342c22020-03-24 16:15:02 +0100845def dec_addr_tlv(hexstr):
Supreeth Herled572ede2020-03-22 09:55:04 +0100846 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100847 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
848 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 +0100849 """
850
851 # Convert from hex str to int bytes list
Supreeth Herle3b342c22020-03-24 16:15:02 +0100852 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100853
Supreeth Herled572ede2020-03-22 09:55:04 +0100854 # Get list of tuples containing parsed TLVs
Supreeth Herle3b342c22020-03-24 16:15:02 +0100855 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100856
857 for tlv in tlvs:
858 # tlv = (T, L, [V])
859 # T = Tag
860 # L = Length
861 # [V] = List of value
862
863 # Invalid Tag value scenario
864 if tlv[0] != 0x80:
865 continue
866
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200867 # Empty field - Zero length
868 if tlv[1] == 0:
869 continue
870
Supreeth Herled572ede2020-03-22 09:55:04 +0100871 # First byte in the value has the address type
872 addr_type = tlv[2][0]
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100873 # TODO: Support parsing of IPv6
Supreeth Herle3b342c22020-03-24 16:15:02 +0100874 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
Supreeth Herled572ede2020-03-22 09:55:04 +0100875 if addr_type == 0x00: #FQDN
876 # Skip address tye byte i.e. first byte in value list
877 content = tlv[2][1:]
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200878 return (i2s(content), '00')
879
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100880 elif addr_type == 0x01: #IPv4
881 # Skip address tye byte i.e. first byte in value list
882 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
883 ipv4 = tlv[2][2:]
884 content = '.'.join(str(x) for x in ipv4)
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200885 return (content, '01')
886 else:
887 raise ValueError("Invalid address type")
Supreeth Herled572ede2020-03-22 09:55:04 +0100888
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200889 return (None, None)
Philipp Maierff84c232020-05-12 17:24:18 +0200890
Supreeth Herle3b342c22020-03-24 16:15:02 +0100891def enc_addr_tlv(addr, addr_type='00'):
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100892 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100893 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
894 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 +0100895
896 Default values:
Supreeth Herle3b342c22020-03-24 16:15:02 +0100897 - addr_type: 00 - FQDN format of Address
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100898 """
899
900 s = ""
901
Supreeth Herle654eca72020-03-25 14:25:38 +0100902 # TODO: Encoding of IPv6 address
903 if addr_type == '00': #FQDN
Supreeth Herle3b342c22020-03-24 16:15:02 +0100904 hex_str = s2h(addr)
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100905 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
Supreeth Herle654eca72020-03-25 14:25:38 +0100906 elif addr_type == '01': #IPv4
907 ipv4_list = addr.split('.')
908 ipv4_str = ""
909 for i in ipv4_list:
910 ipv4_str += ('%02x' % (int(i)))
911
912 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
913 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
914 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100915
916 return s
917
Harald Welte52255572021-04-03 09:56:32 +0200918def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
Philipp Maier47236502021-03-09 21:28:25 +0100919 """
920 Check if a string is a valid hexstring
921 """
922
923 # Filter obviously bad strings
924 if not string:
925 return False
926 if len(string) < minlen or minlen < 2:
927 return False
928 if len(string) % 2:
929 return False
930 if maxlen and len(string) > maxlen:
931 return False
932
933 # Try actual encoding to be sure
934 try:
935 try_encode = h2b(string)
936 return True
937 except:
938 return False
939
Harald Welte52255572021-04-03 09:56:32 +0200940def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
Philipp Maiere8536c02020-05-11 21:35:01 +0200941 """
942 The ADM pin can be supplied either in its hexadecimal form or as
943 ascii string. This function checks the supplied opts parameter and
Harald Welte236a65f2021-04-03 10:20:11 +0200944 returns the pin_adm as hex encoded string, regardless in which form
Philipp Maiere8536c02020-05-11 21:35:01 +0200945 it was originally supplied by the user
946 """
947
Harald Welte79b5ba42021-01-08 21:22:38 +0100948 if pin_adm is not None:
949 if len(pin_adm) <= 8:
950 pin_adm = ''.join(['%02x'%(ord(x)) for x in pin_adm])
Philipp Maiere8536c02020-05-11 21:35:01 +0200951 pin_adm = rpad(pin_adm, 16)
952
953 else:
954 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
955
Harald Welte79b5ba42021-01-08 21:22:38 +0100956 if pin_adm_hex is not None:
957 if len(pin_adm_hex) == 16:
958 pin_adm = pin_adm_hex
Philipp Maiere8536c02020-05-11 21:35:01 +0200959 # Ensure that it's hex-encoded
960 try:
961 try_encode = h2b(pin_adm)
962 except ValueError:
963 raise ValueError("PIN-ADM needs to be hex encoded using this option")
964 else:
965 raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)")
966
967 return pin_adm
968
Supreeth Herle95ec7722020-03-24 13:09:03 +0100969def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
970 """
971 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
972 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
973
974 Default values:
975 - epdg_priority: '0001' - 1st Priority
976 - epdg_fqdn_format: '00' - Operator Identifier FQDN
977 """
978
979 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
980 # TODO: Handle encoding of Length field for length more than 127 Bytes
981 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
982 content = rpad(content, len(hexstr))
983 return content
984
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100985def dec_ePDGSelection(sixhexbytes):
986 """
987 Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
988 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
989 """
990
991 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
992 plmn_chars = 6
993 epdg_priority_chars = 4
994 epdg_fqdn_format_chars = 2
995 # first three bytes (six ascii hex chars)
996 plmn_str = sixhexbytes[:plmn_chars]
997 # two bytes after first three bytes
998 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars]
999 # one byte after first five bytes
1000 epdg_fqdn_format_str = sixhexbytes[plmn_chars + epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
1001 res['mcc'] = dec_mcc_from_plmn(plmn_str)
1002 res['mnc'] = dec_mnc_from_plmn(plmn_str)
1003 res['epdg_priority'] = epdg_priority_str
1004 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
1005 return res
1006
1007def format_ePDGSelection(hexstr):
1008 ePDGSelection_info_tag_chars = 2
1009 ePDGSelection_info_tag_str = hexstr[:2]
herlesupreeth3a261d32021-01-05 09:20:11 +01001010 s = ""
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001011 # Minimum length
1012 len_chars = 2
1013 # TODO: Need to determine length properly - definite length support only
1014 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
1015 # As per spec, length is 5n, n - number of PLMNs
1016 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
1017 # Totalling to 6 Bytes, maybe length should be 6n
1018 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +01001019
1020 # Not programmed scenario
1021 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
1022 len_chars = 0
1023 ePDGSelection_info_tag_chars = 0
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001024 if len_str[0] == '8':
1025 # The bits 7 to 1 denotes the number of length octets if length > 127
1026 if int(len_str[1]) > 0:
1027 # Update number of length octets
1028 len_chars = len_chars * int(len_str[1])
1029 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
1030
1031 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
1032 # Right pad to prevent index out of range - multiple of 6 bytes
1033 content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12)))
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001034 for rec_data in hexstr_to_Nbytearr(content_str, 6):
1035 rec_info = dec_ePDGSelection(rec_data)
1036 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
1037 rec_str = "unused"
1038 else:
1039 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
1040 (rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
1041 s += "\t%s # %s\n" % (rec_data, rec_str)
1042 return s
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001043
1044def get_addr_type(addr):
1045 """
1046 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
1047 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
1048
1049 TODO: Handle IPv6
1050 """
1051
1052 # Empty address string
1053 if not len(addr):
1054 return None
1055
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001056 addr_list = addr.split('.')
1057
1058 # Check for IPv4/IPv6
1059 try:
1060 import ipaddress
1061 # Throws ValueError if addr is not correct
Denis 'GNUtoo' Carikli79f5b602020-02-15 04:02:57 +07001062 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001063
1064 if ipa.version == 4:
1065 return 0x01
1066 elif ipa.version == 6:
1067 return 0x02
1068 except Exception as e:
1069 invalid_ipv4 = True
1070 for i in addr_list:
1071 # Invalid IPv4 may qualify for a valid FQDN, so make check here
1072 # e.g. 172.24.15.300
1073 import re
1074 if not re.match('^[0-9_]+$', i):
1075 invalid_ipv4 = False
1076 break
1077
1078 if invalid_ipv4:
1079 return None
1080
1081 fqdn_flag = True
1082 for i in addr_list:
1083 # Only Alpha-numeric characters and hyphen - RFC 1035
1084 import re
1085 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
1086 fqdn_flag = False
1087 break
1088
1089 # FQDN
1090 if fqdn_flag:
1091 return 0x00
1092
1093 return None
Harald Welte67d551a2021-01-21 14:50:01 +01001094
Harald Welte1e456572021-04-02 17:16:30 +02001095def sw_match(sw:str, pattern:str) -> bool:
Harald Welte67d551a2021-01-21 14:50:01 +01001096 """Match given SW against given pattern."""
1097 # Create a masked version of the returned status word
1098 sw_lower = sw.lower()
1099 sw_masked = ""
1100 for i in range(0, 4):
Philipp Maier78e32f22021-03-22 20:39:24 +01001101 if pattern[i] == '?':
Harald Welte67d551a2021-01-21 14:50:01 +01001102 sw_masked = sw_masked + '?'
Philipp Maier78e32f22021-03-22 20:39:24 +01001103 elif pattern[i] == 'x':
Harald Welte67d551a2021-01-21 14:50:01 +01001104 sw_masked = sw_masked + 'x'
1105 else:
1106 sw_masked = sw_masked + sw_lower[i]
Philipp Maier78e32f22021-03-22 20:39:24 +01001107 # Compare the masked version against the pattern
Harald Welte67d551a2021-01-21 14:50:01 +01001108 return sw_masked == pattern
Philipp Maier5d3e2592021-02-22 17:22:16 +01001109
Harald Welteee3501f2021-04-02 13:00:18 +02001110def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
Harald Welte52255572021-04-03 09:56:32 +02001111 align_left:bool = True) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +02001112 """Pretty print a list of strings into a tabulated form.
1113
1114 Args:
1115 width : total width in characters per line
1116 space : horizontal space between cells
1117 lspace : number of spaces before row
1118 align_lef : Align text to the left side
1119 Returns:
1120 multi-line string containing formatted table
1121 """
Philipp Maier5d3e2592021-02-22 17:22:16 +01001122 if str_list == None:
1123 return ""
1124 if len(str_list) <= 0:
1125 return ""
1126 longest_str = max(str_list, key=len)
1127 cellwith = len(longest_str) + hspace
1128 cols = width // cellwith
1129 rows = (len(str_list) - 1) // cols + 1
1130 table = []
1131 for i in iter(range(rows)):
1132 str_list_row = str_list[i::rows]
1133 if (align_left):
1134 format_str_cell = '%%-%ds'
1135 else:
1136 format_str_cell = '%%%ds'
1137 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
1138 format_str_row = (" " * lspace) + format_str_row
1139 table.append(format_str_row % tuple(str_list_row))
1140 return '\n'.join(table)
Harald Welte5e749a72021-04-10 17:18:17 +02001141
Harald Welte917d98c2021-04-21 11:51:25 +02001142def auto_int(x):
1143 """Helper function for argparse to accept hexadecimal integers."""
1144 return int(x, 0)
1145
Harald Welte5e749a72021-04-10 17:18:17 +02001146class JsonEncoder(json.JSONEncoder):
1147 """Extend the standard library JSONEncoder with support for more types."""
1148 def default(self, o):
1149 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
1150 return b2h(o)
1151 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +02001152
1153def boxed_heading_str(heading, width=80):
1154 """Generate a string that contains a boxed heading."""
1155 # Auto-enlarge box if heading exceeds length
1156 if len(heading) > width - 4:
1157 width = len(heading) + 4
1158
1159 res = "#" * width
1160 fstr = "\n# %-" + str(width - 4) + "s #\n"
1161 res += fstr % (heading)
1162 res += "#" * width
1163 return res
Harald Welte3de6ca22021-05-02 20:05:56 +02001164
1165
1166
1167class DataObject(abc.ABC):
1168 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
1169 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
1170 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
1171 tags at specific points in a stream. This class tries to represent this."""
1172 def __init__(self, name, desc = None, tag = None):
1173 """
1174 Args:
1175 name: A brief, all-lowercase, underscore separated string identifier
1176 desc: A human-readable description of what this DO represents
1177 tag : The tag associated with this DO
1178 """
1179 self.name = name
1180 self.desc = desc
1181 self.tag = tag
1182 self.decoded = None
1183 self.encoded = None
1184
1185 def __str__(self):
1186 return self.name
1187
1188 def __repr__(self):
1189 return '%s(%s)' % (self.__class__, self.name)
1190
1191 def __or__(self, other):
1192 """OR-ing DataObjects together renders a DataObjectChoice."""
1193 if isinstance(other, DataObject):
1194 # DataObject | DataObject = DataObjectChoice
1195 return DataObjectChoice(None, members=[self, other])
1196 else:
1197 raise TypeError
1198
1199 def __add__(self, other):
1200 """ADD-ing DataObjects together renders a DataObjectCollection."""
1201 if isinstance(other, DataObject):
1202 # DataObject + DataObject = DataObjectCollectin
1203 return DataObjectCollection(None, members=[self, other])
1204
1205 def _compute_tag(self):
1206 """Compute the tag (sometimes the tag encodes part of the value)."""
1207 return self.tag
1208
1209 def to_dict(self):
1210 """Return a dict in form "name: decoded_value" """
1211 return {self.name: self.decoded}
1212
1213 @abc.abstractmethod
1214 def from_bytes(self, do:bytes):
1215 """Parse the value part of the DO into the internal state of this instance.
1216 Args:
1217 do : binary encoded bytes
1218 """
1219
1220 @abc.abstractmethod
1221 def to_bytes(self):
1222 """Encode the internal state of this instance into the TLV value part.
1223 Returns:
1224 binary bytes encoding the internal state
1225 """
1226
1227 def from_tlv(self, do:bytes):
1228 """Parse binary TLV representation into internal state. The resulting decoded
1229 representation is _not_ returned, but just internalized in the object instance!
1230 Args:
1231 do : input bytes containing TLV-encoded representation
1232 Returns:
1233 bytes remaining at end of 'do' after parsing one TLV/DO.
1234 """
1235 if do[0] != self.tag:
1236 raise ValueError('%s: Can only decode tag 0x%02x' % (self, self.tag))
1237 length = do[1]
1238 val = do[2:2+length]
1239 self.from_bytes(val)
1240 # return remaining bytes
1241 return do[2+length:]
1242
1243 def to_tlv(self):
1244 """Encode internal representation to binary TLV.
1245 Returns:
1246 bytes encoded in TLV format.
1247 """
1248 val = self.to_bytes()
1249 return bytes(self._compute_tag()) + bytes(len(val)) + val
1250
1251 # 'codec' interface
1252 def decode(self, binary:bytes):
1253 """Decode a single DOs from the input data.
1254 Args:
1255 binary : binary bytes of encoded data
1256 Returns:
1257 tuple of (decoded_result, binary_remainder)
1258 """
1259 tag = binary[0]
1260 if tag != self.tag:
1261 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1262 (self, tag, binary, self.tag))
1263 remainder = self.from_tlv(binary)
1264 return (self.to_dict(), remainder)
1265
1266 # 'codec' interface
1267 def encode(self):
1268 return self.to_tlv()
1269
1270class TL0_DataObject(DataObject):
1271 """Data Object that has Tag, Len=0 and no Value part."""
1272 def __init__(self, name, desc, tag, val=None):
1273 super().__init__(name, desc, tag)
1274 self.val = val
1275
1276 def from_bytes(self, binary:bytes):
1277 if len(binary) != 0:
1278 raise ValueError
1279 self.decoded = self.val
1280
1281 def to_bytes(self):
1282 return b''
1283
1284
1285class DataObjectCollection:
1286 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1287 A given encoded DO may contain any of them in any order, and may contain multiple instances
1288 of each DO."""
1289 def __init__(self, name, desc = None, members=None):
1290 self.name = name
1291 self.desc = desc
1292 self.members = members or []
1293 self.members_by_tag = {}
1294 self.members_by_name = {}
1295 self.members_by_tag = { m.tag:m for m in members }
1296 self.members_by_name = { m.name:m for m in members }
1297
1298 def __str__(self):
1299 member_strs = [str(x) for x in self.members]
1300 return '%s(%s)' % (self.name, ','.join(member_strs))
1301
1302 def __repr__(self):
1303 member_strs = [repr(x) for x in self.members]
1304 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1305
1306 def __add__(self, other):
1307 """Extending DataCollections with other DataCollections or DataObjects."""
1308 if isinstance(other, DataObjectCollection):
1309 # adding one collection to another
1310 members = self.members + other.members
1311 return DataObjectCollection(self.name, self.desc, members)
1312 elif isinstance(other, DataObject):
1313 # adding a member to a collection
1314 return DataObjectCollection(self.name, self.desc, self.members + [other])
1315 else:
1316 raise TypeError
1317
1318 # 'codec' interface
1319 def decode(self, binary:bytes):
1320 """Decode any number of DOs from the collection until the end of the input data,
1321 or uninitialized memory (0xFF) is found.
1322 Args:
1323 binary : binary bytes of encoded data
1324 Returns:
1325 tuple of (decoded_result, binary_remainder)
1326 """
1327 res = []
1328 remainder = binary
1329 # iterate until no binary trailer is left
1330 while len(remainder):
1331 tag = remainder[0]
1332 if tag == 0xff: # uninitialized memory at the end?
1333 return (res, remainder)
1334 if not tag in self.members_by_tag:
1335 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1336 (self, tag, remainder, self.members_by_tag.keys()))
1337 obj = self.members_by_tag[tag]
1338 # DO from_tlv returns remainder of binary
1339 remainder = obj.from_tlv(remainder)
1340 # collect our results
1341 res.append(obj.to_dict())
1342 return (res, remainder)
1343
1344 # 'codec' interface
1345 def encode(self, decoded):
1346 res = bytearray()
1347 for i in decoded:
1348 obj = self.members_by_name(i[0])
1349 res.append(obj.to_tlv())
1350 return res
1351
1352class DataObjectChoice(DataObjectCollection):
1353 """One Data Object from within a choice, identified by its tag.
1354 This means that exactly one member of the choice must occur, and which one occurs depends
1355 on the tag."""
1356 def __add__(self, other):
1357 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1358 raise TypeError
1359
1360 def __or__(self, other):
1361 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1362 if isinstance(other, DataObjectChoice):
1363 # adding one collection to another
1364 members = self.members + other.members
1365 return DataObjectChoice(self.name, self.desc, members)
1366 elif isinstance(other, DataObject):
1367 # adding a member to a collection
1368 return DataObjectChoice(self.name, self.desc, self.members + [other])
1369 else:
1370 raise TypeError
1371
1372 # 'codec' interface
1373 def decode(self, binary:bytes):
1374 """Decode a single DOs from the choice based on the tag.
1375 Args:
1376 binary : binary bytes of encoded data
1377 Returns:
1378 tuple of (decoded_result, binary_remainder)
1379 """
1380 tag = binary[0]
1381 if tag == 0xff:
1382 return (None, binary)
1383 if not tag in self.members_by_tag:
1384 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1385 (self, tag, binary, self.members_by_tag.keys()))
1386 obj = self.members_by_tag[tag]
1387 remainder = obj.from_tlv(binary)
1388 return (obj.to_dict(), remainder)
1389
1390 # 'codec' interface
1391 def encode(self, decoded):
1392 obj = self.members_by_name(decoded[0])
1393 return obj.to_tlv()
1394
1395class DataObjectSequence:
1396 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1397 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1398 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1399 for encoding or decoding an entire sequence."""
1400 def __init__(self, name, desc=None, sequence=None):
1401 self.sequence = sequence or []
1402 self.name = name
1403 self.desc = desc
1404
1405 def __str__(self):
1406 member_strs = [str(x) for x in self.sequence]
1407 return '%s(%s)' % (self.name, ','.join(member_strs))
1408
1409 def __repr__(self):
1410 member_strs = [repr(x) for x in self.sequence]
1411 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1412
1413 def __add__(self, other):
1414 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1415 if isinstance(other, 'DataObject'):
1416 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1417 elif isinstance(other, 'DataObjectChoice'):
1418 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1419 elif isinstance(other, 'DataObjectSequence'):
1420 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
1421
1422 # 'codec' interface
1423 def decode(self, binary:bytes):
1424 """Decode a sequence by calling the decoder of each element in the sequence.
1425 Args:
1426 binary : binary bytes of encoded data
1427 Returns:
1428 tuple of (decoded_result, binary_remainder)
1429 """
1430 remainder = binary
1431 res = []
1432 for e in self.sequence:
1433 (r, remainder) = e.decode(remainder)
1434 if r:
1435 res.append(r)
1436 return (res, remainder)
1437
1438 # 'codec' interface
1439 def decode_multi(self, do:bytes):
1440 """Decode multiple occurrences of the sequence from the binary input data.
1441 Args:
1442 do : binary input data to be decoded
1443 Returns:
1444 list of results of the decoder of this sequences
1445 """
1446 remainder = do
1447 res = []
1448 while len(remainder):
1449 (r, remainder2) = self.decode(remainder)
1450 if r:
1451 res.append(r)
1452 if len(remainder2) < len(remainder):
1453 remainder = remainder2
1454 else:
1455 remainder = remainder2
1456 break
1457 return (res, remainder)
1458
1459 # 'codec' interface
1460 def encode(self, decoded):
1461 """Encode a sequence by calling the encoder of each element in the sequence."""
1462 encoded = bytearray()
1463 i = 0
1464 for e in self.sequence:
1465 encoded += e.encode(decoded[i])
1466 i += 1
1467 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001468
1469class CardCommand:
1470 """A single card command / instruction."""
1471 def __init__(self, name, ins, cla_list=None, desc=None):
1472 self.name = name
1473 self.ins = ins
1474 self.cla_list = cla_list or []
1475 self.cla_list = [x.lower() for x in self.cla_list]
1476 self.desc = desc
1477
1478 def __str__(self):
1479 return self.name
1480
1481 def __repr__(self):
1482 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1483
1484 def match_cla(self, cla):
1485 """Does the given CLA match the CLA list of the command?."""
1486 if not isinstance(cla, str):
1487 cla = '%02u' % cla
1488 cla = cla.lower()
1489 for cla_match in self.cla_list:
1490 cla_masked = ""
1491 for i in range(0, 2):
1492 if cla_match[i] == 'x':
1493 cla_masked += 'x'
1494 else:
1495 cla_masked += cla[i]
1496 if cla_masked == cla_match:
1497 return True
1498 return False
1499
1500
1501class CardCommandSet:
1502 """A set of card instructions, typically specified within one spec."""
1503 def __init__(self, name, cmds=[]):
1504 self.name = name
1505 self.cmds = { c.ins : c for c in cmds }
1506
1507 def __str__(self):
1508 return self.name
1509
1510 def __getitem__(self, idx):
1511 return self.cmds[idx]
1512
1513 def __add__(self, other):
1514 if isinstance(other, CardCommand):
1515 if other.ins in self.cmds:
1516 raise ValueError('%s: INS 0x%02x already defined: %s' %
1517 (self, other.ins, self.cmds[other.ins]))
1518 self.cmds[other.ins] = other
1519 elif isinstance(other, CardCommandSet):
1520 for c in other.cmds.keys():
1521 self.cmds[c] = other.cmds[c]
1522 else:
1523 raise ValueError('%s: Unsupported type to add operator: %s' % (self, other))
1524
1525 def lookup(self, ins, cla=None):
1526 """look-up the command within the CommandSet."""
1527 ins = int(ins)
1528 if not ins in self.cmds:
1529 return None
1530 cmd = self.cmds[ins]
1531 if cla and not cmd.match_cla(cla):
1532 return None
1533 return cmd