blob: 97eaf90f6059a552674f3ea469cbbf18fca1c119 [file] [log] [blame]
"""Module for processing TCG TPM2 library object descriptions.
The descriptions are scraped from the tables of parts 2 and 3 of the
specification by a different module and fed through this module for
from __future__ import print_function
import re
import sys
from command_generator import Command
from structure_generator import AttributeStructure
from structure_generator import ConstantType
from structure_generator import Field
from structure_generator import Interface
from structure_generator import Structure
from structure_generator import Typedef
from structure_generator import Union
def _DebugLog(*args, **kwargs):
"""When used - sends its inputs to stderr.
This function can be used when debugging this module. Its footprint is
similar to print(), but the default destination is sys.stderr, which is
handy when the script generates stdio output redirected into a file.
*args: a list of items of various types to print. Printed space separated,
each one converted to str before being printed.
**kwargs: a dictionary of variables to pass to print(), if any. In fact the
only key this function cares about is 'endl', which allows to
suppress adding a newline to the printed string.
endl = kwargs.get('endl', '\n')
print(' '.join(str(x) for x in args), end=endl, file=sys.stderr)
class Table(object):
"""Representation of TCG TPM2 library specification tables.
The purpose of this class is to both generate new TPM2 objects and to keep
track of the previously generated objects for post processing (generating C
The HTML scraper finds tables in the specifications and builds up the
tables' contents in this object, one at a time. This object's table
representation includes table title, table header and one or more then table
The table title must start with 'Table ### xxx', where ### is monotonously
increasing table number and xxx is some description allowing to deduce the
type of the object defined by this table.
The cells of the table include names and types of the components, various
decorations are used to convey additional information: array boundaries,
values' limits, return values, selectors, etc, etc.
Once the entire table is scraped, the scraper invokes a method to process it
(ProcessTable). The title of the table is examined by this module and the
appropriate processing method is invoked to actually convert the internal
table representation into a TPM2 object.
Two maps are maintained persistently over the life time of this object, the
map of types (keyed by the type name scraped from part 2) and map of
commands (keyed by the command name, scraped from part 3).
One other thing this module produces is the text for the .h file defining
all structures and types this module encountered.
_alg_id_table: actual table of various TPM2 algorithms, a copy of Table 9
from part 2. It is used to convert encoded algorithm specs
used in other tables into a list of matching algorithms.
_h_file: a multiline string, the accumulated .h file defining all TPM
objects processed so far.
_type_map: a dictionary of various TPM types, keyed by the string - the
type name
_command_map: a dictionary of command_generator.Command objects, keyed by
the string, the command name
skip_tables: a tuple of integers, the numbers of tables which should not
be included in the .h file, as the same information was
derived from part 4 earlier.
_title: a string, title of the currently being processed specification
_title_type: a string, base type of the object defined by the currently
being processed specification table
_alg_type: a string, in some tables included in the title in curly
brackets, to indicate what type of the algorithm this table
deals with (usually RSA or ECC)
_body: a list of strings, rows of the currently being processed
specification table
_has_selector_column: a Boolean, set to True if the third column of the
table is the selector to be used to process this row (as in
picking the object type when the table represents a union)
_skip_printing: a Boolean, set to True if the table contents should not be
included on tpm_types.h - some tables are also defined in
files extracted from Part 4 of the specification.
# Match table titles with processing routines.
(re.compile('(Constants|Defines for Logic Values)'), '_ProcessConstants'),
(re.compile('(of Types for|Base Types)'), '_ProcessTypes'),
(re.compile('Definition of .* Type'), '_ProcessInterfaceOrType'),
(re.compile('Unmarshaling Errors'), '_ProcessEnum'),
(re.compile(r'Definition of [\S]+ (Structure|Union)'),
(re.compile('Definition of .* Bits'), '_ProcessBits'),
(re.compile(r' TPM2_\S+ (Command|Response)'), '_ProcessCommand'),
# The TCG spec in some cases uses so called 'Algorithm macros' to describe
# all algorithms a type should apply to. The macros are described in details
# in section 4.12 of part 2 of the spec.
# Basically, the macro consists of the prefix '!ALG' followed by dot
# separated descriptions of algorithm types this marco applies to.
# The algorithm types are expressed as sequences or lower or upper case
# letters, and should match the third column of Table 9 either inclusively
# (in case the type letters are in upper case, or exclusively, in case the
# type letters are in lower case.
_alg_macro = re.compile(r'!ALG\.([a-z\.]+)', re.IGNORECASE)
def __init__(self):
"""Create a Table class instance."""
self._alg_id_table = []
# Allow re-initializing attributes outside __init__() (in Init())
self._h_file = ''
self._type_map = {}
self._command_map = {}
self.skip_tables = ()
def Init(self, title=''):
"""Initialize a new table.
This function is invoked each time a new table is encountered in the spec.
A typical table header could look like this:
'Table 10 - Definition of (UINT16) {ECC} TPM_ECC_CURVE Constants'
The algorithm type in curly brackets, if present, is redundant, it is
stripped off before the table header comment is generated for the .h file.
Some titles include the parenthesized base type the defined object should
be typedef'ed from.
title: a string, the title of the table as included in the TCG spec.
title_bracket_filter = re.compile(r'({.*?}) ?')
title_type_filter = re.compile(r'(\(.*?\)) ?')
# Retrieve base type, if present in the table title.
m =
if m:
# the header shown in the docstring above would result in the match of
# '(UINT16)', remove the parenthesis and save the base type.
self._title_type = m.groups()[0][1:-1]
self._title = title_type_filter.sub('', title).strip()
self._title_type = ''
self._title = title.strip()
# Now retrieve algorithm type, if present in the table title.
m =
self._alg_type = ''
if m:
self._title = title_bracket_filter.sub('', self._title).strip()
alg_type = m.groups()[0][1:-1].strip()
if not alg_type.startswith('!'):
self._alg_type = alg_type
self._body = []
self._has_selector_column = False
self._skip_printing = False
def _SplitByAlg(self, word):
"""Split the passed in word by the regex used to pick TPM algorithms.
The TPM algorithm regex is used all over the spec in situations where
similar code needs to be generated for different algorithms of a certain
A string in the spec might look like one of the following:
The split would result in a three element list: the part before !ALG
(could be empty), the letters between '!ALG.' and _ or end of the string,
and the part after the letters included in the algorithm regex.
The first and last elements of the split are used as the prefix and suffix
of the type names included in the generated file.
In some cases there is no regex suffix present, only the !ALG string, as
in the selector column in table 127 (TPM_ALG_!ALG) In this case the split
by the !ALG string is attempted, and the generated list has just two
In yet some other cases, say in Table 126 where the type field does not
change at all set to TPMI_ALG_SYM_MODE for all fields. In such cases the
split returns a single element list, the second element set to None is
added to the list.
word: a string, the encoded algorithm string to be split.
a tuple of two strings, first and last elements of the split, either one
could be empty.
parts = self._alg_macro.split(word)
if len(parts) == 1:
parts = word.split('!ALG')
if len(parts) == 1:
return word, None
return parts[0].strip('_'), parts[-1].strip('_')
def SetSkipTables(self, skip_tables):
"""Set the tuple of table numbers to be ignored by the parser."""
self.skip_tables = skip_tables
def _AddToHfile(self, text=''):
self._h_file += text + '\n'
def _SetBaseType(self, old_type, tpm_obj):
"""Set the base type for a new object.
Many TPM objects are typedefed hierarchically, for instance
uint16_t => UINT16 => TPM_ALG_ID_Marshal => TPMI_ALG_HASH_Marshal
This causes excessive nesting when marshaling and unmarshaling, which is
bad from both performance and stack size requirements point of view.
This function will discover the 'basest' type and set it in the tpm
object, this would help to generate direct marshaling/unmarshaling
old_type: a string, name of the immediate type this tpm object typedefed
tpm_obj: a tpm object, derived from TPMType
base_type = old_type
while base_type in self._type_map:
base_type = self._type_map[base_type].old_type
except AttributeError:
break # The underlying type is not a typedef
def _AddTypedef(self, old_type, new_type):
if not self._skip_printing:
self._AddToHfile('typedef %s %s;' % (old_type, new_type))
# No need to generate marshaling/unmarshaling code for BOOL type.
if new_type != 'BOOL':
self._type_map[new_type] = Typedef(old_type, new_type)
self._SetBaseType(old_type, self._type_map[new_type])
def InProgress(self):
"""Return True when the parser is in the middle of a table."""
return self._title
def _GetMaxLengths(self, table):
"""Find out maximum lengths of the table columns.
This function helps to generate nicely aligned definitions in the output
.h file, by making sure that each field's name starts in the same column,
far enough for all fields' types to fit.
table: a list of string lists. Each component consists of at least two
elements, the first one the field or constant type, the
second one the field name or constant value.
a tuple of two integers, the first one - the length of the longest
string in the first colume, the second one - the length of the
longest string in the second column.
lengths = [0, 0]
for row in table:
for i in range(len(lengths)):
if len(row[i]) > lengths[i]:
lengths[i] = len(row[i])
return tuple(lengths)
def NewRow(self):
"""Start a new row in the internal table representation."""
def NewCell(self):
"""Start a new cell in the last row."""
def AddData(self, data):
"""Add data to the last cell of the last row."""
if not self._body:
return # Ignore end of line and whitespace formatting.
self._body[-1][-1] += data
def ProcessTable(self):
"""Process accumulated table contents.
This function is invoked when the parser state machine detects that the
entire HTML table has been processed. The received contents is handled
based on the table title by finding the matching entry in TABLE_ROUTER
The result of processing is adding a new TPM type to the _type_map
dictionary, or a new command descriptor to the _command_map dictionary.
# The table has a selector column if it has at least three columns, and
# the third column is titled 'Selector'.
self._has_selector_column = (len(self._body[0]) >= 3 and
self._body[0][2].strip() == 'Selector')
# Preprocess representation of the table scraped from the spec. Namely,
# remove the header row, and strip all other cells before adding them to
# self._body[], which becomes a list including all scraped table cells,
# stripped.
self._body = [[cell.strip() for cell in row] for row in self._body[1:]]
if 'TPM_ALG_ID Constants' in self._title:
self._alg_id_table = [[x[0], x[2].replace(' ', ''), x[3]]
for x in self._body]
# The name of the type defined in the table, when present, is always the
# fifth element in the stripped header, for instance:
# 'Table 10 - Definition of TPM_ECC_CURVE Constants'
type_name = self._title.split()[4]
except IndexError:
type_name = ''
# Based on the table title, find the function to process the table and
# generate a TPM specification object of a certain type.
table_func = ''
for regex, func in self.TABLE_ROUTER:
table_func = func
self._AddToHfile('// Unprocessed: %s' % self._title)
if int(self._title.split()[1]) in self.skip_tables:
self._skip_printing = True
self._AddToHfile('// Skipped: %s' % self._title)
self._AddToHfile('// %s' % self._title)
# Invoke a TPM type specific processing function.
getattr(self, table_func)(type_name)
def _ProcessCommand(self, _):
"""Process command description table from part 3.
Each TPM command has two tables associated with it, one describing the
request structure, and another one describing the response structure. The
first time a TPM command is encountered, a Command object is created and
its 'request_args' property is set, the second time it is encountered -
the existing object's 'response_args' property is set.
command_name = self._title.split()[2]
if command_name not in self._command_map:
command = Command(command_name)
self._command_map[command_name] = command
command = self._command_map[command_name]
params = []
# The first three fields in each request and response are always the same
# and are not included in the generated structures. Let's iterate over the
# rest of the fields.
for row in self._body[3:]:
# A dictionary describing a request or response structure field.
field = {}
# Ignore the '@' decoration for now.
field_type, field['name'] = row[0], row[1].lstrip('@')
# The '+' decoration means this field can be conditional.
if field_type.endswith('+'):
field_type = field_type[:-1]
field['has_conditional'] = 'TRUE'
field['has_conditional'] = 'FALSE'
field['type'] = field_type
if len(row) > 2:
field['description'] = row[2]
field['description'] = ''
# Add the new field to the list of request or response fields.
if ' Command' in self._title:
command.request_args = params
command.response_args = params
def _PickAlgEntries(self, alg_type_str):
"""Process algorithm id table and find all matching entries.
See comments to _alg_macro above.
alg_type_str: a string, one or more dot separated encoded algorithm types.
A table of alg_type (Table 9 of part 2) entries matching the passed in
encoded type string.
filtered_table = []
for alg_type in alg_type_str.split('.'):
if re.match('^[A-Z]+$', alg_type):
# Exclusive selection, must exactly match algorithm type from table 9
# (which is in the second column). Add to the return value all
# matching rows of table 9.
extension = []
for row in self._alg_id_table:
if row[1] == alg_type:
if self._alg_type and self._alg_type != row[2]:
elif re.match('^[a-z]+$', alg_type):
# Inclusive selection. All type letters must be present in the type
# column, but no exact match is required.
for row in self._alg_id_table:
for char in alg_type.upper():
if char not in row[1]:
if not self._alg_type or self._alg_type == row[2]:
return filtered_table
def _ParseAlgorithmRegex(self, token):
"""Process a token as an algorithm regex.
This function tries to interpret the passed in token as an encoded
algorithm specification.
In case the encoded algorithm regex matches, the function splits the token
into prefix, algorithm description and suffix, and then retrieves the list
of all algorithms matching the algorithm description.
token: a string, potentially including the algorithm regex.
in case the regex matched returns a tri-tuple of two strings (prefix and
suffix, either one could be empty) and a list of matching algorithms
from the algorithm descriptors table. If there has been no match -
returns None.
elements = self._alg_macro.split(token)
if len(elements) == 3:
# The token matched the algorithm regex, Find out prefix and suffix to
# be used on the generated type names, and the algorithm regex suffix to
# use to find matching entries in the algorithm table.
name_prefix, alg_suffix, name_suffix = tuple(elements)
name_prefix = name_prefix.strip('_')
name_suffix = name_suffix.strip('_')
return name_prefix, name_suffix, self._PickAlgEntries(alg_suffix)
def _ProcessInterface(self, type_name):
"""Processes spec tables describing interfaces."""
result = self._ParseAlgorithmRegex(type_name)
if result:
name_prefix, name_suffix, alg_list = tuple(result)
# Process all matching algorithm types
for alg_desc in alg_list:
alg_base = alg_desc[0].replace('TPM_ALG_', '')
new_name = '_'.join([name_prefix,
alg_base, name_suffix]).strip('_')
new_if = Interface(self._title_type, new_name)
self._AddTypedef(self._title_type, new_name)
for row in self._body:
new_value = row[0]
if new_value.startswith('$!ALG'):
new_if.supported_values = alg_base + '_' + '_'.join(
elif new_value.startswith('$'):
new_if.supported_values = new_value[1:]
elif new_value.startswith('#'):
new_if.error_code = new_value[1:]
self._type_map[new_name] = new_if
new_if = Interface(self._title_type, type_name)
self._AddTypedef(self._title_type, type_name)
self._type_map[type_name] = new_if
self._SetBaseType(type_name, new_if)
for row in self._body:
new_value = row[0]
result = self._ParseAlgorithmRegex(new_value)
if result:
# The field is described using the algorithm regex. The above comment
# applies.
name_prefix, name_suffix, alg_list = tuple(result)
for alg_desc in alg_list:
alg_base = alg_desc[0].replace('TPM_ALG_', '')
[name_prefix, alg_base, name_suffix]).strip('_'))
if new_value.startswith('{'):
bounds = tuple(
[x.strip() for x in new_value[1:-1].strip().split(':')])
elif new_value.startswith('+'):
new_if.conditional_value = new_value[1:]
elif new_value.startswith('#'):
new_if.error_code = new_value[1:]
elif new_value.startswith('$'):
new_if.supported_values = new_value[1:]
def _ProcessTypedefs(self, type_name):
"""Processes spec tables defining new types."""
result = self._ParseAlgorithmRegex(type_name)
if result:
name_prefix, name_suffix, alg_list = tuple(result)
for alg_desc in alg_list:
alg_base = alg_desc[0].replace('TPM_ALG_', '')
new_type = '%s_%s_%s' % (name_prefix, alg_base, name_suffix)
self._AddTypedef(self._title_type, new_type)
self._AddTypedef(self._title_type, type_name)
def _ProcessBits(self, type_name):
"""Processes spec tables describing attributes (bit fields)."""
bits_lines = []
base_bit = 0
tpm_obj = AttributeStructure(self._title_type, type_name)
self._type_map[type_name] = tpm_obj
self._SetBaseType(self._title_type, tpm_obj)
for bits_line in self._body:
field, name = tuple(bits_line[:2])
if not field:
if name.startswith('TPM_'):
# Spec inconsistency fix.
name_pieces = [x.lower() for x in name.split('_')[1:]]
name = name_pieces[0]
for piece in name_pieces[1:]:
name += piece[0].upper() + piece[1:]
bit_range = [x.replace(' ', '') for x in field.split(':')]
field_base = int(bit_range[-1])
if field_base != base_bit:
field_name = 'reserved%d' % base_bit
field_width = field_base - base_bit
if field_width > 1:
field_name += '_%d' % (field_base - 1)
bits_lines.append(['%s : %d' % (field_name, field_width)])
tpm_obj.reserved.append(field_name.replace('reserved', ''))
if len(bit_range) > 1:
field_width = int(bit_range[0]) - field_base + 1
field_width = 1
if re.match('reserved', name, re.IGNORECASE):
name = 'reserved%d' % field_base
if field_width > 1:
name += '_%d' % (field_base + field_width - 1)
tpm_obj.reserved.append(name.replace('reserved', ''))
bits_lines.append([name, ': %d' % field_width])
base_bit = field_base + field_width
max_type_len, _ = self._GetMaxLengths(bits_lines)
self._AddToHfile('typedef struct {')
for bits_line in bits_lines:
self._AddToHfile(' %s %-*s %s;' % (self._title_type, max_type_len,
bits_line[0], bits_line[1]))
self._AddToHfile('} %s;\n' % type_name)
def _ExpandAlgs(self, row):
"""Find all algorithms encoded in the variable name.
row: a list of strings, a row of a structure or union table scraped from
part 2.
A list for structure_generator.Field objects, one per expanded
alg_spec = row[0].split()
expansion = []
m =[0])
if m:
alg_type = m.groups()[0]
# Find all algorithms of this type in the alg id table
alg_entries = self._PickAlgEntries(alg_type)
if len(alg_spec) == 2 and alg_spec[1][0] == '[':
# This is the case of a union of arrays.
raw_size_parts = self._alg_macro.split(alg_spec[1][1:-1])
size_prefix = raw_size_parts[0].strip('_')
size_suffix = raw_size_parts[2].strip('_')
for alg_desc in alg_entries:
alg_base = alg_desc[0].replace('TPM_ALG_', '')
size = '_'.join([size_prefix, alg_base, size_suffix]).strip('_')
if self._has_selector_column:
selector_parts = self._alg_macro.split(row[2])
selector_prefix = selector_parts[0].strip('_')
selector_suffix = selector_parts[2].strip('_')
selector = '_'.join([selector_prefix,
alg_base, selector_suffix]).strip('_')
selector = ''
expansion.append(Field(row[1], alg_base.lower(),
selector=selector, array_size=size))
type_prefix, type_suffix = self._SplitByAlg(row[1])
if self._has_selector_column:
selector_prefix, selector_suffix = self._SplitByAlg(row[2])
selector = ''
for alg_desc in alg_entries:
alg_base = alg_desc[0].replace('TPM_ALG_', '')
if type_suffix is not None:
var_type = '_'.join([type_prefix, alg_base, type_suffix]).strip('_')
var_type = type_prefix
if self._has_selector_column:
selector = '_'.join([selector_prefix, alg_base,
expansion.append(Field(var_type, alg_base.lower(),
return expansion
def _ProcessInterfaceOrType(self, type_name):
if type_name.startswith('TPMI_'):
def _StructOrUnionToHfile(self, body_fields, type_name, union_mode, tpm_obj):
body_lines = []
for field in body_fields:
body_lines.append([field.field_type, field.field_name])
if field.array_size:
body_lines[-1][-1] += '[%s]' % field.array_size
if field.selector_value:
max_type_len, _ = self._GetMaxLengths(body_lines)
tpm2b_mode = type_name.startswith('TPM2B_')
space_prefix = ''
if union_mode:
self._AddToHfile('typedef union {')
if tpm2b_mode:
self._AddToHfile('typedef union {')
space_prefix = ' '
self._AddToHfile(' struct {')
self._AddToHfile('typedef struct {')
for line in body_lines:
guard_required = len(line) > 2 and line[2].startswith('TPM_ALG_')
if not line[1]:
if guard_required:
self._AddToHfile('#ifdef %s' % line[2])
self._AddToHfile(space_prefix + ' %-*s %s;' % (
max_type_len, line[0], line[1]))
if guard_required:
if tpm2b_mode:
self._AddToHfile(' } t;')
self._AddToHfile(' TPM2B b;')
self._AddToHfile('} %s;\n' % type_name)
self._type_map[type_name] = tpm_obj
def _ProcessStructureOrUnion(self, type_name):
"""Processes spec tables describing structure and unions.
Both of these object types share a lot of similarities. Union types have
the word 'Union' in the table title.
type_name: a string, name of the TPM object type
union_mode = 'Union' in self._title
if union_mode:
tpm_obj = Union(type_name)
tpm_obj = Structure(type_name)
body_fields = []
for row in self._body:
if row[0].startswith('#'):
tpm_obj.error_code = row[0][1:]
if (len(row) < 2 or
row[1].startswith('#') or
value = row[0]
if value.endswith('='):
value = value[:-1]
tpm_obj.size_check = True
array_size = None
run_time_size = None
vm ='^(\S+)\s*\[(\S+)\]\s*\{(.*:*)\}', value)
selector = ''
if vm:
value, run_time_size, bounds = vm.groups()
lower, upper = [x.strip() for x in bounds.split(':')]
if upper:
array_size = upper
tpm_obj.AddUpperBound(run_time_size, upper)
array_size = run_time_size
if lower:
tpm_obj.AddLowerBound(run_time_size, lower)
vm ='^\[(\S+)\]\s*(\S+)', value)
if vm:
selector, value = vm.groups()
vm ='^(\S+)\s*\{(.+)\}', value)
if vm:
value, bounds = vm.groups()
if ':' in bounds:
lower, upper = [x.strip() for x in bounds.split(':')]
if upper:
tpm_obj.AddUpperBound(value, upper)
if lower:
tpm_obj.AddLowerBound(value, lower)
if self._has_selector_column:
selector = row[2]
field_type = row[1]
if field_type.startswith('+') or field_type.endswith('+'):
field_type = field_type.strip('+')
conditional = 'TRUE'
conditional = 'FALSE'
if field_type or value:
self._StructOrUnionToHfile(body_fields, type_name, union_mode, tpm_obj)
def _ProcessEnum(self, _):
"""Processes spec tables describing enums."""
if self._skip_printing:
self._AddToHfile('enum {')
for value, row in enumerate(self._body):
self._AddToHfile(' %s = %d,' % (row[0], value + 1))
def _ProcessTypes(self, _):
"""Processes spec tables describing new types."""
for type_, name_, _ in self._body:
m =
if not m:
self._AddTypedef(type_, name_)
qualifier = [x for x in ('ECC', 'RSA') if x == type_.split('_')[-1]]
alg_suffix = m.groups()[0]
type_base = self._alg_macro.split(name_)[0]
for alg_desc in self._PickAlgEntries(alg_suffix):
if qualifier and alg_desc[2] != qualifier[0]:
self._AddTypedef(type_, alg_desc[0].replace('TPM_ALG_', type_base))
def _ProcessConstants(self, type_name):
"""Processes spec tables describing constants."""
if self._title_type:
self._AddTypedef(self._title_type, type_name)
constant_defs = []
tpm_obj = ConstantType(self._title_type, type_name)
for row in self._body:
name = row[0].strip()
if not name:
if name.startswith('#'):
tpm_obj.error_code = name[1:]
if name == 'reserved' or len(row) < 2:
value = row[1].strip()
rm = re.match(r'^(\(.*?\))', value)
if rm:
value = '%s' % rm.groups()[0]
v_list = value.split()
if len(v_list) > 2 and v_list[1] == '+':
value = '((%s)(%s))' % (type_name, ' '.join(v_list[:3]))
if ' ' in value and not value.startswith('('):
value = '(%s)' % value
constant_defs.append([name, value])
if self._title_type:
self._type_map[type_name] = tpm_obj
self._SetBaseType(self._title_type, tpm_obj)
if self._skip_printing:
max_name_len, max_value_len = self._GetMaxLengths(constant_defs)
for row in constant_defs:
self._AddToHfile('#define %-*s %*s' % (max_name_len, row[0],
max_value_len, row[1]))
def GetHFile(self):
return self._h_file
def GetCommandList(self):
return sorted(self._command_map.values(),
cmp=lambda x, y: cmp(,
def GetTypeMap(self):
return self._type_map