1# 2# Copyright (C) 2016 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17import copy 18import logging 19import random 20import sys 21 22from vts.utils.python.fuzzer import FuzzerUtils 23from vts.utils.python.mirror import mirror_object_for_types 24from vts.proto import ComponentSpecificationMessage_pb2 as CompSpecMsg 25from google.protobuf import text_format 26 27# a dict containing the IDs of the registered function pointers. 28_function_pointer_id_dict = {} 29 30INTERFACE = "interface" 31API = "api" 32 33class MirrorObjectError(Exception): 34 """Raised when there is a general error in manipulating a mirror object.""" 35 pass 36 37 38class MirrorObject(object): 39 """The class that mirrors objects on the native side. 40 41 This class exists on the host and can be used to communicate to a 42 particular HAL in the HAL agent on the target side. 43 44 Attributes: 45 _client: the TCP client instance. 46 _if_spec_msg: the interface specification message of a host object to 47 mirror. 48 _callback_server: the instance of a callback server. 49 _parent_path: the name of a sub struct this object mirrors. 50 _last_raw_code_coverage_data: NativeCodeCoverageRawDataMessage, 51 last seen raw code coverage data. 52 __caller_uid: string, the caller's UID if not None. 53 """ 54 55 def __init__(self, client, msg, callback_server, parent_path=None): 56 self._client = client 57 self._if_spec_msg = msg 58 self._callback_server = callback_server 59 self._parent_path = parent_path 60 self._last_raw_code_coverage_data = None 61 self.__caller_uid = None 62 63 def GetFunctionPointerID(self, function_pointer): 64 """Returns the function pointer ID for the given one.""" 65 max_num = 0 66 for key in _function_pointer_id_dict: 67 if _function_pointer_id_dict[key] == function_pointer: 68 return str(key) 69 if not max_num or key > max_num: 70 max_num = key 71 _function_pointer_id_dict[max_num + 1] = function_pointer 72 id = str(max_num + 1) 73 if self._callback_server: 74 self._callback_server.RegisterCallback(id, function_pointer) 75 return id 76 77 def OpenConventionalHal(self, module_name=None): 78 """Opens the target conventional HAL component. 79 80 This is only needed for conventional HAL. 81 82 Args: 83 module_name: string, the name of a module to load. 84 """ 85 func_msg = CompSpecMsg.FunctionSpecificationMessage() 86 func_msg.name = "#Open" 87 logging.debug("remote call %s", func_msg.name) 88 if module_name: 89 arg = func_msg.arg.add() 90 arg.type = CompSpecMsg.TYPE_STRING 91 arg.string_value.message = module_name 92 93 func_msg.return_type.type == CompSpecMsg.TYPE_SCALAR 94 func_msg.return_type.scalar_type = "int32_t" 95 logging.debug("final msg %s", func_msg) 96 97 result = self._client.CallApi(text_format.MessageToString(func_msg), 98 self.__caller_uid) 99 logging.debug(result) 100 return result 101 102 def SetCallerUid(self, uid): 103 """Sets target-side caller's UID. 104 105 Args: 106 uid: string, the caller's UID. 107 """ 108 self.__caller_uid = uid 109 110 def GetAttributeValue(self, attribute_name): 111 """Retrieves the value of an attribute from a target. 112 113 Args: 114 attribute_name: string, the name of an attribute. 115 116 Returns: 117 FunctionSpecificationMessage which contains the value. 118 """ 119 def RemoteCallToGetAttribute(*args, **kwargs): 120 """Makes a remote call and retrieves an attribute.""" 121 func_msg = self.GetAttribute(attribute_name) 122 if not func_msg: 123 raise MirrorObjectError("attribute %s unknown", func_msg) 124 125 logging.debug("remote call %s.%s", self._parent_path, attribute_name) 126 logging.info("remote call %s%s", attribute_name, args) 127 if self._parent_path: 128 func_msg.parent_path = self._parent_path 129 try: 130 if isinstance(self._if_spec_msg, 131 CompSpecMsg.ComponentSpecificationMessage): 132 logging.info("component_class %s", 133 self._if_spec_msg.component_class) 134 if (self._if_spec_msg.component_class 135 == CompSpecMsg.HAL_CONVENTIONAL_SUBMODULE): 136 submodule_name = self._if_spec_msg.original_data_structure_name 137 if submodule_name.endswith("*"): 138 submodule_name = submodule_name[:-1] 139 func_msg.submodule_name = submodule_name 140 elif isinstance(self._if_spec_msg, 141 CompSpecMsg.StructSpecificationMessage): 142 pass 143 else: 144 logging.error("unknown type %s", type(self._if_spec_msg)) 145 sys.exit(1) 146 except AttributeError as e: 147 logging.exception("%s" % e) 148 pass 149 result = self._client.GetAttribute( 150 text_format.MessageToString(func_msg)) 151 logging.debug(result) 152 return result 153 154 var_msg = self.GetAttribute(attribute_name) 155 if var_msg: 156 logging.debug("attribute: %s", var_msg) 157 return RemoteCallToGetAttribute() 158 raise MirrorObjectError("unknown attribute name %s" % attribute_name) 159 160 def GetHidlCallbackInterface(self, interface_name, **kwargs): 161 var_msg = CompSpecMsg.VariableSpecificationMessage() 162 var_msg.name = interface_name 163 var_msg.type = CompSpecMsg.TYPE_FUNCTION_POINTER 164 var_msg.is_callback = True 165 166 msg = self._if_spec_msg 167 specification = self._client.ReadSpecification( 168 interface_name, 169 msg.component_class, 170 msg.component_type, 171 msg.component_type_version, 172 msg.package) 173 logging.info("specification: %s", specification) 174 interface = getattr(specification, INTERFACE, None) 175 apis = getattr(interface, API, []) 176 for api in apis: 177 function_pointer = None 178 if api.name in kwargs: 179 function_pointer = kwargs[api.name] 180 else: 181 def dummy(*args): 182 """Dummy implementation for any callback function.""" 183 logging.info("Entering dummy implementation" 184 " for callback function: %s", api.name) 185 for arg_index in range(len(args)): 186 logging.info("arg%s: %s", arg_index, args[arg_index]) 187 function_pointer = dummy 188 func_pt_msg = var_msg.function_pointer.add() 189 func_pt_msg.function_name = api.name 190 func_pt_msg.id = self.GetFunctionPointerID(function_pointer) 191 192 return var_msg 193 194 def GetHidlTypeInterface(self, interface_name, 195 target_class=None, target_type=None, 196 version=None, package=None): 197 """Gets HIDL type interface's host-side mirror. 198 199 Args: 200 interface_name: string, the name of a target interface to read. 201 target_class: integer, optional used to override the loaded HAL's 202 component_class. 203 target_type: integer, optional used to override the loaded HAL's 204 component_type. 205 version: integer, optional used to override the loaded HAL's 206 component_type_version. 207 package: integer, optional used to override the loaded HAL's 208 package. 209 210 Returns: 211 a host-side mirror of a HIDL interface 212 """ 213 msg = self._if_spec_msg 214 result = self._client.ReadSpecification( 215 interface_name, 216 msg.component_class if target_class is None else target_class, 217 msg.component_type if target_type is None else target_type, 218 msg.component_type_version if version is None else version, 219 msg.package if package is None else package, 220 recursive=True) 221 222 logging.info("result %s", result) 223 return mirror_object_for_types.MirrorObjectForTypes(result) 224 225 def CleanUp(self): 226 self._client.Disconnect() 227 228 def GetApi(self, api_name): 229 """Returns the Function Specification Message. 230 231 Args: 232 api_name: string, the name of the target function API. 233 234 Returns: 235 FunctionSpecificationMessage or StructSpecificationMessage if found, 236 None otherwise 237 """ 238 logging.debug("GetAPI %s for %s", api_name, self._if_spec_msg) 239 # handle reserved methods first. 240 if api_name == "notifySyspropsChanged": 241 func_msg = CompSpecMsg.FunctionSpecificationMessage() 242 func_msg.name = api_name 243 return func_msg 244 if isinstance(self._if_spec_msg, CompSpecMsg.ComponentSpecificationMessage): 245 if len(self._if_spec_msg.interface.api) > 0: 246 for api in self._if_spec_msg.interface.api: 247 if api.name == api_name: 248 return copy.copy(api) 249 elif isinstance(self._if_spec_msg, CompSpecMsg.StructSpecificationMessage): 250 if len(self._if_spec_msg.api) > 0: 251 for api in self._if_spec_msg.api: 252 logging.info("api %s", api) 253 if api.name == api_name: 254 return copy.copy(api) 255 if len(self._if_spec_msg.sub_struct) > 0: 256 for sub_struct in self._if_spec_msg.sub_struct: 257 if len(sub_struct.api) > 0: 258 for api in sub_struct.api: 259 logging.info("api %s", api) 260 if api.name == api_name: 261 return copy.copy(api) 262 else: 263 logging.error("unknown spec type %s", type(self._if_spec_msg)) 264 sys.exit(1) 265 return None 266 267 def GetAttribute(self, attribute_name): 268 """Returns the Message. 269 """ 270 logging.debug("GetAttribute %s for %s", 271 attribute_name, self._if_spec_msg) 272 if self._if_spec_msg.attribute: 273 for attribute in self._if_spec_msg.attribute: 274 if attribute.name == attribute_name: 275 func_msg = CompSpecMsg.FunctionSpecificationMessage() 276 func_msg.name = attribute.name 277 func_msg.return_type.type = attribute.type 278 if func_msg.return_type.type == CompSpecMsg.TYPE_SCALAR: 279 func_msg.return_type.scalar_type = attribute.scalar_type 280 else: 281 func_msg.return_type.predefined_type = attribute.predefined_type 282 logging.info("GetAttribute attribute: %s", attribute) 283 logging.info("GetAttribute request: %s", func_msg) 284 return copy.copy(func_msg) 285 if (self._if_spec_msg.interface and 286 self._if_spec_msg.interface.attribute): 287 for attribute in self._if_spec_msg.interface.attribute: 288 if attribute.name == attribute_name: 289 func_msg = CompSpecMsg.FunctionSpecificationMessage() 290 func_msg.name = attribute.name 291 func_msg.return_type.type = attribute.type 292 if func_msg.return_type.type == CompSpecMsg.TYPE_SCALAR: 293 func_msg.return_type.scalar_type = attribute.scalar_type 294 else: 295 func_msg.return_type.predefined_type = attribute.predefined_type 296 logging.info("GetAttribute attribute: %s", attribute) 297 logging.info("GetAttribute request: %s", func_msg) 298 return copy.copy(func_msg) 299 return None 300 301 def GetSubStruct(self, sub_struct_name): 302 """Returns the Struct Specification Message. 303 304 Args: 305 sub_struct_name: string, the name of the target sub struct attribute. 306 307 Returns: 308 StructSpecificationMessage if found, None otherwise 309 """ 310 if isinstance(self._if_spec_msg, CompSpecMsg.ComponentSpecificationMessage): 311 if (self._if_spec_msg.interface and 312 self._if_spec_msg.interface.sub_struct): 313 for sub_struct in self._if_spec_msg.interface.sub_struct: 314 if sub_struct.name == sub_struct_name: 315 return copy.copy(sub_struct) 316 elif isinstance(self._if_spec_msg, CompSpecMsg.StructSpecificationMessage): 317 if len(self._if_spec_msg.sub_struct) > 0: 318 for sub_struct in self._if_spec_msg.sub_struct: 319 if sub_struct.name == sub_struct_name: 320 return copy.copy(sub_struct) 321 return None 322 323 def GetCustomAggregateType(self, type_name): 324 """Returns the Argument Specification Message. 325 326 Args: 327 type_name: string, the name of the target data type. 328 329 Returns: 330 VariableSpecificationMessage if found, None otherwise 331 """ 332 try: 333 if self._if_spec_msg.attribute: 334 for attribute in self._if_spec_msg.attribute: 335 if not attribute.is_const and attribute.name == type_name: 336 return copy.copy(attribute) 337 if (self._if_spec_msg.interface and 338 self._if_spec_msg.interface.attribute): 339 for attribute in self._if_spec_msg.interface.attribute: 340 if not attribute.is_const and attribute.name == type_name: 341 return copy.copy(attribute) 342 return None 343 except AttributeError as e: 344 # TODO: check in advance whether self._if_spec_msg Interface 345 # SpecificationMessage. 346 return None 347 348 def GetConstType(self, type_name): 349 """Returns the Argument Specification Message. 350 351 Args: 352 type_name: string, the name of the target const data variable. 353 354 Returns: 355 VariableSpecificationMessage if found, None otherwise 356 """ 357 try: 358 if self._if_spec_msg.attribute: 359 for attribute in self._if_spec_msg.attribute: 360 if attribute.is_const and attribute.name == type_name: 361 return copy.copy(attribute) 362 elif attribute.type == CompSpecMsg.TYPE_ENUM: 363 for enumerator in attribute.enum_value.enumerator: 364 if enumerator == type_name: 365 return copy.copy(attribute) 366 if self._if_spec_msg.interface and self._if_spec_msg.interface.attribute: 367 for attribute in self._if_spec_msg.interface.attribute: 368 if attribute.is_const and attribute.name == type_name: 369 return copy.copy(attribute) 370 elif attribute.type == CompSpecMsg.TYPE_ENUM: 371 for enumerator in attribute.enum_value.enumerator: 372 if enumerator == type_name: 373 return copy.copy(attribute) 374 return None 375 except AttributeError as e: 376 # TODO: check in advance whether self._if_spec_msg Interface 377 # SpecificationMessage. 378 return None 379 380 def ArgToPb(self, arg_msg, value_msg): 381 """Converts args to a ProtoBuf message. 382 383 Args: 384 arg_msg: the ProtoBuf message where the result will be stored in. 385 value_msg: value given as an argument. it can be either Python 386 data structure, a ProtoBuf message, or both. 387 388 Raises: 389 AttributeError: when unexpected type is seen. 390 """ 391 if isinstance(value_msg, CompSpecMsg.VariableSpecificationMessage): 392 arg_msg.CopyFrom(value_msg) 393 elif isinstance(value_msg, int): 394 arg_msg.type = CompSpecMsg.TYPE_SCALAR 395 if not arg_msg.scalar_type: 396 arg_msg.scalar_type = "int32_t" 397 setattr(arg_msg.scalar_value, arg_msg.scalar_type, value_msg) 398 elif isinstance(value_msg, float): 399 arg_msg.type = CompSpecMsg.TYPE_SCALAR 400 arg_msg.scalar_value.float_t = value_msg 401 arg_msg.scalar_type = "float_t" 402 elif isinstance(value_msg, str): 403 if ((arg_msg.type == CompSpecMsg.TYPE_SCALAR and 404 (arg_msg.scalar_type == "char_pointer" or 405 arg_msg.scalar_type == "uchar_pointer")) or 406 arg_msg.type == CompSpecMsg.TYPE_STRING): 407 arg_msg.string_value.message = value_msg 408 arg_msg.string_value.length = len(value_msg) 409 else: 410 raise MirrorObjectError( 411 "unsupported type %s for str" % arg_msg) 412 elif isinstance(value_msg, list): 413 if (arg_msg.type == CompSpecMsg.TYPE_VECTOR or 414 arg_msg.type == CompSpecMsg.TYPE_ARRAY): 415 first = True 416 for list_element in value_msg: 417 if first: 418 self.ArgToPb(arg_msg.vector_value[0], list_element) 419 first = False 420 else: 421 self.ArgToPb(arg_msg.vector_value.add(), list_element) 422 arg_msg.vector_size = len(value_msg) 423 else: 424 raise MirrorObjectError( 425 "unsupported arg_msg type %s for list" % arg_msg.type) 426 else: 427 raise MirrorObjectError( 428 "unsupported value type %s" % type(value_msg)) 429 430 # TODO: Guard against calls to this function after self.CleanUp is called. 431 def __getattr__(self, api_name, *args, **kwargs): 432 """Calls a target component's API. 433 434 Args: 435 api_name: string, the name of an API function to call. 436 *args: a list of arguments 437 **kwargs: a dict for the arg name and value pairs 438 """ 439 def RemoteCall(*args, **kwargs): 440 """Dynamically calls a remote API.""" 441 func_msg = self.GetApi(api_name) 442 if not func_msg: 443 raise MirrorObjectError("api %s unknown", func_msg) 444 445 logging.debug("remote call %s.%s", self._parent_path, api_name) 446 logging.info("remote call %s%s", api_name, args) 447 if args: 448 for arg_msg, value_msg in zip(func_msg.arg, args): 449 logging.debug("arg msg %s", arg_msg) 450 logging.debug("value %s", value_msg) 451 if value_msg is not None: 452 self.ArgToPb(arg_msg, value_msg) 453 454 logging.debug("final msg %s", func_msg) 455 else: 456 # TODO: use kwargs 457 for arg in func_msg.arg: 458 # TODO: handle other 459 if (arg.type == CompSpecMsg.TYPE_SCALAR 460 and arg.scalar_type == "pointer"): 461 arg.scalar_value.pointer = 0 462 logging.debug(func_msg) 463 464 if self._parent_path: 465 func_msg.parent_path = self._parent_path 466 467 if isinstance(self._if_spec_msg, CompSpecMsg.ComponentSpecificationMessage): 468 if self._if_spec_msg.component_class: 469 logging.info("component_class %s", 470 self._if_spec_msg.component_class) 471 if self._if_spec_msg.component_class == CompSpecMsg.HAL_CONVENTIONAL_SUBMODULE: 472 submodule_name = self._if_spec_msg.original_data_structure_name 473 if submodule_name.endswith("*"): 474 submodule_name = submodule_name[:-1] 475 func_msg.submodule_name = submodule_name 476 result = self._client.CallApi(text_format.MessageToString(func_msg), 477 self.__caller_uid) 478 logging.debug(result) 479 if (isinstance(result, tuple) and len(result) == 2 and 480 isinstance(result[1], dict) and "coverage" in result[1]): 481 self._last_raw_code_coverage_data = result[1]["coverage"] 482 return result[0] 483 return result 484 485 def MessageGenerator(*args, **kwargs): 486 """Dynamically generates a custom message instance.""" 487 arg_msg = self.GetCustomAggregateType(api_name) 488 if not arg_msg: 489 raise MirrorObjectError("arg %s unknown" % arg_msg) 490 logging.info("MessageGenerator %s %s", api_name, arg_msg) 491 logging.debug("MESSAGE %s", api_name) 492 if arg_msg.type == CompSpecMsg.TYPE_STRUCT: 493 for struct_value in arg_msg.struct_value: 494 logging.debug("for %s %s", 495 struct_value.name, struct_value.scalar_type) 496 for given_name, given_value in kwargs.iteritems(): 497 logging.debug("check %s %s", struct_value.name, given_name) 498 if given_name == struct_value.name: 499 logging.debug("match type=%s", struct_value.scalar_type) 500 if struct_value.type == CompSpecMsg.TYPE_SCALAR: 501 if struct_value.scalar_type == "uint32_t": 502 struct_value.scalar_value.uint32_t = given_value 503 elif struct_value.scalar_type == "int32_t": 504 struct_value.scalar_value.int32_t = given_value 505 else: 506 raise MirrorObjectError( 507 "support %s" % struct_value.scalar_type) 508 continue 509 elif arg_msg.type == CompSpecMsg.TYPE_FUNCTION_POINTER: 510 for fp_value in arg_msg.function_pointer: 511 logging.debug("for %s", fp_value.function_name) 512 for given_name, given_value in kwargs.iteritems(): 513 logging.debug("check %s %s", fp_value.function_name, given_name) 514 if given_name == fp_value.function_name: 515 fp_value.id = self.GetFunctionPointerID(given_value) 516 break 517 518 if arg_msg.type == CompSpecMsg.TYPE_STRUCT: 519 for struct_value, given_value in zip(arg_msg.struct_value, args): 520 logging.debug("arg match type=%s", struct_value.scalar_type) 521 if struct_value.type == CompSpecMsg.TYPE_SCALAR: 522 if struct_value.scalar_type == "uint32_t": 523 struct_value.scalar_value.uint32_t = given_value 524 elif struct_value.scalar_type == "int32_t": 525 struct_value.scalar_value.int32_t = given_value 526 else: 527 raise MirrorObjectError("support %s" % p_type) 528 elif arg_msg.type == CompSpecMsg.TYPE_FUNCTION_POINTER: 529 for fp_value, given_value in zip(arg_msg.function_pointer, args): 530 logging.debug("for %s", fp_value.function_name) 531 fp_value.id = self.GetFunctionPointerID(given_value) 532 logging.debug("fp %s", fp_value) 533 logging.debug("generated %s", arg_msg) 534 return arg_msg 535 536 def MessageFuzzer(arg_msg): 537 """Fuzz a custom message instance.""" 538 if not self.GetCustomAggregateType(api_name): 539 raise MirrorObjectError("fuzz arg %s unknown" % arg_msg) 540 541 if arg_msg.type == CompSpecMsg.TYPE_STRUCT: 542 index = random.randint(0, len(arg_msg.struct_value)) 543 count = 0 544 for struct_value in arg_msg.struct_value: 545 if count == index: 546 if struct_value.scalar_type == "uint32_t": 547 struct_value.scalar_value.uint32_t ^= FuzzerUtils.mask_uint32_t() 548 elif struct_value.scalar_type == "int32_t": 549 mask = FuzzerUtils.mask_int32_t() 550 if mask == (1 << 31): 551 struct_value.scalar_value.int32_t *= -1 552 struct_value.scalar_value.int32_t += 1 553 else: 554 struct_value.scalar_value.int32_t ^= mask 555 else: 556 raise MirrorObjectError( 557 "support %s" % struct_value.scalar_type) 558 break 559 count += 1 560 logging.debug("fuzzed %s", arg_msg) 561 else: 562 raise MirrorObjectError( 563 "unsupported fuzz message type %s." % arg_msg.type) 564 return arg_msg 565 566 def ConstGenerator(): 567 """Dynamically generates a const variable's value.""" 568 arg_msg = self.GetConstType(api_name) 569 if not arg_msg: 570 raise MirrorObjectError("const %s unknown" % arg_msg) 571 logging.debug("check %s", api_name) 572 if arg_msg.type == CompSpecMsg.TYPE_SCALAR: 573 ret_v = getattr(arg_msg.scalar_value, arg_msg.scalar_type, None) 574 if ret_v is None: 575 raise MirrorObjectError( 576 "No value found for type %s in %s." % 577 (arg_msg.scalar_type, api_name)) 578 return ret_v 579 elif arg_msg.type == CompSpecMsg.TYPE_STRING: 580 return arg_msg.string_value.message 581 elif arg_msg.type == CompSpecMsg.TYPE_ENUM: 582 for enumerator, scalar_value in zip( 583 arg_msg.enum_value.enumerator, 584 arg_msg.enum_value.scalar_value): 585 if enumerator == api_name: 586 return getattr(scalar_value, 587 arg_msg.enum_value.scalar_type) 588 raise MirrorObjectError("const %s not found" % api_name) 589 590 # handle APIs. 591 func_msg = self.GetApi(api_name) 592 if func_msg: 593 logging.debug("api %s", func_msg) 594 return RemoteCall 595 596 struct_msg = self.GetSubStruct(api_name) 597 if struct_msg: 598 logging.debug("sub_struct %s", struct_msg) 599 if self._parent_path: 600 parent_name = "%s.%s" % (self._parent_path, api_name) 601 else: 602 parent_name = api_name 603 return MirrorObject(self._client, struct_msg, 604 self._callback_server, 605 parent_path=parent_name) 606 607 # handle attributes. 608 fuzz = False 609 if api_name.endswith("_fuzz"): 610 fuzz = True 611 api_name = api_name[:-5] 612 arg_msg = self.GetCustomAggregateType(api_name) 613 if arg_msg: 614 logging.debug("arg %s", arg_msg) 615 if not fuzz: 616 return MessageGenerator 617 else: 618 return MessageFuzzer 619 620 arg_msg = self.GetConstType(api_name) 621 if arg_msg: 622 logging.debug("const %s *\n%s", api_name, arg_msg) 623 return ConstGenerator() 624 raise MirrorObjectError("unknown api name %s" % api_name) 625 626 def GetRawCodeCoverage(self): 627 """Returns any measured raw code coverage data.""" 628 return self._last_raw_code_coverage_data 629 630 def __str__(self): 631 """Prints all the attributes and methods.""" 632 result = "" 633 if self._if_spec_msg: 634 if self._if_spec_msg.attribute: 635 for attribute in self._if_spec_msg.attribute: 636 result += "attribute %s\n" % attribute.name 637 if self._if_spec_msg.api: 638 for api in self._if_spec_msg.api: 639 result += "api %s\n" % api.name 640 return result 641