2010-02-24 01:45:26 +00:00
|
|
|
import sys;sys.path.append(".."); sys.path.append(".")
|
2006-05-18 20:22:15 +00:00
|
|
|
import TLV_utils
|
2010-10-15 06:20:22 +00:00
|
|
|
from iso_card import *
|
2007-02-10 21:52:06 +00:00
|
|
|
from generic_application import Application
|
2007-06-02 06:14:18 +00:00
|
|
|
import building_blocks
|
2006-05-18 20:22:15 +00:00
|
|
|
|
2010-02-24 01:45:26 +00:00
|
|
|
class iso_node(object):
|
|
|
|
SORT_NONE = False
|
|
|
|
SORT_NORMAL = True
|
|
|
|
SORT_DFFIRST = object() # Random object, just for identity testing
|
|
|
|
|
|
|
|
def __init__(self, parent=None, management_information=None, card_object=None, generic_description=None):
|
|
|
|
self._parent = parent
|
|
|
|
self._management_information = management_information # FCI, FCP or FMD
|
|
|
|
self._card_object = card_object
|
|
|
|
self._children = []
|
|
|
|
self._generic_description = generic_description # Note: Only used for iso_node, not for iso_ef or iso_df
|
|
|
|
|
|
|
|
if self._parent is not None:
|
|
|
|
self._parent.add_child(self)
|
|
|
|
if self._card_object is None and self._parent._card_object is not None:
|
|
|
|
self._card_object = self._parent._card_object
|
|
|
|
|
|
|
|
def print_node(self, indent=0, stream=None, stringlist=None, **kwargs):
|
|
|
|
result = self._format_node(indent, **kwargs)
|
|
|
|
if stream is not None:
|
|
|
|
stream.write("\n".join(result))
|
|
|
|
elif stringlist is not None:
|
|
|
|
stringlist.extend(result)
|
|
|
|
else:
|
|
|
|
print "\n".join(result)
|
|
|
|
|
|
|
|
if kwargs.get("recurse", True):
|
|
|
|
tosort = kwargs.get("sort", self.SORT_NONE)
|
|
|
|
|
|
|
|
def cmp_normal(a,b): return cmp(a.fid,b.fid)
|
|
|
|
def cmp_dffirst(a,b):
|
|
|
|
return (a.__class__ != b.__class__) and (a.__class__ == iso_df and -1 or 1) or cmp(a.fid,b.fid)
|
|
|
|
|
|
|
|
if tosort is self.SORT_DFFIRST:
|
|
|
|
for child in sorted(self._children, cmp=cmp_dffirst):
|
|
|
|
child.print_node(indent+1, **kwargs)
|
|
|
|
elif tosort:
|
|
|
|
for child in sorted(self._children, cmp=cmp_normal):
|
|
|
|
child.print_node(indent+1, **kwargs)
|
|
|
|
else:
|
|
|
|
for child in self._children:
|
|
|
|
child.print_node(indent+1, **kwargs)
|
|
|
|
|
|
|
|
def _dump_internal(self, data, indent, do_tlv=True):
|
|
|
|
c = utils.hexdump(data)
|
|
|
|
r = map(lambda a: self.get_indent(indent)+a, c.splitlines(False))
|
|
|
|
if do_tlv:
|
|
|
|
try:
|
|
|
|
if self._card_object is not None:
|
|
|
|
c = TLV_utils.decode(data, tags=self._card_object.TLV_OBJECTS, context = self._card_object.DEFAULT_CONTEXT)
|
|
|
|
else:
|
|
|
|
c = TLV_utils.decode(data)
|
|
|
|
r.append( self.get_indent(indent) + "Trying TLV parse:" )
|
|
|
|
r.extend( map(lambda a: self.get_indent(indent)+a, c.splitlines(False)) )
|
|
|
|
except (SystemExit, KeyboardInterrupt):
|
|
|
|
raise
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
return r
|
|
|
|
|
|
|
|
def _format_node(self, indent, **kwargs):
|
|
|
|
result = []
|
|
|
|
if self._generic_description:
|
|
|
|
result.append(self.get_indent(indent) + "+ " + self._generic_description)
|
|
|
|
else:
|
|
|
|
result.append(self.get_indent(indent) + "+ Generic Node")
|
|
|
|
if kwargs.get("with_management_information", True):
|
|
|
|
result.extend(self._format_management_information(indent))
|
|
|
|
return result
|
|
|
|
|
|
|
|
def _format_management_information(self, indent):
|
|
|
|
result = []
|
|
|
|
if self._management_information is None: return result
|
|
|
|
|
|
|
|
try:
|
|
|
|
if self._card_object is not None:
|
|
|
|
c = TLV_utils.decode(self._management_information, tags=self._card_object.TLV_OBJECTS, context = self._card_object.DEFAULT_CONTEXT)
|
|
|
|
else:
|
|
|
|
c = TLV_utils.decode(self._management_information)
|
|
|
|
result.append(self.get_indent(indent+1) + "Management information:")
|
|
|
|
result.extend( map(lambda a: self.get_indent(indent+2)+a, c.splitlines(False)) )
|
|
|
|
except (SystemExit, KeyboardInterrupt):
|
|
|
|
raise
|
|
|
|
except:
|
|
|
|
result.append(self.get_indent(indent+1) + "Raw dump of unparseable management information following:")
|
|
|
|
result.extend(self._dump_internal(self._management_information, indent=indent+2, do_tlv=False))
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
def add_child(self, node):
|
|
|
|
raise NotImplementedError, "Can't add a child to a mere node"
|
|
|
|
|
|
|
|
def get_fid(self): return self._fid
|
|
|
|
def get_parent(self): return self._parent
|
|
|
|
|
|
|
|
fid = property(get_fid)
|
|
|
|
parent = property(get_parent)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_indent(indent):
|
|
|
|
return "\t"*indent
|
|
|
|
|
|
|
|
class iso_ef(iso_node):
|
|
|
|
TYPE_TRANSPARENT = 1
|
|
|
|
TYPE_RECORD = 2
|
|
|
|
|
|
|
|
def __init__(self, fid, type=None, **kwargs):
|
|
|
|
super(iso_ef, self).__init__(**kwargs)
|
|
|
|
self._fid = fid
|
|
|
|
self._content = None
|
|
|
|
self._type = type
|
|
|
|
|
|
|
|
def _format_node(self, indent, **kwargs):
|
|
|
|
result = [self.get_indent(indent) + "+ EF: %s" % utils.hexdump(self.fid, short=True)]
|
|
|
|
|
|
|
|
if kwargs.get("with_management_information", True):
|
|
|
|
result.extend(self._format_management_information(indent))
|
|
|
|
|
|
|
|
if kwargs.get("with_content", True) and self._content is not None:
|
|
|
|
if self._type is self.TYPE_TRANSPARENT:
|
|
|
|
result.append(self.get_indent(indent+1) +
|
|
|
|
"Contents (length: %i (0x%0X)):" % (len(self._content),len(self._content))
|
|
|
|
)
|
|
|
|
result.extend(self._dump_internal(self._content,indent=indent+2))
|
|
|
|
elif self._type is self.TYPE_RECORD:
|
|
|
|
result.append(self.get_indent(indent+1) + "%i (0x0%X) records following:" % (len(self._content),len(self._content)) )
|
|
|
|
|
|
|
|
for i,d in enumerate(self._content):
|
|
|
|
result.append(self.get_indent(indent+2) +
|
|
|
|
"Record %i (length: %i (0x%0X)):" % (i, len(d),len(d))
|
|
|
|
)
|
|
|
|
result.extend(self._dump_internal(d,indent=indent+3))
|
|
|
|
else:
|
|
|
|
result.append(self.get_indent(indent+1) + "Contents:")
|
|
|
|
result.append(self.get_indent(indent+2) + repr(self._content))
|
|
|
|
return result
|
|
|
|
|
|
|
|
class iso_df(iso_node):
|
|
|
|
def __init__(self, fid, **kwargs):
|
|
|
|
super(iso_df, self).__init__(**kwargs)
|
|
|
|
self._fid = fid
|
|
|
|
|
|
|
|
def _format_node(self, indent, **kwargs):
|
|
|
|
result = [self.get_indent(indent) + "+ DF: %s" % utils.hexdump(self.fid, short=True)]
|
|
|
|
if kwargs.get("with_management_information", True):
|
|
|
|
result.extend(self._format_management_information(indent))
|
|
|
|
return result
|
|
|
|
|
|
|
|
def add_child(self, node):
|
|
|
|
if node not in self._children:
|
|
|
|
self._children.append(node)
|
|
|
|
|
2010-10-15 06:20:22 +00:00
|
|
|
class ISO_7816_4_Card(building_blocks.Card_with_read_binary,ISO_Card):
|
2007-01-16 17:14:37 +00:00
|
|
|
APDU_SELECT_APPLICATION = C_APDU(ins=0xa4,p1=0x04)
|
2007-02-15 21:19:18 +00:00
|
|
|
APDU_SELECT_FILE = C_APDU(ins=0xa4, le=0)
|
2006-11-19 04:44:45 +00:00
|
|
|
APDU_READ_BINARY = C_APDU(ins=0xb0,le=0)
|
|
|
|
APDU_READ_RECORD = C_APDU(ins=0xb2,le=0)
|
2007-03-18 23:38:24 +00:00
|
|
|
DRIVER_NAME = ["ISO 7816-4"]
|
2006-05-21 13:04:48 +00:00
|
|
|
FID_MF = "\x3f\x00"
|
2006-05-18 20:22:15 +00:00
|
|
|
|
2008-06-10 03:34:21 +00:00
|
|
|
SELECT_FILE_P1 = 0x02
|
2007-01-12 11:28:16 +00:00
|
|
|
SELECT_P2 = 0x0
|
2010-02-24 01:45:26 +00:00
|
|
|
SELECT_FILE_LE = None
|
|
|
|
|
|
|
|
EF_CLASS = iso_ef
|
|
|
|
DF_CLASS = iso_df
|
2007-01-12 11:28:16 +00:00
|
|
|
|
2006-05-19 00:19:08 +00:00
|
|
|
## def can_handle(cls, card):
|
|
|
|
## return True
|
|
|
|
## can_handle = classmethod(can_handle)
|
2006-05-18 20:22:15 +00:00
|
|
|
|
|
|
|
def select_file(self, p1, p2, fid):
|
|
|
|
result = self.send_apdu(
|
|
|
|
C_APDU(self.APDU_SELECT_FILE,
|
|
|
|
p1 = p1, p2 = p2,
|
2010-02-24 01:45:26 +00:00
|
|
|
data = fid, le = self.SELECT_FILE_LE) )
|
2006-05-18 20:22:15 +00:00
|
|
|
return result
|
|
|
|
|
2006-05-21 13:04:48 +00:00
|
|
|
def change_dir(self, fid = None):
|
|
|
|
"Change to a child DF. Alternatively, change to MF if fid is None."
|
|
|
|
if fid is None:
|
2007-01-12 11:28:16 +00:00
|
|
|
return self.select_file(0x00, self.SELECT_P2, "")
|
2006-05-21 13:04:48 +00:00
|
|
|
else:
|
2007-01-12 11:28:16 +00:00
|
|
|
return self.select_file(0x01, self.SELECT_P2, fid)
|
2006-05-21 13:04:48 +00:00
|
|
|
|
|
|
|
def cmd_cd(self, dir = None):
|
|
|
|
"Change into a DF, or into the MF if no dir is given"
|
|
|
|
|
|
|
|
if dir is None:
|
|
|
|
result = self.change_dir()
|
|
|
|
else:
|
|
|
|
fid = binascii.a2b_hex("".join(dir.split()))
|
|
|
|
result = self.change_dir(fid)
|
|
|
|
|
|
|
|
if len(result.data) > 0:
|
|
|
|
print utils.hexdump(result.data)
|
2006-06-16 23:14:14 +00:00
|
|
|
print TLV_utils.decode(result.data,tags=self.TLV_OBJECTS)
|
2006-05-21 13:04:48 +00:00
|
|
|
|
2007-02-13 13:43:56 +00:00
|
|
|
def open_file(self, fid, p2 = None):
|
2006-05-21 13:04:48 +00:00
|
|
|
"Open an EF under the current DF"
|
2007-02-13 13:43:56 +00:00
|
|
|
if p2 is None: p2 = self.SELECT_P2
|
2008-06-10 03:34:21 +00:00
|
|
|
return self.select_file(self.SELECT_FILE_P1, p2, fid)
|
2006-05-21 13:04:48 +00:00
|
|
|
|
|
|
|
def cmd_open(self, file):
|
|
|
|
"Open a file"
|
|
|
|
fid = binascii.a2b_hex("".join(file.split()))
|
|
|
|
|
|
|
|
result = self.open_file(fid)
|
|
|
|
if len(result.data) > 0:
|
|
|
|
print utils.hexdump(result.data)
|
2006-06-16 23:14:14 +00:00
|
|
|
print TLV_utils.decode(result.data,tags=self.TLV_OBJECTS)
|
2006-05-21 13:04:48 +00:00
|
|
|
|
2006-10-19 10:20:32 +00:00
|
|
|
def read_record(self, p1 = 0, p2 = 0, le = 0):
|
|
|
|
"Read a record from the currently selected file"
|
|
|
|
command = C_APDU(self.APDU_READ_RECORD, p1 = p1, p2 = p2, le = le)
|
|
|
|
result = self.send_apdu(command)
|
|
|
|
return result.data
|
|
|
|
|
2007-01-13 02:25:09 +00:00
|
|
|
def cmd_read_record(self, p1 = None, p2 = None, le = "0"):
|
2006-10-19 10:20:32 +00:00
|
|
|
"Read a record"
|
2007-01-13 02:25:09 +00:00
|
|
|
if p1 is None and p2 is None:
|
|
|
|
p1 = p2 = "0"
|
|
|
|
elif p2 is None:
|
|
|
|
p2 = "0x04" # Use record number in P1
|
|
|
|
contents = self.read_record(p1 = int(p1,0), p2 = int(p2,0), le = int(le,0))
|
2006-10-19 10:20:32 +00:00
|
|
|
print utils.hexdump(contents)
|
|
|
|
|
2007-01-13 02:25:09 +00:00
|
|
|
def cmd_next_record(self, le = "0"):
|
2006-10-19 10:20:32 +00:00
|
|
|
"Read the next record"
|
2007-01-13 02:25:09 +00:00
|
|
|
return self.cmd_read_record(p1 = "0", p2 = "2", le = le)
|
2006-10-19 10:20:32 +00:00
|
|
|
|
2006-05-18 20:22:15 +00:00
|
|
|
def cmd_selectfile(self, p1, p2, fid):
|
|
|
|
"""Select a file on the card."""
|
|
|
|
|
|
|
|
p1 = binascii.a2b_hex("".join(p1.split()))
|
|
|
|
p2 = binascii.a2b_hex("".join(p2.split()))
|
|
|
|
fid = binascii.a2b_hex("".join(fid.split()))
|
|
|
|
|
|
|
|
result = self.select_file(p1, p2, fid)
|
|
|
|
if len(result.data) > 0:
|
|
|
|
print utils.hexdump(result.data)
|
2006-06-16 23:14:14 +00:00
|
|
|
print TLV_utils.decode(result.data,tags=self.TLV_OBJECTS)
|
2006-05-18 20:22:15 +00:00
|
|
|
|
2007-06-01 20:37:41 +00:00
|
|
|
def select_application(self, aid, le=0, **kwargs):
|
2007-01-16 17:14:37 +00:00
|
|
|
result = self.send_apdu(
|
|
|
|
C_APDU(self.APDU_SELECT_APPLICATION,
|
2007-06-01 20:37:41 +00:00
|
|
|
data = aid, le = le, **kwargs) ) ## FIXME With or without le
|
2007-02-13 00:32:04 +00:00
|
|
|
if self.check_sw(result.sw):
|
2007-02-10 21:52:06 +00:00
|
|
|
Application.load_applications(self, aid)
|
2007-01-16 17:14:37 +00:00
|
|
|
return result
|
|
|
|
|
2007-05-30 16:47:43 +00:00
|
|
|
def resolve_symbolic_aid(self, symbolic_name):
|
|
|
|
"Returns symbolic_name, or if symbolic_name is a known symbolic_name then its corresponding aid."
|
2007-02-10 21:52:06 +00:00
|
|
|
s = [a for a,b in self.APPLICATIONS.items()
|
2007-05-30 16:47:43 +00:00
|
|
|
if (b[0] is not None and b[0].lower() == symbolic_name.lower())
|
|
|
|
or (len(b) > 2 and symbolic_name.lower() in [c.lower() for c in b[2].get("alias", [])])
|
2007-02-10 21:52:06 +00:00
|
|
|
]
|
2007-01-16 17:14:37 +00:00
|
|
|
if len(s) > 0:
|
|
|
|
aid = s[0]
|
|
|
|
else:
|
2007-05-30 16:47:43 +00:00
|
|
|
aid = binascii.a2b_hex("".join(symbolic_name.split()))
|
|
|
|
|
|
|
|
return aid
|
|
|
|
|
|
|
|
def cmd_selectapplication(self, application):
|
|
|
|
"""Select an application on the card.
|
|
|
|
application can be given either as hexadecimal aid or by symbolic name (if known)."""
|
|
|
|
|
|
|
|
aid = self.resolve_symbolic_aid(application)
|
|
|
|
|
2007-01-16 17:14:37 +00:00
|
|
|
result = self.select_application(aid)
|
|
|
|
if len(result.data) > 0:
|
|
|
|
print utils.hexdump(result.data)
|
|
|
|
print TLV_utils.decode(result.data,tags=self.TLV_OBJECTS)
|
|
|
|
|
2007-06-05 02:31:17 +00:00
|
|
|
def cmd_pretendapplication(self, application):
|
|
|
|
"Pretend that an application has been selected on the card without actually sending a SELECT APPLICATION. Basically for debugging purposes."
|
|
|
|
aid = self.resolve_symbolic_aid(application)
|
|
|
|
Application.load_applications(self, aid)
|
|
|
|
|
2010-10-15 06:20:22 +00:00
|
|
|
ATRS = list(ISO_Card.ATRS)
|
2006-05-19 00:19:08 +00:00
|
|
|
ATRS.extend( [
|
|
|
|
(".*", None), ## For now we accept any card
|
|
|
|
] )
|
2006-05-18 20:22:15 +00:00
|
|
|
|
2010-10-15 06:20:22 +00:00
|
|
|
STOP_ATRS = list(ISO_Card.STOP_ATRS)
|
2007-05-30 16:47:43 +00:00
|
|
|
STOP_ATRS.extend( [
|
2010-03-03 02:13:02 +00:00
|
|
|
("3b8f8001804f0ca000000306......00000000..", None), # Contactless storage cards (PC/SC spec part 3 section 3.1.3.2.3
|
|
|
|
("3b8180018080", None), # Mifare DESfire (special case of contactless smartcard, ibid.)
|
2007-05-30 16:47:43 +00:00
|
|
|
] )
|
|
|
|
|
2010-10-15 06:20:22 +00:00
|
|
|
COMMANDS = dict(ISO_Card.COMMANDS)
|
2007-06-02 06:14:18 +00:00
|
|
|
COMMANDS.update(building_blocks.Card_with_read_binary.COMMANDS)
|
2006-05-18 20:22:15 +00:00
|
|
|
COMMANDS.update( {
|
2007-01-16 17:14:37 +00:00
|
|
|
"select_application": cmd_selectapplication,
|
2007-06-05 02:31:17 +00:00
|
|
|
"pretend_application": cmd_pretendapplication,
|
2006-05-18 20:22:15 +00:00
|
|
|
"select_file": cmd_selectfile,
|
2006-05-21 13:04:48 +00:00
|
|
|
"cd": cmd_cd,
|
|
|
|
"open": cmd_open,
|
2006-10-19 10:20:32 +00:00
|
|
|
"read_record": cmd_read_record,
|
|
|
|
"next_record": cmd_next_record,
|
2006-05-18 20:22:15 +00:00
|
|
|
} )
|
|
|
|
|
2010-10-15 06:20:22 +00:00
|
|
|
STATUS_WORDS = dict(ISO_Card.STATUS_WORDS)
|
2006-05-18 20:22:15 +00:00
|
|
|
STATUS_WORDS.update( {
|
|
|
|
"62??": "Warning, State of non-volatile memory unchanged",
|
|
|
|
"63??": "Warning, State of non-volatile memory changed",
|
|
|
|
"64??": "Error, State of non-volatile memory unchanged",
|
|
|
|
"65??": "Error, State of non-volatile memory changed",
|
|
|
|
"66??": "Reserved for security-related issues",
|
|
|
|
"6700": "Wrong length",
|
|
|
|
"68??": "Functions in CLA not supported",
|
|
|
|
"69??": "Command not allowed",
|
|
|
|
"6A??": "Wrong parameter(s) P1-P2",
|
|
|
|
"6B00": "Wrong parameter(s) P1-P2",
|
|
|
|
"6D00": "Instruction code not supported or invalid",
|
|
|
|
"6E00": "Class not supported",
|
|
|
|
"6F00": "No precise diagnosis",
|
|
|
|
|
|
|
|
"6200": "Warning, State of non-volatile memory unchanged, No information given",
|
|
|
|
"6281": "Warning, State of non-volatile memory unchanged, Part of returned data may be corrupted",
|
|
|
|
"6282": "Warning, State of non-volatile memory unchanged, End of file/record reached before reading Le bytes",
|
|
|
|
"6283": "Warning, State of non-volatile memory unchanged, Selected file invalidated",
|
|
|
|
"6284": "Warning, State of non-volatile memory unchanged, FCI not formatted according to ISO-7816-4 5.1.5",
|
|
|
|
|
|
|
|
"6300": "Warning, State of non-volatile memory changed, No information given",
|
|
|
|
"6381": "Warning, State of non-volatile memory changed, File filled up by the last write",
|
|
|
|
"63C?": lambda SW1,SW2: "Warning, State of non-volatile memory changed, Counter provided by '%i'" % (SW2%16),
|
|
|
|
|
|
|
|
"6500": "Error, State of non-volatile memory changed, No information given",
|
|
|
|
"6581": "Error, State of non-volatile memory changed, Memory failure",
|
|
|
|
|
|
|
|
"6800": "Functions in CLA not supported, No information given",
|
|
|
|
"6881": "Functions in CLA not supported, Logical channel not supported",
|
|
|
|
"6882": "Functions in CLA not supported, Secure messaging not supported",
|
|
|
|
|
|
|
|
"6900": "Command not allowed, No information given",
|
|
|
|
"6981": "Command not allowed, Command incompatible with file structure",
|
|
|
|
"6982": "Command not allowed, Security status not satisfied",
|
|
|
|
"6983": "Command not allowed, Authentication method blocked",
|
|
|
|
"6984": "Command not allowed, Referenced data invalidated",
|
|
|
|
"6985": "Command not allowed, Conditions of use not satisfied",
|
|
|
|
"6986": "Command not allowed, Command not allowed (no current EF)",
|
|
|
|
"6987": "Command not allowed, Expected SM data objects missing",
|
|
|
|
"6988": "Command not allowed, SM data objects incorrect",
|
|
|
|
|
|
|
|
"6A00": "Wrong parameter(s) P1-P2, No information given",
|
|
|
|
"6A80": "Wrong parameter(s) P1-P2, Incorrect parameters in the data field",
|
|
|
|
"6A81": "Wrong parameter(s) P1-P2, Function not supported",
|
|
|
|
"6A82": "Wrong parameter(s) P1-P2, File not found",
|
|
|
|
"6A83": "Wrong parameter(s) P1-P2, Record not found",
|
|
|
|
"6A84": "Wrong parameter(s) P1-P2, Not enough memory space in the file",
|
|
|
|
"6A85": "Wrong parameter(s) P1-P2, Lc inconsistent with TLV structure",
|
|
|
|
"6A86": "Wrong parameter(s) P1-P2, Incorrect parameters P1-P2",
|
|
|
|
"6A87": "Wrong parameter(s) P1-P2, Lc inconsistent with P1-P2",
|
|
|
|
"6A88": "Wrong parameter(s) P1-P2, Referenced data not found",
|
|
|
|
} )
|
2006-06-16 23:14:14 +00:00
|
|
|
|
|
|
|
TLV_OBJECTS = TLV_utils.tags
|
2010-02-24 01:45:26 +00:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
|
|
root = iso_df('\x3f\x00')
|
|
|
|
ef_one = iso_ef('\xb0\x01', parent=root, type=iso_ef.TYPE_TRANSPARENT, management_information="he")
|
|
|
|
ef_two = iso_ef('\xa0\x00', parent=root, type=iso_ef.TYPE_RECORD, management_information="h")
|
|
|
|
df_one = iso_df('\xa0\x01', parent=root)
|
|
|
|
df_two = iso_df('\xa0\x00', parent=df_one)
|
|
|
|
ef_three = iso_ef('\x00\x03', parent=df_one, type=iso_ef.TYPE_RECORD)
|
|
|
|
ef_four = iso_ef('\x00\x04', parent=df_one)
|
|
|
|
ef_five = iso_ef('\x00\x05', parent=df_two)
|
|
|
|
df_three = iso_df('\xa0\x00', parent=df_one)
|
|
|
|
|
|
|
|
ef_one._content = "Foobaluhahihohoahajabla"
|
|
|
|
ef_two._content = ("bla", "bli", "blu")
|
|
|
|
ef_three._content = ("Foobaluhahihohoahajabla",)
|
|
|
|
|
|
|
|
root.print_node(sort=root.SORT_DFFIRST)
|