blob: 777638c2258236dde4e6f7639b227af097f992f1 [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
Harald Welte5e749a72021-04-10 17:18:17 +02008from io import BytesIO
Harald Welte52255572021-04-03 09:56:32 +02009from typing import Optional, List, Dict, Any, Tuple
10
Sylvain Munaut76504e02010-12-07 00:24:32 +010011# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
Harald Welte5e749a72021-04-10 17:18:17 +020012# Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
Sylvain Munaut76504e02010-12-07 00:24:32 +010013#
14# This program is free software: you can redistribute it and/or modify
15# it under the terms of the GNU General Public License as published by
16# the Free Software Foundation, either version 2 of the License, or
17# (at your option) any later version.
18#
19# This program is distributed in the hope that it will be useful,
20# but WITHOUT ANY WARRANTY; without even the implied warranty of
21# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22# GNU General Public License for more details.
23#
24# You should have received a copy of the GNU General Public License
25# along with this program. If not, see <http://www.gnu.org/licenses/>.
26#
27
Harald Welte52255572021-04-03 09:56:32 +020028# just to differentiate strings of hex nibbles from everything else
29Hexstr = str
Sylvain Munaut76504e02010-12-07 00:24:32 +010030
Harald Welte52255572021-04-03 09:56:32 +020031def h2b(s:Hexstr) -> bytearray:
Harald Welte4f6ca432021-02-01 17:51:56 +010032 """convert from a string of hex nibbles to a sequence of bytes"""
33 return bytearray.fromhex(s)
Sylvain Munaut76504e02010-12-07 00:24:32 +010034
Harald Welte52255572021-04-03 09:56:32 +020035def b2h(b:bytearray) -> Hexstr:
Harald Welte4f6ca432021-02-01 17:51:56 +010036 """convert from a sequence of bytes to a string of hex nibbles"""
37 return ''.join(['%02x'%(x) for x in b])
Sylvain Munaut76504e02010-12-07 00:24:32 +010038
Harald Welte52255572021-04-03 09:56:32 +020039def h2i(s:Hexstr) -> List[int]:
Harald Welteee3501f2021-04-02 13:00:18 +020040 """convert from a string of hex nibbles to a list of integers"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010041 return [(int(x,16)<<4)+int(y,16) for x,y in zip(s[0::2], s[1::2])]
42
Harald Welte52255572021-04-03 09:56:32 +020043def i2h(s:List[int]) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020044 """convert from a list of integers to a string of hex nibbles"""
Sylvain Munaut76504e02010-12-07 00:24:32 +010045 return ''.join(['%02x'%(x) for x in s])
46
Harald Welte52255572021-04-03 09:56:32 +020047def h2s(s:Hexstr) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +020048 """convert from a string of hex nibbles to an ASCII string"""
Vadim Yanitskiyeb06b452020-05-10 02:32:46 +070049 return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])
50 if int(x + y, 16) != 0xff])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030051
Harald Welte52255572021-04-03 09:56:32 +020052def s2h(s:str) -> Hexstr:
Harald Welteee3501f2021-04-02 13:00:18 +020053 """convert from an ASCII string to a string of hex nibbles"""
Harald Welte4f6ca432021-02-01 17:51:56 +010054 b = bytearray()
55 b.extend(map(ord, s))
56 return b2h(b)
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030057
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020058# List of bytes to string
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
Harald Welte917d98c2021-04-21 11:51:25 +020092#########################################################################
Harald Welte9f3b44d2021-05-04 18:18:09 +020093# poor man's COMPREHENSION-TLV decoder.
94#########################################################################
95
Harald Welte6912b1b2021-05-24 23:16:44 +020096def comprehensiontlv_parse_tag_raw(binary:bytes) -> Tuple[int, bytes]:
97 """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
98 if binary[0] in [0x00, 0x80, 0xff]:
99 raise ValueError("Found illegal value 0x%02x in %s" % (binary[0], binary))
100 if binary[0] == 0x7f:
101 # three-byte tag
102 tag = binary[0] << 16 | binary[1] << 8 | binary[2]
103 return (tag, binary[3:])
104 elif binary[0] == 0xff:
105 return None, binary
106 else:
107 # single byte tag
108 tag = binary[0]
109 return (tag, binary[1:])
110
Harald Welte9f3b44d2021-05-04 18:18:09 +0200111def comprehensiontlv_parse_tag(binary:bytes) -> Tuple[dict, 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[1] & 0x7f) << 8
118 tag |= binary[2]
119 compr = True if binary[1] & 0x80 else False
120 return ({'comprehension': compr, 'tag': tag}, binary[3:])
121 else:
122 # single byte tag
123 tag = binary[0] & 0x7f
124 compr = True if binary[0] & 0x80 else False
125 return ({'comprehension': compr, 'tag': tag}, binary[1:])
126
127def comprehensiontlv_encode_tag(tag) -> bytes:
128 """Encode a single Tag according to ETSI TS 101 220 Section 7.1.1"""
129 # permit caller to specify tag also as integer value
130 if isinstance(tag, int):
131 compr = True if tag < 0xff and tag & 0x80 else False
132 tag = {'tag': tag, 'comprehension': compr}
133 compr = tag.get('comprehension', False)
134 if tag['tag'] in [0x00, 0x80, 0xff] or tag['tag'] > 0xff:
135 # 3-byte format
136 byte3 = tag['tag'] & 0xff;
137 byte2 = (tag['tag'] >> 8) & 0x7f
138 if compr:
139 byte2 |= 0x80
140 return b'\x7f' + byte2.to_bytes(1, 'big') + byte3.to_bytes(1, 'big')
141 else:
142 # 1-byte format
143 ret = tag['tag']
144 if compr:
145 ret |= 0x80
146 return ret.to_bytes(1, 'big')
147
148# length value coding is equal to BER-TLV
149
Harald Welte6912b1b2021-05-24 23:16:44 +0200150def comprehensiontlv_parse_one(binary:bytes) -> (dict, int, bytes, bytes):
151 """Parse a single TLV IE at the start of the given binary data.
152 Args:
153 binary : binary input data of BER-TLV length field
154 Returns:
155 Tuple of (tag:dict, len:int, remainder:bytes)
156 """
157 (tagdict, remainder) = comprehensiontlv_parse_tag(binary)
158 (length, remainder) = bertlv_parse_len(remainder)
159 value = remainder[:length]
160 remainder = remainder[length:]
161 return (tagdict, length, value, remainder)
162
163
Harald Welte9f3b44d2021-05-04 18:18:09 +0200164
165#########################################################################
Harald Welte917d98c2021-04-21 11:51:25 +0200166# poor man's BER-TLV decoder. To be a more sophisticated OO library later
167#########################################################################
168
Harald Welte6912b1b2021-05-24 23:16:44 +0200169def bertlv_parse_tag_raw(binary:bytes) -> Tuple[int, bytes]:
170 """Get a single raw Tag from start of input according to ITU-T X.690 8.1.2
171 Args:
172 binary : binary input data of BER-TLV length field
173 Returns:
174 Tuple of (tag:int, remainder:bytes)
175 """
176 if binary[0] == 0xff:
177 return None, binary
178 tag = binary[0] & 0x1f
179 if tag <= 30:
180 return binary[0], binary[1:]
181 else: # multi-byte tag
182 tag = binary[0]
183 i = 1
184 last = False
185 while not last:
186 last = False if binary[i] & 0x80 else True
187 tag <<= 8
188 tag |= binary[i]
189 i += 1
190 return tag, binary[i:]
191
Harald Welte917d98c2021-04-21 11:51:25 +0200192def bertlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
193 """Parse a single Tag value according to ITU-T X.690 8.1.2
194 Args:
195 binary : binary input data of BER-TLV length field
196 Returns:
197 Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes)
198 """
199 cls = binary[0] >> 6
200 constructed = True if binary[0] & 0x20 else False
201 tag = binary[0] & 0x1f
202 if tag <= 30:
203 return ({'class':cls, 'constructed':constructed, 'tag': tag}, binary[1:])
204 else: # multi-byte tag
205 tag = 0
206 i = 1
207 last = False
208 while not last:
209 last = False if binary[i] & 0x80 else True
210 tag <<= 7
211 tag |= binary[i] & 0x7f
212 i += 1
213 return ({'class':cls, 'constructed':constructed, 'tag':tag}, binary[i:])
214
Harald Weltef0885b12021-05-24 23:18:59 +0200215def bertlv_encode_tag(t) -> bytes:
216 """Encode a single Tag value according to ITU-T X.690 8.1.2
217 """
218 def get_top7_bits(inp:int) -> Tuple[int, int]:
219 """Get top 7 bits of integer. Returns those 7 bits as integer and the remaining LSBs."""
220 remain_bits = inp.bit_length()
221 if remain_bits >= 7:
222 bitcnt = 7
223 else:
224 bitcnt = remain_bits
225 outp = inp >> (remain_bits - bitcnt)
226 remainder = inp & ~ (inp << (remain_bits - bitcnt))
227 return outp, remainder
228
229 if isinstance(t, int):
230 # FIXME: multiple byte tags
231 tag = t & 0x1f
232 constructed = True if t & 0x20 else False
233 cls = t >> 6
234 else:
235 tag = t['tag']
236 constructed = t['constructed']
237 cls = t['class']
238 if tag <= 30:
239 t = tag & 0x1f
240 if constructed:
241 t |= 0x20
242 t |= (cls & 3) << 6
243 return bytes([t])
244 else: # multi-byte tag
245 t = 0x1f;
246 if constructed:
247 t |= 0x20
248 t |= (cls & 3) << 6
249 tag_bytes = bytes([t])
250 remain = tag
251 while True:
252 t, remain = get_top7_bits(remain)
253 if remain:
254 t |= 0x80
255 tag_bytes += bytes([t])
256 if not remain:
257 break
258 return tag_bytes
259
Harald Welte917d98c2021-04-21 11:51:25 +0200260def bertlv_parse_len(binary:bytes) -> Tuple[int, bytes]:
261 """Parse a single Length value according to ITU-T X.690 8.1.3;
262 only the definite form is supported here.
263 Args:
264 binary : binary input data of BER-TLV length field
265 Returns:
266 Tuple of (length, remainder)
267 """
268 if binary[0] < 0x80:
269 return (binary[0], binary[1:])
270 else:
271 num_len_oct = binary[0] & 0x7f
272 length = 0
273 for i in range(1, 1+num_len_oct):
274 length <<= 8
275 length |= binary[i]
Harald Weltede027182021-05-04 20:06:45 +0200276 return (length, binary[1+num_len_oct:])
Harald Welte917d98c2021-04-21 11:51:25 +0200277
278def bertlv_encode_len(length:int) -> bytes:
279 """Encode a single Length value according to ITU-T X.690 8.1.3;
280 only the definite form is supported here.
281 Args:
282 length : length value to be encoded
283 Returns:
284 binary output data of BER-TLV length field
285 """
286 if length < 0x80:
287 return length.to_bytes(1, 'big')
288 elif length <= 0xff:
289 return b'\x81' + length.to_bytes(1, 'big')
290 elif length <= 0xffff:
291 return b'\x82' + length.to_bytes(2, 'big')
292 elif length <= 0xffffff:
293 return b'\x83' + length.to_bytes(3, 'big')
294 elif length <= 0xffffffff:
295 return b'\x84' + length.to_bytes(4, 'big')
296 else:
297 raise ValueError("Length > 32bits not supported")
298
Harald Weltec1475302021-05-21 21:47:55 +0200299def bertlv_parse_one(binary:bytes) -> (dict, int, bytes, bytes):
Harald Welte917d98c2021-04-21 11:51:25 +0200300 """Parse a single TLV IE at the start of the given binary data.
301 Args:
302 binary : binary input data of BER-TLV length field
303 Returns:
304 Tuple of (tag:dict, len:int, remainder:bytes)
305 """
306 (tagdict, remainder) = bertlv_parse_tag(binary)
307 (length, remainder) = bertlv_parse_len(remainder)
Harald Weltec1475302021-05-21 21:47:55 +0200308 value = remainder[:length]
309 remainder = remainder[length:]
310 return (tagdict, length, value, remainder)
Harald Welte917d98c2021-04-21 11:51:25 +0200311
312
313
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200314# IMSI encoded format:
315# For IMSI 0123456789ABCDE:
316#
317# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
318# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
319#
320# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
321#
322# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
323# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
324# encode itself.
325#
326# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
327# even length IMSI only uses half of the last byte.
328
Harald Welteee3501f2021-04-02 13:00:18 +0200329def enc_imsi(imsi:str):
330 """Converts a string IMSI into the encoded value of the EF"""
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200331 l = half_round_up(len(imsi) + 1) # Required bytes - include space for odd/even indicator
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400332 oe = len(imsi) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200333 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15)))
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400334 return ei
335
Harald Welte52255572021-04-03 09:56:32 +0200336def dec_imsi(ef:Hexstr) -> Optional[str]:
Harald Weltec9cdce32021-04-11 10:28:28 +0200337 """Converts an EF value to the IMSI string representation"""
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400338 if len(ef) < 4:
339 return None
Pau Espin Pedrol665bd222017-12-29 20:30:35 +0100340 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200341 l = l - 1 # Encoded length byte includes oe nibble
342 swapped = swap_nibbles(ef[2:]).rstrip('f')
Philipp Maiercd3d6262020-05-11 21:41:56 +0200343 if len(swapped) < 1:
344 return None
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400345 oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200346 if not oe:
347 # if even, only half of last byte was used
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400348 l = l-1
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200349 if l != len(swapped) - 1:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400350 return None
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200351 imsi = swapped[1:]
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400352 return imsi
353
Harald Welte52255572021-04-03 09:56:32 +0200354def dec_iccid(ef:Hexstr) -> str:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400355 return swap_nibbles(ef).strip('f')
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400356
Harald Welte52255572021-04-03 09:56:32 +0200357def enc_iccid(iccid:str) -> Hexstr:
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400358 return swap_nibbles(rpad(iccid, 20))
359
Philipp Maiere6f8d682021-04-23 21:14:41 +0200360def enc_plmn(mcc:Hexstr, mnc:Hexstr) -> Hexstr:
Alexander Chemerisdddbf522017-07-18 16:49:59 +0300361 """Converts integer MCC/MNC into 3 bytes for EF"""
Philipp Maier6c5cd802021-04-23 21:19:36 +0200362
363 # Make sure there are no excess whitespaces in the input
364 # parameters
365 mcc = mcc.strip()
366 mnc = mnc.strip()
367
368 # Make sure that MCC/MNC are correctly padded with leading
369 # zeros or 'F', depending on the length.
370 if len(mnc) == 0:
371 mnc = "FFF"
372 elif len(mnc) == 1:
373 mnc = "F0" + mnc
374 elif len(mnc) == 2:
375 mnc += "F"
376
377 if len(mcc) == 0:
378 mcc = "FFF"
379 elif len(mcc) == 1:
380 mcc = "00" + mcc
381 elif len(mcc) == 2:
382 mcc = "0" + mcc
383
Vadim Yanitskiyc8458e22021-03-12 00:34:10 +0100384 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300385
Harald Welted7a7e172021-04-07 00:30:10 +0200386def dec_plmn(threehexbytes:Hexstr) -> dict:
Philipp Maier6c5cd802021-04-23 21:19:36 +0200387 res = {'mcc': "0", 'mnc': "0" }
388 dec_mcc_from_plmn_str(threehexbytes)
389 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
390 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
Harald Welted7a7e172021-04-07 00:30:10 +0200391 return res
392
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300393def dec_spn(ef):
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200394 """Obsolete, kept for API compatibility"""
395 from ts_51_011 import EF_SPN
396 abstract_data = EF_SPN().decode_hex(ef)
397 show_in_hplmn = abstract_data['show_in_hplmn']
398 hide_in_oplmn = abstract_data['hide_in_oplmn']
399 name = abstract_data['spn']
400 return (name, show_in_hplmn, hide_in_oplmn)
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300401
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200402def enc_spn(name:str, show_in_hplmn=False, hide_in_oplmn=False):
403 """Obsolete, kept for API compatibility"""
404 from ts_51_011 import EF_SPN
405 abstract_data = {
406 'hide_in_oplmn' : hide_in_oplmn,
407 'show_in_hplmn' : show_in_hplmn,
408 'spn' : name,
409 }
410 return EF_SPN().encode_hex(abstract_data)
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900411
Supreeth Herlef3948532020-03-24 12:23:51 +0100412def hexstr_to_Nbytearr(s, nbytes):
413 return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]
414
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100415# Accepts hex string representing three bytes
Harald Welte52255572021-04-03 09:56:32 +0200416def dec_mcc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100417 ia = h2i(plmn)
418 digit1 = ia[0] & 0x0F # 1st byte, LSB
419 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
420 digit3 = ia[1] & 0x0F # 2nd byte, LSB
421 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
422 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100423 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100424
Philipp Maier6c5cd802021-04-23 21:19:36 +0200425def dec_mcc_from_plmn_str(plmn:Hexstr) -> str:
426 digit1 = plmn[1] # 1st byte, LSB
427 digit2 = plmn[0] # 1st byte, MSB
428 digit3 = plmn[3] # 2nd byte, LSB
429 res = digit1 + digit2 + digit3
430 return res.upper().strip("F")
431
Harald Welte52255572021-04-03 09:56:32 +0200432def dec_mnc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100433 ia = h2i(plmn)
Vadim Yanitskiyb271be32021-03-11 23:56:58 +0100434 digit1 = ia[2] & 0x0F # 3rd byte, LSB
435 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
436 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100437 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
438 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100439 return derive_mnc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100440
Philipp Maier6c5cd802021-04-23 21:19:36 +0200441def dec_mnc_from_plmn_str(plmn:Hexstr) -> str:
442 digit1 = plmn[5] # 3rd byte, LSB
443 digit2 = plmn[4] # 3rd byte, MSB
444 digit3 = plmn[2] # 2nd byte, MSB
445 res = digit1 + digit2 + digit3
446 return res.upper().strip("F")
447
Harald Welte52255572021-04-03 09:56:32 +0200448def dec_act(twohexbytes:Hexstr) -> List[str]:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100449 act_list = [
450 {'bit': 15, 'name': "UTRAN"},
451 {'bit': 14, 'name': "E-UTRAN"},
452 {'bit': 7, 'name': "GSM"},
453 {'bit': 6, 'name': "GSM COMPACT"},
454 {'bit': 5, 'name': "cdma2000 HRPD"},
455 {'bit': 4, 'name': "cdma2000 1xRTT"},
456 ]
457 ia = h2i(twohexbytes)
458 u16t = (ia[0] << 8)|ia[1]
459 sel = []
460 for a in act_list:
461 if u16t & (1 << a['bit']):
Philipp Maiere7d41792021-04-29 16:20:07 +0200462 if a['name'] == "E-UTRAN":
463 # The Access technology identifier of E-UTRAN
464 # allows a more detailed specification:
465 if u16t & (1 << 13) and u16t & (1 << 12):
466 sel.append("E-UTRAN WB-S1")
467 sel.append("E-UTRAN NB-S1")
468 elif u16t & (1 << 13):
469 sel.append("E-UTRAN WB-S1")
470 elif u16t & (1 << 12):
471 sel.append("E-UTRAN NB-S1")
472 else:
473 sel.append("E-UTRAN")
474 else:
475 sel.append(a['name'])
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100476 return sel
477
Harald Welte52255572021-04-03 09:56:32 +0200478def dec_xplmn_w_act(fivehexbytes:Hexstr) -> Dict[str,Any]:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200479 res = {'mcc': "0", 'mnc': "0", 'act': []}
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100480 plmn_chars = 6
481 act_chars = 4
482 plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
483 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
Philipp Maierb919f8b2021-04-27 18:28:27 +0200484 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
485 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100486 res['act'] = dec_act(act_str)
487 return res
488
489def format_xplmn_w_act(hexstr):
490 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200491 for rec_data in hexstr_to_Nbytearr(hexstr, 5):
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100492 rec_info = dec_xplmn_w_act(rec_data)
Philipp Maierb919f8b2021-04-27 18:28:27 +0200493 if rec_info['mcc'] == "" and rec_info['mnc'] == "":
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100494 rec_str = "unused"
495 else:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200496 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 +0100497 s += "\t%s # %s\n" % (rec_data, rec_str)
498 return s
499
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100500def dec_loci(hexstr):
501 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
502 res['tmsi'] = hexstr[:8]
503 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
504 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
505 res['lac'] = hexstr[14:18]
506 res['status'] = h2i(hexstr[20:22])
507 return res
508
509def dec_psloci(hexstr):
510 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
511 res['p-tmsi'] = hexstr[:8]
512 res['p-tmsi-sig'] = hexstr[8:14]
513 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
514 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
515 res['lac'] = hexstr[20:24]
516 res['rac'] = hexstr[24:26]
517 res['status'] = h2i(hexstr[26:28])
518 return res
519
520def dec_epsloci(hexstr):
521 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
522 res['guti'] = hexstr[:24]
523 res['tai'] = hexstr[24:34]
524 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
525 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
526 res['tac'] = hexstr[30:34]
527 res['status'] = h2i(hexstr[34:36])
528 return res
529
Harald Welte52255572021-04-03 09:56:32 +0200530def dec_xplmn(threehexbytes:Hexstr) -> dict:
Harald Welteca673942020-06-03 15:19:40 +0200531 res = {'mcc': 0, 'mnc': 0, 'act': []}
532 plmn_chars = 6
533 plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
534 res['mcc'] = dec_mcc_from_plmn(plmn_str)
535 res['mnc'] = dec_mnc_from_plmn(plmn_str)
536 return res
537
Harald Welte52255572021-04-03 09:56:32 +0200538def format_xplmn(hexstr:Hexstr) -> str:
Harald Welteca673942020-06-03 15:19:40 +0200539 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200540 for rec_data in hexstr_to_Nbytearr(hexstr, 3):
Harald Welteca673942020-06-03 15:19:40 +0200541 rec_info = dec_xplmn(rec_data)
542 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
543 rec_str = "unused"
544 else:
545 rec_str = "MCC: %03d MNC: %03d" % (rec_info['mcc'], rec_info['mnc'])
546 s += "\t%s # %s\n" % (rec_data, rec_str)
547 return s
548
Harald Welte52255572021-04-03 09:56:32 +0200549def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900550 """
551 Run the milenage algorithm to calculate OPC from Ki and OP
552 """
553 from Crypto.Cipher import AES
554 from Crypto.Util.strxor import strxor
555 from pySim.utils import b2h
556
557 # We pass in hex string and now need to work on bytes
Harald Welteeab8d2a2021-03-05 18:30:23 +0100558 ki_bytes = bytes(h2b(ki_hex))
559 op_bytes = bytes(h2b(op_hex))
Harald Welteab34fa82021-03-05 18:39:59 +0100560 aes = AES.new(ki_bytes, AES.MODE_ECB)
Harald Welteeab8d2a2021-03-05 18:30:23 +0100561 opc_bytes = aes.encrypt(op_bytes)
562 return b2h(strxor(opc_bytes, op_bytes))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900563
Harald Welte52255572021-04-03 09:56:32 +0200564def calculate_luhn(cc) -> int:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900565 """
566 Calculate Luhn checksum used in e.g. ICCID and IMEI
567 """
Daniel Willmanndd014ea2020-10-19 10:35:11 +0200568 num = list(map(int, str(cc)))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900569 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
570 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200571
Harald Welte52255572021-04-03 09:56:32 +0200572def mcc_from_imsi(imsi:str) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200573 """
574 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
575 """
576 if imsi == None:
577 return None
578
579 if len(imsi) > 3:
580 return imsi[:3]
581 else:
582 return None
583
Harald Welte52255572021-04-03 09:56:32 +0200584def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200585 """
586 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
587 """
588 if imsi == None:
589 return None
590
591 if len(imsi) > 3:
592 if long:
593 return imsi[3:6]
594 else:
595 return imsi[3:5]
596 else:
597 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100598
Harald Welte52255572021-04-03 09:56:32 +0200599def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100600 """
601 Derive decimal representation of the MCC (Mobile Country Code)
602 from three given digits.
603 """
604
605 mcc = 0
606
607 if digit1 != 0x0f:
608 mcc += digit1 * 100
609 if digit2 != 0x0f:
610 mcc += digit2 * 10
611 if digit3 != 0x0f:
612 mcc += digit3
613
614 return mcc
615
Harald Welte52255572021-04-03 09:56:32 +0200616def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100617 """
618 Derive decimal representation of the MNC (Mobile Network Code)
619 from two or (optionally) three given digits.
620 """
621
622 mnc = 0
623
624 # 3-rd digit is optional for the MNC. If present
625 # the algorythm is the same as for the MCC.
626 if digit3 != 0x0f:
627 return derive_mcc(digit1, digit2, digit3)
628
629 if digit1 != 0x0f:
630 mnc += digit1 * 10
631 if digit2 != 0x0f:
632 mnc += digit2
633
634 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100635
Harald Welte52255572021-04-03 09:56:32 +0200636def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100637 """
638 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
639 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
640 """
641
642 # Convert from str to (kind of) 'bytes'
643 ef_msisdn = h2b(ef_msisdn)
644
645 # Make sure mandatory fields are present
646 if len(ef_msisdn) < 14:
647 raise ValueError("EF.MSISDN is too short")
648
649 # Skip optional Alpha Identifier
650 xlen = len(ef_msisdn) - 14
651 msisdn_lhv = ef_msisdn[xlen:]
652
653 # Parse the length (in bytes) of the BCD encoded number
Harald Welte4f6ca432021-02-01 17:51:56 +0100654 bcd_len = msisdn_lhv[0]
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100655 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
656 if bcd_len == 0xff:
657 return None
658 elif bcd_len > 11 or bcd_len < 1:
659 raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len)
660
661 # Parse ToN / NPI
Harald Welte4f6ca432021-02-01 17:51:56 +0100662 ton = (msisdn_lhv[1] >> 4) & 0x07
663 npi = msisdn_lhv[1] & 0x0f
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100664 bcd_len -= 1
665
666 # No MSISDN?
667 if not bcd_len:
668 return (npi, ton, None)
669
670 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
671 # International number 10.5.118/3GPP TS 24.008
Vadim Yanitskiy7ba24282020-02-27 00:04:13 +0700672 if ton == 0x01:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100673 msisdn = '+' + msisdn
674
675 return (npi, ton, msisdn)
Supreeth Herle5a541012019-12-22 08:59:16 +0100676
Harald Welte52255572021-04-03 09:56:32 +0200677def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr:
Supreeth Herle5a541012019-12-22 08:59:16 +0100678 """
679 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200680 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
681 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100682
683 Default NPI / ToN values:
684 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
685 - ToN: network specific or international number (if starts with '+').
686 """
687
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200688 # If no MSISDN is supplied then encode the file contents as all "ff"
689 if msisdn == "" or msisdn == "+":
690 return "ff" * 14
691
Supreeth Herle5a541012019-12-22 08:59:16 +0100692 # Leading '+' indicates International Number
693 if msisdn[0] == '+':
694 msisdn = msisdn[1:]
695 ton = 0x01
696
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200697 # An MSISDN must not exceed 20 digits
698 if len(msisdn) > 20:
699 raise ValueError("msisdn must not be longer than 20 digits")
700
Supreeth Herle5a541012019-12-22 08:59:16 +0100701 # Append 'f' padding if number of digits is odd
702 if len(msisdn) % 2 > 0:
703 msisdn += 'f'
704
705 # BCD length also includes NPI/ToN header
706 bcd_len = len(msisdn) // 2 + 1
707 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
708 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
709
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200710 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
711
Supreeth Herle441c4a72020-03-24 10:19:15 +0100712
Harald Welte52255572021-04-03 09:56:32 +0200713def dec_st(st, table="sim") -> str:
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200714 """
715 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
716 """
717
Supreeth Herledf330372020-04-20 14:48:55 +0200718 if table == "isim":
719 from pySim.ts_31_103 import EF_IST_map
720 lookup_map = EF_IST_map
721 elif table == "usim":
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200722 from pySim.ts_31_102 import EF_UST_map
723 lookup_map = EF_UST_map
724 else:
725 from pySim.ts_51_011 import EF_SST_map
726 lookup_map = EF_SST_map
727
728 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
729
730 avail_st = ""
731 # Get each byte and check for available services
732 for i in range(0, len(st_bytes)):
733 # Byte i contains info about Services num (8i+1) to num (8i+8)
734 byte = int(st_bytes[i], 16)
735 # Services in each byte are in order MSB to LSB
736 # MSB - Service (8i+8)
737 # LSB - Service (8i+1)
738 for j in range(1, 9):
739 if byte&0x01 == 0x01 and ((8*i) + j in lookup_map):
740 # Byte X contains info about Services num (8X-7) to num (8X)
741 # bit = 1: service available
742 # bit = 0: service not available
743 avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j])
744 byte = byte >> 1
745 return avail_st
Supreeth Herle98370552020-05-11 09:04:41 +0200746
747def first_TLV_parser(bytelist):
748 '''
749 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
750
751 parses first TLV format record in a list of bytelist
752 returns a 3-Tuple: Tag, Length, Value
753 Value is a list of bytes
754 parsing of length is ETSI'style 101.220
755 '''
756 Tag = bytelist[0]
757 if bytelist[1] == 0xFF:
758 Len = bytelist[2]*256 + bytelist[3]
759 Val = bytelist[4:4+Len]
760 else:
761 Len = bytelist[1]
762 Val = bytelist[2:2+Len]
763 return (Tag, Len, Val)
764
765def TLV_parser(bytelist):
766 '''
767 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
768
769 loops on the input list of bytes with the "first_TLV_parser()" function
770 returns a list of 3-Tuples
771 '''
772 ret = []
773 while len(bytelist) > 0:
774 T, L, V = first_TLV_parser(bytelist)
775 if T == 0xFF:
776 # padding bytes
777 break
778 ret.append( (T, L, V) )
779 # need to manage length of L
780 if L > 0xFE:
781 bytelist = bytelist[ L+4 : ]
782 else:
783 bytelist = bytelist[ L+2 : ]
784 return ret
Supreeth Herled572ede2020-03-22 09:55:04 +0100785
Supreeth Herled84daa12020-03-24 12:20:40 +0100786def enc_st(st, service, state=1):
787 """
788 Encodes the EF S/U/IST/EST and returns the updated Service Table
789
790 Parameters:
791 st - Current value of SIM/USIM/ISIM Service Table
792 service - Service Number to encode as activated/de-activated
793 state - 1 mean activate, 0 means de-activate
794
795 Returns:
796 s - Modified value of SIM/USIM/ISIM Service Table
797
798 Default values:
799 - state: 1 - Sets the particular Service bit to 1
800 """
801 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
802
803 s = ""
804 # Check whether the requested service is present in each byte
805 for i in range(0, len(st_bytes)):
806 # Byte i contains info about Services num (8i+1) to num (8i+8)
807 if service in range((8*i) + 1, (8*i) + 9):
808 byte = int(st_bytes[i], 16)
809 # Services in each byte are in order MSB to LSB
810 # MSB - Service (8i+8)
811 # LSB - Service (8i+1)
812 mod_byte = 0x00
813 # Copy bit by bit contents of byte to mod_byte with modified bit
814 # for requested service
815 for j in range(1, 9):
816 mod_byte = mod_byte >> 1
817 if service == (8*i) + j:
818 mod_byte = state == 1 and mod_byte|0x80 or mod_byte&0x7f
819 else:
820 mod_byte = byte&0x01 == 0x01 and mod_byte|0x80 or mod_byte&0x7f
821 byte = byte >> 1
822
823 s += ('%02x' % (mod_byte))
824 else:
825 s += st_bytes[i]
826
827 return s
828
Supreeth Herle3b342c22020-03-24 16:15:02 +0100829def dec_addr_tlv(hexstr):
Supreeth Herled572ede2020-03-22 09:55:04 +0100830 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100831 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
832 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 +0100833 """
834
835 # Convert from hex str to int bytes list
Supreeth Herle3b342c22020-03-24 16:15:02 +0100836 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100837
Supreeth Herled572ede2020-03-22 09:55:04 +0100838 # Get list of tuples containing parsed TLVs
Supreeth Herle3b342c22020-03-24 16:15:02 +0100839 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100840
841 for tlv in tlvs:
842 # tlv = (T, L, [V])
843 # T = Tag
844 # L = Length
845 # [V] = List of value
846
847 # Invalid Tag value scenario
848 if tlv[0] != 0x80:
849 continue
850
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200851 # Empty field - Zero length
852 if tlv[1] == 0:
853 continue
854
Supreeth Herled572ede2020-03-22 09:55:04 +0100855 # First byte in the value has the address type
856 addr_type = tlv[2][0]
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100857 # TODO: Support parsing of IPv6
Supreeth Herle3b342c22020-03-24 16:15:02 +0100858 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
Supreeth Herled572ede2020-03-22 09:55:04 +0100859 if addr_type == 0x00: #FQDN
860 # Skip address tye byte i.e. first byte in value list
861 content = tlv[2][1:]
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200862 return (i2s(content), '00')
863
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100864 elif addr_type == 0x01: #IPv4
865 # Skip address tye byte i.e. first byte in value list
866 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
867 ipv4 = tlv[2][2:]
868 content = '.'.join(str(x) for x in ipv4)
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200869 return (content, '01')
870 else:
871 raise ValueError("Invalid address type")
Supreeth Herled572ede2020-03-22 09:55:04 +0100872
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200873 return (None, None)
Philipp Maierff84c232020-05-12 17:24:18 +0200874
Supreeth Herle3b342c22020-03-24 16:15:02 +0100875def enc_addr_tlv(addr, addr_type='00'):
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100876 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100877 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
878 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 +0100879
880 Default values:
Supreeth Herle3b342c22020-03-24 16:15:02 +0100881 - addr_type: 00 - FQDN format of Address
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100882 """
883
884 s = ""
885
Supreeth Herle654eca72020-03-25 14:25:38 +0100886 # TODO: Encoding of IPv6 address
887 if addr_type == '00': #FQDN
Supreeth Herle3b342c22020-03-24 16:15:02 +0100888 hex_str = s2h(addr)
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100889 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
Supreeth Herle654eca72020-03-25 14:25:38 +0100890 elif addr_type == '01': #IPv4
891 ipv4_list = addr.split('.')
892 ipv4_str = ""
893 for i in ipv4_list:
894 ipv4_str += ('%02x' % (int(i)))
895
896 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
897 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
898 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100899
900 return s
901
Harald Welte52255572021-04-03 09:56:32 +0200902def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
Philipp Maier47236502021-03-09 21:28:25 +0100903 """
904 Check if a string is a valid hexstring
905 """
906
907 # Filter obviously bad strings
908 if not string:
909 return False
910 if len(string) < minlen or minlen < 2:
911 return False
912 if len(string) % 2:
913 return False
914 if maxlen and len(string) > maxlen:
915 return False
916
917 # Try actual encoding to be sure
918 try:
919 try_encode = h2b(string)
920 return True
921 except:
922 return False
923
Harald Welte52255572021-04-03 09:56:32 +0200924def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
Philipp Maiere8536c02020-05-11 21:35:01 +0200925 """
926 The ADM pin can be supplied either in its hexadecimal form or as
927 ascii string. This function checks the supplied opts parameter and
Harald Welte236a65f2021-04-03 10:20:11 +0200928 returns the pin_adm as hex encoded string, regardless in which form
Philipp Maiere8536c02020-05-11 21:35:01 +0200929 it was originally supplied by the user
930 """
931
Harald Welte79b5ba42021-01-08 21:22:38 +0100932 if pin_adm is not None:
933 if len(pin_adm) <= 8:
934 pin_adm = ''.join(['%02x'%(ord(x)) for x in pin_adm])
Philipp Maiere8536c02020-05-11 21:35:01 +0200935 pin_adm = rpad(pin_adm, 16)
936
937 else:
938 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
939
Harald Welte79b5ba42021-01-08 21:22:38 +0100940 if pin_adm_hex is not None:
941 if len(pin_adm_hex) == 16:
942 pin_adm = pin_adm_hex
Philipp Maiere8536c02020-05-11 21:35:01 +0200943 # Ensure that it's hex-encoded
944 try:
945 try_encode = h2b(pin_adm)
946 except ValueError:
947 raise ValueError("PIN-ADM needs to be hex encoded using this option")
948 else:
949 raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)")
950
951 return pin_adm
952
Supreeth Herle95ec7722020-03-24 13:09:03 +0100953def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
954 """
955 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
956 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
957
958 Default values:
959 - epdg_priority: '0001' - 1st Priority
960 - epdg_fqdn_format: '00' - Operator Identifier FQDN
961 """
962
963 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
964 # TODO: Handle encoding of Length field for length more than 127 Bytes
965 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
966 content = rpad(content, len(hexstr))
967 return content
968
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100969def dec_ePDGSelection(sixhexbytes):
970 """
971 Decode ePDGSelection to get 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
975 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
976 plmn_chars = 6
977 epdg_priority_chars = 4
978 epdg_fqdn_format_chars = 2
979 # first three bytes (six ascii hex chars)
980 plmn_str = sixhexbytes[:plmn_chars]
981 # two bytes after first three bytes
982 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars]
983 # one byte after first five bytes
984 epdg_fqdn_format_str = sixhexbytes[plmn_chars + epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
985 res['mcc'] = dec_mcc_from_plmn(plmn_str)
986 res['mnc'] = dec_mnc_from_plmn(plmn_str)
987 res['epdg_priority'] = epdg_priority_str
988 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
989 return res
990
991def format_ePDGSelection(hexstr):
992 ePDGSelection_info_tag_chars = 2
993 ePDGSelection_info_tag_str = hexstr[:2]
herlesupreeth3a261d32021-01-05 09:20:11 +0100994 s = ""
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100995 # Minimum length
996 len_chars = 2
997 # TODO: Need to determine length properly - definite length support only
998 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
999 # As per spec, length is 5n, n - number of PLMNs
1000 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
1001 # Totalling to 6 Bytes, maybe length should be 6n
1002 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +01001003
1004 # Not programmed scenario
1005 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
1006 len_chars = 0
1007 ePDGSelection_info_tag_chars = 0
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001008 if len_str[0] == '8':
1009 # The bits 7 to 1 denotes the number of length octets if length > 127
1010 if int(len_str[1]) > 0:
1011 # Update number of length octets
1012 len_chars = len_chars * int(len_str[1])
1013 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
1014
1015 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
1016 # Right pad to prevent index out of range - multiple of 6 bytes
1017 content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12)))
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001018 for rec_data in hexstr_to_Nbytearr(content_str, 6):
1019 rec_info = dec_ePDGSelection(rec_data)
1020 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
1021 rec_str = "unused"
1022 else:
1023 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
1024 (rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
1025 s += "\t%s # %s\n" % (rec_data, rec_str)
1026 return s
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001027
1028def get_addr_type(addr):
1029 """
1030 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
1031 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
1032
1033 TODO: Handle IPv6
1034 """
1035
1036 # Empty address string
1037 if not len(addr):
1038 return None
1039
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001040 addr_list = addr.split('.')
1041
1042 # Check for IPv4/IPv6
1043 try:
1044 import ipaddress
1045 # Throws ValueError if addr is not correct
Denis 'GNUtoo' Carikli79f5b602020-02-15 04:02:57 +07001046 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001047
1048 if ipa.version == 4:
1049 return 0x01
1050 elif ipa.version == 6:
1051 return 0x02
1052 except Exception as e:
1053 invalid_ipv4 = True
1054 for i in addr_list:
1055 # Invalid IPv4 may qualify for a valid FQDN, so make check here
1056 # e.g. 172.24.15.300
1057 import re
1058 if not re.match('^[0-9_]+$', i):
1059 invalid_ipv4 = False
1060 break
1061
1062 if invalid_ipv4:
1063 return None
1064
1065 fqdn_flag = True
1066 for i in addr_list:
1067 # Only Alpha-numeric characters and hyphen - RFC 1035
1068 import re
1069 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
1070 fqdn_flag = False
1071 break
1072
1073 # FQDN
1074 if fqdn_flag:
1075 return 0x00
1076
1077 return None
Harald Welte67d551a2021-01-21 14:50:01 +01001078
Harald Welte1e456572021-04-02 17:16:30 +02001079def sw_match(sw:str, pattern:str) -> bool:
Harald Welte67d551a2021-01-21 14:50:01 +01001080 """Match given SW against given pattern."""
1081 # Create a masked version of the returned status word
1082 sw_lower = sw.lower()
1083 sw_masked = ""
1084 for i in range(0, 4):
Philipp Maier78e32f22021-03-22 20:39:24 +01001085 if pattern[i] == '?':
Harald Welte67d551a2021-01-21 14:50:01 +01001086 sw_masked = sw_masked + '?'
Philipp Maier78e32f22021-03-22 20:39:24 +01001087 elif pattern[i] == 'x':
Harald Welte67d551a2021-01-21 14:50:01 +01001088 sw_masked = sw_masked + 'x'
1089 else:
1090 sw_masked = sw_masked + sw_lower[i]
Philipp Maier78e32f22021-03-22 20:39:24 +01001091 # Compare the masked version against the pattern
Harald Welte67d551a2021-01-21 14:50:01 +01001092 return sw_masked == pattern
Philipp Maier5d3e2592021-02-22 17:22:16 +01001093
Harald Welteee3501f2021-04-02 13:00:18 +02001094def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
Harald Welte52255572021-04-03 09:56:32 +02001095 align_left:bool = True) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +02001096 """Pretty print a list of strings into a tabulated form.
1097
1098 Args:
1099 width : total width in characters per line
1100 space : horizontal space between cells
1101 lspace : number of spaces before row
1102 align_lef : Align text to the left side
1103 Returns:
1104 multi-line string containing formatted table
1105 """
Philipp Maier5d3e2592021-02-22 17:22:16 +01001106 if str_list == None:
1107 return ""
1108 if len(str_list) <= 0:
1109 return ""
1110 longest_str = max(str_list, key=len)
1111 cellwith = len(longest_str) + hspace
1112 cols = width // cellwith
1113 rows = (len(str_list) - 1) // cols + 1
1114 table = []
1115 for i in iter(range(rows)):
1116 str_list_row = str_list[i::rows]
1117 if (align_left):
1118 format_str_cell = '%%-%ds'
1119 else:
1120 format_str_cell = '%%%ds'
1121 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
1122 format_str_row = (" " * lspace) + format_str_row
1123 table.append(format_str_row % tuple(str_list_row))
1124 return '\n'.join(table)
Harald Welte5e749a72021-04-10 17:18:17 +02001125
Harald Welte917d98c2021-04-21 11:51:25 +02001126def auto_int(x):
1127 """Helper function for argparse to accept hexadecimal integers."""
1128 return int(x, 0)
1129
Harald Welte5e749a72021-04-10 17:18:17 +02001130class JsonEncoder(json.JSONEncoder):
1131 """Extend the standard library JSONEncoder with support for more types."""
1132 def default(self, o):
1133 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
1134 return b2h(o)
1135 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +02001136
1137def boxed_heading_str(heading, width=80):
1138 """Generate a string that contains a boxed heading."""
1139 # Auto-enlarge box if heading exceeds length
1140 if len(heading) > width - 4:
1141 width = len(heading) + 4
1142
1143 res = "#" * width
1144 fstr = "\n# %-" + str(width - 4) + "s #\n"
1145 res += fstr % (heading)
1146 res += "#" * width
1147 return res
Harald Welte3de6ca22021-05-02 20:05:56 +02001148
1149
1150
1151class DataObject(abc.ABC):
1152 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
1153 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
1154 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
1155 tags at specific points in a stream. This class tries to represent this."""
1156 def __init__(self, name, desc = None, tag = None):
1157 """
1158 Args:
1159 name: A brief, all-lowercase, underscore separated string identifier
1160 desc: A human-readable description of what this DO represents
1161 tag : The tag associated with this DO
1162 """
1163 self.name = name
1164 self.desc = desc
1165 self.tag = tag
1166 self.decoded = None
1167 self.encoded = None
1168
1169 def __str__(self):
1170 return self.name
1171
1172 def __repr__(self):
1173 return '%s(%s)' % (self.__class__, self.name)
1174
1175 def __or__(self, other):
1176 """OR-ing DataObjects together renders a DataObjectChoice."""
1177 if isinstance(other, DataObject):
1178 # DataObject | DataObject = DataObjectChoice
1179 return DataObjectChoice(None, members=[self, other])
1180 else:
1181 raise TypeError
1182
1183 def __add__(self, other):
1184 """ADD-ing DataObjects together renders a DataObjectCollection."""
1185 if isinstance(other, DataObject):
1186 # DataObject + DataObject = DataObjectCollectin
1187 return DataObjectCollection(None, members=[self, other])
1188
1189 def _compute_tag(self):
1190 """Compute the tag (sometimes the tag encodes part of the value)."""
1191 return self.tag
1192
1193 def to_dict(self):
1194 """Return a dict in form "name: decoded_value" """
1195 return {self.name: self.decoded}
1196
1197 @abc.abstractmethod
1198 def from_bytes(self, do:bytes):
1199 """Parse the value part of the DO into the internal state of this instance.
1200 Args:
1201 do : binary encoded bytes
1202 """
1203
1204 @abc.abstractmethod
1205 def to_bytes(self):
1206 """Encode the internal state of this instance into the TLV value part.
1207 Returns:
1208 binary bytes encoding the internal state
1209 """
1210
1211 def from_tlv(self, do:bytes):
1212 """Parse binary TLV representation into internal state. The resulting decoded
1213 representation is _not_ returned, but just internalized in the object instance!
1214 Args:
1215 do : input bytes containing TLV-encoded representation
1216 Returns:
1217 bytes remaining at end of 'do' after parsing one TLV/DO.
1218 """
1219 if do[0] != self.tag:
1220 raise ValueError('%s: Can only decode tag 0x%02x' % (self, self.tag))
1221 length = do[1]
1222 val = do[2:2+length]
1223 self.from_bytes(val)
1224 # return remaining bytes
1225 return do[2+length:]
1226
1227 def to_tlv(self):
1228 """Encode internal representation to binary TLV.
1229 Returns:
1230 bytes encoded in TLV format.
1231 """
1232 val = self.to_bytes()
1233 return bytes(self._compute_tag()) + bytes(len(val)) + val
1234
1235 # 'codec' interface
1236 def decode(self, binary:bytes):
1237 """Decode a single DOs from the input data.
1238 Args:
1239 binary : binary bytes of encoded data
1240 Returns:
1241 tuple of (decoded_result, binary_remainder)
1242 """
1243 tag = binary[0]
1244 if tag != self.tag:
1245 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1246 (self, tag, binary, self.tag))
1247 remainder = self.from_tlv(binary)
1248 return (self.to_dict(), remainder)
1249
1250 # 'codec' interface
1251 def encode(self):
1252 return self.to_tlv()
1253
1254class TL0_DataObject(DataObject):
1255 """Data Object that has Tag, Len=0 and no Value part."""
1256 def __init__(self, name, desc, tag, val=None):
1257 super().__init__(name, desc, tag)
1258 self.val = val
1259
1260 def from_bytes(self, binary:bytes):
1261 if len(binary) != 0:
1262 raise ValueError
1263 self.decoded = self.val
1264
1265 def to_bytes(self):
1266 return b''
1267
1268
1269class DataObjectCollection:
1270 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1271 A given encoded DO may contain any of them in any order, and may contain multiple instances
1272 of each DO."""
1273 def __init__(self, name, desc = None, members=None):
1274 self.name = name
1275 self.desc = desc
1276 self.members = members or []
1277 self.members_by_tag = {}
1278 self.members_by_name = {}
1279 self.members_by_tag = { m.tag:m for m in members }
1280 self.members_by_name = { m.name:m for m in members }
1281
1282 def __str__(self):
1283 member_strs = [str(x) for x in self.members]
1284 return '%s(%s)' % (self.name, ','.join(member_strs))
1285
1286 def __repr__(self):
1287 member_strs = [repr(x) for x in self.members]
1288 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1289
1290 def __add__(self, other):
1291 """Extending DataCollections with other DataCollections or DataObjects."""
1292 if isinstance(other, DataObjectCollection):
1293 # adding one collection to another
1294 members = self.members + other.members
1295 return DataObjectCollection(self.name, self.desc, members)
1296 elif isinstance(other, DataObject):
1297 # adding a member to a collection
1298 return DataObjectCollection(self.name, self.desc, self.members + [other])
1299 else:
1300 raise TypeError
1301
1302 # 'codec' interface
1303 def decode(self, binary:bytes):
1304 """Decode any number of DOs from the collection until the end of the input data,
1305 or uninitialized memory (0xFF) is found.
1306 Args:
1307 binary : binary bytes of encoded data
1308 Returns:
1309 tuple of (decoded_result, binary_remainder)
1310 """
1311 res = []
1312 remainder = binary
1313 # iterate until no binary trailer is left
1314 while len(remainder):
1315 tag = remainder[0]
1316 if tag == 0xff: # uninitialized memory at the end?
1317 return (res, remainder)
1318 if not tag in self.members_by_tag:
1319 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1320 (self, tag, remainder, self.members_by_tag.keys()))
1321 obj = self.members_by_tag[tag]
1322 # DO from_tlv returns remainder of binary
1323 remainder = obj.from_tlv(remainder)
1324 # collect our results
1325 res.append(obj.to_dict())
1326 return (res, remainder)
1327
1328 # 'codec' interface
1329 def encode(self, decoded):
1330 res = bytearray()
1331 for i in decoded:
1332 obj = self.members_by_name(i[0])
1333 res.append(obj.to_tlv())
1334 return res
1335
1336class DataObjectChoice(DataObjectCollection):
1337 """One Data Object from within a choice, identified by its tag.
1338 This means that exactly one member of the choice must occur, and which one occurs depends
1339 on the tag."""
1340 def __add__(self, other):
1341 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1342 raise TypeError
1343
1344 def __or__(self, other):
1345 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1346 if isinstance(other, DataObjectChoice):
1347 # adding one collection to another
1348 members = self.members + other.members
1349 return DataObjectChoice(self.name, self.desc, members)
1350 elif isinstance(other, DataObject):
1351 # adding a member to a collection
1352 return DataObjectChoice(self.name, self.desc, self.members + [other])
1353 else:
1354 raise TypeError
1355
1356 # 'codec' interface
1357 def decode(self, binary:bytes):
1358 """Decode a single DOs from the choice based on the tag.
1359 Args:
1360 binary : binary bytes of encoded data
1361 Returns:
1362 tuple of (decoded_result, binary_remainder)
1363 """
1364 tag = binary[0]
1365 if tag == 0xff:
1366 return (None, binary)
1367 if not tag in self.members_by_tag:
1368 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1369 (self, tag, binary, self.members_by_tag.keys()))
1370 obj = self.members_by_tag[tag]
1371 remainder = obj.from_tlv(binary)
1372 return (obj.to_dict(), remainder)
1373
1374 # 'codec' interface
1375 def encode(self, decoded):
1376 obj = self.members_by_name(decoded[0])
1377 return obj.to_tlv()
1378
1379class DataObjectSequence:
1380 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1381 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1382 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1383 for encoding or decoding an entire sequence."""
1384 def __init__(self, name, desc=None, sequence=None):
1385 self.sequence = sequence or []
1386 self.name = name
1387 self.desc = desc
1388
1389 def __str__(self):
1390 member_strs = [str(x) for x in self.sequence]
1391 return '%s(%s)' % (self.name, ','.join(member_strs))
1392
1393 def __repr__(self):
1394 member_strs = [repr(x) for x in self.sequence]
1395 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1396
1397 def __add__(self, other):
1398 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1399 if isinstance(other, 'DataObject'):
1400 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1401 elif isinstance(other, 'DataObjectChoice'):
1402 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1403 elif isinstance(other, 'DataObjectSequence'):
1404 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
1405
1406 # 'codec' interface
1407 def decode(self, binary:bytes):
1408 """Decode a sequence by calling the decoder of each element in the sequence.
1409 Args:
1410 binary : binary bytes of encoded data
1411 Returns:
1412 tuple of (decoded_result, binary_remainder)
1413 """
1414 remainder = binary
1415 res = []
1416 for e in self.sequence:
1417 (r, remainder) = e.decode(remainder)
1418 if r:
1419 res.append(r)
1420 return (res, remainder)
1421
1422 # 'codec' interface
1423 def decode_multi(self, do:bytes):
1424 """Decode multiple occurrences of the sequence from the binary input data.
1425 Args:
1426 do : binary input data to be decoded
1427 Returns:
1428 list of results of the decoder of this sequences
1429 """
1430 remainder = do
1431 res = []
1432 while len(remainder):
1433 (r, remainder2) = self.decode(remainder)
1434 if r:
1435 res.append(r)
1436 if len(remainder2) < len(remainder):
1437 remainder = remainder2
1438 else:
1439 remainder = remainder2
1440 break
1441 return (res, remainder)
1442
1443 # 'codec' interface
1444 def encode(self, decoded):
1445 """Encode a sequence by calling the encoder of each element in the sequence."""
1446 encoded = bytearray()
1447 i = 0
1448 for e in self.sequence:
1449 encoded += e.encode(decoded[i])
1450 i += 1
1451 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001452
1453class CardCommand:
1454 """A single card command / instruction."""
1455 def __init__(self, name, ins, cla_list=None, desc=None):
1456 self.name = name
1457 self.ins = ins
1458 self.cla_list = cla_list or []
1459 self.cla_list = [x.lower() for x in self.cla_list]
1460 self.desc = desc
1461
1462 def __str__(self):
1463 return self.name
1464
1465 def __repr__(self):
1466 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1467
1468 def match_cla(self, cla):
1469 """Does the given CLA match the CLA list of the command?."""
1470 if not isinstance(cla, str):
1471 cla = '%02u' % cla
1472 cla = cla.lower()
1473 for cla_match in self.cla_list:
1474 cla_masked = ""
1475 for i in range(0, 2):
1476 if cla_match[i] == 'x':
1477 cla_masked += 'x'
1478 else:
1479 cla_masked += cla[i]
1480 if cla_masked == cla_match:
1481 return True
1482 return False
1483
1484
1485class CardCommandSet:
1486 """A set of card instructions, typically specified within one spec."""
1487 def __init__(self, name, cmds=[]):
1488 self.name = name
1489 self.cmds = { c.ins : c for c in cmds }
1490
1491 def __str__(self):
1492 return self.name
1493
1494 def __getitem__(self, idx):
1495 return self.cmds[idx]
1496
1497 def __add__(self, other):
1498 if isinstance(other, CardCommand):
1499 if other.ins in self.cmds:
1500 raise ValueError('%s: INS 0x%02x already defined: %s' %
1501 (self, other.ins, self.cmds[other.ins]))
1502 self.cmds[other.ins] = other
1503 elif isinstance(other, CardCommandSet):
1504 for c in other.cmds.keys():
1505 self.cmds[c] = other.cmds[c]
1506 else:
1507 raise ValueError('%s: Unsupported type to add operator: %s' % (self, other))
1508
1509 def lookup(self, ins, cla=None):
1510 """look-up the command within the CommandSet."""
1511 ins = int(ins)
1512 if not ins in self.cmds:
1513 return None
1514 cmd = self.cmds[ins]
1515 if cla and not cmd.match_cla(cla):
1516 return None
1517 return cmd