831 lines
32 KiB
Python
831 lines
32 KiB
Python
"""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
|
|
processing.
|
|
"""
|
|
|
|
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:
|
|
*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
|
|
code).
|
|
|
|
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
|
|
rows.
|
|
|
|
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.
|
|
|
|
Attributes:
|
|
|
|
_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
|
|
table
|
|
_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.
|
|
TABLE_ROUTER = (
|
|
(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)'),
|
|
'_ProcessStructureOrUnion'),
|
|
(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.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.
|
|
|
|
Args:
|
|
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 = title_type_filter.search(title)
|
|
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()
|
|
else:
|
|
self._title_type = ''
|
|
self._title = title.strip()
|
|
# Now retrieve algorithm type, if present in the table title.
|
|
m = title_bracket_filter.search(self._title)
|
|
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
|
|
type.
|
|
|
|
A string in the spec might look like one of the following:
|
|
TPMI_!ALG.S_KEY_BITS or !ALG.S_KEY_SIZES_BITS.
|
|
|
|
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.
|
|
|
|
TPMI_!ALG.S_KEY_BITS => ['TPMI_', 'S', '_KEY_BITS']
|
|
!ALG.S_KEY_SIZES_BITS => ['', 'S', '_KEY_SIZES_BITS']
|
|
|
|
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
|
|
elements.
|
|
|
|
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.
|
|
|
|
Args:
|
|
word: a string, the encoded algorithm string to be split.
|
|
|
|
Returns:
|
|
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
|
|
functions.
|
|
|
|
Args:
|
|
old_type: a string, name of the immediate type this tpm object typedefed
|
|
from.
|
|
tpm_obj: a tpm object, derived from TPMType
|
|
"""
|
|
base_type = old_type
|
|
while base_type in self._type_map:
|
|
try:
|
|
base_type = self._type_map[base_type].old_type
|
|
except AttributeError:
|
|
break # The underlying type is not a typedef
|
|
tpm_obj.SetBaseType(base_type)
|
|
|
|
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.
|
|
|
|
Args:
|
|
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.
|
|
|
|
Returns:
|
|
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."""
|
|
self._body.append([])
|
|
|
|
def NewCell(self):
|
|
"""Start a new cell in the last row."""
|
|
self._body[-1].append('')
|
|
|
|
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
|
|
tuple.
|
|
|
|
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'
|
|
try:
|
|
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:
|
|
if regex.search(self._title):
|
|
table_func = func
|
|
break
|
|
else:
|
|
self._AddToHfile('// Unprocessed: %s' % self._title)
|
|
return
|
|
|
|
if int(self._title.split()[1]) in self.skip_tables:
|
|
self._skip_printing = True
|
|
self._AddToHfile('// Skipped: %s' % self._title)
|
|
else:
|
|
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
|
|
else:
|
|
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'
|
|
else:
|
|
field['has_conditional'] = 'FALSE'
|
|
field['type'] = field_type
|
|
if len(row) > 2:
|
|
field['description'] = row[2]
|
|
else:
|
|
field['description'] = ''
|
|
# Add the new field to the list of request or response fields.
|
|
params.append(field)
|
|
if ' Command' in self._title:
|
|
command.request_args = params
|
|
else:
|
|
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.
|
|
|
|
Args:
|
|
alg_type_str: a string, one or more dot separated encoded algorithm types.
|
|
|
|
Returns:
|
|
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]:
|
|
continue
|
|
extension.append(row)
|
|
filtered_table.extend(extension)
|
|
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]:
|
|
break
|
|
else:
|
|
if not self._alg_type or self._alg_type == row[2]:
|
|
filtered_table.append(row)
|
|
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.
|
|
|
|
Args:
|
|
token: a string, potentially including the algorithm regex.
|
|
|
|
Returns:
|
|
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(
|
|
new_value.split('_')[1:])
|
|
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
|
|
self._AddToHfile('\n')
|
|
return
|
|
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_', '')
|
|
new_if.valid_values.append('_'.join(
|
|
[name_prefix, alg_base, name_suffix]).strip('_'))
|
|
else:
|
|
if new_value.startswith('{'):
|
|
bounds = tuple(
|
|
[x.strip() for x in new_value[1:-1].strip().split(':')])
|
|
new_if.bounds.append(bounds)
|
|
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:]
|
|
else:
|
|
new_if.valid_values.append(new_value)
|
|
return
|
|
|
|
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._AddToHfile('\n')
|
|
else:
|
|
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:
|
|
continue
|
|
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
|
|
else:
|
|
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.
|
|
|
|
Args:
|
|
row: a list of strings, a row of a structure or union table scraped from
|
|
part 2.
|
|
|
|
Returns:
|
|
A list for structure_generator.Field objects, one per expanded
|
|
algorithm.
|
|
|
|
"""
|
|
alg_spec = row[0].split()
|
|
expansion = []
|
|
m = self._alg_macro.search(alg_spec[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('_')
|
|
else:
|
|
selector = ''
|
|
expansion.append(Field(row[1], alg_base.lower(),
|
|
selector=selector, array_size=size))
|
|
else:
|
|
type_prefix, type_suffix = self._SplitByAlg(row[1])
|
|
if self._has_selector_column:
|
|
selector_prefix, selector_suffix = self._SplitByAlg(row[2])
|
|
else:
|
|
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('_')
|
|
else:
|
|
var_type = type_prefix
|
|
if self._has_selector_column:
|
|
selector = '_'.join([selector_prefix, alg_base,
|
|
selector_suffix]).strip('_')
|
|
expansion.append(Field(var_type, alg_base.lower(),
|
|
selector=selector))
|
|
return expansion
|
|
|
|
def _ProcessInterfaceOrType(self, type_name):
|
|
if type_name.startswith('TPMI_'):
|
|
self._ProcessInterface(type_name)
|
|
else:
|
|
self._ProcessTypedefs(type_name)
|
|
|
|
def _StructOrUnionToHfile(self, body_fields, type_name, union_mode, tpm_obj):
|
|
body_lines = []
|
|
for field in body_fields:
|
|
tpm_obj.AddField(field)
|
|
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:
|
|
body_lines[-1].append(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 {')
|
|
else:
|
|
if tpm2b_mode:
|
|
self._AddToHfile('typedef union {')
|
|
space_prefix = ' '
|
|
self._AddToHfile(' struct {')
|
|
else:
|
|
self._AddToHfile('typedef struct {')
|
|
for line in body_lines:
|
|
guard_required = len(line) > 2 and line[2].startswith('TPM_ALG_')
|
|
if not line[1]:
|
|
continue
|
|
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:
|
|
self._AddToHfile('#endif')
|
|
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.
|
|
|
|
Args:
|
|
type_name: a string, name of the TPM object type
|
|
"""
|
|
union_mode = 'Union' in self._title
|
|
if union_mode:
|
|
tpm_obj = Union(type_name)
|
|
else:
|
|
tpm_obj = Structure(type_name)
|
|
body_fields = []
|
|
for row in self._body:
|
|
if row[0].startswith('#'):
|
|
tpm_obj.error_code = row[0][1:]
|
|
continue
|
|
if (len(row) < 2 or
|
|
row[1].startswith('#') or
|
|
row[0].startswith('//')):
|
|
continue
|
|
value = row[0]
|
|
if value.endswith('='):
|
|
value = value[:-1]
|
|
tpm_obj.size_check = True
|
|
if self._alg_macro.search(value):
|
|
body_fields.extend(self._ExpandAlgs(row))
|
|
continue
|
|
array_size = None
|
|
run_time_size = None
|
|
vm = re.search(r'^(\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)
|
|
else:
|
|
array_size = run_time_size
|
|
if lower:
|
|
tpm_obj.AddLowerBound(run_time_size, lower)
|
|
else:
|
|
vm = re.search(r'^\[(\S+)\]\s*(\S+)', value)
|
|
if vm:
|
|
selector, value = vm.groups()
|
|
else:
|
|
vm = re.search(r'^(\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'
|
|
else:
|
|
conditional = 'FALSE'
|
|
if field_type or value:
|
|
body_fields.append(Field(field_type,
|
|
value,
|
|
array_size=array_size,
|
|
run_time_size=run_time_size,
|
|
selector=selector,
|
|
conditional_value=conditional))
|
|
|
|
self._StructOrUnionToHfile(body_fields, type_name, union_mode, tpm_obj)
|
|
|
|
def _ProcessEnum(self, _):
|
|
"""Processes spec tables describing enums."""
|
|
if self._skip_printing:
|
|
return
|
|
self._AddToHfile('enum {')
|
|
for value, row in enumerate(self._body):
|
|
self._AddToHfile(' %s = %d,' % (row[0], value + 1))
|
|
self._AddToHfile('};\n')
|
|
|
|
def _ProcessTypes(self, _):
|
|
"""Processes spec tables describing new types."""
|
|
for type_, name_, _ in self._body:
|
|
m = self._alg_macro.search(name_)
|
|
if not m:
|
|
self._AddTypedef(type_, name_)
|
|
continue
|
|
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]:
|
|
continue
|
|
self._AddTypedef(type_, alg_desc[0].replace('TPM_ALG_', type_base))
|
|
self._AddToHfile()
|
|
|
|
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:
|
|
continue
|
|
if name.startswith('#'):
|
|
tpm_obj.error_code = name[1:]
|
|
continue
|
|
if name == 'reserved' or len(row) < 2:
|
|
continue
|
|
value = row[1].strip()
|
|
rm = re.match(r'^(\(.*?\))', value)
|
|
if rm:
|
|
value = '%s' % rm.groups()[0]
|
|
else:
|
|
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])
|
|
tpm_obj.valid_values.append(name)
|
|
if self._title_type:
|
|
self._type_map[type_name] = tpm_obj
|
|
self._SetBaseType(self._title_type, tpm_obj)
|
|
if self._skip_printing:
|
|
return
|
|
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]))
|
|
self._AddToHfile()
|
|
|
|
def GetHFile(self):
|
|
return self._h_file
|
|
|
|
def GetCommandList(self):
|
|
return sorted(self._command_map.values(),
|
|
cmp=lambda x, y: cmp(x.name, y.name))
|
|
|
|
def GetTypeMap(self):
|
|
return self._type_map
|