blob: 2bcbe10d6f09311b370dae7ee4c2b522ffda900b [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.
10
11(C) 2021 by Harald Welte <laforge@osmocom.org>
12
13This program is free software: you can redistribute it and/or modify
14it under the terms of the GNU General Public License as published by
15the Free Software Foundation, either version 2 of the License, or
16(at your option) any later version.
17
18This program is distributed in the hope that it will be useful,
19but WITHOUT ANY WARRANTY; without even the implied warranty of
20MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21GNU General Public License for more details.
22
23You should have received a copy of the GNU General Public License
24along with this program. If not, see <http://www.gnu.org/licenses/>.
25"""
26
27import code
28import json
29
30import cmd2
31from cmd2 import CommandSet, with_default_category, with_argparser
32import argparse
33
34from pySim.utils import sw_match, h2b, b2h
35from pySim.exceptions import *
36
37class CardFile(object):
38 """Base class for all objects in the smart card filesystem.
39 Serve as a common ancestor to all other file types; rarely used directly.
40 """
41 RESERVED_NAMES = ['..', '.', '/', 'MF']
42 RESERVED_FIDS = ['3f00']
43
44 def __init__(self, fid=None, sfid=None, name=None, desc=None, parent=None):
45 if not isinstance(self, CardADF) and fid == None:
46 raise ValueError("fid is mandatory")
47 if fid:
48 fid = fid.lower()
49 self.fid = fid # file identifier
50 self.sfid = sfid # short file identifier
51 self.name = name # human readable name
52 self.desc = desc # human readable description
53 self.parent = parent
54 if self.parent and self.parent != self and self.fid:
55 self.parent.add_file(self)
56 self.shell_commands = []
57
58 def __str__(self):
59 if self.name:
60 return self.name
61 else:
62 return self.fid
63
64 def _path_element(self, prefer_name):
65 if prefer_name and self.name:
66 return self.name
67 else:
68 return self.fid
69
70 def fully_qualified_path(self, prefer_name=True):
71 """Return fully qualified path to file as list of FID or name strings."""
72 if self.parent != self:
73 ret = self.parent.fully_qualified_path(prefer_name)
74 else:
75 ret = []
76 ret.append(self._path_element(prefer_name))
77 return ret
78
79 def get_mf(self):
80 """Return the MF (root) of the file system."""
81 if self.parent == None:
82 return None
83 # iterate towards the top. MF has parent == self
84 node = self
85 while node.parent != node:
86 node = node.parent
87 return node
88
89 def _get_self_selectables(self, alias=None):
90 """Return a dict of {'identifier': self} tuples"""
91 sels = {}
92 if alias:
93 sels.update({alias: self})
94 if self.fid:
95 sels.update({self.fid: self})
96 if self.name:
97 sels.update({self.name: self})
98 return sels
99
100 def get_selectables(self):
101 """Return a dict of {'identifier': File} that is selectable from the current file."""
102 # we can always select ourself
103 sels = self._get_self_selectables('.')
104 # we can always select our parent
105 sels = self.parent._get_self_selectables('..')
106 # if we have a MF, we can always select its applications
107 mf = self.get_mf()
108 if mf:
109 sels.update(mf._get_self_selectables())
110 sels.update(mf.get_app_selectables())
111 return sels
112
113 def get_selectable_names(self):
114 """Return a list of strings for all identifiers that are selectable from the current file."""
115 sels = self.get_selectables()
116 return sels.keys()
117
118 def decode_select_response(self, data_hex):
119 """Decode the response to a SELECT command."""
120 return self.parent.decode_select_response(data_hex)
121
122
123class CardDF(CardFile):
124 """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
125 def __init__(self, **kwargs):
126 if not isinstance(self, CardADF):
127 if not 'fid' in kwargs:
128 raise TypeError('fid is mandatory for all DF')
129 super().__init__(**kwargs)
130 self.children = dict()
131
132 def __str__(self):
133 return "DF(%s)" % (super().__str__())
134
135 def add_file(self, child, ignore_existing=False):
136 """Add a child (DF/EF) to this DF"""
137 if not isinstance(child, CardFile):
138 raise TypeError("Expected a File instance")
139 if child.name in CardFile.RESERVED_NAMES:
140 raise ValueError("File name %s is a reserved name" % (child.name))
141 if child.fid in CardFile.RESERVED_FIDS:
142 raise ValueError("File fid %s is a reserved name" % (child.fid))
143 if child.fid in self.children:
144 if ignore_existing:
145 return
146 raise ValueError("File with given fid %s already exists" % (child.fid))
147 if self.lookup_file_by_sfid(child.sfid):
148 raise ValueError("File with given sfid %s already exists" % (child.sfid))
149 if self.lookup_file_by_name(child.name):
150 if ignore_existing:
151 return
152 raise ValueError("File with given name %s already exists" % (child.name))
153 self.children[child.fid] = child
154 child.parent = self
155
156 def add_files(self, children, ignore_existing=False):
157 """Add a list of child (DF/EF) to this DF"""
158 for child in children:
159 self.add_file(child, ignore_existing)
160
161 def get_selectables(self):
162 """Get selectable (DF/EF names) from current DF"""
163 # global selectables + our children
164 sels = super().get_selectables()
165 sels.update({x.fid: x for x in self.children.values() if x.fid})
166 sels.update({x.name: x for x in self.children.values() if x.name})
167 return sels
168
169 def lookup_file_by_name(self, name):
170 if name == None:
171 return None
172 for i in self.children.values():
173 if i.name and i.name == name:
174 return i
175 return None
176
177 def lookup_file_by_sfid(self, sfid):
178 if sfid == None:
179 return None
180 for i in self.children.values():
181 if i.sfid == int(sfid):
182 return i
183 return None
184
185 def lookup_file_by_fid(self, fid):
186 if fid in self.children:
187 return self.children[fid]
188 return None
189
190
191class CardMF(CardDF):
192 """MF (Master File) in the smart card filesystem"""
193 def __init__(self, **kwargs):
194 # can be overridden; use setdefault
195 kwargs.setdefault('fid', '3f00')
196 kwargs.setdefault('name', 'MF')
197 kwargs.setdefault('desc', 'Master File (directory root)')
198 # cannot be overridden; use assignment
199 kwargs['parent'] = self
200 super().__init__(**kwargs)
201 self.applications = dict()
202
203 def __str__(self):
204 return "MF(%s)" % (self.fid)
205
206 def add_application(self, app):
207 """Add an ADF (Application Dedicated File) to the MF"""
208 if not isinstance(app, CardADF):
209 raise TypeError("Expected an ADF instance")
210 if app.aid in self.applications:
211 raise ValueError("AID %s already exists" % (app.aid))
212 self.applications[app.aid] = app
213 app.parent=self
214
215 def get_app_names(self):
216 """Get list of completions (AID names)"""
217 return [x.name for x in self.applications]
218
219 def get_selectables(self):
220 """Get list of completions (DF/EF/ADF names) from current DF"""
221 sels = super().get_selectables()
222 sels.update(self.get_app_selectables())
223 return sels
224
225 def get_app_selectables(self):
226 # applications by AID + name
227 sels = {x.aid: x for x in self.applications.values()}
228 sels.update({x.name: x for x in self.applications.values() if x.name})
229 return sels
230
231 def decode_select_response(self, data_hex):
232 """Decode the response to a SELECT command."""
233 return data_hex
234
235
236
237class CardADF(CardDF):
238 """ADF (Application Dedicated File) in the smart card filesystem"""
239 def __init__(self, aid, **kwargs):
240 super().__init__(**kwargs)
241 self.aid = aid # Application Identifier
242 if self.parent:
243 self.parent.add_application(self)
244
245 def __str__(self):
246 return "ADF(%s)" % (self.aid)
247
248 def _path_element(self, prefer_name):
249 if self.name and prefer_name:
250 return self.name
251 else:
252 return self.aid
253
254
255class CardEF(CardFile):
256 """EF (Entry File) in the smart card filesystem"""
257 def __init__(self, *, fid, **kwargs):
258 kwargs['fid'] = fid
259 super().__init__(**kwargs)
260
261 def __str__(self):
262 return "EF(%s)" % (super().__str__())
263
264 def get_selectables(self):
265 """Get list of completions (EF names) from current DF"""
266 #global selectable names + those of the parent DF
267 sels = super().get_selectables()
268 sels.update({x.name:x for x in self.parent.children.values() if x != self})
269 return sels
270
271
272class TransparentEF(CardEF):
273 """Transparent EF (Entry File) in the smart card filesystem"""
274
275 @with_default_category('Transparent EF Commands')
276 class ShellCommands(CommandSet):
277 def __init__(self):
278 super().__init__()
279
280 read_bin_parser = argparse.ArgumentParser()
281 read_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
282 read_bin_parser.add_argument('--length', type=int, help='Number of bytes to read')
283 @cmd2.with_argparser(read_bin_parser)
284 def do_read_binary(self, opts):
285 """Read binary data from a transparent EF"""
286 (data, sw) = self._cmd.rs.read_binary(opts.length, opts.offset)
287 self._cmd.poutput(data)
288
289 def do_read_binary_decoded(self, opts):
290 """Read + decode data from a transparent EF"""
291 (data, sw) = self._cmd.rs.read_binary_dec()
292 self._cmd.poutput(json.dumps(data, indent=4))
293
294 upd_bin_parser = argparse.ArgumentParser()
295 upd_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
296 upd_bin_parser.add_argument('data', help='Data bytes (hex format) to write')
297 @cmd2.with_argparser(upd_bin_parser)
298 def do_update_binary(self, opts):
299 """Update (Write) data of a transparent EF"""
300 (data, sw) = self._cmd.rs.update_binary(opts.data, opts.offset)
301 self._cmd.poutput(data)
302
303 upd_bin_dec_parser = argparse.ArgumentParser()
304 upd_bin_dec_parser.add_argument('data', help='Abstract data (JSON format) to write')
305 @cmd2.with_argparser(upd_bin_dec_parser)
306 def do_update_binary_decoded(self, opts):
307 """Encode + Update (Write) data of a transparent EF"""
308 data_json = json.loads(opts.data)
309 (data, sw) = self._cmd.rs.update_binary_dec(data_json)
310 self._cmd.poutput(json.dumps(data, indent=4))
311
312 def __init__(self, fid, sfid=None, name=None, desc=None, parent=None, size={1,None}):
313 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
314 self.size = size
315 self.shell_commands = [self.ShellCommands()]
316
317 def decode_bin(self, raw_bin_data):
318 """Decode raw (binary) data into abstract representation. Overloaded by specific classes."""
319 method = getattr(self, '_decode_bin', None)
320 if callable(method):
321 return method(raw_bin_data)
322 method = getattr(self, '_decode_hex', None)
323 if callable(method):
324 return method(b2h(raw_bin_data))
325 return {'raw': raw_bin_data.hex()}
326
327 def decode_hex(self, raw_hex_data):
328 """Decode raw (hex string) data into abstract representation. Overloaded by specific classes."""
329 method = getattr(self, '_decode_hex', None)
330 if callable(method):
331 return method(raw_hex_data)
332 raw_bin_data = h2b(raw_hex_data)
333 method = getattr(self, '_decode_bin', None)
334 if callable(method):
335 return method(raw_bin_data)
336 return {'raw': raw_bin_data.hex()}
337
338 def encode_bin(self, abstract_data):
339 """Encode abstract representation into raw (binary) data. Overloaded by specific classes."""
340 method = getattr(self, '_encode_bin', None)
341 if callable(method):
342 return method(abstract_data)
343 method = getattr(self, '_encode_hex', None)
344 if callable(method):
345 return h2b(method(abstract_data))
346 raise NotImplementedError
347
348 def encode_hex(self, abstract_data):
349 """Encode abstract representation into raw (hex string) data. Overloaded by specific classes."""
350 method = getattr(self, '_encode_hex', None)
351 if callable(method):
352 return method(abstract_data)
353 method = getattr(self, '_encode_bin', None)
354 if callable(method):
355 raw_bin_data = method(abstract_data)
356 return b2h(raw_bin_data)
357 raise NotImplementedError
358
359
360class LinFixedEF(CardEF):
361 """Linear Fixed EF (Entry File) in the smart card filesystem"""
362
363 @with_default_category('Linear Fixed EF Commands')
364 class ShellCommands(CommandSet):
365 def __init__(self):
366 super().__init__()
367
368 read_rec_parser = argparse.ArgumentParser()
369 read_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
370 @cmd2.with_argparser(read_rec_parser)
371 def do_read_record(self, opts):
372 """Read a record from a record-oriented EF"""
373 (data, sw) = self._cmd.rs.read_record(opts.record_nr)
374 self._cmd.poutput(data)
375
376 read_rec_dec_parser = argparse.ArgumentParser()
377 read_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
378 @cmd2.with_argparser(read_rec_dec_parser)
379 def do_read_record_decoded(self, opts):
380 """Read + decode a record from a record-oriented EF"""
381 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
382 self._cmd.poutput(json.dumps(data, indent=4))
383
384 upd_rec_parser = argparse.ArgumentParser()
385 upd_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
386 upd_rec_parser.add_argument('data', help='Data bytes (hex format) to write')
387 @cmd2.with_argparser(upd_rec_parser)
388 def do_update_record(self, opts):
389 """Update (write) data to a record-oriented EF"""
390 (data, sw) = self._cmd.rs.update_record(opts.record_nr, opts.data)
391 self._cmd.poutput(data)
392
393 upd_rec_dec_parser = argparse.ArgumentParser()
394 upd_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
395 upd_rec_dec_parser.add_argument('data', help='Data bytes (hex format) to write')
396 @cmd2.with_argparser(upd_rec_dec_parser)
397 def do_update_record_decoded(self, opts):
398 """Encode + Update (write) data to a record-oriented EF"""
399 (data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, opts.data)
400 self._cmd.poutput(data)
401
402 def __init__(self, fid, sfid=None, name=None, desc=None, parent=None, rec_len={1,None}):
403 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
404 self.rec_len = rec_len
405 self.shell_commands = [self.ShellCommands()]
406
407 def decode_record_hex(self, raw_hex_data):
408 """Decode raw (hex string) data into abstract representation. Overloaded by specific classes."""
409 method = getattr(self, '_decode_record_hex', None)
410 if callable(method):
411 return method(raw_hex_data)
412 raw_bin_data = h2b(raw_hex_data)
413 method = getattr(self, '_decode_record_bin', None)
414 if callable(method):
415 return method(raw_bin_data)
416 return {'raw': raw_bin_data.hex()}
417
418 def decode_record_bin(self, raw_bin_data):
419 """Decode raw (binary) data into abstract representation. Overloaded by specific classes."""
420 method = getattr(self, '_decode_record_bin', None)
421 if callable(method):
422 return method(raw_bin_data)
423 raw_hex_data = b2h(raw_bin_data)
424 method = getattr(self, '_decode_record_hex', None)
425 if callable(method):
426 return method(raw_hex_data)
427 return {'raw': raw_hex_data}
428
429 def encode_record_hex(self, abstract_data):
430 """Encode abstract representation into raw (hex string) data. Overloaded by specific classes."""
431 method = getattr(self, '_encode_record_hex', None)
432 if callable(method):
433 return method(abstract_data)
434 method = getattr(self, '_encode_record_bin', None)
435 if callable(method):
436 raw_bin_data = method(abstract_data)
437 return b2h(raww_bin_data)
438 raise NotImplementedError
439
440 def encode_record_bin(self, abstract_data):
441 """Encode abstract representation into raw (binary) data. Overloaded by specific classes."""
442 method = getattr(self, '_encode_record_bin', None)
443 if callable(method):
444 return method(abstract_data)
445 method = getattr(self, '_encode_record_hex', None)
446 if callable(method):
447 return b2h(method(abstract_data))
448 raise NotImplementedError
449
450class CyclicEF(LinFixedEF):
451 """Cyclic EF (Entry File) in the smart card filesystem"""
452 # we don't really have any special support for those; just recycling LinFixedEF here
453 def __init__(self, fid, sfid=None, name=None, desc=None, parent=None, rec_len={1,None}):
454 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len)
455
456class TransRecEF(TransparentEF):
457 """Transparent EF (Entry File) containing fixed-size records.
458 These are the real odd-balls and mostly look like mistakes in the specification:
459 Specified as 'transparent' EF, but actually containing several fixed-length records
460 inside.
461 We add a special class for those, so the user only has to provide encoder/decoder functions
462 for a record, while this class takes care of split / merge of records.
463 """
464 def __init__(self, fid, sfid=None, name=None, desc=None, parent=None, rec_len=None, size={1,None}):
465 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size)
466 self.rec_len = rec_len
467
468 def decode_record_hex(self, raw_hex_data):
469 """Decode raw (hex string) data into abstract representation. Overloaded by specific classes."""
470 method = getattr(self, '_decode_record_hex', None)
471 if callable(method):
472 return method(raw_hex_data)
473 method = getattr(self, '_decode_record_bin', None)
474 if callable(method):
475 raw_bin_data = h2b(raw_hex_data)
476 return method(raw_bin_data)
477 return {'raw': raw_hex_data}
478
479 def decode_record_bin(self, raw_bin_data):
480 """Decode raw (hex string) data into abstract representation. Overloaded by specific classes."""
481 method = getattr(self, '_decode_record_bin', None)
482 if callable(method):
483 return method(raw_bin_data)
484 raw_hex_data = b2h(raw_bin_data)
485 method = getattr(self, '_decode_record_hex', None)
486 if callable(method):
487 return method(raw_hex_data)
488 return {'raw': raw_hex_data}
489
490 def encode_record_hex(self, abstract_data):
491 """Encode abstract representation into raw (hex string) data. Overloaded by specific classes."""
492 method = getattr(self, '_encode_record_hex', None)
493 if callable(method):
494 return method(abstract_data)
495 method = getattr(self, '_encode_record_bin', None)
496 if callable(method):
497 return h2b(method(abstract_data))
498 raise NotImplementedError
499
500 def encode_record_bin(self, abstract_data):
501 """Encode abstract representation into raw (binary) data. Overloaded by specific classes."""
502 method = getattr(self, '_encode_record_bin', None)
503 if callable(method):
504 return method(abstract_data)
505 method = getattr(self, '_encode_record_hex', None)
506 if callable(method):
507 return h2b(method(abstract_data))
508 raise NotImplementedError
509
510 def _decode_bin(self, raw_bin_data):
511 chunks = [raw_bin_data[i:i+self.rec_len] for i in range(0, len(raw_bin_data), self.rec_len)]
512 return [self.decode_record_bin(x) for x in chunks]
513
514 def _encode_bin(self, abstract_data):
515 chunks = [self.encode_record_bin(x) for x in abstract_data]
516 # FIXME: pad to file size
517 return b''.join(chunks)
518
519
520
521
522
523class RuntimeState(object):
524 """Represent the runtime state of a session with a card."""
525 def __init__(self, card, profile):
526 self.mf = CardMF()
527 self.card = card
528 self.selected_file = self.mf
529 self.profile = profile
530 # add applications + MF-files from profile
531 for a in self.profile.applications:
532 self.mf.add_application(a)
533 for f in self.profile.files_in_mf:
534 self.mf.add_file(f)
535
536 def get_cwd(self):
537 """Obtain the current working directory."""
538 if isinstance(self.selected_file, CardDF):
539 return self.selected_file
540 else:
541 return self.selected_file.parent
542
543 def get_application(self):
544 """Obtain the currently selected application (if any)."""
545 # iterate upwards from selected file; check if any is an ADF
546 node = self.selected_file
547 while node.parent != node:
548 if isinstance(node, CardADF):
549 return node
550 node = node.parent
551 return None
552
553 def interpret_sw(self, sw):
554 """Interpret the given SW relative to the currently selected Application
555 or the underlying profile."""
556 app = self.get_application()
557 if app:
558 # The application either comes with its own interpret_sw
559 # method or we will use the interpret_sw method from the
560 # card profile.
561 if hasattr(app, "interpret_sw"):
562 return app.interpret_sw(sw)
563 else:
564 return self.profile.interpret_sw(sw)
565 return app.interpret_sw(sw)
566 else:
567 return self.profile.interpret_sw(sw)
568
569 def select(self, name, cmd_app=None):
570 """Change current directory"""
571 sels = self.selected_file.get_selectables()
572 if name in sels:
573 f = sels[name]
574 # unregister commands of old file
575 if cmd_app and self.selected_file.shell_commands:
576 for c in self.selected_file.shell_commands:
577 cmd_app.unregister_command_set(c)
578 try:
579 if isinstance(f, CardADF):
580 (data, sw) = self.card._scc.select_adf(f.aid)
581 else:
582 (data, sw) = self.card._scc.select_file(f.fid)
583 self.selected_file = f
584 except SwMatchError as swm:
585 k = self.interpret_sw(swm.sw_actual)
586 if not k:
587 raise(swm)
588 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
589 # register commands of new file
590 if cmd_app and self.selected_file.shell_commands:
591 for c in self.selected_file.shell_commands:
592 cmd_app.register_command_set(c)
593 return f.decode_select_response(data)
594 #elif looks_like_fid(name):
595 else:
596 raise ValueError("Cannot select unknown %s" % (name))
597
598 def read_binary(self, length=None, offset=0):
599 if not isinstance(self.selected_file, TransparentEF):
600 raise TypeError("Only works with TransparentEF")
601 return self.card._scc.read_binary(self.selected_file.fid, length, offset)
602
603 def read_binary_dec(self):
604 (data, sw) = self.read_binary()
605 dec_data = self.selected_file.decode_hex(data)
606 print("%s: %s -> %s" % (sw, data, dec_data))
607 return (dec_data, sw)
608
609 def update_binary(self, data_hex, offset=0):
610 if not isinstance(self.selected_file, TransparentEF):
611 raise TypeError("Only works with TransparentEF")
612 return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset)
613
614 def update_binary_dec(self, data):
615 data_hex = self.selected_file.encode_hex(data)
616 print("%s -> %s" % (data, data_hex))
617 return self.update_binary(data_hex)
618
619 def read_record(self, rec_nr=0):
620 if not isinstance(self.selected_file, LinFixedEF):
621 raise TypeError("Only works with Linear Fixed EF")
622 # returns a string of hex nibbles
623 return self.card._scc.read_record(self.selected_file.fid, rec_nr)
624
625 def read_record_dec(self, rec_nr=0):
626 (data, sw) = self.read_record(rec_nr)
627 return (self.selected_file.decode_record_hex(data), sw)
628
629 def update_record(self, rec_nr, data_hex):
630 if not isinstance(self.selected_file, LinFixedEF):
631 raise TypeError("Only works with Linear Fixed EF")
632 return self.card._scc.update_record(self.selected_file.fid, rec_nr, data_hex)
633
634 def update_record_dec(self, rec_nr, data):
635 hex_data = self.selected_file.encode_record_hex(data)
636 return self.update_record(self, rec_nr, data_hex)
637
638
639
640class FileData(object):
641 """Represent the runtime, on-card data."""
642 def __init__(self, fdesc):
643 self.desc = fdesc
644 self.fcp = None
645
646
647def interpret_sw(sw_data, sw):
648 """Interpret a given status word within the profile. Returns tuple of
649 two strings"""
650 for class_str, swdict in sw_data.items():
651 # first try direct match
652 if sw in swdict:
653 return (class_str, swdict[sw])
654 # next try wildcard matches
655 for pattern, descr in swdict.items():
656 if sw_match(sw, pattern):
657 return (class_str, descr)
658 return None
659
660class CardApplication(object):
661 """A card application is represented by an ADF (with contained hierarchy) and optionally
662 some SW definitions."""
663 def __init__(self, name, adf=None, sw={}):
664 self.name = name
665 self.adf = adf
666 self.sw = sw
667
668 def __str__(self):
669 return "APP(%s)" % (self.name)
670
671 def interpret_sw(self, sw):
672 """Interpret a given status word within the application. Returns tuple of
673 two strings"""
674 return interpret_sw(self.sw, sw)
675
676class CardProfile(object):
677 """A Card Profile describes a card, it's filessystem hierarchy, an [initial] list of
678 applications as well as profile-specific SW and shell commands. Every card has
679 one card profile, but there may be multiple applications within that profile."""
680 def __init__(self, name, desc=None, files_in_mf=[], sw=[], applications=[], shell_cmdsets=[]):
681 self.name = name
682 self.desc = desc
683 self.files_in_mf = files_in_mf
684 self.sw = sw
685 self.applications = applications
686 self.shell_cmdsets = shell_cmdsets
687
688 def __str__(self):
689 return self.name
690
691 def add_application(self, app):
692 self.applications.add(app)
693
694 def interpret_sw(self, sw):
695 """Interpret a given status word within the profile. Returns tuple of
696 two strings"""
697 return interpret_sw(self.sw, sw)
698
699
700######################################################################
701
702if __name__ == '__main__':
703 mf = CardMF()
704
705 adf_usim = ADF('a0000000871002', name='ADF_USIM')
706 mf.add_application(adf_usim)
707 df_pb = CardDF('5f3a', name='DF.PHONEBOOK')
708 adf_usim.add_file(df_pb)
709 adf_usim.add_file(TransparentEF('6f05', name='EF.LI', size={2,16}))
710 adf_usim.add_file(TransparentEF('6f07', name='EF.IMSI', size={9,9}))
711
712 rss = RuntimeState(mf, None)
713
714 interp = code.InteractiveConsole(locals={'mf':mf, 'rss':rss})
715 interp.interact()