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