1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 """Code to provide functions for FAFT tests. 6 7 These can be exposed via a xmlrpci server running on the DUT. 8 """ 9 10 import functools, os, tempfile 11 12 import common 13 from autotest_lib.client.cros.faft.utils import (cgpt_handler, 14 os_interface, 15 firmware_check_keys, 16 firmware_updater, 17 flashrom_handler, 18 kernel_handler, 19 rootfs_handler, 20 saft_flashrom_util, 21 tpm_handler, 22 ) 23 24 25 def allow_multiple_section_input(image_operator): 26 """Decorate a method to support multiple sections. 27 28 @param image_operator: Method accepting one section as its argument. 29 """ 30 @functools.wraps(image_operator) 31 def wrapper(self, section): 32 """Wrapper method to support multiple sections. 33 34 @param section: A list of sections of just a section. 35 """ 36 if type(section) in (tuple, list): 37 for sec in section: 38 image_operator(self, sec) 39 else: 40 image_operator(self, section) 41 return wrapper 42 43 44 class LazyFlashromHandlerProxy: 45 """Proxy of FlashromHandler for lazy initialization.""" 46 _loaded = False 47 _obj = None 48 49 def __init__(self, *args, **kargs): 50 self._args = args 51 self._kargs = kargs 52 53 def _load(self): 54 self._obj = flashrom_handler.FlashromHandler() 55 self._obj.init(*self._args, **self._kargs) 56 self._obj.new_image() 57 self._loaded = True 58 59 def __getattr__(self, name): 60 if not self._loaded: 61 self._load() 62 return getattr(self._obj, name) 63 64 def reload(self): 65 """Reload the FlashromHandler class.""" 66 self._loaded = False 67 68 69 class RPCFunctions(object): 70 """A class which aggregates some useful functions for firmware testing. 71 72 This class can be exposed via a XMLRPC server such that its functions can 73 be accessed remotely. Method naming should fit the naming rule 74 '_[categories]_[method_name]' where categories contains system, ec, bios, 75 kernel, cgpt, tpm, updater, etc. Methods should be called by 76 'FAFTClient.[categories].[method_name]', because _dispatch will rename 77 this name to '_[categories]_[method_name]'. 78 79 Attributes: 80 _os_if: An object to encapsulate OS services functions. 81 _bios_handler: An object to automate BIOS flashrom testing. 82 _ec_handler: An object to automate EC flashrom testing. 83 _kernel_handler: An object to provide kernel related actions. 84 _log_file: Path of the log file. 85 _tpm_handler: An object to control TPM device. 86 _updater: An object to update firmware. 87 _temp_path: Path of a temp directory. 88 _keys_path: Path of a directory, keys/, in temp directory. 89 _work_path: Path of a directory, work/, in temp directory. 90 """ 91 def __init__(self): 92 """Initialize the data attributes of this class.""" 93 # TODO(waihong): Move the explicit object.init() methods to the 94 # objects' constructors (OSInterface, FlashromHandler, 95 # KernelHandler, and TpmHandler). 96 self._os_if = os_interface.OSInterface() 97 # We keep the state of FAFT test in a permanent directory over reboots. 98 state_dir = '/var/tmp/faft' 99 self._log_file = os.path.join(state_dir, 'faft_client.log') 100 self._os_if.init(state_dir, log_file=self._log_file) 101 os.chdir(state_dir) 102 103 self._bios_handler = LazyFlashromHandlerProxy( 104 saft_flashrom_util, 105 self._os_if, 106 None, 107 '/usr/share/vboot/devkeys', 108 'bios') 109 110 self._ec_handler = None 111 if self._os_if.run_shell_command_get_status('mosys ec info') == 0: 112 self._ec_handler = LazyFlashromHandlerProxy( 113 saft_flashrom_util, 114 self._os_if, 115 'ec_root_key.vpubk', 116 '/usr/share/vboot/devkeys', 117 'ec') 118 else: 119 self._os_if.log('No EC is reported by mosys.') 120 121 self._kernel_handler = kernel_handler.KernelHandler() 122 self._kernel_handler.init(self._os_if, 123 dev_key_path='/usr/share/vboot/devkeys', 124 internal_disk=True) 125 126 # FIXME(waihong): Add back the TPM support. 127 if not self._os_if.is_android: 128 self._tpm_handler = tpm_handler.TpmHandler() 129 self._tpm_handler.init(self._os_if) 130 else: 131 self._tpm_handler = None 132 133 self._cgpt_handler = cgpt_handler.CgptHandler(self._os_if) 134 135 self._rootfs_handler = rootfs_handler.RootfsHandler() 136 self._rootfs_handler.init(self._os_if) 137 138 self._updater = firmware_updater.FirmwareUpdater(self._os_if) 139 self._check_keys = firmware_check_keys.firmwareCheckKeys() 140 141 # Initialize temporary directory path 142 self._temp_path = '/var/tmp/faft/autest' 143 self._keys_path = os.path.join(self._temp_path, 'keys') 144 self._work_path = os.path.join(self._temp_path, 'work') 145 146 def _dispatch(self, method, params): 147 """This _dispatch method handles string conversion especially. 148 149 Since we turn off allow_dotted_names option. So any string conversion, 150 like str(FAFTClient.method), i.e. FAFTClient.method.__str__, failed 151 via XML RPC call. 152 """ 153 is_str = method.endswith('.__str__') 154 if is_str: 155 method = method.rsplit('.', 1)[0] 156 157 categories = ('system', 'host', 'bios', 'ec', 'kernel', 158 'tpm', 'cgpt', 'updater', 'rootfs') 159 try: 160 if method.split('.', 1)[0] in categories: 161 func = getattr(self, '_%s_%s' % (method.split('.', 1)[0], 162 method.split('.', 1)[1])) 163 else: 164 func = getattr(self, method) 165 except AttributeError: 166 raise Exception('method "%s" is not supported' % method) 167 168 if is_str: 169 return str(func) 170 else: 171 self._os_if.log('Dispatching method %s with args %r' % 172 (func.__name__, params)) 173 return func(*params) 174 175 def _system_is_available(self): 176 """Function for polling the RPC server availability. 177 178 @return: Always True. 179 """ 180 return True 181 182 def _system_has_host(self): 183 """Return True if a host is connected to DUT.""" 184 return self._os_if.has_host() 185 186 def _system_wait_for_client(self, timeout): 187 """Wait for the client to come back online. 188 189 @param timeout: Time in seconds to wait for the client SSH daemon to 190 come up. 191 @return: True if succeed; otherwise False. 192 """ 193 return self._os_if.wait_for_device(timeout) 194 195 def _system_wait_for_client_offline(self, timeout): 196 """Wait for the client to come offline. 197 198 @param timeout: Time in seconds to wait the client to come offline. 199 @return: True if succeed; otherwise False. 200 """ 201 return self._os_if.wait_for_no_device(timeout) 202 203 def _system_dump_log(self, remove_log=False): 204 """Dump the log file. 205 206 @param remove_log: Remove the log file after dump. 207 @return: String of the log file content. 208 """ 209 log = open(self._log_file).read() 210 if remove_log: 211 os.remove(self._log_file) 212 return log 213 214 def _system_run_shell_command(self, command): 215 """Run shell command. 216 217 @param command: A shell command to be run. 218 """ 219 self._os_if.run_shell_command(command) 220 221 def _system_run_shell_command_get_output(self, command): 222 """Run shell command and get its console output. 223 224 @param command: A shell command to be run. 225 @return: A list of strings stripped of the newline characters. 226 """ 227 return self._os_if.run_shell_command_get_output(command) 228 229 def _host_run_shell_command(self, command): 230 """Run shell command on the host. 231 232 @param command: A shell command to be run. 233 """ 234 self._os_if.run_host_shell_command(command) 235 236 def _host_run_shell_command_get_output(self, command): 237 """Run shell command and get its console output on the host. 238 239 @param command: A shell command to be run. 240 @return: A list of strings stripped of the newline characters. 241 """ 242 return self._os_if.run_host_shell_command_get_output(command) 243 244 def _host_run_nonblock_shell_command(self, command): 245 """Run non-blocking shell command 246 247 @param command: A shell command to be run. 248 @return: none 249 """ 250 return self._os_if.run_host_shell_command(command, False) 251 252 def _system_software_reboot(self): 253 """Request software reboot.""" 254 self._os_if.run_shell_command('reboot') 255 256 def _system_get_platform_name(self): 257 """Get the platform name of the current system. 258 259 @return: A string of the platform name. 260 """ 261 # 'mosys platform name' sometimes fails. Let's get the verbose output. 262 lines = self._os_if.run_shell_command_get_output( 263 '(mosys -vvv platform name 2>&1) || echo Failed') 264 if lines[-1].strip() == 'Failed': 265 raise Exception('Failed getting platform name: ' + '\n'.join(lines)) 266 return lines[-1] 267 268 def _system_get_crossystem_value(self, key): 269 """Get crossystem value of the requested key. 270 271 @param key: A crossystem key. 272 @return: A string of the requested crossystem value. 273 """ 274 return self._os_if.run_shell_command_get_output( 275 'crossystem %s' % key)[0] 276 277 def _system_get_root_dev(self): 278 """Get the name of root device without partition number. 279 280 @return: A string of the root device without partition number. 281 """ 282 return self._os_if.get_root_dev() 283 284 def _system_get_root_part(self): 285 """Get the name of root device with partition number. 286 287 @return: A string of the root device with partition number. 288 """ 289 return self._os_if.get_root_part() 290 291 def _system_set_try_fw_b(self, count=1): 292 """Set 'Try Frimware B' flag in crossystem. 293 294 @param count: # times to try booting into FW B 295 """ 296 self._os_if.cs.fwb_tries = count 297 298 def _system_set_fw_try_next(self, next, count=0): 299 """Set fw_try_next to A or B 300 301 @param next: Next FW to reboot to (A or B) 302 @param count: # of times to try booting into FW <next> 303 """ 304 self._os_if.cs.fw_try_next = next 305 if count: 306 self._os_if.cs.fw_try_count = count 307 308 def _system_get_fw_vboot2(self): 309 """Get fw_vboot2""" 310 try: 311 return self._os_if.cs.fw_vboot2 == '1' 312 except os_interface.OSInterfaceError: 313 return False 314 315 def _system_request_recovery_boot(self): 316 """Request running in recovery mode on the restart.""" 317 self._os_if.cs.request_recovery() 318 319 def _system_get_dev_boot_usb(self): 320 """Get dev_boot_usb value which controls developer mode boot from USB. 321 322 @return: True if enable, False if disable. 323 """ 324 return self._os_if.cs.dev_boot_usb == '1' 325 326 def _system_set_dev_boot_usb(self, value): 327 """Set dev_boot_usb value which controls developer mode boot from USB. 328 329 @param value: True to enable, False to disable. 330 """ 331 self._os_if.cs.dev_boot_usb = 1 if value else 0 332 333 def _system_is_removable_device_boot(self): 334 """Check the current boot device is removable. 335 336 @return: True: if a removable device boots. 337 False: if a non-removable device boots. 338 """ 339 root_part = self._os_if.get_root_part() 340 return self._os_if.is_removable_device(root_part) 341 342 def _system_create_temp_dir(self, prefix='backup_'): 343 """Create a temporary directory and return the path.""" 344 return tempfile.mkdtemp(prefix=prefix) 345 346 def _bios_reload(self): 347 """Reload the firmware image that may be changed.""" 348 self._bios_handler.reload() 349 350 def _bios_get_gbb_flags(self): 351 """Get the GBB flags. 352 353 @return: An integer of the GBB flags. 354 """ 355 return self._bios_handler.get_gbb_flags() 356 357 def _bios_set_gbb_flags(self, flags): 358 """Set the GBB flags. 359 360 @param flags: An integer of the GBB flags. 361 """ 362 self._bios_handler.set_gbb_flags(flags, write_through=True) 363 364 def _bios_get_preamble_flags(self, section): 365 """Get the preamble flags of a firmware section. 366 367 @param section: A firmware section, either 'a' or 'b'. 368 @return: An integer of the preamble flags. 369 """ 370 return self._bios_handler.get_section_flags(section) 371 372 def _bios_set_preamble_flags(self, section, flags): 373 """Set the preamble flags of a firmware section. 374 375 @param section: A firmware section, either 'a' or 'b'. 376 @param flags: An integer of preamble flags. 377 """ 378 version = self._bios_get_version(section) 379 self._bios_handler.set_section_version(section, version, flags, 380 write_through=True) 381 382 def _bios_get_body_sha(self, section): 383 """Get SHA1 hash of BIOS RW firmware section. 384 385 @param section: A firmware section, either 'a' or 'b'. 386 @param flags: An integer of preamble flags. 387 """ 388 return self._bios_handler.get_section_sha(section) 389 390 def _bios_get_sig_sha(self, section): 391 """Get SHA1 hash of firmware vblock in section.""" 392 return self._bios_handler.get_section_sig_sha(section) 393 394 @allow_multiple_section_input 395 def _bios_corrupt_sig(self, section): 396 """Corrupt the requested firmware section signature. 397 398 @param section: A firmware section, either 'a' or 'b'. 399 """ 400 self._bios_handler.corrupt_firmware(section) 401 402 @allow_multiple_section_input 403 def _bios_restore_sig(self, section): 404 """Restore the previously corrupted firmware section signature. 405 406 @param section: A firmware section, either 'a' or 'b'. 407 """ 408 self._bios_handler.restore_firmware(section) 409 410 @allow_multiple_section_input 411 def _bios_corrupt_body(self, section): 412 """Corrupt the requested firmware section body. 413 414 @param section: A firmware section, either 'a' or 'b'. 415 """ 416 self._bios_handler.corrupt_firmware_body(section) 417 418 @allow_multiple_section_input 419 def _bios_restore_body(self, section): 420 """Restore the previously corrupted firmware section body. 421 422 @param section: A firmware section, either 'a' or 'b'. 423 """ 424 self._bios_handler.restore_firmware_body(section) 425 426 def __bios_modify_version(self, section, delta): 427 """Modify firmware version for the requested section, by adding delta. 428 429 The passed in delta, a positive or a negative number, is added to the 430 original firmware version. 431 """ 432 original_version = self._bios_get_version(section) 433 new_version = original_version + delta 434 flags = self._bios_handler.get_section_flags(section) 435 self._os_if.log( 436 'Setting firmware section %s version from %d to %d' % ( 437 section, original_version, new_version)) 438 self._bios_handler.set_section_version(section, new_version, flags, 439 write_through=True) 440 441 @allow_multiple_section_input 442 def _bios_move_version_backward(self, section): 443 """Decrement firmware version for the requested section.""" 444 self.__bios_modify_version(section, -1) 445 446 @allow_multiple_section_input 447 def _bios_move_version_forward(self, section): 448 """Increase firmware version for the requested section.""" 449 self.__bios_modify_version(section, 1) 450 451 def _bios_get_version(self, section): 452 """Retrieve firmware version of a section.""" 453 return self._bios_handler.get_section_version(section) 454 455 def _bios_get_datakey_version(self, section): 456 """Return firmware data key version.""" 457 return self._bios_handler.get_section_datakey_version(section) 458 459 def _bios_get_kernel_subkey_version(self, section): 460 """Return kernel subkey version.""" 461 return self._bios_handler.get_section_kernel_subkey_version(section) 462 463 def _bios_dump_whole(self, bios_path): 464 """Dump the current BIOS firmware to a file, specified by bios_path. 465 466 @param bios_path: The path of the BIOS image to be written. 467 """ 468 self._bios_handler.dump_whole(bios_path) 469 470 def _bios_write_whole(self, bios_path): 471 """Write the firmware from bios_path to the current system. 472 473 @param bios_path: The path of the source BIOS image. 474 """ 475 self._bios_handler.new_image(bios_path) 476 self._bios_handler.write_whole() 477 478 def _ec_get_version(self): 479 """Get EC version via mosys. 480 481 @return: A string of the EC version. 482 """ 483 return self._os_if.run_shell_command_get_output( 484 'mosys ec info | sed "s/.*| //"')[0] 485 486 def _ec_get_firmware_sha(self): 487 """Get SHA1 hash of EC RW firmware section.""" 488 return self._ec_handler.get_section_sha('rw') 489 490 @allow_multiple_section_input 491 def _ec_corrupt_sig(self, section): 492 """Corrupt the requested EC section signature. 493 494 @param section: A EC section, either 'a' or 'b'. 495 """ 496 self._ec_handler.corrupt_firmware(section, corrupt_all=True) 497 498 @allow_multiple_section_input 499 def _ec_restore_sig(self, section): 500 """Restore the previously corrupted EC section signature. 501 502 @param section: An EC section, either 'a' or 'b'. 503 """ 504 self._ec_handler.restore_firmware(section, restore_all=True) 505 506 @allow_multiple_section_input 507 def _ec_corrupt_body(self, section): 508 """Corrupt the requested EC section body. 509 510 @param section: An EC section, either 'a' or 'b'. 511 """ 512 self._ec_handler.corrupt_firmware_body(section, corrupt_all=True) 513 514 @allow_multiple_section_input 515 def _ec_restore_body(self, section): 516 """Restore the previously corrupted EC section body. 517 518 @param section: An EC section, either 'a' or 'b'. 519 """ 520 self._ec_handler.restore_firmware_body(section, restore_all=True) 521 522 def _ec_dump_firmware(self, ec_path): 523 """Dump the current EC firmware to a file, specified by ec_path. 524 525 @param ec_path: The path of the EC image to be written. 526 """ 527 self._ec_handler.dump_whole(ec_path) 528 529 def _ec_set_write_protect(self, enable): 530 """Enable write protect of the EC flash chip. 531 532 @param enable: True if activating EC write protect. Otherwise, False. 533 """ 534 if enable: 535 self._ec_handler.enable_write_protect() 536 else: 537 self._ec_handler.disable_write_protect() 538 539 @allow_multiple_section_input 540 def _kernel_corrupt_sig(self, section): 541 """Corrupt the requested kernel section. 542 543 @param section: A kernel section, either 'a' or 'b'. 544 """ 545 self._kernel_handler.corrupt_kernel(section) 546 547 @allow_multiple_section_input 548 def _kernel_restore_sig(self, section): 549 """Restore the requested kernel section (previously corrupted). 550 551 @param section: A kernel section, either 'a' or 'b'. 552 """ 553 self._kernel_handler.restore_kernel(section) 554 555 def __kernel_modify_version(self, section, delta): 556 """Modify kernel version for the requested section, by adding delta. 557 558 The passed in delta, a positive or a negative number, is added to the 559 original kernel version. 560 """ 561 original_version = self._kernel_handler.get_version(section) 562 new_version = original_version + delta 563 self._os_if.log( 564 'Setting kernel section %s version from %d to %d' % ( 565 section, original_version, new_version)) 566 self._kernel_handler.set_version(section, new_version) 567 568 @allow_multiple_section_input 569 def _kernel_move_version_backward(self, section): 570 """Decrement kernel version for the requested section.""" 571 self.__kernel_modify_version(section, -1) 572 573 @allow_multiple_section_input 574 def _kernel_move_version_forward(self, section): 575 """Increase kernel version for the requested section.""" 576 self.__kernel_modify_version(section, 1) 577 578 def _kernel_get_version(self, section): 579 """Return kernel version.""" 580 return self._kernel_handler.get_version(section) 581 582 def _kernel_get_datakey_version(self, section): 583 """Return kernel datakey version.""" 584 return self._kernel_handler.get_datakey_version(section) 585 586 def _kernel_diff_a_b(self): 587 """Compare kernel A with B. 588 589 @return: True: if kernel A is different with B. 590 False: if kernel A is the same as B. 591 """ 592 rootdev = self._os_if.get_root_dev() 593 kernel_a = self._os_if.join_part(rootdev, '2') 594 kernel_b = self._os_if.join_part(rootdev, '4') 595 596 # The signature (some kind of hash) for the kernel body is stored in 597 # the beginning. So compare the first 64KB (including header, preamble, 598 # and signature) should be enough to check them identical. 599 header_a = self._os_if.read_partition(kernel_a, 0x10000) 600 header_b = self._os_if.read_partition(kernel_b, 0x10000) 601 602 return header_a != header_b 603 604 def _kernel_resign_with_keys(self, section, key_path=None): 605 """Resign kernel with temporary key.""" 606 self._kernel_handler.resign_kernel(section, key_path) 607 608 def _kernel_dump(self, section, kernel_path): 609 """Dump the specified kernel to a file. 610 611 @param section: The kernel to dump. May be A or B. 612 @param kernel_path: The path to the kernel image to be written. 613 """ 614 self._kernel_handler.dump_kernel(section, kernel_path) 615 616 def _kernel_write(self, section, kernel_path): 617 """Write a kernel image to the specified section. 618 619 @param section: The kernel to dump. May be A or B. 620 @param kernel_path: The path to the kernel image. 621 """ 622 self._kernel_handler.write_kernel(section, kernel_path) 623 624 def _kernel_get_sha(self, section): 625 """Return the SHA1 hash of the specified kernel section.""" 626 return self._kernel_handler.get_sha(section) 627 628 def _tpm_get_firmware_version(self): 629 """Retrieve tpm firmware body version.""" 630 return self._tpm_handler.get_fw_version() 631 632 def _tpm_get_firmware_datakey_version(self): 633 """Retrieve tpm firmware data key version.""" 634 return self._tpm_handler.get_fw_body_version() 635 636 def _cgpt_get_attributes(self): 637 """Get kernel attributes.""" 638 rootdev = self._system_get_root_dev() 639 self._cgpt_handler.read_device_info(rootdev) 640 return {'A': self._cgpt_handler.get_partition(rootdev, 'KERN-A'), 641 'B': self._cgpt_handler.get_partition(rootdev, 'KERN-B')} 642 643 def _cgpt_set_attributes(self, attributes): 644 """Set kernel attributes.""" 645 rootdev = self._system_get_root_dev() 646 allowed = ['priority', 'tries', 'successful'] 647 for p in ('A', 'B'): 648 if p not in attributes: 649 continue 650 attr = dict() 651 for k in allowed: 652 if k in attributes[p]: 653 attr[k] = attributes[p][k] 654 if attr: 655 self._cgpt_handler.set_partition(rootdev, 'KERN-%s' % p, attr) 656 657 def _updater_cleanup(self): 658 self._updater.cleanup_temp_dir() 659 660 def _updater_get_fwid(self): 661 """Retrieve shellball's fwid. 662 663 @return: Shellball's fwid. 664 """ 665 return self._updater.retrieve_fwid() 666 667 def _updater_resign_firmware(self, version): 668 """Resign firmware with version. 669 670 @param version: new version number. 671 """ 672 self._updater.resign_firmware(version) 673 674 def _updater_repack_shellball(self, append): 675 """Repack shellball with new fwid. 676 677 @param append: use for new fwid naming. 678 """ 679 self._updater.repack_shellball(append) 680 681 def _updater_run_autoupdate(self, append): 682 """Run chromeos-firmwareupdate with autoupdate mode.""" 683 options = ['--noupdate_ec', '--nocheck_rw_compatible'] 684 self._updater.run_firmwareupdate(mode='autoupdate', 685 updater_append=append, 686 options=options) 687 688 def _updater_run_factory_install(self): 689 """Run chromeos-firmwareupdate with factory_install mode.""" 690 options = ['--noupdate_ec'] 691 self._updater.run_firmwareupdate(mode='factory_install', 692 options=options) 693 694 def _updater_run_bootok(self, append): 695 """Run chromeos-firmwareupdate with bootok mode.""" 696 self._updater.run_firmwareupdate(mode='bootok', 697 updater_append=append) 698 699 def _updater_run_recovery(self): 700 """Run chromeos-firmwareupdate with recovery mode.""" 701 options = ['--noupdate_ec', '--nocheck_rw_compatible'] 702 self._updater.run_firmwareupdate(mode='recovery', 703 options=options) 704 705 def _updater_get_temp_path(self): 706 """Get updater's temp directory path.""" 707 return self._updater.get_temp_path() 708 709 def _updater_get_keys_path(self): 710 """Get updater's keys directory path.""" 711 return self._updater.get_keys_path() 712 713 def _updater_get_work_path(self): 714 """Get updater's work directory path.""" 715 return self._updater.get_work_path() 716 717 def _rootfs_verify_rootfs(self, section): 718 """Verifies the integrity of the root FS. 719 720 @param section: The rootfs to verify. May be A or B. 721 """ 722 return self._rootfs_handler.verify_rootfs(section) 723 724 def _system_check_keys(self, expected_sequence): 725 """Check the keys sequence was as expected. 726 727 @param expected_sequence: A list of expected key sequences. 728 """ 729 return self._check_keys.check_keys(expected_sequence) 730 731 def cleanup(self): 732 """Cleanup for the RPC server. Currently nothing.""" 733 pass 734