blob: def88f03b713ac5f1a2fb654f7b33f8476c5d354 [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 """
Harald Welte9a754102021-10-21 13:46:59 +0200176 # check for FF padding at the end, as customary in SIM card files
177 if binary[0] == 0xff and len(binary) == 1 or binary[0] == 0xff and binary[1] == 0xff:
Harald Welte6912b1b2021-05-24 23:16:44 +0200178 return None, binary
179 tag = binary[0] & 0x1f
180 if tag <= 30:
181 return binary[0], binary[1:]
182 else: # multi-byte tag
183 tag = binary[0]
184 i = 1
185 last = False
186 while not last:
187 last = False if binary[i] & 0x80 else True
188 tag <<= 8
189 tag |= binary[i]
190 i += 1
191 return tag, binary[i:]
192
Harald Welte917d98c2021-04-21 11:51:25 +0200193def bertlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
194 """Parse a single Tag value according to ITU-T X.690 8.1.2
195 Args:
196 binary : binary input data of BER-TLV length field
197 Returns:
198 Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes)
199 """
200 cls = binary[0] >> 6
201 constructed = True if binary[0] & 0x20 else False
202 tag = binary[0] & 0x1f
203 if tag <= 30:
204 return ({'class':cls, 'constructed':constructed, 'tag': tag}, binary[1:])
205 else: # multi-byte tag
206 tag = 0
207 i = 1
208 last = False
209 while not last:
210 last = False if binary[i] & 0x80 else True
211 tag <<= 7
212 tag |= binary[i] & 0x7f
213 i += 1
214 return ({'class':cls, 'constructed':constructed, 'tag':tag}, binary[i:])
215
Harald Weltef0885b12021-05-24 23:18:59 +0200216def bertlv_encode_tag(t) -> bytes:
217 """Encode a single Tag value according to ITU-T X.690 8.1.2
218 """
219 def get_top7_bits(inp:int) -> Tuple[int, int]:
220 """Get top 7 bits of integer. Returns those 7 bits as integer and the remaining LSBs."""
221 remain_bits = inp.bit_length()
222 if remain_bits >= 7:
223 bitcnt = 7
224 else:
225 bitcnt = remain_bits
226 outp = inp >> (remain_bits - bitcnt)
227 remainder = inp & ~ (inp << (remain_bits - bitcnt))
228 return outp, remainder
229
230 if isinstance(t, int):
231 # FIXME: multiple byte tags
232 tag = t & 0x1f
233 constructed = True if t & 0x20 else False
234 cls = t >> 6
235 else:
236 tag = t['tag']
237 constructed = t['constructed']
238 cls = t['class']
239 if tag <= 30:
240 t = tag & 0x1f
241 if constructed:
242 t |= 0x20
243 t |= (cls & 3) << 6
244 return bytes([t])
245 else: # multi-byte tag
246 t = 0x1f;
247 if constructed:
248 t |= 0x20
249 t |= (cls & 3) << 6
250 tag_bytes = bytes([t])
251 remain = tag
252 while True:
253 t, remain = get_top7_bits(remain)
254 if remain:
255 t |= 0x80
256 tag_bytes += bytes([t])
257 if not remain:
258 break
259 return tag_bytes
260
Harald Welte917d98c2021-04-21 11:51:25 +0200261def bertlv_parse_len(binary:bytes) -> Tuple[int, bytes]:
262 """Parse a single Length value according to ITU-T X.690 8.1.3;
263 only the definite form is supported here.
264 Args:
265 binary : binary input data of BER-TLV length field
266 Returns:
267 Tuple of (length, remainder)
268 """
269 if binary[0] < 0x80:
270 return (binary[0], binary[1:])
271 else:
272 num_len_oct = binary[0] & 0x7f
273 length = 0
274 for i in range(1, 1+num_len_oct):
275 length <<= 8
276 length |= binary[i]
Harald Weltede027182021-05-04 20:06:45 +0200277 return (length, binary[1+num_len_oct:])
Harald Welte917d98c2021-04-21 11:51:25 +0200278
279def bertlv_encode_len(length:int) -> bytes:
280 """Encode a single Length value according to ITU-T X.690 8.1.3;
281 only the definite form is supported here.
282 Args:
283 length : length value to be encoded
284 Returns:
285 binary output data of BER-TLV length field
286 """
287 if length < 0x80:
288 return length.to_bytes(1, 'big')
289 elif length <= 0xff:
290 return b'\x81' + length.to_bytes(1, 'big')
291 elif length <= 0xffff:
292 return b'\x82' + length.to_bytes(2, 'big')
293 elif length <= 0xffffff:
294 return b'\x83' + length.to_bytes(3, 'big')
295 elif length <= 0xffffffff:
296 return b'\x84' + length.to_bytes(4, 'big')
297 else:
298 raise ValueError("Length > 32bits not supported")
299
Harald Weltec1475302021-05-21 21:47:55 +0200300def bertlv_parse_one(binary:bytes) -> (dict, int, bytes, bytes):
Harald Welte917d98c2021-04-21 11:51:25 +0200301 """Parse a single TLV IE at the start of the given binary data.
302 Args:
303 binary : binary input data of BER-TLV length field
304 Returns:
305 Tuple of (tag:dict, len:int, remainder:bytes)
306 """
307 (tagdict, remainder) = bertlv_parse_tag(binary)
308 (length, remainder) = bertlv_parse_len(remainder)
Harald Weltec1475302021-05-21 21:47:55 +0200309 value = remainder[:length]
310 remainder = remainder[length:]
311 return (tagdict, length, value, remainder)
Harald Welte917d98c2021-04-21 11:51:25 +0200312
313
314
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200315# IMSI encoded format:
316# For IMSI 0123456789ABCDE:
317#
318# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
319# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
320#
321# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
322#
323# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
324# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
325# encode itself.
326#
327# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
328# even length IMSI only uses half of the last byte.
329
Harald Welteee3501f2021-04-02 13:00:18 +0200330def enc_imsi(imsi:str):
331 """Converts a string IMSI into the encoded value of the EF"""
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200332 l = half_round_up(len(imsi) + 1) # Required bytes - include space for odd/even indicator
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400333 oe = len(imsi) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200334 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15)))
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400335 return ei
336
Harald Welte52255572021-04-03 09:56:32 +0200337def dec_imsi(ef:Hexstr) -> Optional[str]:
Harald Weltec9cdce32021-04-11 10:28:28 +0200338 """Converts an EF value to the IMSI string representation"""
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400339 if len(ef) < 4:
340 return None
Pau Espin Pedrol665bd222017-12-29 20:30:35 +0100341 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200342 l = l - 1 # Encoded length byte includes oe nibble
343 swapped = swap_nibbles(ef[2:]).rstrip('f')
Philipp Maiercd3d6262020-05-11 21:41:56 +0200344 if len(swapped) < 1:
345 return None
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400346 oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200347 if not oe:
348 # if even, only half of last byte was used
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400349 l = l-1
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200350 if l != len(swapped) - 1:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400351 return None
Ben Fox-Moore0ec14752018-09-24 15:47:02 +0200352 imsi = swapped[1:]
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400353 return imsi
354
Harald Welte52255572021-04-03 09:56:32 +0200355def dec_iccid(ef:Hexstr) -> str:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +0400356 return swap_nibbles(ef).strip('f')
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400357
Harald Welte52255572021-04-03 09:56:32 +0200358def enc_iccid(iccid:str) -> Hexstr:
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400359 return swap_nibbles(rpad(iccid, 20))
360
Philipp Maiere6f8d682021-04-23 21:14:41 +0200361def enc_plmn(mcc:Hexstr, mnc:Hexstr) -> Hexstr:
Alexander Chemerisdddbf522017-07-18 16:49:59 +0300362 """Converts integer MCC/MNC into 3 bytes for EF"""
Philipp Maier6c5cd802021-04-23 21:19:36 +0200363
364 # Make sure there are no excess whitespaces in the input
365 # parameters
366 mcc = mcc.strip()
367 mnc = mnc.strip()
368
369 # Make sure that MCC/MNC are correctly padded with leading
370 # zeros or 'F', depending on the length.
371 if len(mnc) == 0:
372 mnc = "FFF"
373 elif len(mnc) == 1:
374 mnc = "F0" + mnc
375 elif len(mnc) == 2:
376 mnc += "F"
377
378 if len(mcc) == 0:
379 mcc = "FFF"
380 elif len(mcc) == 1:
381 mcc = "00" + mcc
382 elif len(mcc) == 2:
383 mcc = "0" + mcc
384
Vadim Yanitskiyc8458e22021-03-12 00:34:10 +0100385 return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300386
Harald Welted7a7e172021-04-07 00:30:10 +0200387def dec_plmn(threehexbytes:Hexstr) -> dict:
Philipp Maier6c5cd802021-04-23 21:19:36 +0200388 res = {'mcc': "0", 'mnc': "0" }
389 dec_mcc_from_plmn_str(threehexbytes)
390 res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
391 res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
Harald Welted7a7e172021-04-07 00:30:10 +0200392 return res
393
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300394def dec_spn(ef):
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200395 """Obsolete, kept for API compatibility"""
396 from ts_51_011 import EF_SPN
397 abstract_data = EF_SPN().decode_hex(ef)
398 show_in_hplmn = abstract_data['show_in_hplmn']
399 hide_in_oplmn = abstract_data['hide_in_oplmn']
400 name = abstract_data['spn']
401 return (name, show_in_hplmn, hide_in_oplmn)
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300402
Robert Falkenbergb07a3e92021-05-07 15:23:20 +0200403def enc_spn(name:str, show_in_hplmn=False, hide_in_oplmn=False):
404 """Obsolete, kept for API compatibility"""
405 from ts_51_011 import EF_SPN
406 abstract_data = {
407 'hide_in_oplmn' : hide_in_oplmn,
408 'show_in_hplmn' : show_in_hplmn,
409 'spn' : name,
410 }
411 return EF_SPN().encode_hex(abstract_data)
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900412
Supreeth Herlef3948532020-03-24 12:23:51 +0100413def hexstr_to_Nbytearr(s, nbytes):
414 return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ]
415
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100416# Accepts hex string representing three bytes
Harald Welte52255572021-04-03 09:56:32 +0200417def dec_mcc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100418 ia = h2i(plmn)
419 digit1 = ia[0] & 0x0F # 1st byte, LSB
420 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
421 digit3 = ia[1] & 0x0F # 2nd byte, LSB
422 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
423 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100424 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100425
Philipp Maier6c5cd802021-04-23 21:19:36 +0200426def dec_mcc_from_plmn_str(plmn:Hexstr) -> str:
427 digit1 = plmn[1] # 1st byte, LSB
428 digit2 = plmn[0] # 1st byte, MSB
429 digit3 = plmn[3] # 2nd byte, LSB
430 res = digit1 + digit2 + digit3
431 return res.upper().strip("F")
432
Harald Welte52255572021-04-03 09:56:32 +0200433def dec_mnc_from_plmn(plmn:Hexstr) -> int:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100434 ia = h2i(plmn)
Vadim Yanitskiyb271be32021-03-11 23:56:58 +0100435 digit1 = ia[2] & 0x0F # 3rd byte, LSB
436 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
437 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100438 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
439 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100440 return derive_mnc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100441
Philipp Maier6c5cd802021-04-23 21:19:36 +0200442def dec_mnc_from_plmn_str(plmn:Hexstr) -> str:
443 digit1 = plmn[5] # 3rd byte, LSB
444 digit2 = plmn[4] # 3rd byte, MSB
445 digit3 = plmn[2] # 2nd byte, MSB
446 res = digit1 + digit2 + digit3
447 return res.upper().strip("F")
448
Harald Welte52255572021-04-03 09:56:32 +0200449def dec_act(twohexbytes:Hexstr) -> List[str]:
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100450 act_list = [
451 {'bit': 15, 'name': "UTRAN"},
452 {'bit': 14, 'name': "E-UTRAN"},
453 {'bit': 7, 'name': "GSM"},
454 {'bit': 6, 'name': "GSM COMPACT"},
455 {'bit': 5, 'name': "cdma2000 HRPD"},
456 {'bit': 4, 'name': "cdma2000 1xRTT"},
457 ]
458 ia = h2i(twohexbytes)
459 u16t = (ia[0] << 8)|ia[1]
460 sel = []
461 for a in act_list:
462 if u16t & (1 << a['bit']):
Philipp Maiere7d41792021-04-29 16:20:07 +0200463 if a['name'] == "E-UTRAN":
464 # The Access technology identifier of E-UTRAN
465 # allows a more detailed specification:
466 if u16t & (1 << 13) and u16t & (1 << 12):
467 sel.append("E-UTRAN WB-S1")
468 sel.append("E-UTRAN NB-S1")
469 elif u16t & (1 << 13):
470 sel.append("E-UTRAN WB-S1")
471 elif u16t & (1 << 12):
472 sel.append("E-UTRAN NB-S1")
473 else:
474 sel.append("E-UTRAN")
475 else:
476 sel.append(a['name'])
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100477 return sel
478
Harald Welte52255572021-04-03 09:56:32 +0200479def dec_xplmn_w_act(fivehexbytes:Hexstr) -> Dict[str,Any]:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200480 res = {'mcc': "0", 'mnc': "0", 'act': []}
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100481 plmn_chars = 6
482 act_chars = 4
483 plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
484 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
Philipp Maierb919f8b2021-04-27 18:28:27 +0200485 res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
486 res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100487 res['act'] = dec_act(act_str)
488 return res
489
490def format_xplmn_w_act(hexstr):
491 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200492 for rec_data in hexstr_to_Nbytearr(hexstr, 5):
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100493 rec_info = dec_xplmn_w_act(rec_data)
Philipp Maierb919f8b2021-04-27 18:28:27 +0200494 if rec_info['mcc'] == "" and rec_info['mnc'] == "":
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100495 rec_str = "unused"
496 else:
Philipp Maierb919f8b2021-04-27 18:28:27 +0200497 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 +0100498 s += "\t%s # %s\n" % (rec_data, rec_str)
499 return s
500
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100501def dec_loci(hexstr):
502 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
503 res['tmsi'] = hexstr[:8]
504 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
505 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
506 res['lac'] = hexstr[14:18]
507 res['status'] = h2i(hexstr[20:22])
508 return res
509
510def dec_psloci(hexstr):
511 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
512 res['p-tmsi'] = hexstr[:8]
513 res['p-tmsi-sig'] = hexstr[8:14]
514 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
515 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
516 res['lac'] = hexstr[20:24]
517 res['rac'] = hexstr[24:26]
518 res['status'] = h2i(hexstr[26:28])
519 return res
520
521def dec_epsloci(hexstr):
522 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
523 res['guti'] = hexstr[:24]
524 res['tai'] = hexstr[24:34]
525 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
526 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
527 res['tac'] = hexstr[30:34]
528 res['status'] = h2i(hexstr[34:36])
529 return res
530
Harald Welte52255572021-04-03 09:56:32 +0200531def dec_xplmn(threehexbytes:Hexstr) -> dict:
Harald Welteca673942020-06-03 15:19:40 +0200532 res = {'mcc': 0, 'mnc': 0, 'act': []}
533 plmn_chars = 6
534 plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
535 res['mcc'] = dec_mcc_from_plmn(plmn_str)
536 res['mnc'] = dec_mnc_from_plmn(plmn_str)
537 return res
538
Harald Welte52255572021-04-03 09:56:32 +0200539def format_xplmn(hexstr:Hexstr) -> str:
Harald Welteca673942020-06-03 15:19:40 +0200540 s = ""
herlesupreeth45fa6042020-09-18 15:32:20 +0200541 for rec_data in hexstr_to_Nbytearr(hexstr, 3):
Harald Welteca673942020-06-03 15:19:40 +0200542 rec_info = dec_xplmn(rec_data)
543 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
544 rec_str = "unused"
545 else:
546 rec_str = "MCC: %03d MNC: %03d" % (rec_info['mcc'], rec_info['mnc'])
547 s += "\t%s # %s\n" % (rec_data, rec_str)
548 return s
549
Harald Welte52255572021-04-03 09:56:32 +0200550def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900551 """
552 Run the milenage algorithm to calculate OPC from Ki and OP
553 """
554 from Crypto.Cipher import AES
555 from Crypto.Util.strxor import strxor
556 from pySim.utils import b2h
557
558 # We pass in hex string and now need to work on bytes
Harald Welteeab8d2a2021-03-05 18:30:23 +0100559 ki_bytes = bytes(h2b(ki_hex))
560 op_bytes = bytes(h2b(op_hex))
Harald Welteab34fa82021-03-05 18:39:59 +0100561 aes = AES.new(ki_bytes, AES.MODE_ECB)
Harald Welteeab8d2a2021-03-05 18:30:23 +0100562 opc_bytes = aes.encrypt(op_bytes)
563 return b2h(strxor(opc_bytes, op_bytes))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900564
Harald Welte52255572021-04-03 09:56:32 +0200565def calculate_luhn(cc) -> int:
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900566 """
567 Calculate Luhn checksum used in e.g. ICCID and IMEI
568 """
Daniel Willmanndd014ea2020-10-19 10:35:11 +0200569 num = list(map(int, str(cc)))
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900570 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
571 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200572
Harald Welte52255572021-04-03 09:56:32 +0200573def mcc_from_imsi(imsi:str) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200574 """
575 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
576 """
577 if imsi == None:
578 return None
579
580 if len(imsi) > 3:
581 return imsi[:3]
582 else:
583 return None
584
Harald Welte52255572021-04-03 09:56:32 +0200585def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
Philipp Maier7592eee2019-09-12 13:03:23 +0200586 """
587 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
588 """
589 if imsi == None:
590 return None
591
592 if len(imsi) > 3:
593 if long:
594 return imsi[3:6]
595 else:
596 return imsi[3:5]
597 else:
598 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100599
Harald Welte52255572021-04-03 09:56:32 +0200600def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100601 """
602 Derive decimal representation of the MCC (Mobile Country Code)
603 from three given digits.
604 """
605
606 mcc = 0
607
608 if digit1 != 0x0f:
609 mcc += digit1 * 100
610 if digit2 != 0x0f:
611 mcc += digit2 * 10
612 if digit3 != 0x0f:
613 mcc += digit3
614
615 return mcc
616
Harald Welte52255572021-04-03 09:56:32 +0200617def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
Supreeth Herled24f1632019-11-30 10:37:09 +0100618 """
619 Derive decimal representation of the MNC (Mobile Network Code)
620 from two or (optionally) three given digits.
621 """
622
623 mnc = 0
624
625 # 3-rd digit is optional for the MNC. If present
626 # the algorythm is the same as for the MCC.
627 if digit3 != 0x0f:
628 return derive_mcc(digit1, digit2, digit3)
629
630 if digit1 != 0x0f:
631 mnc += digit1 * 10
632 if digit2 != 0x0f:
633 mnc += digit2
634
635 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100636
Harald Welte52255572021-04-03 09:56:32 +0200637def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100638 """
639 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
640 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
641 """
642
643 # Convert from str to (kind of) 'bytes'
644 ef_msisdn = h2b(ef_msisdn)
645
646 # Make sure mandatory fields are present
647 if len(ef_msisdn) < 14:
648 raise ValueError("EF.MSISDN is too short")
649
650 # Skip optional Alpha Identifier
651 xlen = len(ef_msisdn) - 14
652 msisdn_lhv = ef_msisdn[xlen:]
653
654 # Parse the length (in bytes) of the BCD encoded number
Harald Welte4f6ca432021-02-01 17:51:56 +0100655 bcd_len = msisdn_lhv[0]
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100656 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
657 if bcd_len == 0xff:
658 return None
659 elif bcd_len > 11 or bcd_len < 1:
660 raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len)
661
662 # Parse ToN / NPI
Harald Welte4f6ca432021-02-01 17:51:56 +0100663 ton = (msisdn_lhv[1] >> 4) & 0x07
664 npi = msisdn_lhv[1] & 0x0f
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100665 bcd_len -= 1
666
667 # No MSISDN?
668 if not bcd_len:
669 return (npi, ton, None)
670
671 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
672 # International number 10.5.118/3GPP TS 24.008
Vadim Yanitskiy7ba24282020-02-27 00:04:13 +0700673 if ton == 0x01:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100674 msisdn = '+' + msisdn
675
676 return (npi, ton, msisdn)
Supreeth Herle5a541012019-12-22 08:59:16 +0100677
Harald Welte52255572021-04-03 09:56:32 +0200678def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr:
Supreeth Herle5a541012019-12-22 08:59:16 +0100679 """
680 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200681 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. (The result
682 will not contain the optional Alpha Identifier at the beginning.)
Supreeth Herle5a541012019-12-22 08:59:16 +0100683
684 Default NPI / ToN values:
685 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
686 - ToN: network specific or international number (if starts with '+').
687 """
688
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200689 # If no MSISDN is supplied then encode the file contents as all "ff"
690 if msisdn == "" or msisdn == "+":
691 return "ff" * 14
692
Supreeth Herle5a541012019-12-22 08:59:16 +0100693 # Leading '+' indicates International Number
694 if msisdn[0] == '+':
695 msisdn = msisdn[1:]
696 ton = 0x01
697
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200698 # An MSISDN must not exceed 20 digits
699 if len(msisdn) > 20:
700 raise ValueError("msisdn must not be longer than 20 digits")
701
Supreeth Herle5a541012019-12-22 08:59:16 +0100702 # Append 'f' padding if number of digits is odd
703 if len(msisdn) % 2 > 0:
704 msisdn += 'f'
705
706 # BCD length also includes NPI/ToN header
707 bcd_len = len(msisdn) // 2 + 1
708 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
709 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
710
Philipp Maierb46cb3f2021-04-20 22:38:21 +0200711 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2)
712
Supreeth Herle441c4a72020-03-24 10:19:15 +0100713
Harald Welte52255572021-04-03 09:56:32 +0200714def dec_st(st, table="sim") -> str:
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200715 """
716 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
717 """
718
Supreeth Herledf330372020-04-20 14:48:55 +0200719 if table == "isim":
720 from pySim.ts_31_103 import EF_IST_map
721 lookup_map = EF_IST_map
722 elif table == "usim":
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200723 from pySim.ts_31_102 import EF_UST_map
724 lookup_map = EF_UST_map
725 else:
726 from pySim.ts_51_011 import EF_SST_map
727 lookup_map = EF_SST_map
728
729 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
730
731 avail_st = ""
732 # Get each byte and check for available services
733 for i in range(0, len(st_bytes)):
734 # Byte i contains info about Services num (8i+1) to num (8i+8)
735 byte = int(st_bytes[i], 16)
736 # Services in each byte are in order MSB to LSB
737 # MSB - Service (8i+8)
738 # LSB - Service (8i+1)
739 for j in range(1, 9):
740 if byte&0x01 == 0x01 and ((8*i) + j in lookup_map):
741 # Byte X contains info about Services num (8X-7) to num (8X)
742 # bit = 1: service available
743 # bit = 0: service not available
744 avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j])
745 byte = byte >> 1
746 return avail_st
Supreeth Herle98370552020-05-11 09:04:41 +0200747
748def first_TLV_parser(bytelist):
749 '''
750 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
751
752 parses first TLV format record in a list of bytelist
753 returns a 3-Tuple: Tag, Length, Value
754 Value is a list of bytes
755 parsing of length is ETSI'style 101.220
756 '''
757 Tag = bytelist[0]
758 if bytelist[1] == 0xFF:
759 Len = bytelist[2]*256 + bytelist[3]
760 Val = bytelist[4:4+Len]
761 else:
762 Len = bytelist[1]
763 Val = bytelist[2:2+Len]
764 return (Tag, Len, Val)
765
766def TLV_parser(bytelist):
767 '''
768 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
769
770 loops on the input list of bytes with the "first_TLV_parser()" function
771 returns a list of 3-Tuples
772 '''
773 ret = []
774 while len(bytelist) > 0:
775 T, L, V = first_TLV_parser(bytelist)
776 if T == 0xFF:
777 # padding bytes
778 break
779 ret.append( (T, L, V) )
780 # need to manage length of L
781 if L > 0xFE:
782 bytelist = bytelist[ L+4 : ]
783 else:
784 bytelist = bytelist[ L+2 : ]
785 return ret
Supreeth Herled572ede2020-03-22 09:55:04 +0100786
Supreeth Herled84daa12020-03-24 12:20:40 +0100787def enc_st(st, service, state=1):
788 """
789 Encodes the EF S/U/IST/EST and returns the updated Service Table
790
791 Parameters:
792 st - Current value of SIM/USIM/ISIM Service Table
793 service - Service Number to encode as activated/de-activated
794 state - 1 mean activate, 0 means de-activate
795
796 Returns:
797 s - Modified value of SIM/USIM/ISIM Service Table
798
799 Default values:
800 - state: 1 - Sets the particular Service bit to 1
801 """
802 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
803
804 s = ""
805 # Check whether the requested service is present in each byte
806 for i in range(0, len(st_bytes)):
807 # Byte i contains info about Services num (8i+1) to num (8i+8)
808 if service in range((8*i) + 1, (8*i) + 9):
809 byte = int(st_bytes[i], 16)
810 # Services in each byte are in order MSB to LSB
811 # MSB - Service (8i+8)
812 # LSB - Service (8i+1)
813 mod_byte = 0x00
814 # Copy bit by bit contents of byte to mod_byte with modified bit
815 # for requested service
816 for j in range(1, 9):
817 mod_byte = mod_byte >> 1
818 if service == (8*i) + j:
819 mod_byte = state == 1 and mod_byte|0x80 or mod_byte&0x7f
820 else:
821 mod_byte = byte&0x01 == 0x01 and mod_byte|0x80 or mod_byte&0x7f
822 byte = byte >> 1
823
824 s += ('%02x' % (mod_byte))
825 else:
826 s += st_bytes[i]
827
828 return s
829
Supreeth Herle3b342c22020-03-24 16:15:02 +0100830def dec_addr_tlv(hexstr):
Supreeth Herled572ede2020-03-22 09:55:04 +0100831 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100832 Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
833 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 +0100834 """
835
836 # Convert from hex str to int bytes list
Supreeth Herle3b342c22020-03-24 16:15:02 +0100837 addr_tlv_bytes = h2i(hexstr)
Supreeth Herled572ede2020-03-22 09:55:04 +0100838
Supreeth Herled572ede2020-03-22 09:55:04 +0100839 # Get list of tuples containing parsed TLVs
Supreeth Herle3b342c22020-03-24 16:15:02 +0100840 tlvs = TLV_parser(addr_tlv_bytes)
Supreeth Herled572ede2020-03-22 09:55:04 +0100841
842 for tlv in tlvs:
843 # tlv = (T, L, [V])
844 # T = Tag
845 # L = Length
846 # [V] = List of value
847
848 # Invalid Tag value scenario
849 if tlv[0] != 0x80:
850 continue
851
Supreeth Herled6a5ec52020-06-01 12:27:51 +0200852 # Empty field - Zero length
853 if tlv[1] == 0:
854 continue
855
Supreeth Herled572ede2020-03-22 09:55:04 +0100856 # First byte in the value has the address type
857 addr_type = tlv[2][0]
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100858 # TODO: Support parsing of IPv6
Supreeth Herle3b342c22020-03-24 16:15:02 +0100859 # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved)
Supreeth Herled572ede2020-03-22 09:55:04 +0100860 if addr_type == 0x00: #FQDN
861 # Skip address tye byte i.e. first byte in value list
862 content = tlv[2][1:]
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200863 return (i2s(content), '00')
864
Supreeth Herle43fd03b2020-03-25 14:52:46 +0100865 elif addr_type == 0x01: #IPv4
866 # Skip address tye byte i.e. first byte in value list
867 # Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
868 ipv4 = tlv[2][2:]
869 content = '.'.join(str(x) for x in ipv4)
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200870 return (content, '01')
871 else:
872 raise ValueError("Invalid address type")
Supreeth Herled572ede2020-03-22 09:55:04 +0100873
Philipp Maierbe18f2a2021-04-30 15:00:27 +0200874 return (None, None)
Philipp Maierff84c232020-05-12 17:24:18 +0200875
Supreeth Herle3b342c22020-03-24 16:15:02 +0100876def enc_addr_tlv(addr, addr_type='00'):
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100877 """
Supreeth Herle3b342c22020-03-24 16:15:02 +0100878 Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
879 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 +0100880
881 Default values:
Supreeth Herle3b342c22020-03-24 16:15:02 +0100882 - addr_type: 00 - FQDN format of Address
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100883 """
884
885 s = ""
886
Supreeth Herle654eca72020-03-25 14:25:38 +0100887 # TODO: Encoding of IPv6 address
888 if addr_type == '00': #FQDN
Supreeth Herle3b342c22020-03-24 16:15:02 +0100889 hex_str = s2h(addr)
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100890 s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str
Supreeth Herle654eca72020-03-25 14:25:38 +0100891 elif addr_type == '01': #IPv4
892 ipv4_list = addr.split('.')
893 ipv4_str = ""
894 for i in ipv4_list:
895 ipv4_str += ('%02x' % (int(i)))
896
897 # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used
898 # IPv4 Address is in octet 5 to octet 8 of the TLV data object
899 s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str
Supreeth Herle3c0bd7a2020-03-23 11:59:33 +0100900
901 return s
902
Harald Welte52255572021-04-03 09:56:32 +0200903def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
Philipp Maier47236502021-03-09 21:28:25 +0100904 """
905 Check if a string is a valid hexstring
906 """
907
908 # Filter obviously bad strings
909 if not string:
910 return False
911 if len(string) < minlen or minlen < 2:
912 return False
913 if len(string) % 2:
914 return False
915 if maxlen and len(string) > maxlen:
916 return False
917
918 # Try actual encoding to be sure
919 try:
920 try_encode = h2b(string)
921 return True
922 except:
923 return False
924
Harald Welte52255572021-04-03 09:56:32 +0200925def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
Philipp Maiere8536c02020-05-11 21:35:01 +0200926 """
927 The ADM pin can be supplied either in its hexadecimal form or as
928 ascii string. This function checks the supplied opts parameter and
Harald Welte236a65f2021-04-03 10:20:11 +0200929 returns the pin_adm as hex encoded string, regardless in which form
Philipp Maiere8536c02020-05-11 21:35:01 +0200930 it was originally supplied by the user
931 """
932
Harald Welte79b5ba42021-01-08 21:22:38 +0100933 if pin_adm is not None:
934 if len(pin_adm) <= 8:
935 pin_adm = ''.join(['%02x'%(ord(x)) for x in pin_adm])
Philipp Maiere8536c02020-05-11 21:35:01 +0200936 pin_adm = rpad(pin_adm, 16)
937
938 else:
939 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
940
Harald Welte79b5ba42021-01-08 21:22:38 +0100941 if pin_adm_hex is not None:
942 if len(pin_adm_hex) == 16:
943 pin_adm = pin_adm_hex
Philipp Maiere8536c02020-05-11 21:35:01 +0200944 # Ensure that it's hex-encoded
945 try:
946 try_encode = h2b(pin_adm)
947 except ValueError:
948 raise ValueError("PIN-ADM needs to be hex encoded using this option")
949 else:
950 raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)")
951
952 return pin_adm
953
Supreeth Herle95ec7722020-03-24 13:09:03 +0100954def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
955 """
956 Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
957 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
958
959 Default values:
960 - epdg_priority: '0001' - 1st Priority
961 - epdg_fqdn_format: '00' - Operator Identifier FQDN
962 """
963
964 plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format
965 # TODO: Handle encoding of Length field for length more than 127 Bytes
966 content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1
967 content = rpad(content, len(hexstr))
968 return content
969
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100970def dec_ePDGSelection(sixhexbytes):
971 """
972 Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
973 See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106.
974 """
975
976 res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''}
977 plmn_chars = 6
978 epdg_priority_chars = 4
979 epdg_fqdn_format_chars = 2
980 # first three bytes (six ascii hex chars)
981 plmn_str = sixhexbytes[:plmn_chars]
982 # two bytes after first three bytes
983 epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars]
984 # one byte after first five bytes
985 epdg_fqdn_format_str = sixhexbytes[plmn_chars + epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
986 res['mcc'] = dec_mcc_from_plmn(plmn_str)
987 res['mnc'] = dec_mnc_from_plmn(plmn_str)
988 res['epdg_priority'] = epdg_priority_str
989 res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
990 return res
991
992def format_ePDGSelection(hexstr):
993 ePDGSelection_info_tag_chars = 2
994 ePDGSelection_info_tag_str = hexstr[:2]
herlesupreeth3a261d32021-01-05 09:20:11 +0100995 s = ""
Supreeth Herle95b4e8d2020-03-24 12:49:16 +0100996 # Minimum length
997 len_chars = 2
998 # TODO: Need to determine length properly - definite length support only
999 # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104
1000 # As per spec, length is 5n, n - number of PLMNs
1001 # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte)
1002 # Totalling to 6 Bytes, maybe length should be 6n
1003 len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars]
herlesupreeth3a261d32021-01-05 09:20:11 +01001004
1005 # Not programmed scenario
1006 if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255:
1007 len_chars = 0
1008 ePDGSelection_info_tag_chars = 0
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001009 if len_str[0] == '8':
1010 # The bits 7 to 1 denotes the number of length octets if length > 127
1011 if int(len_str[1]) > 0:
1012 # Update number of length octets
1013 len_chars = len_chars * int(len_str[1])
1014 len_str = hexstr[ePDGSelection_info_tag_chars:len_chars]
1015
1016 content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
1017 # Right pad to prevent index out of range - multiple of 6 bytes
1018 content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12)))
Supreeth Herle95b4e8d2020-03-24 12:49:16 +01001019 for rec_data in hexstr_to_Nbytearr(content_str, 6):
1020 rec_info = dec_ePDGSelection(rec_data)
1021 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
1022 rec_str = "unused"
1023 else:
1024 rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
1025 (rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
1026 s += "\t%s # %s\n" % (rec_data, rec_str)
1027 return s
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001028
1029def get_addr_type(addr):
1030 """
1031 Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
1032 Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given)
1033
1034 TODO: Handle IPv6
1035 """
1036
1037 # Empty address string
1038 if not len(addr):
1039 return None
1040
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001041 addr_list = addr.split('.')
1042
1043 # Check for IPv4/IPv6
1044 try:
1045 import ipaddress
1046 # Throws ValueError if addr is not correct
Denis 'GNUtoo' Carikli79f5b602020-02-15 04:02:57 +07001047 ipa = ipaddress.ip_address(addr)
Supreeth Herle556b0fe2020-03-25 11:26:57 +01001048
1049 if ipa.version == 4:
1050 return 0x01
1051 elif ipa.version == 6:
1052 return 0x02
1053 except Exception as e:
1054 invalid_ipv4 = True
1055 for i in addr_list:
1056 # Invalid IPv4 may qualify for a valid FQDN, so make check here
1057 # e.g. 172.24.15.300
1058 import re
1059 if not re.match('^[0-9_]+$', i):
1060 invalid_ipv4 = False
1061 break
1062
1063 if invalid_ipv4:
1064 return None
1065
1066 fqdn_flag = True
1067 for i in addr_list:
1068 # Only Alpha-numeric characters and hyphen - RFC 1035
1069 import re
1070 if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
1071 fqdn_flag = False
1072 break
1073
1074 # FQDN
1075 if fqdn_flag:
1076 return 0x00
1077
1078 return None
Harald Welte67d551a2021-01-21 14:50:01 +01001079
Harald Welte1e456572021-04-02 17:16:30 +02001080def sw_match(sw:str, pattern:str) -> bool:
Harald Welte67d551a2021-01-21 14:50:01 +01001081 """Match given SW against given pattern."""
1082 # Create a masked version of the returned status word
1083 sw_lower = sw.lower()
1084 sw_masked = ""
1085 for i in range(0, 4):
Philipp Maier78e32f22021-03-22 20:39:24 +01001086 if pattern[i] == '?':
Harald Welte67d551a2021-01-21 14:50:01 +01001087 sw_masked = sw_masked + '?'
Philipp Maier78e32f22021-03-22 20:39:24 +01001088 elif pattern[i] == 'x':
Harald Welte67d551a2021-01-21 14:50:01 +01001089 sw_masked = sw_masked + 'x'
1090 else:
1091 sw_masked = sw_masked + sw_lower[i]
Philipp Maier78e32f22021-03-22 20:39:24 +01001092 # Compare the masked version against the pattern
Harald Welte67d551a2021-01-21 14:50:01 +01001093 return sw_masked == pattern
Philipp Maier5d3e2592021-02-22 17:22:16 +01001094
Harald Welteee3501f2021-04-02 13:00:18 +02001095def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
Harald Welte52255572021-04-03 09:56:32 +02001096 align_left:bool = True) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +02001097 """Pretty print a list of strings into a tabulated form.
1098
1099 Args:
1100 width : total width in characters per line
1101 space : horizontal space between cells
1102 lspace : number of spaces before row
1103 align_lef : Align text to the left side
1104 Returns:
1105 multi-line string containing formatted table
1106 """
Philipp Maier5d3e2592021-02-22 17:22:16 +01001107 if str_list == None:
1108 return ""
1109 if len(str_list) <= 0:
1110 return ""
1111 longest_str = max(str_list, key=len)
1112 cellwith = len(longest_str) + hspace
1113 cols = width // cellwith
1114 rows = (len(str_list) - 1) // cols + 1
1115 table = []
1116 for i in iter(range(rows)):
1117 str_list_row = str_list[i::rows]
1118 if (align_left):
1119 format_str_cell = '%%-%ds'
1120 else:
1121 format_str_cell = '%%%ds'
1122 format_str_row = (format_str_cell % cellwith) * len(str_list_row)
1123 format_str_row = (" " * lspace) + format_str_row
1124 table.append(format_str_row % tuple(str_list_row))
1125 return '\n'.join(table)
Harald Welte5e749a72021-04-10 17:18:17 +02001126
Harald Welte917d98c2021-04-21 11:51:25 +02001127def auto_int(x):
1128 """Helper function for argparse to accept hexadecimal integers."""
1129 return int(x, 0)
1130
Harald Welte5e749a72021-04-10 17:18:17 +02001131class JsonEncoder(json.JSONEncoder):
1132 """Extend the standard library JSONEncoder with support for more types."""
1133 def default(self, o):
1134 if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
1135 return b2h(o)
1136 return json.JSONEncoder.default(self, o)
Philipp Maier80ce71f2021-04-19 21:24:23 +02001137
1138def boxed_heading_str(heading, width=80):
1139 """Generate a string that contains a boxed heading."""
1140 # Auto-enlarge box if heading exceeds length
1141 if len(heading) > width - 4:
1142 width = len(heading) + 4
1143
1144 res = "#" * width
1145 fstr = "\n# %-" + str(width - 4) + "s #\n"
1146 res += fstr % (heading)
1147 res += "#" * width
1148 return res
Harald Welte3de6ca22021-05-02 20:05:56 +02001149
1150
1151
1152class DataObject(abc.ABC):
1153 """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
1154 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
1155 has the habit of specifying TLV data but with very spcific ordering, or specific choices of
1156 tags at specific points in a stream. This class tries to represent this."""
1157 def __init__(self, name, desc = None, tag = None):
1158 """
1159 Args:
1160 name: A brief, all-lowercase, underscore separated string identifier
1161 desc: A human-readable description of what this DO represents
1162 tag : The tag associated with this DO
1163 """
1164 self.name = name
1165 self.desc = desc
1166 self.tag = tag
1167 self.decoded = None
1168 self.encoded = None
1169
1170 def __str__(self):
1171 return self.name
1172
1173 def __repr__(self):
1174 return '%s(%s)' % (self.__class__, self.name)
1175
1176 def __or__(self, other):
1177 """OR-ing DataObjects together renders a DataObjectChoice."""
1178 if isinstance(other, DataObject):
1179 # DataObject | DataObject = DataObjectChoice
1180 return DataObjectChoice(None, members=[self, other])
1181 else:
1182 raise TypeError
1183
1184 def __add__(self, other):
1185 """ADD-ing DataObjects together renders a DataObjectCollection."""
1186 if isinstance(other, DataObject):
1187 # DataObject + DataObject = DataObjectCollectin
1188 return DataObjectCollection(None, members=[self, other])
1189
1190 def _compute_tag(self):
1191 """Compute the tag (sometimes the tag encodes part of the value)."""
1192 return self.tag
1193
1194 def to_dict(self):
1195 """Return a dict in form "name: decoded_value" """
1196 return {self.name: self.decoded}
1197
1198 @abc.abstractmethod
1199 def from_bytes(self, do:bytes):
1200 """Parse the value part of the DO into the internal state of this instance.
1201 Args:
1202 do : binary encoded bytes
1203 """
1204
1205 @abc.abstractmethod
1206 def to_bytes(self):
1207 """Encode the internal state of this instance into the TLV value part.
1208 Returns:
1209 binary bytes encoding the internal state
1210 """
1211
1212 def from_tlv(self, do:bytes):
1213 """Parse binary TLV representation into internal state. The resulting decoded
1214 representation is _not_ returned, but just internalized in the object instance!
1215 Args:
1216 do : input bytes containing TLV-encoded representation
1217 Returns:
1218 bytes remaining at end of 'do' after parsing one TLV/DO.
1219 """
1220 if do[0] != self.tag:
1221 raise ValueError('%s: Can only decode tag 0x%02x' % (self, self.tag))
1222 length = do[1]
1223 val = do[2:2+length]
1224 self.from_bytes(val)
1225 # return remaining bytes
1226 return do[2+length:]
1227
1228 def to_tlv(self):
1229 """Encode internal representation to binary TLV.
1230 Returns:
1231 bytes encoded in TLV format.
1232 """
1233 val = self.to_bytes()
1234 return bytes(self._compute_tag()) + bytes(len(val)) + val
1235
1236 # 'codec' interface
1237 def decode(self, binary:bytes):
1238 """Decode a single DOs from the input data.
1239 Args:
1240 binary : binary bytes of encoded data
1241 Returns:
1242 tuple of (decoded_result, binary_remainder)
1243 """
1244 tag = binary[0]
1245 if tag != self.tag:
1246 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
1247 (self, tag, binary, self.tag))
1248 remainder = self.from_tlv(binary)
1249 return (self.to_dict(), remainder)
1250
1251 # 'codec' interface
1252 def encode(self):
1253 return self.to_tlv()
1254
1255class TL0_DataObject(DataObject):
1256 """Data Object that has Tag, Len=0 and no Value part."""
1257 def __init__(self, name, desc, tag, val=None):
1258 super().__init__(name, desc, tag)
1259 self.val = val
1260
1261 def from_bytes(self, binary:bytes):
1262 if len(binary) != 0:
1263 raise ValueError
1264 self.decoded = self.val
1265
1266 def to_bytes(self):
1267 return b''
1268
1269
1270class DataObjectCollection:
1271 """A DataObjectCollection consits of multiple Data Objects identified by their tags.
1272 A given encoded DO may contain any of them in any order, and may contain multiple instances
1273 of each DO."""
1274 def __init__(self, name, desc = None, members=None):
1275 self.name = name
1276 self.desc = desc
1277 self.members = members or []
1278 self.members_by_tag = {}
1279 self.members_by_name = {}
1280 self.members_by_tag = { m.tag:m for m in members }
1281 self.members_by_name = { m.name:m for m in members }
1282
1283 def __str__(self):
1284 member_strs = [str(x) for x in self.members]
1285 return '%s(%s)' % (self.name, ','.join(member_strs))
1286
1287 def __repr__(self):
1288 member_strs = [repr(x) for x in self.members]
1289 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1290
1291 def __add__(self, other):
1292 """Extending DataCollections with other DataCollections or DataObjects."""
1293 if isinstance(other, DataObjectCollection):
1294 # adding one collection to another
1295 members = self.members + other.members
1296 return DataObjectCollection(self.name, self.desc, members)
1297 elif isinstance(other, DataObject):
1298 # adding a member to a collection
1299 return DataObjectCollection(self.name, self.desc, self.members + [other])
1300 else:
1301 raise TypeError
1302
1303 # 'codec' interface
1304 def decode(self, binary:bytes):
1305 """Decode any number of DOs from the collection until the end of the input data,
1306 or uninitialized memory (0xFF) is found.
1307 Args:
1308 binary : binary bytes of encoded data
1309 Returns:
1310 tuple of (decoded_result, binary_remainder)
1311 """
1312 res = []
1313 remainder = binary
1314 # iterate until no binary trailer is left
1315 while len(remainder):
1316 tag = remainder[0]
1317 if tag == 0xff: # uninitialized memory at the end?
1318 return (res, remainder)
1319 if not tag in self.members_by_tag:
1320 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1321 (self, tag, remainder, self.members_by_tag.keys()))
1322 obj = self.members_by_tag[tag]
1323 # DO from_tlv returns remainder of binary
1324 remainder = obj.from_tlv(remainder)
1325 # collect our results
1326 res.append(obj.to_dict())
1327 return (res, remainder)
1328
1329 # 'codec' interface
1330 def encode(self, decoded):
1331 res = bytearray()
1332 for i in decoded:
1333 obj = self.members_by_name(i[0])
1334 res.append(obj.to_tlv())
1335 return res
1336
1337class DataObjectChoice(DataObjectCollection):
1338 """One Data Object from within a choice, identified by its tag.
1339 This means that exactly one member of the choice must occur, and which one occurs depends
1340 on the tag."""
1341 def __add__(self, other):
1342 """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
1343 raise TypeError
1344
1345 def __or__(self, other):
1346 """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
1347 if isinstance(other, DataObjectChoice):
1348 # adding one collection to another
1349 members = self.members + other.members
1350 return DataObjectChoice(self.name, self.desc, members)
1351 elif isinstance(other, DataObject):
1352 # adding a member to a collection
1353 return DataObjectChoice(self.name, self.desc, self.members + [other])
1354 else:
1355 raise TypeError
1356
1357 # 'codec' interface
1358 def decode(self, binary:bytes):
1359 """Decode a single DOs from the choice based on the tag.
1360 Args:
1361 binary : binary bytes of encoded data
1362 Returns:
1363 tuple of (decoded_result, binary_remainder)
1364 """
1365 tag = binary[0]
1366 if tag == 0xff:
1367 return (None, binary)
1368 if not tag in self.members_by_tag:
1369 raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
1370 (self, tag, binary, self.members_by_tag.keys()))
1371 obj = self.members_by_tag[tag]
1372 remainder = obj.from_tlv(binary)
1373 return (obj.to_dict(), remainder)
1374
1375 # 'codec' interface
1376 def encode(self, decoded):
1377 obj = self.members_by_name(decoded[0])
1378 return obj.to_tlv()
1379
1380class DataObjectSequence:
1381 """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
1382 ordered sequence of DOs or choices of DOs that have to appear as per the specification.
1383 By wrapping them into this formal DataObjectSequence, we can offer convenience methods
1384 for encoding or decoding an entire sequence."""
1385 def __init__(self, name, desc=None, sequence=None):
1386 self.sequence = sequence or []
1387 self.name = name
1388 self.desc = desc
1389
1390 def __str__(self):
1391 member_strs = [str(x) for x in self.sequence]
1392 return '%s(%s)' % (self.name, ','.join(member_strs))
1393
1394 def __repr__(self):
1395 member_strs = [repr(x) for x in self.sequence]
1396 return '%s(%s)' % (self.__class__, ','.join(member_strs))
1397
1398 def __add__(self, other):
1399 """Add (append) a DataObject or DataObjectChoice to the sequence."""
1400 if isinstance(other, 'DataObject'):
1401 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1402 elif isinstance(other, 'DataObjectChoice'):
1403 return DataObjectSequence(self.name, self.desc, self.sequence + [other])
1404 elif isinstance(other, 'DataObjectSequence'):
1405 return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence)
1406
1407 # 'codec' interface
1408 def decode(self, binary:bytes):
1409 """Decode a sequence by calling the decoder of each element in the sequence.
1410 Args:
1411 binary : binary bytes of encoded data
1412 Returns:
1413 tuple of (decoded_result, binary_remainder)
1414 """
1415 remainder = binary
1416 res = []
1417 for e in self.sequence:
1418 (r, remainder) = e.decode(remainder)
1419 if r:
1420 res.append(r)
1421 return (res, remainder)
1422
1423 # 'codec' interface
1424 def decode_multi(self, do:bytes):
1425 """Decode multiple occurrences of the sequence from the binary input data.
1426 Args:
1427 do : binary input data to be decoded
1428 Returns:
1429 list of results of the decoder of this sequences
1430 """
1431 remainder = do
1432 res = []
1433 while len(remainder):
1434 (r, remainder2) = self.decode(remainder)
1435 if r:
1436 res.append(r)
1437 if len(remainder2) < len(remainder):
1438 remainder = remainder2
1439 else:
1440 remainder = remainder2
1441 break
1442 return (res, remainder)
1443
1444 # 'codec' interface
1445 def encode(self, decoded):
1446 """Encode a sequence by calling the encoder of each element in the sequence."""
1447 encoded = bytearray()
1448 i = 0
1449 for e in self.sequence:
1450 encoded += e.encode(decoded[i])
1451 i += 1
1452 return encoded
Harald Welte90441432021-05-02 21:28:12 +02001453
1454class CardCommand:
1455 """A single card command / instruction."""
1456 def __init__(self, name, ins, cla_list=None, desc=None):
1457 self.name = name
1458 self.ins = ins
1459 self.cla_list = cla_list or []
1460 self.cla_list = [x.lower() for x in self.cla_list]
1461 self.desc = desc
1462
1463 def __str__(self):
1464 return self.name
1465
1466 def __repr__(self):
1467 return '%s(INS=%02x,CLA=%s)' % (self.name, self.ins, self.cla_list)
1468
1469 def match_cla(self, cla):
1470 """Does the given CLA match the CLA list of the command?."""
1471 if not isinstance(cla, str):
1472 cla = '%02u' % cla
1473 cla = cla.lower()
1474 for cla_match in self.cla_list:
1475 cla_masked = ""
1476 for i in range(0, 2):
1477 if cla_match[i] == 'x':
1478 cla_masked += 'x'
1479 else:
1480 cla_masked += cla[i]
1481 if cla_masked == cla_match:
1482 return True
1483 return False
1484
1485
1486class CardCommandSet:
1487 """A set of card instructions, typically specified within one spec."""
1488 def __init__(self, name, cmds=[]):
1489 self.name = name
1490 self.cmds = { c.ins : c for c in cmds }
1491
1492 def __str__(self):
1493 return self.name
1494
1495 def __getitem__(self, idx):
1496 return self.cmds[idx]
1497
1498 def __add__(self, other):
1499 if isinstance(other, CardCommand):
1500 if other.ins in self.cmds:
1501 raise ValueError('%s: INS 0x%02x already defined: %s' %
1502 (self, other.ins, self.cmds[other.ins]))
1503 self.cmds[other.ins] = other
1504 elif isinstance(other, CardCommandSet):
1505 for c in other.cmds.keys():
1506 self.cmds[c] = other.cmds[c]
1507 else:
1508 raise ValueError('%s: Unsupported type to add operator: %s' % (self, other))
1509
1510 def lookup(self, ins, cla=None):
1511 """look-up the command within the CommandSet."""
1512 ins = int(ins)
1513 if not ins in self.cmds:
1514 return None
1515 cmd = self.cmds[ins]
1516 if cla and not cmd.match_cla(cla):
1517 return None
1518 return cmd