1"""Module for processing TCG TPM2 library object descriptions.
2
3The descriptions are scraped from the tables of parts 2 and 3 of the
4specification by a different module and fed through this module for
5processing.
6"""
7
8from __future__ import print_function
9
10import re
11import sys
12
13from command_generator import Command
14from structure_generator import AttributeStructure
15from structure_generator import ConstantType
16from structure_generator import Field
17from structure_generator import Interface
18from structure_generator import Structure
19from structure_generator import Typedef
20from structure_generator import Union
21
22
23def _DebugLog(*args, **kwargs):
24  """When used - sends its inputs to stderr.
25
26  This function can be used when debugging this module. Its footprint is
27  similar to print(), but the default destination is sys.stderr, which is
28  handy when the script generates stdio output redirected into a file.
29
30  Args:
31    *args: a list of items of various types to print. Printed space separated,
32           each one converted to str before being printed.
33    **kwargs: a dictionary of variables to pass to print(), if any. In fact the
34              only key this function cares about is 'endl', which allows to
35              suppress adding a newline to the printed string.
36  """
37  endl = kwargs.get('endl', '\n')
38  print(' '.join(str(x) for x in args), end=endl, file=sys.stderr)
39
40
41class Table(object):
42  """Representation of TCG TPM2 library specification tables.
43
44  The purpose of this class is to both generate new TPM2 objects and to keep
45  track of the previously generated objects for post processing (generating C
46  code).
47
48  The HTML scraper finds tables in the specifications and builds up the
49  tables' contents in this object, one at a time. This object's table
50  representation includes table title, table header and one or more then table
51  rows.
52
53  The table title must start with 'Table ### xxx', where ### is monotonously
54  increasing table number and xxx is some description allowing to deduce the
55  type of the object defined by this table.
56
57  The cells of the table include names and types of the components, various
58  decorations are used to convey additional information: array boundaries,
59  values' limits, return values, selectors, etc, etc.
60
61  Once the entire table is scraped, the scraper invokes a method to process it
62  (ProcessTable). The title of the table is examined by this module and the
63  appropriate processing method is invoked to actually convert the internal
64  table representation into a TPM2 object.
65
66  Two maps are maintained persistently over the life time of this object, the
67  map of types (keyed by the type name scraped from part 2) and map of
68  commands (keyed by the command name, scraped from part 3).
69
70  One other thing this module produces is the text for the .h file defining
71  all structures and types this module encountered.
72
73  Attributes:
74
75    _alg_id_table: actual table of various TPM2 algorithms, a copy of Table 9
76                   from part 2. It is used to convert encoded algorithm specs
77                   used in other tables into a list of matching algorithms.
78    _h_file: a multiline string, the accumulated .h file defining all TPM
79                   objects processed so far.
80    _type_map: a dictionary of various TPM types, keyed by the string - the
81                   type name
82    _command_map: a dictionary of command_generator.Command objects, keyed by
83                   the string, the command name
84    skip_tables: a tuple of integers, the numbers of tables which should not
85                   be included in the .h file, as the same information was
86                   derived from part 4 earlier.
87    _title: a string, title of the currently being processed specification
88                   table
89    _title_type: a string, base type of the object defined by the currently
90                   being processed specification table
91    _alg_type: a string, in some tables included in the title in curly
92                   brackets, to indicate what type of the algorithm this table
93                   deals with (usually RSA or ECC)
94    _body: a list of strings, rows of the currently being processed
95                   specification table
96    _has_selector_column: a Boolean, set to True if the third column of the
97                   table is the selector to be used to process this row (as in
98                   picking the object type when the table represents a union)
99    _skip_printing: a Boolean, set to True if the table contents should not be
100                   included on tpm_types.h - some tables are also defined in
101                   files extracted from Part 4 of the specification.
102
103  """
104
105  # Match table titles with processing routines.
106  TABLE_ROUTER = (
107      (re.compile('(Constants|Defines for Logic Values)'), '_ProcessConstants'),
108      (re.compile('(of Types for|Base Types)'), '_ProcessTypes'),
109      (re.compile('Definition of .* Type'), '_ProcessInterfaceOrType'),
110      (re.compile('Unmarshaling Errors'), '_ProcessEnum'),
111      (re.compile(r'Definition of [\S]+ (Structure|Union)'),
112       '_ProcessStructureOrUnion'),
113      (re.compile('Definition of .* Bits'), '_ProcessBits'),
114      (re.compile(r' TPM2_\S+ (Command|Response)'), '_ProcessCommand'),
115  )
116
117
118  # The TCG spec in some cases uses so called 'Algorithm macros' to describe
119  # all algorithms a type should apply to. The macros are described in details
120  # in section 4.12 of part 2 of the spec.
121  #
122  # Basically, the macro consists of the prefix '!ALG' followed by dot
123  # separated descriptions of algorithm types this marco applies to.
124  #
125  # The algorithm types are expressed as sequences or lower or upper case
126  # letters, and should match the third column of Table 9 either inclusively
127  # (in case the type letters are in upper case, or exclusively, in case the
128  # type letters are in lower case.
129  _alg_macro = re.compile(r'!ALG\.([a-z\.]+)', re.IGNORECASE)
130
131  def __init__(self):
132    """Create a Table class instance."""
133    self._alg_id_table = []
134    # Allow re-initializing attributes outside __init__() (in Init())
135    self.Init()
136    self._h_file = ''
137    self._type_map = {}
138    self._command_map = {}
139    self.skip_tables = ()
140
141  def Init(self, title=''):
142    """Initialize a new table.
143
144    This function is invoked each time a new table is encountered in the spec.
145
146    A typical table header could look like this:
147
148    'Table 10 - Definition of (UINT16) {ECC} TPM_ECC_CURVE Constants'
149
150    The algorithm type in curly brackets, if present, is redundant, it is
151    stripped off before the table header comment is generated for the .h file.
152
153    Some titles include the parenthesized base type the defined object should
154    be typedef'ed from.
155
156    Args:
157      title: a string, the title of the table as included in the TCG spec.
158    """
159    title_bracket_filter = re.compile(r'({.*?}) ?')
160    title_type_filter = re.compile(r'(\(.*?\)) ?')
161    # Retrieve base type, if present in the table title.
162    m = title_type_filter.search(title)
163    if m:
164      # the header shown in the docstring above would result in the match of
165      # '(UINT16)', remove the parenthesis and save the base type.
166      self._title_type = m.groups()[0][1:-1]
167      self._title = title_type_filter.sub('', title).strip()
168    else:
169      self._title_type = ''
170      self._title = title.strip()
171    # Now retrieve algorithm type, if present in the table title.
172    m = title_bracket_filter.search(self._title)
173    self._alg_type = ''
174    if m:
175      self._title = title_bracket_filter.sub('', self._title).strip()
176      alg_type = m.groups()[0][1:-1].strip()
177      if not alg_type.startswith('!'):
178        self._alg_type = alg_type
179    self._body = []
180    self._has_selector_column = False
181    self._skip_printing = False
182
183  def _SplitByAlg(self, word):
184    """Split the passed in word by the regex used to pick TPM algorithms.
185
186    The TPM algorithm regex is used all over the spec in situations where
187    similar code needs to be generated for different algorithms of a certain
188    type.
189
190    A string in the spec might look like one of the following:
191    TPMI_!ALG.S_KEY_BITS or !ALG.S_KEY_SIZES_BITS.
192
193    The split would result in a three element list: the part before !ALG
194    (could be empty), the letters between '!ALG.' and _ or end of the string,
195    and the part after the letters included in the algorithm regex.
196
197    TPMI_!ALG.S_KEY_BITS => ['TPMI_', 'S', '_KEY_BITS']
198    !ALG.S_KEY_SIZES_BITS => ['', 'S', '_KEY_SIZES_BITS']
199
200    The first and last elements of the split are used as the prefix and suffix
201    of the type names included in the generated file.
202
203    In some cases there is no regex suffix present, only the !ALG string, as
204    in the selector column in table 127 (TPM_ALG_!ALG) In this case the split
205    by the !ALG string is attempted, and the generated list has just two
206    elements.
207
208    In yet some other cases, say in Table 126 where the type field does not
209    change at all set to TPMI_ALG_SYM_MODE for all fields. In such cases the
210    split returns a single element list, the second element set to None is
211    added to the list.
212
213    Args:
214      word: a string, the encoded algorithm string to be split.
215
216    Returns:
217      a tuple of two strings, first and last elements of the split, either one
218      could be empty.
219
220    """
221    parts = self._alg_macro.split(word)
222    if len(parts) == 1:
223      parts = word.split('!ALG')
224      if len(parts) == 1:
225        return word, None
226    return parts[0].strip('_'), parts[-1].strip('_')
227
228  def SetSkipTables(self, skip_tables):
229    """Set the tuple of table numbers to be ignored by the parser."""
230    self.skip_tables = skip_tables
231
232  def _AddToHfile(self, text=''):
233    self._h_file += text + '\n'
234
235  def _SetBaseType(self, old_type, tpm_obj):
236    """Set the base type for a new object.
237
238    Many TPM objects are typedefed hierarchically, for instance
239
240    uint16_t => UINT16 => TPM_ALG_ID_Marshal => TPMI_ALG_HASH_Marshal
241
242    This causes excessive nesting when marshaling and unmarshaling, which is
243    bad from both performance and stack size requirements point of view.
244
245    This function will discover the 'basest' type and set it in the tpm
246    object, this would help to generate direct marshaling/unmarshaling
247    functions.
248
249    Args:
250      old_type: a string, name of the immediate type this tpm object typedefed
251                from.
252      tpm_obj: a tpm object, derived from TPMType
253    """
254    base_type = old_type
255    while base_type in self._type_map:
256      try:
257        base_type = self._type_map[base_type].old_type
258      except AttributeError:
259        break  # The underlying type is not a typedef
260    tpm_obj.SetBaseType(base_type)
261
262  def _AddTypedef(self, old_type, new_type):
263    if not self._skip_printing:
264      self._AddToHfile('typedef %s %s;' % (old_type, new_type))
265    # No need to generate marshaling/unmarshaling code for BOOL type.
266    if new_type != 'BOOL':
267      self._type_map[new_type] = Typedef(old_type, new_type)
268      self._SetBaseType(old_type, self._type_map[new_type])
269
270  def InProgress(self):
271    """Return True when the parser is in the middle of a table."""
272    return self._title
273
274  def _GetMaxLengths(self, table):
275    """Find out maximum lengths of the table columns.
276
277    This function helps to generate nicely aligned definitions in the output
278    .h file, by making sure that each field's name starts in the same column,
279    far enough for all fields' types to fit.
280
281    Args:
282      table: a list of string lists. Each component consists of at least two
283             elements, the first one the field or constant type, the
284             second one the field name or constant value.
285
286    Returns:
287      a tuple of two integers, the first one - the length of the longest
288              string in the first colume, the second one - the length of the
289              longest string in the second column.
290    """
291    lengths = [0, 0]
292    for row in table:
293      for i in range(len(lengths)):
294        if len(row[i]) > lengths[i]:
295          lengths[i] = len(row[i])
296    return tuple(lengths)
297
298  def NewRow(self):
299    """Start a new row in the internal table representation."""
300    self._body.append([])
301
302  def NewCell(self):
303    """Start a new cell in the last row."""
304    self._body[-1].append('')
305
306  def AddData(self, data):
307    """Add data to the last cell of the last row."""
308    if not self._body:
309      return  # Ignore end of line and whitespace formatting.
310    self._body[-1][-1] += data
311
312  def ProcessTable(self):
313    """Process accumulated table contents.
314
315    This function is invoked when the parser state machine detects that the
316    entire HTML table has been processed. The received contents is handled
317    based on the table title by finding the matching entry in TABLE_ROUTER
318    tuple.
319
320    The result of processing is adding a new TPM type to the _type_map
321    dictionary, or a new command descriptor to the _command_map dictionary.
322    """
323
324    # The table has a selector column if it has at least three columns, and
325    # the third column is titled 'Selector'.
326    self._has_selector_column = (len(self._body[0]) >= 3 and
327                                 self._body[0][2].strip() == 'Selector')
328    # Preprocess representation of the table scraped from the spec. Namely,
329    # remove the header row, and strip all other cells before adding them to
330    # self._body[], which becomes a list including all scraped table cells,
331    # stripped.
332    self._body = [[cell.strip() for cell in row] for row in self._body[1:]]
333    if 'TPM_ALG_ID Constants' in self._title:
334      self._alg_id_table = [[x[0], x[2].replace(' ', ''), x[3]]
335                            for x in self._body]
336
337    # The name of the type defined in the table, when present, is always the
338    # fifth element in the stripped header, for instance:
339    # 'Table 10 - Definition of TPM_ECC_CURVE Constants'
340    try:
341      type_name = self._title.split()[4]
342    except IndexError:
343      type_name = ''
344
345    # Based on the table title, find the function to process the table and
346    # generate a TPM specification object of a certain type.
347    table_func = ''
348    for regex, func in self.TABLE_ROUTER:
349      if regex.search(self._title):
350        table_func = func
351        break
352    else:
353      self._AddToHfile('// Unprocessed: %s' % self._title)
354      return
355
356    if int(self._title.split()[1]) in self.skip_tables:
357      self._skip_printing = True
358      self._AddToHfile('// Skipped: %s' % self._title)
359    else:
360      self._AddToHfile('// %s' % self._title)
361
362    # Invoke a TPM type specific processing function.
363    getattr(self, table_func)(type_name)
364
365  def _ProcessCommand(self, _):
366    """Process command description table from part 3.
367
368    Each TPM command has two tables associated with it, one describing the
369    request structure, and another one describing the response structure. The
370    first time a TPM command is encountered, a Command object is created and
371    its 'request_args' property is set, the second time it is encountered -
372    the existing object's 'response_args' property is set.
373    """
374    command_name = self._title.split()[2]
375    if command_name not in self._command_map:
376      command = Command(command_name)
377      self._command_map[command_name] = command
378    else:
379      command = self._command_map[command_name]
380    params = []
381    # The first three fields in each request and response are always the same
382    # and are not included in the generated structures. Let's iterate over the
383    # rest of the fields.
384    for row in self._body[3:]:
385      # A dictionary describing a request or response structure field.
386      field = {}
387      # Ignore the '@' decoration for now.
388      field_type, field['name'] = row[0], row[1].lstrip('@')
389      # The '+' decoration means this field can be conditional.
390      if field_type.endswith('+'):
391        field_type = field_type[:-1]
392        field['has_conditional'] = 'TRUE'
393      else:
394        field['has_conditional'] = 'FALSE'
395      field['type'] = field_type
396      if len(row) > 2:
397        field['description'] = row[2]
398      else:
399        field['description'] = ''
400      # Add the new field to the list of request or response fields.
401      params.append(field)
402    if ' Command' in self._title:
403      command.request_args = params
404    else:
405      command.response_args = params
406
407  def _PickAlgEntries(self, alg_type_str):
408    """Process algorithm id table and find all matching entries.
409
410    See comments to _alg_macro above.
411
412    Args:
413     alg_type_str: a string, one or more dot separated encoded algorithm types.
414
415    Returns:
416      A table of alg_type (Table 9 of part 2) entries matching the passed in
417      encoded type string.
418    """
419    filtered_table = []
420    for alg_type in alg_type_str.split('.'):
421      if re.match('^[A-Z]+$', alg_type):
422        # Exclusive selection, must exactly match algorithm type from table 9
423        # (which is in the second column). Add to the return value all
424        # matching rows of table 9.
425        extension = []
426        for row in self._alg_id_table:
427          if row[1] == alg_type:
428            if self._alg_type and self._alg_type != row[2]:
429              continue
430            extension.append(row)
431        filtered_table.extend(extension)
432      elif re.match('^[a-z]+$', alg_type):
433        # Inclusive selection. All type letters must be present in the type
434        # column, but no exact match is required.
435        for row in self._alg_id_table:
436          for char in alg_type.upper():
437            if char not in row[1]:
438              break
439          else:
440            if not self._alg_type or self._alg_type == row[2]:
441              filtered_table.append(row)
442    return filtered_table
443
444  def _ParseAlgorithmRegex(self, token):
445    """Process a token as an algorithm regex.
446
447    This function tries to interpret the passed in token as an encoded
448    algorithm specification.
449
450    In case the encoded algorithm regex matches, the function splits the token
451    into prefix, algorithm description and suffix, and then retrieves the list
452    of all algorithms matching the algorithm description.
453
454    Args:
455      token: a string, potentially including the algorithm regex.
456
457    Returns:
458      in case the regex matched returns a tri-tuple of two strings (prefix and
459      suffix, either one could be empty) and a list of matching algorithms
460      from the algorithm descriptors table. If there has been no match -
461      returns None.
462    """
463    elements = self._alg_macro.split(token)
464    if len(elements) == 3:
465      # The token matched the algorithm regex, Find out prefix and suffix to
466      # be used on the generated type names, and the algorithm regex suffix to
467      # use to find matching entries in the algorithm table.
468      name_prefix, alg_suffix, name_suffix = tuple(elements)
469      name_prefix = name_prefix.strip('_')
470      name_suffix = name_suffix.strip('_')
471      return name_prefix, name_suffix, self._PickAlgEntries(alg_suffix)
472
473  def _ProcessInterface(self, type_name):
474    """Processes spec tables describing interfaces."""
475    result = self._ParseAlgorithmRegex(type_name)
476    if result:
477      name_prefix, name_suffix, alg_list = tuple(result)
478      # Process all matching algorithm types
479      for alg_desc in alg_list:
480        alg_base = alg_desc[0].replace('TPM_ALG_', '')
481        new_name = '_'.join([name_prefix,
482                             alg_base, name_suffix]).strip('_')
483        new_if = Interface(self._title_type, new_name)
484        self._AddTypedef(self._title_type, new_name)
485        for row in self._body:
486          new_value = row[0]
487          if new_value.startswith('$!ALG'):
488            new_if.supported_values = alg_base + '_' + '_'.join(
489                new_value.split('_')[1:])
490          elif new_value.startswith('$'):
491            new_if.supported_values = new_value[1:]
492          elif new_value.startswith('#'):
493            new_if.error_code = new_value[1:]
494        self._type_map[new_name] = new_if
495      self._AddToHfile('\n')
496      return
497    new_if = Interface(self._title_type, type_name)
498    self._AddTypedef(self._title_type, type_name)
499    self._type_map[type_name] = new_if
500    self._SetBaseType(type_name, new_if)
501    for row in self._body:
502      new_value = row[0]
503      result = self._ParseAlgorithmRegex(new_value)
504      if result:
505        # The field is described using the algorithm regex. The above comment
506        # applies.
507        name_prefix, name_suffix, alg_list = tuple(result)
508        for alg_desc in alg_list:
509          alg_base = alg_desc[0].replace('TPM_ALG_', '')
510          new_if.valid_values.append('_'.join(
511              [name_prefix, alg_base, name_suffix]).strip('_'))
512      else:
513        if new_value.startswith('{'):
514          bounds = tuple(
515              [x.strip() for x in new_value[1:-1].strip().split(':')])
516          new_if.bounds.append(bounds)
517        elif new_value.startswith('+'):
518          new_if.conditional_value = new_value[1:]
519        elif new_value.startswith('#'):
520          new_if.error_code = new_value[1:]
521        elif new_value.startswith('$'):
522          new_if.supported_values = new_value[1:]
523        else:
524          new_if.valid_values.append(new_value)
525    return
526
527  def _ProcessTypedefs(self, type_name):
528    """Processes spec tables defining new types."""
529    result = self._ParseAlgorithmRegex(type_name)
530    if result:
531      name_prefix, name_suffix, alg_list = tuple(result)
532      for alg_desc in alg_list:
533        alg_base = alg_desc[0].replace('TPM_ALG_', '')
534        new_type = '%s_%s_%s' % (name_prefix, alg_base, name_suffix)
535        self._AddTypedef(self._title_type, new_type)
536      self._AddToHfile('\n')
537    else:
538      self._AddTypedef(self._title_type, type_name)
539
540  def _ProcessBits(self, type_name):
541    """Processes spec tables describing attributes (bit fields)."""
542    bits_lines = []
543    base_bit = 0
544    tpm_obj = AttributeStructure(self._title_type, type_name)
545    self._type_map[type_name] = tpm_obj
546    self._SetBaseType(self._title_type, tpm_obj)
547    for bits_line in self._body:
548      field, name = tuple(bits_line[:2])
549      if not field:
550        continue
551      if name.startswith('TPM_'):
552        # Spec inconsistency fix.
553        name_pieces = [x.lower() for x in name.split('_')[1:]]
554        name = name_pieces[0]
555        for piece in name_pieces[1:]:
556          name += piece[0].upper() + piece[1:]
557      bit_range = [x.replace(' ', '') for x in field.split(':')]
558      field_base = int(bit_range[-1])
559      if field_base != base_bit:
560        field_name = 'reserved%d' % base_bit
561        field_width = field_base - base_bit
562        if field_width > 1:
563          field_name += '_%d' % (field_base - 1)
564        bits_lines.append(['%s : %d' % (field_name, field_width)])
565        tpm_obj.reserved.append(field_name.replace('reserved', ''))
566      if len(bit_range) > 1:
567        field_width = int(bit_range[0]) - field_base + 1
568      else:
569        field_width = 1
570      if re.match('reserved', name, re.IGNORECASE):
571        name = 'reserved%d' % field_base
572        if field_width > 1:
573          name += '_%d' % (field_base + field_width - 1)
574        tpm_obj.reserved.append(name.replace('reserved', ''))
575      bits_lines.append([name, ': %d' % field_width])
576      base_bit = field_base + field_width
577    max_type_len, _ = self._GetMaxLengths(bits_lines)
578    self._AddToHfile('typedef struct {')
579    for bits_line in bits_lines:
580      self._AddToHfile('  %s %-*s %s;' % (self._title_type, max_type_len,
581                                          bits_line[0], bits_line[1]))
582    self._AddToHfile('} %s;\n' % type_name)
583
584  def _ExpandAlgs(self, row):
585    """Find all algorithms encoded in the variable name.
586
587    Args:
588      row: a list of strings, a row of a structure or union table scraped from
589           part 2.
590
591    Returns:
592      A list for structure_generator.Field objects, one per expanded
593      algorithm.
594
595    """
596    alg_spec = row[0].split()
597    expansion = []
598    m = self._alg_macro.search(alg_spec[0])
599    if m:
600      alg_type = m.groups()[0]
601      # Find all algorithms of this type in the alg id table
602      alg_entries = self._PickAlgEntries(alg_type)
603      if len(alg_spec) == 2 and alg_spec[1][0] == '[':
604        # This is the case of a union of arrays.
605        raw_size_parts = self._alg_macro.split(alg_spec[1][1:-1])
606        size_prefix = raw_size_parts[0].strip('_')
607        size_suffix = raw_size_parts[2].strip('_')
608        for alg_desc in alg_entries:
609          alg_base = alg_desc[0].replace('TPM_ALG_', '')
610          size = '_'.join([size_prefix, alg_base, size_suffix]).strip('_')
611          if self._has_selector_column:
612            selector_parts = self._alg_macro.split(row[2])
613            selector_prefix = selector_parts[0].strip('_')
614            selector_suffix = selector_parts[2].strip('_')
615            selector = '_'.join([selector_prefix,
616                                 alg_base, selector_suffix]).strip('_')
617          else:
618            selector = ''
619          expansion.append(Field(row[1], alg_base.lower(),
620                                 selector=selector, array_size=size))
621      else:
622        type_prefix, type_suffix = self._SplitByAlg(row[1])
623        if self._has_selector_column:
624          selector_prefix, selector_suffix = self._SplitByAlg(row[2])
625        else:
626          selector = ''
627        for alg_desc in alg_entries:
628          alg_base = alg_desc[0].replace('TPM_ALG_', '')
629          if type_suffix is not None:
630            var_type = '_'.join([type_prefix, alg_base, type_suffix]).strip('_')
631          else:
632            var_type = type_prefix
633          if self._has_selector_column:
634            selector = '_'.join([selector_prefix, alg_base,
635                                 selector_suffix]).strip('_')
636          expansion.append(Field(var_type, alg_base.lower(),
637                                 selector=selector))
638    return expansion
639
640  def _ProcessInterfaceOrType(self, type_name):
641    if type_name.startswith('TPMI_'):
642      self._ProcessInterface(type_name)
643    else:
644      self._ProcessTypedefs(type_name)
645
646  def _StructOrUnionToHfile(self, body_fields, type_name, union_mode, tpm_obj):
647    body_lines = []
648    for field in body_fields:
649      tpm_obj.AddField(field)
650      body_lines.append([field.field_type, field.field_name])
651      if field.array_size:
652        body_lines[-1][-1] += '[%s]' % field.array_size
653      if field.selector_value:
654        body_lines[-1].append(field.selector_value)
655    max_type_len, _ = self._GetMaxLengths(body_lines)
656    tpm2b_mode = type_name.startswith('TPM2B_')
657    space_prefix = ''
658    if union_mode:
659      self._AddToHfile('typedef union {')
660    else:
661      if tpm2b_mode:
662        self._AddToHfile('typedef union {')
663        space_prefix = '  '
664        self._AddToHfile('  struct {')
665      else:
666        self._AddToHfile('typedef struct {')
667    for line in body_lines:
668      guard_required = len(line) > 2 and line[2].startswith('TPM_ALG_')
669      if not line[1]:
670        continue
671      if guard_required:
672        self._AddToHfile('#ifdef %s' % line[2])
673      self._AddToHfile(space_prefix + '  %-*s  %s;' % (
674          max_type_len, line[0], line[1]))
675      if guard_required:
676        self._AddToHfile('#endif')
677    if tpm2b_mode:
678      self._AddToHfile('  } t;')
679      self._AddToHfile('  TPM2B b;')
680    self._AddToHfile('} %s;\n' % type_name)
681    self._type_map[type_name] = tpm_obj
682
683
684  def _ProcessStructureOrUnion(self, type_name):
685    """Processes spec tables describing structure and unions.
686
687    Both of these object types share a lot of similarities. Union types have
688    the word 'Union' in the table title.
689
690    Args:
691      type_name: a string, name of the TPM object type
692    """
693    union_mode = 'Union' in self._title
694    if union_mode:
695      tpm_obj = Union(type_name)
696    else:
697      tpm_obj = Structure(type_name)
698    body_fields = []
699    for row in self._body:
700      if row[0].startswith('#'):
701        tpm_obj.error_code = row[0][1:]
702        continue
703      if (len(row) < 2 or
704          row[1].startswith('#') or
705          row[0].startswith('//')):
706        continue
707      value = row[0]
708      if value.endswith('='):
709        value = value[:-1]
710        tpm_obj.size_check = True
711      if self._alg_macro.search(value):
712        body_fields.extend(self._ExpandAlgs(row))
713        continue
714      array_size = None
715      run_time_size = None
716      vm = re.search(r'^(\S+)\s*\[(\S+)\]\s*\{(.*:*)\}', value)
717      selector = ''
718      if vm:
719        value, run_time_size, bounds = vm.groups()
720        lower, upper = [x.strip() for x in bounds.split(':')]
721        if upper:
722          array_size = upper
723          tpm_obj.AddUpperBound(run_time_size, upper)
724        else:
725          array_size = run_time_size
726        if lower:
727          tpm_obj.AddLowerBound(run_time_size, lower)
728      else:
729        vm = re.search(r'^\[(\S+)\]\s*(\S+)', value)
730        if vm:
731          selector, value = vm.groups()
732        else:
733          vm = re.search(r'^(\S+)\s*\{(.+)\}', value)
734          if vm:
735            value, bounds = vm.groups()
736            if ':' in bounds:
737              lower, upper = [x.strip() for x in bounds.split(':')]
738              if upper:
739                tpm_obj.AddUpperBound(value, upper)
740              if lower:
741                tpm_obj.AddLowerBound(value, lower)
742      if self._has_selector_column:
743        selector = row[2]
744      field_type = row[1]
745      if field_type.startswith('+') or field_type.endswith('+'):
746        field_type = field_type.strip('+')
747        conditional = 'TRUE'
748      else:
749        conditional = 'FALSE'
750      if field_type or value:
751        body_fields.append(Field(field_type,
752                                 value,
753                                 array_size=array_size,
754                                 run_time_size=run_time_size,
755                                 selector=selector,
756                                 conditional_value=conditional))
757
758    self._StructOrUnionToHfile(body_fields, type_name, union_mode, tpm_obj)
759
760  def _ProcessEnum(self, _):
761    """Processes spec tables describing enums."""
762    if self._skip_printing:
763      return
764    self._AddToHfile('enum {')
765    for value, row in enumerate(self._body):
766      self._AddToHfile('  %s = %d,' % (row[0], value + 1))
767    self._AddToHfile('};\n')
768
769  def _ProcessTypes(self, _):
770    """Processes spec tables describing new types."""
771    for type_, name_, _ in self._body:
772      m = self._alg_macro.search(name_)
773      if not m:
774        self._AddTypedef(type_, name_)
775        continue
776      qualifier = [x for x in ('ECC', 'RSA') if x == type_.split('_')[-1]]
777      alg_suffix = m.groups()[0]
778      type_base = self._alg_macro.split(name_)[0]
779      for alg_desc in self._PickAlgEntries(alg_suffix):
780        if qualifier and alg_desc[2] != qualifier[0]:
781          continue
782        self._AddTypedef(type_, alg_desc[0].replace('TPM_ALG_', type_base))
783    self._AddToHfile()
784
785  def _ProcessConstants(self, type_name):
786    """Processes spec tables describing constants."""
787    if self._title_type:
788      self._AddTypedef(self._title_type, type_name)
789    constant_defs = []
790    tpm_obj = ConstantType(self._title_type, type_name)
791    for row in self._body:
792      name = row[0].strip()
793      if not name:
794        continue
795      if name.startswith('#'):
796        tpm_obj.error_code = name[1:]
797        continue
798      if name == 'reserved' or len(row) < 2:
799        continue
800      value = row[1].strip()
801      rm = re.match(r'^(\(.*?\))', value)
802      if rm:
803        value = '%s' % rm.groups()[0]
804      else:
805        v_list = value.split()
806        if len(v_list) > 2 and v_list[1] == '+':
807          value = '((%s)(%s))' % (type_name, ' '.join(v_list[:3]))
808      if ' ' in value and not value.startswith('('):
809        value = '(%s)' % value
810      constant_defs.append([name, value])
811      tpm_obj.valid_values.append(name)
812    if self._title_type:
813      self._type_map[type_name] = tpm_obj
814      self._SetBaseType(self._title_type, tpm_obj)
815    if self._skip_printing:
816      return
817    max_name_len, max_value_len = self._GetMaxLengths(constant_defs)
818    for row in constant_defs:
819      self._AddToHfile('#define %-*s  %*s' % (max_name_len, row[0],
820                                              max_value_len, row[1]))
821    self._AddToHfile()
822
823  def GetHFile(self):
824    return self._h_file
825
826  def GetCommandList(self):
827    return sorted(self._command_map.values(),
828                  cmp=lambda x, y: cmp(x.name, y.name))
829
830  def GetTypeMap(self):
831    return self._type_map
832