• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3
2#
3# Copyright (C) 2023 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import os
19import subprocess
20
21from datetime import datetime
22
23from utils import CHPP_PARSER_INCLUDE_PATH
24from utils import CHPP_PARSER_SOURCE_PATH
25from utils import LICENSE_HEADER
26from utils import android_build_top_abs_path
27from utils import system_chre_abs_path
28
29
30class CodeGenerator:
31    """Given an ApiParser object, generates a header file with structure definitions in CHPP format.
32    """
33
34    def __init__(self, api, commit_hash):
35        """
36        :param api: ApiParser object
37        """
38
39        self.api = api
40        self.json = api.json
41        # Turn "chre_api/include/chre_api/chre/wwan.h" into "wwan"
42        self.service_name = self.json['filename'].split('/')[-1].split('.')[0]
43        self.capitalized_service_name = self.service_name.capitalize()
44        self.commit_hash = commit_hash
45
46    # ----------------------------------------------------------------------------------------------
47    # Header generation methods (plus some methods shared with encoder generation)
48    # ----------------------------------------------------------------------------------------------
49
50    def _autogen_notice(self):
51        out = []
52        out.append('// This file was automatically generated by {}\n'.format(
53            os.path.basename(__file__)))
54        out.append(
55            '// Date: {} UTC\n'.format(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')))
56        out.append(
57            '// Source: {} @ commit {}\n\n'.format(self.json['filename'], self.commit_hash))
58        out.append(
59            '// DO NOT modify this file directly, as those changes will be lost the next\n')
60        out.append('// time the script is executed\n\n')
61        return out
62
63    def _dump_to_file(self, output_filename, contents, dry_run, skip_clang_fomat):
64        """Outputs contents to output_filename, or prints contents if dry_run is True"""
65
66        if dry_run:
67            print('---- {} ----'.format(output_filename))
68            print(contents)
69            print('---- end of {} ----\n'.format(output_filename))
70        else:
71            with open(output_filename, 'w') as f:
72                f.write(contents)
73
74            if not skip_clang_fomat:
75                clang_format_path = (android_build_top_abs_path() +
76                                     '/prebuilts/clang/host/linux-x86/clang-stable/bin/clang-format')
77                args = [clang_format_path, '-i', output_filename]
78                result = subprocess.run(args)
79                result.check_returncode()
80
81    def _is_array_type(self, type_info):
82        # If this is an array type, declarators will be a tuple containing a list of
83        # a single int element giving the size of the array
84        return len(type_info.declarators) == 1 and isinstance(type_info.declarators[0], list)
85
86    def _get_array_len(self, type_info):
87        return type_info.declarators[0][0]
88
89    def _get_chpp_type_from_chre(self, chre_type):
90        """Returns 'struct ChppWwanCellInfo', etc. given 'chreWwanCellInfo'"""
91
92        prefix = self._get_struct_or_union_prefix(chre_type)
93
94        # First see if we have an explicit name override (e.g. for anonymous types)
95        for annotation in self.api.annotations[chre_type]['.']:
96            if annotation['annotation'] == 'rename_type':
97                return prefix + annotation['type_override']
98
99        # Otherwise, use the existing type name, just replace the "chre" prefix with "Chpp"
100        if chre_type.startswith('chre'):
101            return prefix + 'Chpp' + chre_type[4:]
102        else:
103            raise RuntimeError(
104                "Couldn't figure out new type name for {}".format(chre_type))
105
106    def _get_chre_type_with_prefix(self, chre_type):
107        """Returns 'struct chreWwanCellInfo', etc. given 'chreWwanCellInfo'"""
108
109        return self._get_struct_or_union_prefix(chre_type) + chre_type
110
111    def _get_chpp_header_type_from_chre(self, chre_type):
112        """Returns 'struct ChppWwanCellInfoWithHeader', etc. given 'chreWwanCellInfo'"""
113
114        return self._get_chpp_type_from_chre(chre_type) + 'WithHeader'
115
116    def _get_member_comment(self, member_info):
117        for annotation in member_info['annotations']:
118            if annotation['annotation'] == 'fixed_value':
119                return '  // Input ignored; always set to {}'.format(annotation['value'])
120            elif annotation['annotation'] == 'var_len_array':
121                return '  // References {} instances of {}'.format(
122                    annotation['length_field'], self._get_member_type(member_info))
123        return ''
124
125    def _get_member_type(self, member_info, underlying_vla_type=False):
126        """Gets the CHPP type specification prefix for a struct/union member.
127
128        :param member_info: a dict element from self.api.structs_and_unions[struct]['members']
129        :param underlying_vla_type: (used only for var-len array types) False to output
130            'struct ChppOffset', and True to output the type that ChppOffset references
131        :return: type specification string that prefixes the field name, e.g. 'uint8_t'
132        """
133
134        # 4 cases to handle:
135        #   1) Annotation gives explicit type that we should use
136        #   2) Annotation says this is a variable length array (so use ChppOffset if
137        #      underlying_vla_type is False)
138        #   3) This is a struct/union type, so use the renamed (CHPP) type name
139        #   4) Regular type, e.g. uint32_t, so just use the type spec as-is
140        for annotation in member_info['annotations']:
141            if annotation['annotation'] == 'rewrite_type':
142                return annotation['type_override']
143            elif not underlying_vla_type and annotation['annotation'] in ['var_len_array', 'string']:
144                return 'struct ChppOffset'
145
146        if not underlying_vla_type and len(member_info['type'].declarators) > 0 and \
147                member_info['type'].declarators[0] == '*':
148            # This case should either be handled by rewrite_type (e.g. to uint32_t as
149            # opaque/ignored), or var_len_array
150            raise RuntimeError('Pointer types require annotation\n{}'.format(
151                member_info))
152
153        if member_info['is_nested_type']:
154            return self._get_chpp_type_from_chre(member_info['nested_type_name'])
155
156        return member_info['type'].type_spec
157
158    def _get_member_type_suffix(self, member_info):
159        if self._is_array_type(member_info['type']):
160            return '[{}]'.format(self._get_array_len(member_info['type']))
161        return ''
162
163    def _get_struct_or_union_prefix(self, chre_type):
164        return 'struct ' if not self.api.structs_and_unions[chre_type]['is_union'] else 'union '
165
166    def _gen_header_includes(self):
167        """Generates #include directives for use in <service>_types.h"""
168
169        out = ['#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n\n']
170
171        includes = ['chpp/app.h', 'chpp/macros.h', 'chre_api/chre/version.h']
172        includes.extend(self.json['output_includes'])
173        for incl in sorted(includes):
174            out.append('#include "{}"\n'.format(incl))
175        out.append('\n')
176        return out
177
178    def _gen_struct_or_union(self, name):
179        """Generates the definition for a single struct/union type."""
180
181        out = []
182        if not name.startswith('anon'):
183            out.append('//! See {{@link {}}} for details\n'.format(name))
184        out.append('{} {{\n'.format(self._get_chpp_type_from_chre(name)))
185        for member_info in self.api.structs_and_unions[name]['members']:
186            out.append('  {} {}{};{}\n'.format(self._get_member_type(member_info),
187                                               member_info['name'],
188                                               self._get_member_type_suffix(
189                                                   member_info),
190                                               self._get_member_comment(member_info)))
191
192        out.append('} CHPP_PACKED_ATTR;\n\n')
193        return out
194
195    def _gen_header_struct(self, chre_type):
196        """Generates the definition for the type with header (WithHeader)."""
197
198        out = []
199        out.append('//! CHPP app header plus {}\n'.format(
200            self._get_chpp_header_type_from_chre(chre_type)))
201
202        out.append('{} {{\n'.format(
203            self._get_chpp_header_type_from_chre(chre_type)))
204        out.append('  struct ChppAppHeader header;\n')
205        out.append('  {} payload;\n'.format(
206            self._get_chpp_type_from_chre(chre_type)))
207        out.append('} CHPP_PACKED_ATTR;\n\n')
208
209        return out
210
211    def _gen_structs_and_unions(self):
212        """Generates definitions for all struct/union types required for the root structs."""
213
214        out = []
215        out.append('CHPP_PACKED_START\n\n')
216
217        sorted_structs = self._sorted_structs(self.json['root_structs'])
218        for type_name in sorted_structs:
219            out.extend(self._gen_struct_or_union(type_name))
220
221        for chre_type in self.json['root_structs']:
222            out.extend(self._gen_header_struct(chre_type))
223
224        out.append('CHPP_PACKED_END\n\n')
225        return out
226
227    def _sorted_structs(self, root_nodes):
228        """Implements a topological sort on self.api.structs_and_unions.
229
230        Elements are ordered by definition dependency, i.e. if A includes a field of type B,
231        then B will appear before A in the returned list.
232        :return: list of keys in self.api.structs_and_unions, sorted by dependency order
233        """
234
235        result = []
236        visited = set()
237
238        def sort_helper(collection, key):
239            for dep in sorted(collection[key]['dependencies']):
240                if dep not in visited:
241                    visited.add(dep)
242                    sort_helper(collection, dep)
243            result.append(key)
244
245        for node in sorted(root_nodes):
246            sort_helper(self.api.structs_and_unions, node)
247        return result
248
249    # ----------------------------------------------------------------------------------------------
250    # Encoder function generation methods (CHRE --> CHPP)
251    # ----------------------------------------------------------------------------------------------
252
253    def _get_chpp_member_sizeof_call(self, member_info):
254        """Returns invocation used to determine the size of the provided member when encoded.
255
256        Will be either sizeof(<type in CHPP struct>) or a function call if the member contains a VLA
257        :param member_info: a dict element from self.api.structs_and_unions[struct]['members']
258        :return: string
259        """
260
261        type_name = None
262        if member_info['is_nested_type']:
263            chre_type = member_info['nested_type_name']
264            if self.api.structs_and_unions[chre_type]['has_vla_member']:
265                return '{}(in->{})'.format(self._get_chpp_sizeof_function_name(chre_type),
266                                           member_info['name'])
267            else:
268                type_name = self._get_chpp_type_from_chre(chre_type)
269        else:
270            type_name = member_info['type'].type_spec
271        return 'sizeof({})'.format(type_name)
272
273    def _gen_chpp_sizeof_function(self, chre_type):
274        """Generates a function to determine the encoded size of the CHRE struct, if necessary."""
275
276        out = []
277
278        # Note that this function *should* work with unions as well, but at the time of writing
279        # it'll only be used with structs, so names, etc. are written with that in mind
280        struct_info = self.api.structs_and_unions[chre_type]
281        if not struct_info['has_vla_member']:
282            # No codegen necessary, just sizeof on the CHPP structure name is sufficient
283            return out
284
285        core_type_name = self._strip_prefix_and_service_from_chre_struct_name(
286            chre_type)
287        parameter_name = core_type_name[0].lower() + core_type_name[1:]
288        chpp_type_name = self._get_chpp_header_type_from_chre(chre_type)
289        out.append('//! @return number of bytes required to represent the given\n'
290                   '//! {} along with the CHPP header as\n'
291                   '//! {}\n'
292                   .format(chre_type, chpp_type_name))
293        out.append('static size_t {}(\n        const {}{} *{}) {{\n'
294                   .format(self._get_chpp_sizeof_function_name(chre_type),
295                           self._get_struct_or_union_prefix(
296                               chre_type), chre_type,
297                           parameter_name))
298
299        # sizeof(this struct)
300        out.append('  size_t encodedSize = sizeof({});\n'.format(chpp_type_name))
301
302        # Plus count * sizeof(type) for each var-len array included in this struct
303        for member_info in self.api.structs_and_unions[chre_type]['members']:
304            for annotation in member_info['annotations']:
305                if annotation['annotation'] == 'var_len_array':
306                    # If the VLA field itself contains a VLA, then we'd need to generate a for
307                    # loop to calculate the size of each element individually - I don't think we
308                    # have any of these in the CHRE API today, so leaving this functionality out.
309                    # Also note that to support that case we'd also want to recursively call this
310                    # function to generate sizeof functions for nested fields.
311                    if member_info['is_nested_type'] and \
312                            self.api.structs_and_unions[member_info['nested_type_name']][
313                                'has_vla_member']:
314                        raise RuntimeError(
315                            'Nested variable-length arrays is not currently supported ({} '
316                            'in {})'.format(member_info['name'], chre_type))
317
318                    out.append('  encodedSize += {}->{} * sizeof({});\n'.format(
319                        parameter_name, annotation['length_field'],
320                        self._get_member_type(member_info, True)))
321                elif annotation['annotation'] == 'string':
322                    out.append('  if ({}->{} != NULL) {{'.format(
323                        parameter_name, annotation['field']))
324                    out.append('    encodedSize += strlen({}->{}) + 1;\n'.format(
325                        parameter_name, annotation['field']))
326                    out.append('  }\n')
327
328        out.append('  return encodedSize;\n}\n\n')
329        return out
330
331    def _gen_chpp_sizeof_functions(self):
332        """For each root struct, generate necessary functions to determine their encoded size."""
333
334        out = []
335        for struct in self.json['root_structs']:
336            out.extend(self._gen_chpp_sizeof_function(struct))
337        return out
338
339    def _gen_conversion_includes(self):
340        """Generates #include directives for the conversion source file."""
341
342        out = ['#include "chpp/macros.h"\n'
343               '#include "chpp/memory.h"\n'
344               '#include "chpp/common/{}_types.h"\n\n'.format(self.service_name)]
345        out.append(
346            '#include <stddef.h>\n#include <stdint.h>\n#include <string.h>\n\n')
347        return out
348
349    def _get_chpp_sizeof_function_name(self, chre_struct):
350        """Returns the function name used to compute the encoded size of the given struct at
351        runtime.
352        """
353
354        core_type_name = self._strip_prefix_and_service_from_chre_struct_name(
355            chre_struct)
356        return 'chpp{}SizeOf{}FromChre'.format(self.capitalized_service_name, core_type_name)
357
358    def _get_encoding_function_name(self, chre_type):
359        core_type_name = self._strip_prefix_and_service_from_chre_struct_name(
360            chre_type)
361        return 'chpp{}Convert{}FromChre'.format(self.capitalized_service_name, core_type_name)
362
363    def _gen_encoding_function_signature(self, chre_type):
364        out = []
365        out.append(
366            'void {}(\n'.format(self._get_encoding_function_name(chre_type)))
367        out.append('    const {}{} *in,\n'.format(
368            self._get_struct_or_union_prefix(chre_type), chre_type))
369        out.append('    {} *out'.format(self._get_chpp_type_from_chre(chre_type)))
370        if self.api.structs_and_unions[chre_type]['has_vla_member']:
371            out.append(',\n')
372            out.append('    uint8_t *payload,\n')
373            out.append('    size_t payloadSize,\n')
374            out.append('    uint16_t *vlaOffset')
375        out.append(')')
376        return out
377
378    def _gen_string_encoding(self, member_info, annotation):
379        out = []
380        # Might want to revisit this if we ever end up supporting NULL strings
381        # in our API. We can assert here since there's currently no API that
382        # does so.
383        member_name = member_info['name']
384        out.append('  if (in->{} != NULL) {{\n'.format(member_name))
385        out.append(
386            '    size_t strSize = strlen(in->{}) + 1;\n'.format(member_name))
387        out.append('    memcpy(&payload[*vlaOffset], in->{}, strSize);\n'.format(
388            member_name))
389        out.append('    out->{}.length = (uint16_t)(strSize);\n'.format(
390            member_name))
391        out.append('    out->{}.offset = *vlaOffset;\n'.format(member_name))
392        out.append('    *vlaOffset += out->{}.length;\n'.format(member_name))
393        out.append('  } else {\n')
394        out.append('    out->{}.length = 0;\n'.format(member_name))
395        out.append('    out->{}.offset = 0;\n'.format(member_name))
396        out.append('  }\n\n')
397
398        return out
399
400    def _gen_vla_encoding(self, member_info, annotation):
401        out = []
402
403        variable_name = member_info['name']
404        chpp_type = self._get_member_type(member_info, True)
405
406        if member_info['is_nested_type']:
407            out.append('\n  {} *{} = ({} *) &payload[*vlaOffset];\n'.format(
408                chpp_type, variable_name, chpp_type))
409
410        out.append('  out->{}.length = (uint16_t)(in->{} * {});\n'.format(
411            member_info['name'], annotation['length_field'],
412            self._get_chpp_member_sizeof_call(member_info)))
413
414        out.append('  CHPP_ASSERT((size_t)(*vlaOffset + out->{}.length) <= payloadSize);\n'.format(
415            member_info['name']))
416
417        out.append('  if (out->{}.length > 0 &&\n'
418                   '      *vlaOffset + out->{}.length <= payloadSize) {{\n'.format(
419                       member_info['name'], member_info['name']))
420
421        if member_info['is_nested_type']:
422            out.append('    for (size_t i = 0; i < in->{}; i++) {{\n'.format(
423                annotation['length_field']))
424            out.append('      {}'.format(
425                self._get_assignment_statement_for_field(member_info, in_vla_loop=True)))
426            out.append('    }\n')
427        else:
428            out.append('memcpy(&payload[*vlaOffset], in->{}, in->{} * sizeof({}));\n'.format(
429                member_info['name'], annotation['length_field'], chpp_type))
430
431        out.append(
432            '    out->{}.offset = *vlaOffset;\n'.format(member_info['name']))
433        out.append(
434            '    *vlaOffset += out->{}.length;\n'.format(member_info['name']))
435
436        out.append('  } else {\n')
437        out.append('    out->{}.offset = 0;\n'.format(member_info['name']))
438        out.append('  }\n')
439
440        return out
441
442    # ----------------------------------------------------------------------------------------------
443    # Encoder / decoder function generation methods (CHRE <--> CHPP)
444    # ----------------------------------------------------------------------------------------------
445
446    def _get_assignment_statement_for_field(self, member_info,
447                                            in_vla_loop=False,
448                                            containing_field_name=None,
449                                            decode_mode=False):
450        """Returns a statement to assign the provided member
451
452        :param member_info:
453        :param in_vla_loop: True if we're currently inside a loop and should append [i]
454        :param containing_field_name: Additional member name to use to access the target field, or
455        None; for example the normal case is "out->field = in->field", but if we're generating
456        assignments in the parent conversion function (e.g. as used for union variants), we need to
457        do "out->nested_field.field = in->nested_field.field"
458        :param decode_mode: True converts from CHPP to CHRE. False from CHRE to CHPP
459        :return: assignment statement as a string
460        """
461
462        array_index = '[i]' if in_vla_loop else ''
463        output_accessor = '' if in_vla_loop else 'out->'
464        containing_field = containing_field_name + \
465            '.' if containing_field_name is not None else ''
466
467        output_variable = '{}{}{}{}'.format(output_accessor, containing_field, member_info['name'],
468                                            array_index)
469        input_variable = 'in->{}{}{}'.format(containing_field,
470                                             member_info['name'], array_index)
471
472        if decode_mode and in_vla_loop:
473            output_variable = '{}Out{}'.format(
474                member_info['name'], array_index)
475            input_variable = '{}In{}'.format(member_info['name'], array_index)
476
477        if member_info['is_nested_type']:
478            chre_type = member_info['nested_type_name']
479            has_vla_member = self.api.structs_and_unions[chre_type]['has_vla_member']
480            if decode_mode:
481                # Use decoding function
482                vla_params = ', inSize' if has_vla_member else ''
483                out = 'if (!{}(&{}, &{}{})) {{\n'.format(
484                    self._get_decoding_function_name(
485                        chre_type), input_variable,
486                    output_variable, vla_params)
487                if has_vla_member:
488                    out += '  CHPP_FREE_AND_NULLIFY({}Out);\n'.format(
489                        member_info['name'])
490                out += '  return false;\n'
491                out += '}\n'
492                return out
493            else:
494                # Use encoding function
495                vla_params = ', payload, payloadSize, vlaOffset' if has_vla_member else ''
496                return '{}(&{}, &{}{});\n'.format(
497                    self._get_encoding_function_name(
498                        chre_type), input_variable, output_variable,
499                    vla_params)
500        elif self._is_array_type(member_info['type']):
501            # Array of primitive type (e.g. uint32_t[8]) - use memcpy
502            return 'memcpy({}, {}, sizeof({}));\n'.format(output_variable, input_variable,
503                                                          output_variable)
504        else:
505            # Regular assignment
506            return '{} = {};\n'.format(output_variable, input_variable)
507
508    def _gen_union_variant_conversion_code(self, member_info, annotation, decode_mode):
509        """Generates a switch statement to encode the "active"/"used" field within a union.
510
511        Handles cases where a union has multiple types, but there's another peer/adjacent field
512        which tells you which field in the union is to be used. Outputs code like this:
513        switch (in->{discriminator field}) {
514            case {first discriminator value associated with a fields}:
515                {conversion code for the field associated with this discriminator value}
516                ...
517        :param chre_type: CHRE type of the union
518        :param annotation: Reference to JSON annotation data with the discriminator mapping data
519        :param decode_mode: False encodes from CHRE to CHPP. True decodes from CHPP to CHRE
520        :return: list of strings
521        """
522
523        out = []
524        chre_type = member_info['nested_type_name']
525        struct_info = self.api.structs_and_unions[chre_type]
526
527        # Start off by zeroing out the union field so any padding is set to a consistent value
528        out.append('  memset(&out->{}, 0, sizeof(out->{}));\n'.format(member_info['name'],
529                                                                      member_info['name']))
530
531        # Next, generate the switch statement that will copy over the proper values
532        out.append(
533            '  switch (in->{}) {{\n'.format(annotation['discriminator']))
534        for value, field_name in annotation['mapping']:
535            out.append('    case {}:\n'.format(value))
536
537            found = False
538            for nested_member_info in struct_info['members']:
539                if nested_member_info['name'] == field_name:
540                    out.append('      {}'.format(
541                        self._get_assignment_statement_for_field(
542                            nested_member_info,
543                            containing_field_name=member_info['name'],
544                            decode_mode=decode_mode)))
545                    found = True
546                    break
547
548            if not found:
549                raise RuntimeError("Invalid mapping - couldn't find target field {} in struct {}"
550                                   .format(field_name, chre_type))
551
552            out.append('      break;\n')
553
554        out.append('    default:\n'
555                   '      CHPP_ASSERT(false);\n'
556                   '  }\n')
557
558        return out
559
560    def _gen_conversion_function(self, chre_type, already_generated, decode_mode):
561        out = []
562
563        struct_info = self.api.structs_and_unions[chre_type]
564        for dependency in sorted(struct_info['dependencies']):
565            if dependency not in already_generated:
566                out.extend(
567                    self._gen_conversion_function(dependency, already_generated, decode_mode))
568
569        # Skip if we've already generated code for this type, or if it's a union (in which case we
570        # handle the assignment in the parent structure to enable support for discrimination of
571        # which field in the union to use)
572        if chre_type in already_generated or struct_info['is_union']:
573            return out
574        already_generated.add(chre_type)
575
576        out.append('static ')
577        if decode_mode:
578            out.extend(self._gen_decoding_function_signature(chre_type))
579        else:
580            out.extend(self._gen_encoding_function_signature(chre_type))
581        out.append(' {\n')
582
583        for member_info in self.api.structs_and_unions[chre_type]['members']:
584            generated_by_annotation = False
585            for annotation in member_info['annotations']:
586                if annotation['annotation'] == 'fixed_value':
587                    if self._is_array_type(member_info['type']):
588                        out.append('  memset(&out->{}, {}, sizeof(out->{}));\n'.format(
589                            member_info['name'], annotation['value'], member_info['name']))
590                    else:
591                        out.append('  out->{} = {};\n'.format(member_info['name'],
592                                                              annotation['value']))
593                    generated_by_annotation = True
594                    break
595                elif annotation['annotation'] == 'enum':
596                    # Note: We could generate range verification code here, but it has not
597                    # been considered necessary thus far.
598                    pass
599                elif annotation['annotation'] == 'var_len_array':
600                    if decode_mode:
601                        out.extend(self._gen_vla_decoding(
602                            member_info, annotation))
603                    else:
604                        out.extend(self._gen_vla_encoding(
605                            member_info, annotation))
606                    generated_by_annotation = True
607                    break
608                elif annotation['annotation'] == 'string':
609                    if decode_mode:
610                        out.extend(self._gen_string_decoding(
611                            member_info, annotation))
612                    else:
613                        out.extend(self._gen_string_encoding(
614                            member_info, annotation))
615                    generated_by_annotation = True
616                    break
617                elif annotation['annotation'] == 'union_variant':
618                    out.extend(self._gen_union_variant_conversion_code(
619                        member_info, annotation, decode_mode))
620                    generated_by_annotation = True
621                    break
622
623            if not generated_by_annotation:
624                out.append('  {}'.format(
625                    self._get_assignment_statement_for_field(member_info, decode_mode=decode_mode)))
626
627        if decode_mode:
628            out.append('\n  return true;\n')
629
630        out.append('}\n\n')
631        return out
632
633    def _gen_conversion_functions(self, decode_mode):
634        out = []
635        already_generated = set()
636        for struct in self.json['root_structs']:
637            out.extend(self._gen_conversion_function(
638                struct, already_generated, decode_mode))
639        return out
640
641    def _strip_prefix_and_service_from_chre_struct_name(self, struct):
642        """Strips 'chre' and service prefix, e.g. 'chreWwanCellResultInfo' -> 'CellResultInfo'."""
643
644        chre_stripped = struct[4:]
645        upcased_service_name = self.service_name[0].upper(
646        ) + self.service_name[1:]
647        if not struct.startswith('chre') or not chre_stripped.startswith(upcased_service_name):
648            # If this happens, we need to update the script to handle it. Right we assume struct
649            # naming follows the pattern "chre<Service_name><Thing_name>"
650            raise RuntimeError('Unexpected structure name {}'.format(struct))
651
652        return chre_stripped[len(self.service_name):]
653
654    # ----------------------------------------------------------------------------------------------
655    # Memory allocation generation methods
656    # ----------------------------------------------------------------------------------------------
657
658    def _get_chpp_sizeof_call(self, chre_type):
659        """Returns invocation used to determine the size of the provided CHRE struct (with the CHPP
660        app header) after encoding.
661
662        Like _get_chpp_member_sizeof_call(), except for a top-level type assigned to the variable
663        "in" rather than a member within a structure (e.g. a VLA field)
664        :param chre_type: CHRE type name
665        :return: string
666        """
667
668        if self.api.structs_and_unions[chre_type]['has_vla_member']:
669            return '{}(in)'.format(self._get_chpp_sizeof_function_name(chre_type))
670        else:
671            return 'sizeof({})'.format(self._get_chpp_header_type_from_chre(chre_type))
672
673    def _get_encode_allocation_function_name(self, chre_type):
674        core_type_name = self._strip_prefix_and_service_from_chre_struct_name(
675            chre_type)
676        return 'chpp{}{}FromChre'.format(self.capitalized_service_name, core_type_name)
677
678    def _gen_encode_allocation_function_signature(self, chre_type, gen_docs=False):
679        out = []
680        if gen_docs:
681            out.append('/**\n'
682                       ' * Converts from given CHRE structure to serialized CHPP type.\n'
683                       ' *\n'
684                       ' * @param in Fully-formed CHRE structure.\n'
685                       ' * @param out Upon success, will point to a buffer allocated with '
686                       'chppMalloc().\n'
687                       ' * It is the responsibility of the caller to set the values of the CHPP '
688                       'app layer header, and to free the buffer when it is no longer needed via '
689                       'chppFree() or CHPP_FREE_AND_NULLIFY().\n'
690                       ' * @param outSize Upon success, will be set to the size of the output '
691                       'buffer, in bytes.\n'
692                       ' *\n'
693                       ' * @return true on success, false if memory allocation failed.\n'
694                       ' */\n')
695        out.append(
696            'bool {}(\n'.format(self._get_encode_allocation_function_name(chre_type)))
697        out.append('    const {}{} *in,\n'.format(
698            self._get_struct_or_union_prefix(chre_type), chre_type))
699        out.append(
700            '    {} **out,\n'.format(self._get_chpp_header_type_from_chre(chre_type)))
701        out.append('    size_t *outSize)')
702        return out
703
704    def _gen_encode_allocation_function(self, chre_type):
705        out = []
706        out.extend(self._gen_encode_allocation_function_signature(chre_type))
707        out.append(' {\n')
708        out.append('  CHPP_NOT_NULL(out);\n')
709        out.append('  CHPP_NOT_NULL(outSize);\n\n')
710        out.append('  size_t payloadSize = {};\n'.format(
711            self._get_chpp_sizeof_call(chre_type)))
712        out.append('  *out = chppMalloc(payloadSize);\n')
713
714        out.append('  if (*out != NULL) {\n')
715
716        struct_info = self.api.structs_and_unions[chre_type]
717        if struct_info['has_vla_member']:
718            out.append('    uint8_t *payload = (uint8_t *) &(*out)->payload;\n')
719            out.append('    uint16_t vlaOffset = sizeof({});\n'.format(
720                self._get_chpp_type_from_chre(chre_type)))
721
722        out.append('    {}(in, &(*out)->payload'.format(
723            self._get_encoding_function_name(chre_type)))
724        if struct_info['has_vla_member']:
725            out.append(', payload, payloadSize, &vlaOffset')
726        out.append(');\n')
727        out.append('    *outSize = payloadSize;\n')
728        out.append('    return true;\n')
729        out.append('  }\n')
730
731        out.append('  return false;\n}\n\n')
732        return out
733
734    def _gen_encode_allocation_functions(self):
735        out = []
736        for chre_type in self.json['root_structs']:
737            out.extend(self._gen_encode_allocation_function(chre_type))
738        return out
739
740    def _gen_encode_allocation_function_signatures(self):
741        out = []
742        for chre_type in self.json['root_structs']:
743            out.extend(
744                self._gen_encode_allocation_function_signature(chre_type, True))
745            out.append(';\n\n')
746        return out
747
748    # ----------------------------------------------------------------------------------------------
749    # Decoder function generation methods (CHPP --> CHRE)
750    # ----------------------------------------------------------------------------------------------
751
752    def _get_decoding_function_name(self, chre_type):
753        core_type_name = self._strip_prefix_and_service_from_chre_struct_name(
754            chre_type)
755        return 'chpp{}Convert{}ToChre'.format(self.capitalized_service_name, core_type_name)
756
757    def _gen_decoding_function_signature(self, chre_type):
758        out = []
759        out.append(
760            'bool {}(\n'.format(self._get_decoding_function_name(chre_type)))
761        out.append(
762            '    const {} *in,\n'.format(self._get_chpp_type_from_chre(chre_type)))
763        out.append(
764            '    {} *out'.format(self._get_chre_type_with_prefix(chre_type)))
765        if self.api.structs_and_unions[chre_type]['has_vla_member']:
766            out.append(',\n')
767            out.append('    size_t inSize')
768        out.append(')')
769        return out
770
771    def _gen_string_decoding(self, member_info, annotation):
772        out = []
773        variable_name = member_info['name']
774        out.append('\n')
775        out.append('  if (in->{}.length == 0) {{\n'.format(variable_name))
776        out.append('    out->{} = NULL;\n'.format(variable_name))
777        out.append('  } else {\n')
778        out.append('    char *{}Out = chppMalloc(in->{}.length);\n'.format(
779            variable_name, variable_name))
780        out.append('    if ({}Out == NULL) {{\n'.format(variable_name))
781        out.append('      return false;\n')
782        out.append('    }\n\n')
783        out.append('    memcpy({}Out, &((const uint8_t *)in)[in->{}.offset],\n'.format(
784            variable_name, variable_name))
785        out.append('      in->{}.length);\n'.format(variable_name))
786        out.append('    out->{} = {}Out;\n'.format(variable_name, variable_name))
787        out.append('  }\n')
788
789        return out
790
791    def _gen_vla_decoding(self, member_info, annotation):
792        out = []
793
794        variable_name = member_info['name']
795        chpp_type = self._get_member_type(member_info, True)
796        if member_info['is_nested_type']:
797            chre_type = self._get_chre_type_with_prefix(
798                member_info['nested_type_name'])
799        else:
800            chre_type = chpp_type
801
802        out.append('\n')
803        out.append('  if (in->{}.length == 0) {{\n'.format(variable_name))
804        out.append('    out->{} = NULL;\n'.format(variable_name))
805        out.append('  }\n')
806        out.append('  else {\n')
807        out.append('    if (in->{}.offset + in->{}.length > inSize ||\n'.format(
808            variable_name, variable_name))
809        out.append('        in->{}.length != in->{} * sizeof({})) {{\n'.format(
810            variable_name, annotation['length_field'], chpp_type))
811
812        out.append('      return false;\n')
813        out.append('    }\n\n')
814
815        if member_info['is_nested_type']:
816            out.append(
817                '    const {} *{}In =\n'.format(chpp_type, variable_name))
818            out.append('        (const {} *) &((const uint8_t *)in)[in->{}.offset];\n\n'.format(
819                chpp_type, variable_name))
820
821        out.append('    {} *{}Out = chppMalloc(in->{} * sizeof({}));\n'.format(
822            chre_type, variable_name, annotation['length_field'], chre_type))
823        out.append('    if ({}Out == NULL) {{\n'.format(variable_name))
824        out.append('      return false;\n')
825        out.append('    }\n\n')
826
827        if member_info['is_nested_type']:
828            out.append('    for (size_t i = 0; i < in->{}; i++) {{\n'.format(
829                annotation['length_field'], variable_name))
830            out.append('      {}'.format(self._get_assignment_statement_for_field(
831                member_info, in_vla_loop=True, decode_mode=True)))
832            out.append('    }\n')
833        else:
834            out.append('    memcpy({}Out, &((const uint8_t *)in)[in->{}.offset],\n'.format(
835                variable_name, variable_name))
836            out.append('      in->{} * sizeof({}));\n'.format(
837                annotation['length_field'], chre_type))
838
839        out.append('    out->{} = {}Out;\n'.format(variable_name, variable_name))
840        out.append('  }\n\n')
841
842        return out
843
844    def _get_decode_allocation_function_name(self, chre_type):
845        core_type_name = self._strip_prefix_and_service_from_chre_struct_name(
846            chre_type)
847        return 'chpp{}{}ToChre'.format(self.capitalized_service_name, core_type_name)
848
849    def _gen_decode_allocation_function_signature(self, chre_type, gen_docs=False):
850        out = []
851        if gen_docs:
852            out.append('/**\n'
853                       ' * Converts from serialized CHPP structure to a CHRE type.\n'
854                       ' *\n'
855                       ' * @param in Fully-formed CHPP structure.\n'
856                       ' * @param in Size of the CHPP structure in bytes.\n'
857                       ' *\n'
858                       ' * @return If successful, a pointer to a CHRE structure allocated with '
859                       'chppMalloc(). If unsuccessful, null.\n'
860                       ' * It is the responsibility of the caller to free the buffer when it is no '
861                       'longer needed via chppFree() or CHPP_FREE_AND_NULLIFY().\n'
862                       ' */\n')
863
864        out.append('{} *{}(\n'.format(
865            self._get_chre_type_with_prefix(chre_type),
866            self._get_decode_allocation_function_name(chre_type)))
867        out.append(
868            '    const {} *in,\n'.format(self._get_chpp_type_from_chre(chre_type)))
869        out.append('    size_t inSize)')
870        return out
871
872    def _gen_decode_allocation_function(self, chre_type):
873        out = []
874
875        out.extend(self._gen_decode_allocation_function_signature(chre_type))
876        out.append(' {\n')
877
878        out.append('  {} *out = NULL;\n\n'.format(
879            self._get_chre_type_with_prefix(chre_type)))
880
881        out.append('  if (inSize >= sizeof({})) {{\n'.format(
882            self._get_chpp_type_from_chre(chre_type)))
883
884        out.append('    out = chppMalloc(sizeof({}));\n'.format(
885            self._get_chre_type_with_prefix(chre_type)))
886        out.append('    if (out != NULL) {\n')
887
888        struct_info = self.api.structs_and_unions[chre_type]
889
890        out.append('      if (!{}(in, out'.format(
891            self._get_decoding_function_name(chre_type)))
892        if struct_info['has_vla_member']:
893            out.append(', inSize')
894        out.append(')) {')
895        out.append('        CHPP_FREE_AND_NULLIFY(out);\n')
896        out.append('      }\n')
897
898        out.append('    }\n')
899        out.append('  }\n\n')
900        out.append('  return out;\n')
901        out.append('}\n')
902        return out
903
904    def _gen_decode_allocation_functions(self):
905        out = []
906        for chre_type in self.json['root_structs']:
907            out.extend(self._gen_decode_allocation_function(chre_type))
908        return out
909
910    def _gen_decode_allocation_function_signatures(self):
911        out = []
912        for chre_type in self.json['root_structs']:
913            out.extend(
914                self._gen_decode_allocation_function_signature(chre_type, True))
915            out.append(';\n\n')
916        return out
917
918    # ----------------------------------------------------------------------------------------------
919    # Public methods
920    # ----------------------------------------------------------------------------------------------
921
922    def generate_header_file(self, dry_run=False, skip_clang_format=False):
923        """Creates a C header file for this API and writes it to the file indicated in the JSON."""
924
925        filename = self.service_name + '_types.h'
926        if not dry_run:
927            print('Generating {} ... '.format(filename), end='', flush=True)
928        output_file = os.path.join(
929            system_chre_abs_path(), CHPP_PARSER_INCLUDE_PATH, filename)
930        header = self.generate_header_string()
931        self._dump_to_file(output_file, header, dry_run, skip_clang_format)
932        if not dry_run:
933            print('done')
934
935    def generate_header_string(self):
936        """Returns a C header with structure definitions for this API."""
937
938        # To defer concatenation (speed things up), build the file as a list of strings then only
939        # concatenate once at the end
940        out = [LICENSE_HEADER]
941
942        header_guard = 'CHPP_{}_TYPES_H_'.format(self.service_name.upper())
943
944        out.append('#ifndef {}\n#define {}\n\n'.format(
945            header_guard, header_guard))
946        out.extend(self._autogen_notice())
947        out.extend(self._gen_header_includes())
948        out.append('#ifdef __cplusplus\nextern "C" {\n#endif\n\n')
949        out.extend(self._gen_structs_and_unions())
950
951        out.append('\n// Encoding functions (CHRE --> CHPP)\n\n')
952        out.extend(self._gen_encode_allocation_function_signatures())
953
954        out.append('\n// Decoding functions (CHPP --> CHRE)\n\n')
955        out.extend(self._gen_decode_allocation_function_signatures())
956
957        out.append('#ifdef __cplusplus\n}\n#endif\n\n')
958        out.append('#endif  // {}\n'.format(header_guard))
959        return ''.join(out)
960
961    def generate_conversion_file(self, dry_run=False, skip_clang_format=False):
962        """Generates a .c file with functions for encoding CHRE structs into CHPP and vice versa."""
963
964        filename = self.service_name + '_convert.c'
965        if not dry_run:
966            print('Generating {} ... '.format(filename), end='', flush=True)
967        contents = self.generate_conversion_string()
968        output_file = os.path.join(
969            system_chre_abs_path(), CHPP_PARSER_SOURCE_PATH, filename)
970        self._dump_to_file(output_file, contents, dry_run, skip_clang_format)
971        if not dry_run:
972            print('done')
973
974    def generate_conversion_string(self):
975        """Returns C code for encoding CHRE structs into CHPP and vice versa."""
976
977        out = [LICENSE_HEADER, '\n']
978
979        out.extend(self._autogen_notice())
980        out.extend(self._gen_conversion_includes())
981
982        out.append('\n// Encoding (CHRE --> CHPP) size functions\n\n')
983        out.extend(self._gen_chpp_sizeof_functions())
984        out.append('\n// Encoding (CHRE --> CHPP) conversion functions\n\n')
985        out.extend(self._gen_conversion_functions(False))
986        out.append('\n// Encoding (CHRE --> CHPP) top-level functions\n\n')
987        out.extend(self._gen_encode_allocation_functions())
988
989        out.append('\n// Decoding (CHPP --> CHRE) conversion functions\n\n')
990        out.extend(self._gen_conversion_functions(True))
991        out.append('\n// Decoding (CHPP --> CHRE) top-level functions\n\n')
992        out.extend(self._gen_decode_allocation_functions())
993
994        return ''.join(out)
995