blob: 170429bf1cc34aecbf682e4f7c113001963115b7 [file] [log] [blame]
Harald Welteb2edd142021-01-08 23:29:35 +01001# coding=utf-8
2"""Representation of the ISO7816-4 filesystem model.
3
4The File (and its derived classes) represent the structure / hierarchy
5of the ISO7816-4 smart card file system with the MF, DF, EF and ADF
6entries, further sub-divided into the EF sub-types Transparent, Linear Fixed, etc.
7
8The classes are intended to represent the *specification* of the filesystem,
9not the actual contents / runtime state of interacting with a given smart card.
Harald Welteb2edd142021-01-08 23:29:35 +010010"""
11
Harald Welte5a4fd522021-04-02 16:05:26 +020012# (C) 2021 by Harald Welte <laforge@osmocom.org>
13#
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
Harald Welteb2edd142021-01-08 23:29:35 +010027import code
Harald Welte4145d3c2021-04-08 20:34:13 +020028import tempfile
Harald Welteb2edd142021-01-08 23:29:35 +010029import json
30
31import cmd2
32from cmd2 import CommandSet, with_default_category, with_argparser
33import argparse
34
Harald Welte1e456572021-04-02 17:16:30 +020035from typing import cast, Optional, Iterable, List, Any, Dict, Tuple
Harald Welteee3501f2021-04-02 13:00:18 +020036
Harald Weltedaf2b392021-05-03 23:17:29 +020037from pySim.utils import sw_match, h2b, b2h, i2h, is_hex, auto_int, bertlv_parse_one, Hexstr
Harald Welte07c7b1f2021-05-28 22:01:29 +020038from pySim.construct import filter_dict, parse_construct
Harald Welteb2edd142021-01-08 23:29:35 +010039from pySim.exceptions import *
Harald Welte0d4e98a2021-04-07 00:14:40 +020040from pySim.jsonpath import js_path_find, js_path_modify
Harald Welteb2edd142021-01-08 23:29:35 +010041
42class CardFile(object):
43 """Base class for all objects in the smart card filesystem.
44 Serve as a common ancestor to all other file types; rarely used directly.
45 """
46 RESERVED_NAMES = ['..', '.', '/', 'MF']
47 RESERVED_FIDS = ['3f00']
48
Harald Welteee3501f2021-04-02 13:00:18 +020049 def __init__(self, fid:str=None, sfid:str=None, name:str=None, desc:str=None,
50 parent:Optional['CardDF']=None):
51 """
52 Args:
53 fid : File Identifier (4 hex digits)
54 sfid : Short File Identifier (2 hex digits, optional)
55 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +020056 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +020057 parent : Parent CardFile object within filesystem hierarchy
58 """
Harald Welteb2edd142021-01-08 23:29:35 +010059 if not isinstance(self, CardADF) and fid == None:
60 raise ValueError("fid is mandatory")
61 if fid:
62 fid = fid.lower()
63 self.fid = fid # file identifier
64 self.sfid = sfid # short file identifier
65 self.name = name # human readable name
66 self.desc = desc # human readable description
67 self.parent = parent
68 if self.parent and self.parent != self and self.fid:
69 self.parent.add_file(self)
Vadim Yanitskiya0040792021-04-09 22:44:44 +020070 self.shell_commands = [] # type: List[CommandSet]
Harald Welteb2edd142021-01-08 23:29:35 +010071
Philipp Maier66061582021-03-09 21:57:57 +010072 # Note: the basic properties (fid, name, ect.) are verified when
73 # the file is attached to a parent file. See method add_file() in
74 # class Card DF
75
Harald Welteb2edd142021-01-08 23:29:35 +010076 def __str__(self):
77 if self.name:
78 return self.name
79 else:
80 return self.fid
81
Harald Welteee3501f2021-04-02 13:00:18 +020082 def _path_element(self, prefer_name:bool) -> Optional[str]:
Harald Welteb2edd142021-01-08 23:29:35 +010083 if prefer_name and self.name:
84 return self.name
85 else:
86 return self.fid
87
Harald Welte1e456572021-04-02 17:16:30 +020088 def fully_qualified_path(self, prefer_name:bool=True) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +020089 """Return fully qualified path to file as list of FID or name strings.
90
91 Args:
92 prefer_name : Preferably build path of names; fall-back to FIDs as required
93 """
Harald Welte1e456572021-04-02 17:16:30 +020094 if self.parent and self.parent != self:
Harald Welteb2edd142021-01-08 23:29:35 +010095 ret = self.parent.fully_qualified_path(prefer_name)
96 else:
97 ret = []
Harald Welte1e456572021-04-02 17:16:30 +020098 elem = self._path_element(prefer_name)
99 if elem:
100 ret.append(elem)
Harald Welteb2edd142021-01-08 23:29:35 +0100101 return ret
102
Harald Welteee3501f2021-04-02 13:00:18 +0200103 def get_mf(self) -> Optional['CardMF']:
Harald Welteb2edd142021-01-08 23:29:35 +0100104 """Return the MF (root) of the file system."""
105 if self.parent == None:
106 return None
107 # iterate towards the top. MF has parent == self
108 node = self
Harald Welte1e456572021-04-02 17:16:30 +0200109 while node.parent and node.parent != node:
Harald Welteb2edd142021-01-08 23:29:35 +0100110 node = node.parent
Harald Welte1e456572021-04-02 17:16:30 +0200111 return cast(CardMF, node)
Harald Welteb2edd142021-01-08 23:29:35 +0100112
Harald Welte1e456572021-04-02 17:16:30 +0200113 def _get_self_selectables(self, alias:str=None, flags = []) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200114 """Return a dict of {'identifier': self} tuples.
115
116 Args:
117 alias : Add an alias with given name to 'self'
118 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
119 If not specified, all selectables will be returned.
120 Returns:
121 dict containing reference to 'self' for all identifiers.
122 """
Harald Welteb2edd142021-01-08 23:29:35 +0100123 sels = {}
124 if alias:
125 sels.update({alias: self})
Philipp Maier786f7812021-02-25 16:48:10 +0100126 if self.fid and (flags == [] or 'FIDS' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100127 sels.update({self.fid: self})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100128 if self.name and (flags == [] or 'FNAMES' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100129 sels.update({self.name: self})
130 return sels
131
Harald Welte1e456572021-04-02 17:16:30 +0200132 def get_selectables(self, flags = []) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200133 """Return a dict of {'identifier': File} that is selectable from the current file.
134
135 Args:
136 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
137 If not specified, all selectables will be returned.
138 Returns:
139 dict containing all selectable items. Key is identifier (string), value
140 a reference to a CardFile (or derived class) instance.
141 """
Philipp Maier786f7812021-02-25 16:48:10 +0100142 sels = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100143 # we can always select ourself
Philipp Maier786f7812021-02-25 16:48:10 +0100144 if flags == [] or 'SELF' in flags:
145 sels = self._get_self_selectables('.', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100146 # we can always select our parent
Philipp Maier786f7812021-02-25 16:48:10 +0100147 if flags == [] or 'PARENT' in flags:
Harald Welte1e456572021-04-02 17:16:30 +0200148 if self.parent:
149 sels = self.parent._get_self_selectables('..', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100150 # if we have a MF, we can always select its applications
Philipp Maier786f7812021-02-25 16:48:10 +0100151 if flags == [] or 'MF' in flags:
152 mf = self.get_mf()
153 if mf:
154 sels.update(mf._get_self_selectables(flags = flags))
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100155 sels.update(mf.get_app_selectables(flags = flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100156 return sels
157
Harald Welte1e456572021-04-02 17:16:30 +0200158 def get_selectable_names(self, flags = []) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +0200159 """Return a dict of {'identifier': File} that is selectable from the current file.
160
161 Args:
162 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
163 If not specified, all selectables will be returned.
164 Returns:
Harald Welte1e456572021-04-02 17:16:30 +0200165 list containing all selectable names.
Harald Welteee3501f2021-04-02 13:00:18 +0200166 """
Philipp Maier786f7812021-02-25 16:48:10 +0100167 sels = self.get_selectables(flags)
Harald Welte1e456572021-04-02 17:16:30 +0200168 return list(sels.keys())
Harald Welteb2edd142021-01-08 23:29:35 +0100169
Harald Welteee3501f2021-04-02 13:00:18 +0200170 def decode_select_response(self, data_hex:str):
Harald Welteb2edd142021-01-08 23:29:35 +0100171 """Decode the response to a SELECT command."""
Harald Welte1e456572021-04-02 17:16:30 +0200172 if self.parent:
173 return self.parent.decode_select_response(data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +0100174
175
176class CardDF(CardFile):
177 """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
Philipp Maier63f572d2021-03-09 22:42:47 +0100178
179 @with_default_category('DF/ADF Commands')
180 class ShellCommands(CommandSet):
181 def __init__(self):
182 super().__init__()
183
Harald Welteb2edd142021-01-08 23:29:35 +0100184 def __init__(self, **kwargs):
185 if not isinstance(self, CardADF):
186 if not 'fid' in kwargs:
187 raise TypeError('fid is mandatory for all DF')
188 super().__init__(**kwargs)
189 self.children = dict()
Philipp Maier63f572d2021-03-09 22:42:47 +0100190 self.shell_commands = [self.ShellCommands()]
Harald Welteb2edd142021-01-08 23:29:35 +0100191
192 def __str__(self):
193 return "DF(%s)" % (super().__str__())
194
Harald Welteee3501f2021-04-02 13:00:18 +0200195 def add_file(self, child:CardFile, ignore_existing:bool=False):
196 """Add a child (DF/EF) to this DF.
197 Args:
198 child: The new DF/EF to be added
199 ignore_existing: Ignore, if file with given FID already exists. Old one will be kept.
200 """
Harald Welteb2edd142021-01-08 23:29:35 +0100201 if not isinstance(child, CardFile):
202 raise TypeError("Expected a File instance")
Philipp Maier3aec8712021-03-09 21:49:01 +0100203 if not is_hex(child.fid, minlen = 4, maxlen = 4):
204 raise ValueError("File name %s is not a valid fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100205 if child.name in CardFile.RESERVED_NAMES:
206 raise ValueError("File name %s is a reserved name" % (child.name))
207 if child.fid in CardFile.RESERVED_FIDS:
Philipp Maiere8bc1b42021-03-09 20:33:41 +0100208 raise ValueError("File fid %s is a reserved fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100209 if child.fid in self.children:
210 if ignore_existing:
211 return
Harald Welte977035c2021-04-21 11:01:26 +0200212 raise ValueError("File with given fid %s already exists in %s" % (child.fid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100213 if self.lookup_file_by_sfid(child.sfid):
Harald Welte977035c2021-04-21 11:01:26 +0200214 raise ValueError("File with given sfid %s already exists in %s" % (child.sfid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100215 if self.lookup_file_by_name(child.name):
216 if ignore_existing:
217 return
Harald Welte977035c2021-04-21 11:01:26 +0200218 raise ValueError("File with given name %s already exists in %s" % (child.name, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100219 self.children[child.fid] = child
220 child.parent = self
221
Harald Welteee3501f2021-04-02 13:00:18 +0200222 def add_files(self, children:Iterable[CardFile], ignore_existing:bool=False):
223 """Add a list of child (DF/EF) to this DF
224
225 Args:
226 children: List of new DF/EFs to be added
227 ignore_existing: Ignore, if file[s] with given FID already exists. Old one[s] will be kept.
228 """
Harald Welteb2edd142021-01-08 23:29:35 +0100229 for child in children:
230 self.add_file(child, ignore_existing)
231
Harald Welteee3501f2021-04-02 13:00:18 +0200232 def get_selectables(self, flags = []) -> dict:
233 """Return a dict of {'identifier': File} that is selectable from the current DF.
234
235 Args:
236 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
237 If not specified, all selectables will be returned.
238 Returns:
239 dict containing all selectable items. Key is identifier (string), value
240 a reference to a CardFile (or derived class) instance.
241 """
Harald Welteb2edd142021-01-08 23:29:35 +0100242 # global selectables + our children
Philipp Maier786f7812021-02-25 16:48:10 +0100243 sels = super().get_selectables(flags)
244 if flags == [] or 'FIDS' in flags:
245 sels.update({x.fid: x for x in self.children.values() if x.fid})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100246 if flags == [] or 'FNAMES' in flags:
Philipp Maier786f7812021-02-25 16:48:10 +0100247 sels.update({x.name: x for x in self.children.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100248 return sels
249
Harald Welte1e456572021-04-02 17:16:30 +0200250 def lookup_file_by_name(self, name:Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200251 """Find a file with given name within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100252 if name == None:
253 return None
254 for i in self.children.values():
255 if i.name and i.name == name:
256 return i
257 return None
258
Harald Welte1e456572021-04-02 17:16:30 +0200259 def lookup_file_by_sfid(self, sfid:Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200260 """Find a file with given short file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100261 if sfid == None:
262 return None
263 for i in self.children.values():
Harald Welte1e456572021-04-02 17:16:30 +0200264 if i.sfid == int(str(sfid)):
Harald Welteb2edd142021-01-08 23:29:35 +0100265 return i
266 return None
267
Harald Welteee3501f2021-04-02 13:00:18 +0200268 def lookup_file_by_fid(self, fid:str) -> Optional[CardFile]:
269 """Find a file with given file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100270 if fid in self.children:
271 return self.children[fid]
272 return None
273
274
275class CardMF(CardDF):
276 """MF (Master File) in the smart card filesystem"""
277 def __init__(self, **kwargs):
278 # can be overridden; use setdefault
279 kwargs.setdefault('fid', '3f00')
280 kwargs.setdefault('name', 'MF')
281 kwargs.setdefault('desc', 'Master File (directory root)')
282 # cannot be overridden; use assignment
283 kwargs['parent'] = self
284 super().__init__(**kwargs)
285 self.applications = dict()
286
287 def __str__(self):
288 return "MF(%s)" % (self.fid)
289
Harald Welte5ce35242021-04-02 20:27:05 +0200290 def add_application_df(self, app:'CardADF'):
291 """Add an Application to the MF"""
Harald Welteb2edd142021-01-08 23:29:35 +0100292 if not isinstance(app, CardADF):
293 raise TypeError("Expected an ADF instance")
294 if app.aid in self.applications:
295 raise ValueError("AID %s already exists" % (app.aid))
296 self.applications[app.aid] = app
297 app.parent=self
298
299 def get_app_names(self):
300 """Get list of completions (AID names)"""
301 return [x.name for x in self.applications]
302
Harald Welteee3501f2021-04-02 13:00:18 +0200303 def get_selectables(self, flags = []) -> dict:
304 """Return a dict of {'identifier': File} that is selectable from the current DF.
305
306 Args:
307 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
308 If not specified, all selectables will be returned.
309 Returns:
310 dict containing all selectable items. Key is identifier (string), value
311 a reference to a CardFile (or derived class) instance.
312 """
Philipp Maier786f7812021-02-25 16:48:10 +0100313 sels = super().get_selectables(flags)
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100314 sels.update(self.get_app_selectables(flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100315 return sels
316
Harald Welteee3501f2021-04-02 13:00:18 +0200317 def get_app_selectables(self, flags = []) -> dict:
Philipp Maier786f7812021-02-25 16:48:10 +0100318 """Get applications by AID + name"""
319 sels = {}
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100320 if flags == [] or 'AIDS' in flags:
Philipp Maier786f7812021-02-25 16:48:10 +0100321 sels.update({x.aid: x for x in self.applications.values()})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100322 if flags == [] or 'ANAMES' in flags:
Philipp Maier786f7812021-02-25 16:48:10 +0100323 sels.update({x.name: x for x in self.applications.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100324 return sels
325
Harald Welteee3501f2021-04-02 13:00:18 +0200326 def decode_select_response(self, data_hex:str) -> Any:
327 """Decode the response to a SELECT command.
328
329 This is the fall-back method which doesn't perform any decoding. It mostly
330 exists so specific derived classes can overload it for actual decoding.
331 """
Harald Welteb2edd142021-01-08 23:29:35 +0100332 return data_hex
333
334
335
336class CardADF(CardDF):
337 """ADF (Application Dedicated File) in the smart card filesystem"""
Harald Welteee3501f2021-04-02 13:00:18 +0200338 def __init__(self, aid:str, **kwargs):
Harald Welteb2edd142021-01-08 23:29:35 +0100339 super().__init__(**kwargs)
Harald Welte5ce35242021-04-02 20:27:05 +0200340 # reference to CardApplication may be set from CardApplication constructor
Harald Weltefe8a7442021-04-10 11:51:54 +0200341 self.application = None # type: Optional[CardApplication]
Harald Welteb2edd142021-01-08 23:29:35 +0100342 self.aid = aid # Application Identifier
Harald Welte1e456572021-04-02 17:16:30 +0200343 mf = self.get_mf()
344 if mf:
Harald Welte5ce35242021-04-02 20:27:05 +0200345 mf.add_application_df(self)
Harald Welteb2edd142021-01-08 23:29:35 +0100346
347 def __str__(self):
348 return "ADF(%s)" % (self.aid)
349
Harald Welteee3501f2021-04-02 13:00:18 +0200350 def _path_element(self, prefer_name:bool):
Harald Welteb2edd142021-01-08 23:29:35 +0100351 if self.name and prefer_name:
352 return self.name
353 else:
354 return self.aid
355
356
357class CardEF(CardFile):
358 """EF (Entry File) in the smart card filesystem"""
359 def __init__(self, *, fid, **kwargs):
360 kwargs['fid'] = fid
361 super().__init__(**kwargs)
362
363 def __str__(self):
364 return "EF(%s)" % (super().__str__())
365
Harald Welteee3501f2021-04-02 13:00:18 +0200366 def get_selectables(self, flags = []) -> dict:
367 """Return a dict of {'identifier': File} that is selectable from the current DF.
368
369 Args:
370 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
371 If not specified, all selectables will be returned.
372 Returns:
373 dict containing all selectable items. Key is identifier (string), value
374 a reference to a CardFile (or derived class) instance.
375 """
Harald Welteb2edd142021-01-08 23:29:35 +0100376 #global selectable names + those of the parent DF
Philipp Maier786f7812021-02-25 16:48:10 +0100377 sels = super().get_selectables(flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100378 sels.update({x.name:x for x in self.parent.children.values() if x != self})
379 return sels
380
381
382class TransparentEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200383 """Transparent EF (Entry File) in the smart card filesystem.
384
385 A Transparent EF is a binary file with no formal structure. This is contrary to
386 Record based EFs which have [fixed size] records that can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100387
388 @with_default_category('Transparent EF Commands')
389 class ShellCommands(CommandSet):
Harald Weltec9cdce32021-04-11 10:28:28 +0200390 """Shell commands specific for transparent EFs."""
Harald Welteb2edd142021-01-08 23:29:35 +0100391 def __init__(self):
392 super().__init__()
393
394 read_bin_parser = argparse.ArgumentParser()
395 read_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
396 read_bin_parser.add_argument('--length', type=int, help='Number of bytes to read')
397 @cmd2.with_argparser(read_bin_parser)
398 def do_read_binary(self, opts):
399 """Read binary data from a transparent EF"""
400 (data, sw) = self._cmd.rs.read_binary(opts.length, opts.offset)
401 self._cmd.poutput(data)
402
Harald Weltebcad86c2021-04-06 20:08:39 +0200403 read_bin_dec_parser = argparse.ArgumentParser()
404 read_bin_dec_parser.add_argument('--oneline', action='store_true',
405 help='No JSON pretty-printing, dump as a single line')
406 @cmd2.with_argparser(read_bin_dec_parser)
Harald Welteb2edd142021-01-08 23:29:35 +0100407 def do_read_binary_decoded(self, opts):
408 """Read + decode data from a transparent EF"""
409 (data, sw) = self._cmd.rs.read_binary_dec()
Harald Welte1748b932021-04-06 21:12:25 +0200410 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100411
412 upd_bin_parser = argparse.ArgumentParser()
413 upd_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
414 upd_bin_parser.add_argument('data', help='Data bytes (hex format) to write')
415 @cmd2.with_argparser(upd_bin_parser)
416 def do_update_binary(self, opts):
417 """Update (Write) data of a transparent EF"""
418 (data, sw) = self._cmd.rs.update_binary(opts.data, opts.offset)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100419 if data:
420 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100421
422 upd_bin_dec_parser = argparse.ArgumentParser()
423 upd_bin_dec_parser.add_argument('data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200424 upd_bin_dec_parser.add_argument('--json-path', type=str,
425 help='JSON path to modify specific element of file only')
Harald Welteb2edd142021-01-08 23:29:35 +0100426 @cmd2.with_argparser(upd_bin_dec_parser)
427 def do_update_binary_decoded(self, opts):
428 """Encode + Update (Write) data of a transparent EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200429 if opts.json_path:
430 (data_json, sw) = self._cmd.rs.read_binary_dec()
431 js_path_modify(data_json, opts.json_path, json.loads(opts.data))
432 else:
433 data_json = json.loads(opts.data)
Harald Welteb2edd142021-01-08 23:29:35 +0100434 (data, sw) = self._cmd.rs.update_binary_dec(data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100435 if data:
Harald Welte1748b932021-04-06 21:12:25 +0200436 self._cmd.poutput_json(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100437
Harald Welte4145d3c2021-04-08 20:34:13 +0200438 def do_edit_binary_decoded(self, opts):
439 """Edit the JSON representation of the EF contents in an editor."""
440 (orig_json, sw) = self._cmd.rs.read_binary_dec()
441 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
442 filename = '%s/file' % dirname
443 # write existing data as JSON to file
444 with open(filename, 'w') as text_file:
445 json.dump(orig_json, text_file, indent=4)
446 # run a text editor
447 self._cmd._run_editor(filename)
448 with open(filename, 'r') as text_file:
449 edited_json = json.load(text_file)
450 if edited_json == orig_json:
451 self._cmd.poutput("Data not modified, skipping write")
452 else:
453 (data, sw) = self._cmd.rs.update_binary_dec(edited_json)
454 if data:
455 self._cmd.poutput_json(data)
456
457
Harald Welteee3501f2021-04-02 13:00:18 +0200458 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
459 size={1,None}):
460 """
461 Args:
462 fid : File Identifier (4 hex digits)
463 sfid : Short File Identifier (2 hex digits, optional)
464 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200465 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200466 parent : Parent CardFile object within filesystem hierarchy
467 size : tuple of (minimum_size, recommended_size)
468 """
Harald Welteb2edd142021-01-08 23:29:35 +0100469 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200470 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200471 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100472 self.size = size
473 self.shell_commands = [self.ShellCommands()]
474
Harald Welteee3501f2021-04-02 13:00:18 +0200475 def decode_bin(self, raw_bin_data:bytearray) -> dict:
476 """Decode raw (binary) data into abstract representation.
477
478 A derived class would typically provide a _decode_bin() or _decode_hex() method
479 for implementing this specifically for the given file. This function checks which
480 of the method exists, add calls them (with conversion, as needed).
481
482 Args:
483 raw_bin_data : binary encoded data
484 Returns:
485 abstract_data; dict representing the decoded data
486 """
Harald Welteb2edd142021-01-08 23:29:35 +0100487 method = getattr(self, '_decode_bin', None)
488 if callable(method):
489 return method(raw_bin_data)
490 method = getattr(self, '_decode_hex', None)
491 if callable(method):
492 return method(b2h(raw_bin_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200493 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200494 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200495 elif self._tlv:
496 self._tlv.from_tlv(raw_bin_data)
497 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100498 return {'raw': raw_bin_data.hex()}
499
Harald Welteee3501f2021-04-02 13:00:18 +0200500 def decode_hex(self, raw_hex_data:str) -> dict:
501 """Decode raw (hex string) data into abstract representation.
502
503 A derived class would typically provide a _decode_bin() or _decode_hex() method
504 for implementing this specifically for the given file. This function checks which
505 of the method exists, add calls them (with conversion, as needed).
506
507 Args:
508 raw_hex_data : hex-encoded data
509 Returns:
510 abstract_data; dict representing the decoded data
511 """
Harald Welteb2edd142021-01-08 23:29:35 +0100512 method = getattr(self, '_decode_hex', None)
513 if callable(method):
514 return method(raw_hex_data)
515 raw_bin_data = h2b(raw_hex_data)
516 method = getattr(self, '_decode_bin', None)
517 if callable(method):
518 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200519 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200520 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200521 elif self._tlv:
522 self._tlv.from_tlv(raw_bin_data)
523 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100524 return {'raw': raw_bin_data.hex()}
525
Harald Welteee3501f2021-04-02 13:00:18 +0200526 def encode_bin(self, abstract_data:dict) -> bytearray:
527 """Encode abstract representation into raw (binary) data.
528
529 A derived class would typically provide an _encode_bin() or _encode_hex() method
530 for implementing this specifically for the given file. This function checks which
531 of the method exists, add calls them (with conversion, as needed).
532
533 Args:
534 abstract_data : dict representing the decoded data
535 Returns:
536 binary encoded data
537 """
Harald Welteb2edd142021-01-08 23:29:35 +0100538 method = getattr(self, '_encode_bin', None)
539 if callable(method):
540 return method(abstract_data)
541 method = getattr(self, '_encode_hex', None)
542 if callable(method):
543 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200544 if self._construct:
545 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200546 elif self._tlv:
547 self._tlv.from_dict(abstract_data)
548 return self._tlv.to_tlv()
Harald Welteb2edd142021-01-08 23:29:35 +0100549 raise NotImplementedError
550
Harald Welteee3501f2021-04-02 13:00:18 +0200551 def encode_hex(self, abstract_data:dict) -> str:
552 """Encode abstract representation into raw (hex string) data.
553
554 A derived class would typically provide an _encode_bin() or _encode_hex() method
555 for implementing this specifically for the given file. This function checks which
556 of the method exists, add calls them (with conversion, as needed).
557
558 Args:
559 abstract_data : dict representing the decoded data
560 Returns:
561 hex string encoded data
562 """
Harald Welteb2edd142021-01-08 23:29:35 +0100563 method = getattr(self, '_encode_hex', None)
564 if callable(method):
565 return method(abstract_data)
566 method = getattr(self, '_encode_bin', None)
567 if callable(method):
568 raw_bin_data = method(abstract_data)
569 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200570 if self._construct:
571 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200572 elif self._tlv:
573 self._tlv.from_dict(abstract_data)
574 return b2h(self._tlv.to_tlv())
Harald Welteb2edd142021-01-08 23:29:35 +0100575 raise NotImplementedError
576
577
578class LinFixedEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200579 """Linear Fixed EF (Entry File) in the smart card filesystem.
580
581 Linear Fixed EFs are record oriented files. They consist of a number of fixed-size
582 records. The records can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100583
584 @with_default_category('Linear Fixed EF Commands')
585 class ShellCommands(CommandSet):
Harald Welteee3501f2021-04-02 13:00:18 +0200586 """Shell commands specific for Linear Fixed EFs."""
Harald Welteb2edd142021-01-08 23:29:35 +0100587 def __init__(self):
588 super().__init__()
589
590 read_rec_parser = argparse.ArgumentParser()
591 read_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Philipp Maier41555732021-02-25 16:52:08 +0100592 read_rec_parser.add_argument('--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
Harald Welteb2edd142021-01-08 23:29:35 +0100593 @cmd2.with_argparser(read_rec_parser)
594 def do_read_record(self, opts):
Philipp Maier41555732021-02-25 16:52:08 +0100595 """Read one or multiple records from a record-oriented EF"""
596 for r in range(opts.count):
597 recnr = opts.record_nr + r
598 (data, sw) = self._cmd.rs.read_record(recnr)
599 if (len(data) > 0):
600 recstr = str(data)
601 else:
602 recstr = "(empty)"
603 self._cmd.poutput("%03d %s" % (recnr, recstr))
Harald Welteb2edd142021-01-08 23:29:35 +0100604
605 read_rec_dec_parser = argparse.ArgumentParser()
606 read_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Harald Weltebcad86c2021-04-06 20:08:39 +0200607 read_rec_dec_parser.add_argument('--oneline', action='store_true',
608 help='No JSON pretty-printing, dump as a single line')
Harald Welteb2edd142021-01-08 23:29:35 +0100609 @cmd2.with_argparser(read_rec_dec_parser)
610 def do_read_record_decoded(self, opts):
611 """Read + decode a record from a record-oriented EF"""
612 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Harald Welte1748b932021-04-06 21:12:25 +0200613 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100614
Harald Welte850b72a2021-04-07 09:33:03 +0200615 read_recs_parser = argparse.ArgumentParser()
616 @cmd2.with_argparser(read_recs_parser)
617 def do_read_records(self, opts):
618 """Read all records from a record-oriented EF"""
619 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
620 for recnr in range(1, 1 + num_of_rec):
621 (data, sw) = self._cmd.rs.read_record(recnr)
622 if (len(data) > 0):
623 recstr = str(data)
624 else:
625 recstr = "(empty)"
626 self._cmd.poutput("%03d %s" % (recnr, recstr))
627
628 read_recs_dec_parser = argparse.ArgumentParser()
629 read_recs_dec_parser.add_argument('--oneline', action='store_true',
630 help='No JSON pretty-printing, dump as a single line')
631 @cmd2.with_argparser(read_recs_dec_parser)
632 def do_read_records_decoded(self, opts):
633 """Read + decode all records from a record-oriented EF"""
634 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
635 # collect all results in list so they are rendered as JSON list when printing
636 data_list = []
637 for recnr in range(1, 1 + num_of_rec):
638 (data, sw) = self._cmd.rs.read_record_dec(recnr)
639 data_list.append(data)
640 self._cmd.poutput_json(data_list, opts.oneline)
641
Harald Welteb2edd142021-01-08 23:29:35 +0100642 upd_rec_parser = argparse.ArgumentParser()
643 upd_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
644 upd_rec_parser.add_argument('data', help='Data bytes (hex format) to write')
645 @cmd2.with_argparser(upd_rec_parser)
646 def do_update_record(self, opts):
647 """Update (write) data to a record-oriented EF"""
648 (data, sw) = self._cmd.rs.update_record(opts.record_nr, opts.data)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100649 if data:
650 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100651
652 upd_rec_dec_parser = argparse.ArgumentParser()
653 upd_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Philipp Maierfe1fb032021-04-20 22:33:12 +0200654 upd_rec_dec_parser.add_argument('data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200655 upd_rec_dec_parser.add_argument('--json-path', type=str,
656 help='JSON path to modify specific element of record only')
Harald Welteb2edd142021-01-08 23:29:35 +0100657 @cmd2.with_argparser(upd_rec_dec_parser)
658 def do_update_record_decoded(self, opts):
659 """Encode + Update (write) data to a record-oriented EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200660 if opts.json_path:
661 (data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
662 js_path_modify(data_json, opts.json_path, json.loads(opts.data))
663 else:
664 data_json = json.loads(opts.data)
665 (data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100666 if data:
667 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100668
Harald Welte4145d3c2021-04-08 20:34:13 +0200669 edit_rec_dec_parser = argparse.ArgumentParser()
670 edit_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be edited')
671 @cmd2.with_argparser(edit_rec_dec_parser)
672 def do_edit_record_decoded(self, opts):
673 """Edit the JSON representation of one record in an editor."""
674 (orig_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Vadim Yanitskiy895fa6f2021-05-02 02:36:44 +0200675 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
Harald Welte4145d3c2021-04-08 20:34:13 +0200676 filename = '%s/file' % dirname
677 # write existing data as JSON to file
678 with open(filename, 'w') as text_file:
679 json.dump(orig_json, text_file, indent=4)
680 # run a text editor
681 self._cmd._run_editor(filename)
682 with open(filename, 'r') as text_file:
683 edited_json = json.load(text_file)
684 if edited_json == orig_json:
685 self._cmd.poutput("Data not modified, skipping write")
686 else:
687 (data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, edited_json)
688 if data:
689 self._cmd.poutput_json(data)
Harald Welte4145d3c2021-04-08 20:34:13 +0200690
691
Harald Welteee3501f2021-04-02 13:00:18 +0200692 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None,
693 parent:Optional[CardDF]=None, rec_len={1,None}):
694 """
695 Args:
696 fid : File Identifier (4 hex digits)
697 sfid : Short File Identifier (2 hex digits, optional)
698 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200699 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200700 parent : Parent CardFile object within filesystem hierarchy
Philipp Maier0adabf62021-04-20 22:36:41 +0200701 rec_len : set of {minimum_length, recommended_length}
Harald Welteee3501f2021-04-02 13:00:18 +0200702 """
Harald Welteb2edd142021-01-08 23:29:35 +0100703 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
704 self.rec_len = rec_len
705 self.shell_commands = [self.ShellCommands()]
Harald Welte2db5cfb2021-04-10 19:05:37 +0200706 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200707 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100708
Harald Welteee3501f2021-04-02 13:00:18 +0200709 def decode_record_hex(self, raw_hex_data:str) -> dict:
710 """Decode raw (hex string) data into abstract representation.
711
712 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
713 method for implementing this specifically for the given file. This function checks which
714 of the method exists, add calls them (with conversion, as needed).
715
716 Args:
717 raw_hex_data : hex-encoded data
718 Returns:
719 abstract_data; dict representing the decoded data
720 """
Harald Welteb2edd142021-01-08 23:29:35 +0100721 method = getattr(self, '_decode_record_hex', None)
722 if callable(method):
723 return method(raw_hex_data)
724 raw_bin_data = h2b(raw_hex_data)
725 method = getattr(self, '_decode_record_bin', None)
726 if callable(method):
727 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200728 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200729 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200730 elif self._tlv:
731 self._tlv.from_tlv(raw_bin_data)
732 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100733 return {'raw': raw_bin_data.hex()}
734
Harald Welteee3501f2021-04-02 13:00:18 +0200735 def decode_record_bin(self, raw_bin_data:bytearray) -> dict:
736 """Decode raw (binary) data into abstract representation.
737
738 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
739 method for implementing this specifically for the given file. This function checks which
740 of the method exists, add calls them (with conversion, as needed).
741
742 Args:
743 raw_bin_data : binary encoded data
744 Returns:
745 abstract_data; dict representing the decoded data
746 """
Harald Welteb2edd142021-01-08 23:29:35 +0100747 method = getattr(self, '_decode_record_bin', None)
748 if callable(method):
749 return method(raw_bin_data)
750 raw_hex_data = b2h(raw_bin_data)
751 method = getattr(self, '_decode_record_hex', None)
752 if callable(method):
753 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200754 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200755 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200756 elif self._tlv:
757 self._tlv.from_tlv(raw_bin_data)
758 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100759 return {'raw': raw_hex_data}
760
Harald Welteee3501f2021-04-02 13:00:18 +0200761 def encode_record_hex(self, abstract_data:dict) -> str:
762 """Encode abstract representation into raw (hex string) data.
763
764 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
765 method for implementing this specifically for the given file. This function checks which
766 of the method exists, add calls them (with conversion, as needed).
767
768 Args:
769 abstract_data : dict representing the decoded data
770 Returns:
771 hex string encoded data
772 """
Harald Welteb2edd142021-01-08 23:29:35 +0100773 method = getattr(self, '_encode_record_hex', None)
774 if callable(method):
775 return method(abstract_data)
776 method = getattr(self, '_encode_record_bin', None)
777 if callable(method):
778 raw_bin_data = method(abstract_data)
Harald Welte1e456572021-04-02 17:16:30 +0200779 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200780 if self._construct:
781 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200782 elif self._tlv:
783 self._tlv.from_dict(abstract_data)
784 return b2h(self._tlv.to_tlv())
Harald Welteb2edd142021-01-08 23:29:35 +0100785 raise NotImplementedError
786
Harald Welteee3501f2021-04-02 13:00:18 +0200787 def encode_record_bin(self, abstract_data:dict) -> bytearray:
788 """Encode abstract representation into raw (binary) data.
789
790 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
791 method for implementing this specifically for the given file. This function checks which
792 of the method exists, add calls them (with conversion, as needed).
793
794 Args:
795 abstract_data : dict representing the decoded data
796 Returns:
797 binary encoded data
798 """
Harald Welteb2edd142021-01-08 23:29:35 +0100799 method = getattr(self, '_encode_record_bin', None)
800 if callable(method):
801 return method(abstract_data)
802 method = getattr(self, '_encode_record_hex', None)
803 if callable(method):
Harald Welteee3501f2021-04-02 13:00:18 +0200804 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200805 if self._construct:
806 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200807 elif self._tlv:
808 self._tlv.from_dict(abstract_data)
809 return self._tlv.to_tlv()
Harald Welteb2edd142021-01-08 23:29:35 +0100810 raise NotImplementedError
811
812class CyclicEF(LinFixedEF):
813 """Cyclic EF (Entry File) in the smart card filesystem"""
814 # we don't really have any special support for those; just recycling LinFixedEF here
Harald Welteee3501f2021-04-02 13:00:18 +0200815 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
816 rec_len={1,None}):
Harald Welteb2edd142021-01-08 23:29:35 +0100817 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len)
818
819class TransRecEF(TransparentEF):
820 """Transparent EF (Entry File) containing fixed-size records.
Harald Welteee3501f2021-04-02 13:00:18 +0200821
Harald Welteb2edd142021-01-08 23:29:35 +0100822 These are the real odd-balls and mostly look like mistakes in the specification:
823 Specified as 'transparent' EF, but actually containing several fixed-length records
824 inside.
825 We add a special class for those, so the user only has to provide encoder/decoder functions
826 for a record, while this class takes care of split / merge of records.
827 """
Harald Welte1e456572021-04-02 17:16:30 +0200828 def __init__(self, fid:str, rec_len:int, sfid:str=None, name:str=None, desc:str=None,
829 parent:Optional[CardDF]=None, size={1,None}):
Harald Welteee3501f2021-04-02 13:00:18 +0200830 """
831 Args:
832 fid : File Identifier (4 hex digits)
833 sfid : Short File Identifier (2 hex digits, optional)
Harald Weltec9cdce32021-04-11 10:28:28 +0200834 name : Brief name of the file, like EF_ICCID
835 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200836 parent : Parent CardFile object within filesystem hierarchy
837 rec_len : Length of the fixed-length records within transparent EF
838 size : tuple of (minimum_size, recommended_size)
839 """
Harald Welteb2edd142021-01-08 23:29:35 +0100840 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size)
841 self.rec_len = rec_len
842
Harald Welteee3501f2021-04-02 13:00:18 +0200843 def decode_record_hex(self, raw_hex_data:str) -> dict:
844 """Decode raw (hex string) data into abstract representation.
845
846 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
847 method for implementing this specifically for the given file. This function checks which
848 of the method exists, add calls them (with conversion, as needed).
849
850 Args:
851 raw_hex_data : hex-encoded data
852 Returns:
853 abstract_data; dict representing the decoded data
854 """
Harald Welteb2edd142021-01-08 23:29:35 +0100855 method = getattr(self, '_decode_record_hex', None)
856 if callable(method):
857 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200858 raw_bin_data = h2b(raw_hex_data)
Harald Welteb2edd142021-01-08 23:29:35 +0100859 method = getattr(self, '_decode_record_bin', None)
860 if callable(method):
Harald Welteb2edd142021-01-08 23:29:35 +0100861 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200862 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200863 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200864 elif self._tlv:
865 self._tlv.from_tlv(raw_bin_data)
866 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100867 return {'raw': raw_hex_data}
868
Harald Welteee3501f2021-04-02 13:00:18 +0200869 def decode_record_bin(self, raw_bin_data:bytearray) -> dict:
870 """Decode raw (binary) data into abstract representation.
871
872 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
873 method for implementing this specifically for the given file. This function checks which
874 of the method exists, add calls them (with conversion, as needed).
875
876 Args:
877 raw_bin_data : binary encoded data
878 Returns:
879 abstract_data; dict representing the decoded data
880 """
Harald Welteb2edd142021-01-08 23:29:35 +0100881 method = getattr(self, '_decode_record_bin', None)
882 if callable(method):
883 return method(raw_bin_data)
884 raw_hex_data = b2h(raw_bin_data)
885 method = getattr(self, '_decode_record_hex', None)
886 if callable(method):
887 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200888 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200889 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200890 elif self._tlv:
891 self._tlv.from_tlv(raw_bin_data)
892 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100893 return {'raw': raw_hex_data}
894
Harald Welteee3501f2021-04-02 13:00:18 +0200895 def encode_record_hex(self, abstract_data:dict) -> str:
896 """Encode abstract representation into raw (hex string) data.
897
898 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
899 method for implementing this specifically for the given file. This function checks which
900 of the method exists, add calls them (with conversion, as needed).
901
902 Args:
903 abstract_data : dict representing the decoded data
904 Returns:
905 hex string encoded data
906 """
Harald Welteb2edd142021-01-08 23:29:35 +0100907 method = getattr(self, '_encode_record_hex', None)
908 if callable(method):
909 return method(abstract_data)
910 method = getattr(self, '_encode_record_bin', None)
911 if callable(method):
Harald Welte1e456572021-04-02 17:16:30 +0200912 return b2h(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200913 if self._construct:
914 return b2h(filter_dict(self._construct.build(abstract_data)))
Harald Weltefb506212021-05-29 21:28:24 +0200915 elif self._tlv:
916 self._tlv.from_dict(abstract_data)
917 return b2h(self._tlv.to_tlv())
Harald Welteb2edd142021-01-08 23:29:35 +0100918 raise NotImplementedError
919
Harald Welteee3501f2021-04-02 13:00:18 +0200920 def encode_record_bin(self, abstract_data:dict) -> bytearray:
921 """Encode abstract representation into raw (binary) data.
922
923 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
924 method for implementing this specifically for the given file. This function checks which
925 of the method exists, add calls them (with conversion, as needed).
926
927 Args:
928 abstract_data : dict representing the decoded data
929 Returns:
930 binary encoded data
931 """
Harald Welteb2edd142021-01-08 23:29:35 +0100932 method = getattr(self, '_encode_record_bin', None)
933 if callable(method):
934 return method(abstract_data)
935 method = getattr(self, '_encode_record_hex', None)
936 if callable(method):
937 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200938 if self._construct:
939 return filter_dict(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200940 elif self._tlv:
941 self._tlv.from_dict(abstract_data)
942 return self._tlv.to_tlv()
Harald Welteb2edd142021-01-08 23:29:35 +0100943 raise NotImplementedError
944
Harald Welteee3501f2021-04-02 13:00:18 +0200945 def _decode_bin(self, raw_bin_data:bytearray):
Harald Welteb2edd142021-01-08 23:29:35 +0100946 chunks = [raw_bin_data[i:i+self.rec_len] for i in range(0, len(raw_bin_data), self.rec_len)]
947 return [self.decode_record_bin(x) for x in chunks]
948
Harald Welteee3501f2021-04-02 13:00:18 +0200949 def _encode_bin(self, abstract_data) -> bytes:
Harald Welteb2edd142021-01-08 23:29:35 +0100950 chunks = [self.encode_record_bin(x) for x in abstract_data]
951 # FIXME: pad to file size
952 return b''.join(chunks)
953
954
Harald Welte917d98c2021-04-21 11:51:25 +0200955class BerTlvEF(CardEF):
Harald Welte27881622021-04-21 11:16:31 +0200956 """BER-TLV EF (Entry File) in the smart card filesystem.
957 A BER-TLV EF is a binary file with a BER (Basic Encoding Rules) TLV structure
Harald Welteb2edd142021-01-08 23:29:35 +0100958
Harald Welte27881622021-04-21 11:16:31 +0200959 NOTE: We currently don't really support those, this class is simply a wrapper
960 around TransparentEF as a place-holder, so we can already define EFs of BER-TLV
961 type without fully supporting them."""
Harald Welteb2edd142021-01-08 23:29:35 +0100962
Harald Welte917d98c2021-04-21 11:51:25 +0200963 @with_default_category('BER-TLV EF Commands')
964 class ShellCommands(CommandSet):
965 """Shell commands specific for BER-TLV EFs."""
966 def __init__(self):
967 super().__init__()
968
969 retrieve_data_parser = argparse.ArgumentParser()
970 retrieve_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
971 @cmd2.with_argparser(retrieve_data_parser)
972 def do_retrieve_data(self, opts):
973 """Retrieve (Read) data from a BER-TLV EF"""
974 (data, sw) = self._cmd.rs.retrieve_data(opts.tag)
975 self._cmd.poutput(data)
976
977 def do_retrieve_tags(self, opts):
978 """List tags available in a given BER-TLV EF"""
979 tags = self._cmd.rs.retrieve_tags()
980 self._cmd.poutput(tags)
981
982 set_data_parser = argparse.ArgumentParser()
983 set_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set')
984 set_data_parser.add_argument('data', help='Data bytes (hex format) to write')
985 @cmd2.with_argparser(set_data_parser)
986 def do_set_data(self, opts):
987 """Set (Write) data for a given tag in a BER-TLV EF"""
988 (data, sw) = self._cmd.rs.set_data(opts.tag, opts.data)
989 if data:
990 self._cmd.poutput(data)
991
992 del_data_parser = argparse.ArgumentParser()
993 del_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set')
994 @cmd2.with_argparser(del_data_parser)
995 def do_delete_data(self, opts):
996 """Delete data for a given tag in a BER-TLV EF"""
997 (data, sw) = self._cmd.rs.set_data(opts.tag, None)
998 if data:
999 self._cmd.poutput(data)
1000
1001
1002 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
1003 size={1,None}):
1004 """
1005 Args:
1006 fid : File Identifier (4 hex digits)
1007 sfid : Short File Identifier (2 hex digits, optional)
1008 name : Brief name of the file, lik EF_ICCID
1009 desc : Description of the file
1010 parent : Parent CardFile object within filesystem hierarchy
1011 size : tuple of (minimum_size, recommended_size)
1012 """
1013 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
1014 self._construct = None
1015 self.size = size
1016 self.shell_commands = [self.ShellCommands()]
1017
Harald Welteb2edd142021-01-08 23:29:35 +01001018
1019class RuntimeState(object):
1020 """Represent the runtime state of a session with a card."""
Harald Welteee3501f2021-04-02 13:00:18 +02001021 def __init__(self, card, profile:'CardProfile'):
1022 """
1023 Args:
1024 card : pysim.cards.Card instance
1025 profile : CardProfile instance
1026 """
Harald Welteb2edd142021-01-08 23:29:35 +01001027 self.mf = CardMF()
1028 self.card = card
Harald Weltefe8a7442021-04-10 11:51:54 +02001029 self.selected_file = self.mf # type: CardDF
Harald Welteb2edd142021-01-08 23:29:35 +01001030 self.profile = profile
Harald Welte5ce35242021-04-02 20:27:05 +02001031 # add application ADFs + MF-files from profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001032 apps = self._match_applications()
1033 for a in apps:
Harald Welte5ce35242021-04-02 20:27:05 +02001034 if a.adf:
1035 self.mf.add_application_df(a.adf)
Harald Welteb2edd142021-01-08 23:29:35 +01001036 for f in self.profile.files_in_mf:
1037 self.mf.add_file(f)
Philipp Maier38c74f62021-03-17 17:19:52 +01001038 self.conserve_write = True
Harald Welteb2edd142021-01-08 23:29:35 +01001039
Philipp Maier1e896f32021-03-10 17:02:53 +01001040 def _match_applications(self):
1041 """match the applications from the profile with applications on the card"""
1042 apps_profile = self.profile.applications
1043 aids_card = self.card.read_aids()
1044 apps_taken = []
1045 if aids_card:
1046 aids_taken = []
1047 print("AIDs on card:")
1048 for a in aids_card:
1049 for f in apps_profile:
1050 if f.aid in a:
1051 print(" %s: %s" % (f.name, a))
1052 aids_taken.append(a)
1053 apps_taken.append(f)
1054 aids_unknown = set(aids_card) - set(aids_taken)
1055 for a in aids_unknown:
1056 print(" unknown: %s" % a)
1057 else:
1058 print("error: could not determine card applications")
1059 return apps_taken
1060
Harald Weltedaf2b392021-05-03 23:17:29 +02001061 def reset(self, cmd_app=None) -> Hexstr:
1062 """Perform physical card reset and obtain ATR.
1063 Args:
1064 cmd_app : Command Application State (for unregistering old file commands)
1065 """
1066 self.card._scc._tp.reset_card()
1067 atr = i2h(self.card._scc._tp.get_atr())
1068 # select MF to reset internal state and to verify card really works
1069 self.select('MF', cmd_app)
1070 return atr
1071
Harald Welteee3501f2021-04-02 13:00:18 +02001072 def get_cwd(self) -> CardDF:
1073 """Obtain the current working directory.
1074
1075 Returns:
1076 CardDF instance
1077 """
Harald Welteb2edd142021-01-08 23:29:35 +01001078 if isinstance(self.selected_file, CardDF):
1079 return self.selected_file
1080 else:
1081 return self.selected_file.parent
1082
Harald Welte5ce35242021-04-02 20:27:05 +02001083 def get_application_df(self) -> Optional[CardADF]:
1084 """Obtain the currently selected application DF (if any).
Harald Welteee3501f2021-04-02 13:00:18 +02001085
1086 Returns:
1087 CardADF() instance or None"""
Harald Welteb2edd142021-01-08 23:29:35 +01001088 # iterate upwards from selected file; check if any is an ADF
1089 node = self.selected_file
1090 while node.parent != node:
1091 if isinstance(node, CardADF):
1092 return node
1093 node = node.parent
1094 return None
1095
Harald Welteee3501f2021-04-02 13:00:18 +02001096 def interpret_sw(self, sw:str):
1097 """Interpret a given status word relative to the currently selected application
1098 or the underlying card profile.
1099
1100 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001101 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001102
1103 Returns:
1104 Tuple of two strings
1105 """
Harald Welte86fbd392021-04-02 22:13:09 +02001106 res = None
Harald Welte5ce35242021-04-02 20:27:05 +02001107 adf = self.get_application_df()
1108 if adf:
1109 app = adf.application
Harald Welteb2edd142021-01-08 23:29:35 +01001110 # The application either comes with its own interpret_sw
1111 # method or we will use the interpret_sw method from the
1112 # card profile.
Harald Welte5ce35242021-04-02 20:27:05 +02001113 if app and hasattr(app, "interpret_sw"):
Harald Welte86fbd392021-04-02 22:13:09 +02001114 res = app.interpret_sw(sw)
1115 return res or self.profile.interpret_sw(sw)
Harald Welteb2edd142021-01-08 23:29:35 +01001116
Harald Welteee3501f2021-04-02 13:00:18 +02001117 def probe_file(self, fid:str, cmd_app=None):
1118 """Blindly try to select a file and automatically add a matching file
1119 object if the file actually exists."""
Philipp Maier63f572d2021-03-09 22:42:47 +01001120 if not is_hex(fid, 4, 4):
1121 raise ValueError("Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
1122
1123 try:
1124 (data, sw) = self.card._scc.select_file(fid)
1125 except SwMatchError as swm:
1126 k = self.interpret_sw(swm.sw_actual)
1127 if not k:
1128 raise(swm)
1129 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
1130
1131 select_resp = self.selected_file.decode_select_response(data)
1132 if (select_resp['file_descriptor']['file_type'] == 'df'):
1133 f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(), desc="dedicated file, manually added at runtime")
1134 else:
1135 if (select_resp['file_descriptor']['structure'] == 'transparent'):
Harald Weltec9cdce32021-04-11 10:28:28 +02001136 f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001137 else:
Harald Weltec9cdce32021-04-11 10:28:28 +02001138 f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001139
1140 self.selected_file.add_files([f])
1141 self.selected_file = f
1142 return select_resp
1143
Harald Welteee3501f2021-04-02 13:00:18 +02001144 def select(self, name:str, cmd_app=None):
1145 """Select a file (EF, DF, ADF, MF, ...).
1146
1147 Args:
1148 name : Name of file to select
1149 cmd_app : Command Application State (for unregistering old file commands)
1150 """
Harald Welteb2edd142021-01-08 23:29:35 +01001151 sels = self.selected_file.get_selectables()
Philipp Maier7744b6e2021-03-11 14:29:37 +01001152 if is_hex(name):
1153 name = name.lower()
Philipp Maier63f572d2021-03-09 22:42:47 +01001154
1155 # unregister commands of old file
1156 if cmd_app and self.selected_file.shell_commands:
1157 for c in self.selected_file.shell_commands:
1158 cmd_app.unregister_command_set(c)
1159
Harald Welteb2edd142021-01-08 23:29:35 +01001160 if name in sels:
1161 f = sels[name]
Harald Welteb2edd142021-01-08 23:29:35 +01001162 try:
1163 if isinstance(f, CardADF):
Philipp Maiercba6dbc2021-03-11 13:03:18 +01001164 (data, sw) = self.card.select_adf_by_aid(f.aid)
Harald Welteb2edd142021-01-08 23:29:35 +01001165 else:
1166 (data, sw) = self.card._scc.select_file(f.fid)
1167 self.selected_file = f
1168 except SwMatchError as swm:
1169 k = self.interpret_sw(swm.sw_actual)
1170 if not k:
1171 raise(swm)
1172 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
Philipp Maier63f572d2021-03-09 22:42:47 +01001173 select_resp = f.decode_select_response(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001174 else:
Philipp Maier63f572d2021-03-09 22:42:47 +01001175 select_resp = self.probe_file(name, cmd_app)
Harald Welte850b72a2021-04-07 09:33:03 +02001176 # store the decoded FCP for later reference
1177 self.selected_file_fcp = select_resp
Philipp Maier63f572d2021-03-09 22:42:47 +01001178
1179 # register commands of new file
1180 if cmd_app and self.selected_file.shell_commands:
1181 for c in self.selected_file.shell_commands:
1182 cmd_app.register_command_set(c)
1183
1184 return select_resp
Harald Welteb2edd142021-01-08 23:29:35 +01001185
Harald Welte34b05d32021-05-25 22:03:13 +02001186 def status(self):
1187 """Request STATUS (current selected file FCP) from card."""
1188 (data, sw) = self.card._scc.status()
1189 return self.selected_file.decode_select_response(data)
1190
Harald Welte485692b2021-05-25 22:21:44 +02001191 def activate_file(self, name:str):
1192 """Request ACTIVATE FILE of specified file."""
1193 sels = self.selected_file.get_selectables()
1194 f = sels[name]
1195 data, sw = self.card._scc.activate_file(f.fid)
1196 return data, sw
1197
Harald Welteee3501f2021-04-02 13:00:18 +02001198 def read_binary(self, length:int=None, offset:int=0):
1199 """Read [part of] a transparent EF binary data.
1200
1201 Args:
1202 length : Amount of data to read (None: as much as possible)
1203 offset : Offset into the file from which to read 'length' bytes
1204 Returns:
1205 binary data read from the file
1206 """
Harald Welteb2edd142021-01-08 23:29:35 +01001207 if not isinstance(self.selected_file, TransparentEF):
1208 raise TypeError("Only works with TransparentEF")
1209 return self.card._scc.read_binary(self.selected_file.fid, length, offset)
1210
Harald Welte2d4a64b2021-04-03 09:01:24 +02001211 def read_binary_dec(self) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001212 """Read [part of] a transparent EF binary data and decode it.
1213
1214 Args:
1215 length : Amount of data to read (None: as much as possible)
1216 offset : Offset into the file from which to read 'length' bytes
1217 Returns:
1218 abstract decode data read from the file
1219 """
Harald Welteb2edd142021-01-08 23:29:35 +01001220 (data, sw) = self.read_binary()
1221 dec_data = self.selected_file.decode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001222 return (dec_data, sw)
1223
Harald Welteee3501f2021-04-02 13:00:18 +02001224 def update_binary(self, data_hex:str, offset:int=0):
1225 """Update transparent EF binary data.
1226
1227 Args:
1228 data_hex : hex string of data to be written
1229 offset : Offset into the file from which to write 'data_hex'
1230 """
Harald Welteb2edd142021-01-08 23:29:35 +01001231 if not isinstance(self.selected_file, TransparentEF):
1232 raise TypeError("Only works with TransparentEF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001233 return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write)
Harald Welteb2edd142021-01-08 23:29:35 +01001234
Harald Welteee3501f2021-04-02 13:00:18 +02001235 def update_binary_dec(self, data:dict):
1236 """Update transparent EF from abstract data. Encodes the data to binary and
1237 then updates the EF with it.
1238
1239 Args:
1240 data : abstract data which is to be encoded and written
1241 """
Harald Welteb2edd142021-01-08 23:29:35 +01001242 data_hex = self.selected_file.encode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001243 return self.update_binary(data_hex)
1244
Harald Welteee3501f2021-04-02 13:00:18 +02001245 def read_record(self, rec_nr:int=0):
1246 """Read a record as binary data.
1247
1248 Args:
1249 rec_nr : Record number to read
1250 Returns:
1251 hex string of binary data contained in record
1252 """
Harald Welteb2edd142021-01-08 23:29:35 +01001253 if not isinstance(self.selected_file, LinFixedEF):
1254 raise TypeError("Only works with Linear Fixed EF")
1255 # returns a string of hex nibbles
1256 return self.card._scc.read_record(self.selected_file.fid, rec_nr)
1257
Harald Welteee3501f2021-04-02 13:00:18 +02001258 def read_record_dec(self, rec_nr:int=0) -> Tuple[dict, str]:
1259 """Read a record and decode it to abstract data.
1260
1261 Args:
1262 rec_nr : Record number to read
1263 Returns:
1264 abstract data contained in record
1265 """
Harald Welteb2edd142021-01-08 23:29:35 +01001266 (data, sw) = self.read_record(rec_nr)
1267 return (self.selected_file.decode_record_hex(data), sw)
1268
Harald Welteee3501f2021-04-02 13:00:18 +02001269 def update_record(self, rec_nr:int, data_hex:str):
1270 """Update a record with given binary data
1271
1272 Args:
1273 rec_nr : Record number to read
1274 data_hex : Hex string binary data to be written
1275 """
Harald Welteb2edd142021-01-08 23:29:35 +01001276 if not isinstance(self.selected_file, LinFixedEF):
1277 raise TypeError("Only works with Linear Fixed EF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001278 return self.card._scc.update_record(self.selected_file.fid, rec_nr, data_hex, conserve=self.conserve_write)
Harald Welteb2edd142021-01-08 23:29:35 +01001279
Harald Welteee3501f2021-04-02 13:00:18 +02001280 def update_record_dec(self, rec_nr:int, data:dict):
1281 """Update a record with given abstract data. Will encode abstract to binary data
1282 and then write it to the given record on the card.
1283
1284 Args:
1285 rec_nr : Record number to read
1286 data_hex : Abstract data to be written
1287 """
Harald Welte1e456572021-04-02 17:16:30 +02001288 data_hex = self.selected_file.encode_record_hex(data)
1289 return self.update_record(rec_nr, data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +01001290
Harald Welte917d98c2021-04-21 11:51:25 +02001291 def retrieve_data(self, tag:int=0):
1292 """Read a DO/TLV as binary data.
1293
1294 Args:
1295 tag : Tag of TLV/DO to read
1296 Returns:
1297 hex string of full BER-TLV DO including Tag and Length
1298 """
1299 if not isinstance(self.selected_file, BerTlvEF):
1300 raise TypeError("Only works with BER-TLV EF")
1301 # returns a string of hex nibbles
1302 return self.card._scc.retrieve_data(self.selected_file.fid, tag)
1303
1304 def retrieve_tags(self):
1305 """Retrieve tags available on BER-TLV EF.
1306
1307 Returns:
1308 list of integer tags contained in EF
1309 """
1310 if not isinstance(self.selected_file, BerTlvEF):
1311 raise TypeError("Only works with BER-TLV EF")
1312 data, sw = self.card._scc.retrieve_data(self.selected_file.fid, 0x5c)
Harald Weltec1475302021-05-21 21:47:55 +02001313 tag, length, value, remainder = bertlv_parse_one(h2b(data))
Harald Welte917d98c2021-04-21 11:51:25 +02001314 return list(value)
1315
1316 def set_data(self, tag:int, data_hex:str):
1317 """Update a TLV/DO with given binary data
1318
1319 Args:
1320 tag : Tag of TLV/DO to be written
1321 data_hex : Hex string binary data to be written (value portion)
1322 """
1323 if not isinstance(self.selected_file, BerTlvEF):
1324 raise TypeError("Only works with BER-TLV EF")
1325 return self.card._scc.set_data(self.selected_file.fid, tag, data_hex, conserve=self.conserve_write)
1326
1327
Harald Welteb2edd142021-01-08 23:29:35 +01001328
1329
1330class FileData(object):
1331 """Represent the runtime, on-card data."""
1332 def __init__(self, fdesc):
1333 self.desc = fdesc
1334 self.fcp = None
1335
1336
Harald Welteee3501f2021-04-02 13:00:18 +02001337def interpret_sw(sw_data:dict, sw:str):
1338 """Interpret a given status word.
1339
1340 Args:
1341 sw_data : Hierarchical dict of status word matches
1342 sw : status word to match (string of 4 hex digits)
1343 Returns:
1344 tuple of two strings (class_string, description)
1345 """
Harald Welteb2edd142021-01-08 23:29:35 +01001346 for class_str, swdict in sw_data.items():
1347 # first try direct match
1348 if sw in swdict:
1349 return (class_str, swdict[sw])
1350 # next try wildcard matches
1351 for pattern, descr in swdict.items():
1352 if sw_match(sw, pattern):
1353 return (class_str, descr)
1354 return None
1355
1356class CardApplication(object):
1357 """A card application is represented by an ADF (with contained hierarchy) and optionally
1358 some SW definitions."""
Harald Welte5ce35242021-04-02 20:27:05 +02001359 def __init__(self, name, adf:Optional[CardADF]=None, aid:str=None, sw:dict=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001360 """
1361 Args:
1362 adf : ADF name
1363 sw : Dict of status word conversions
1364 """
Harald Welteb2edd142021-01-08 23:29:35 +01001365 self.name = name
1366 self.adf = adf
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001367 self.sw = sw or dict()
Harald Welte5ce35242021-04-02 20:27:05 +02001368 # back-reference from ADF to Applicaiton
1369 if self.adf:
1370 self.aid = aid or self.adf.aid
1371 self.adf.application = self
1372 else:
1373 self.aid = aid
Harald Welteb2edd142021-01-08 23:29:35 +01001374
1375 def __str__(self):
1376 return "APP(%s)" % (self.name)
1377
1378 def interpret_sw(self, sw):
Harald Welteee3501f2021-04-02 13:00:18 +02001379 """Interpret a given status word within the application.
1380
1381 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001382 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001383
1384 Returns:
1385 Tuple of two strings
1386 """
Harald Welteb2edd142021-01-08 23:29:35 +01001387 return interpret_sw(self.sw, sw)
1388
1389class CardProfile(object):
Harald Weltec9cdce32021-04-11 10:28:28 +02001390 """A Card Profile describes a card, it's filesystem hierarchy, an [initial] list of
Harald Welteb2edd142021-01-08 23:29:35 +01001391 applications as well as profile-specific SW and shell commands. Every card has
1392 one card profile, but there may be multiple applications within that profile."""
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001393 def __init__(self, name, **kw):
Harald Welteee3501f2021-04-02 13:00:18 +02001394 """
1395 Args:
1396 desc (str) : Description
1397 files_in_mf : List of CardEF instances present in MF
1398 applications : List of CardApplications present on card
1399 sw : List of status word definitions
1400 shell_cmdsets : List of cmd2 shell command sets of profile-specific commands
1401 """
Harald Welteb2edd142021-01-08 23:29:35 +01001402 self.name = name
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001403 self.desc = kw.get("desc", None)
1404 self.files_in_mf = kw.get("files_in_mf", [])
1405 self.sw = kw.get("sw", [])
1406 self.applications = kw.get("applications", [])
1407 self.shell_cmdsets = kw.get("shell_cmdsets", [])
Harald Welteb2edd142021-01-08 23:29:35 +01001408
1409 def __str__(self):
1410 return self.name
1411
Harald Welteee3501f2021-04-02 13:00:18 +02001412 def add_application(self, app:CardApplication):
1413 """Add an application to a card profile.
1414
1415 Args:
1416 app : CardApplication instance to be added to profile
1417 """
Philipp Maiereb72fa42021-03-26 21:29:57 +01001418 self.applications.append(app)
Harald Welteb2edd142021-01-08 23:29:35 +01001419
Harald Welteee3501f2021-04-02 13:00:18 +02001420 def interpret_sw(self, sw:str):
1421 """Interpret a given status word within the profile.
1422
1423 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001424 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001425
1426 Returns:
1427 Tuple of two strings
1428 """
Harald Welteb2edd142021-01-08 23:29:35 +01001429 return interpret_sw(self.sw, sw)