1#!/usr/bin/env python3 2# coding=utf-8 3 4# 5# Copyright (c) 2022 Huawei Device Co., Ltd. 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19import os 20import platform 21import socket 22import struct 23import threading 24import time 25import shutil 26import stat 27from dataclasses import dataclass 28 29from xdevice import DeviceOsType 30from xdevice import ReportException 31from xdevice import ExecuteTerminate 32from xdevice import platform_logger 33from xdevice import Plugin 34from xdevice import get_plugin 35from xdevice import IShellReceiver 36from xdevice import exec_cmd 37from xdevice import get_file_absolute_path 38from xdevice import FilePermission 39from xdevice import DeviceError 40from xdevice import HdcError 41from xdevice import HdcCommandRejectedException 42from xdevice import ShellCommandUnresponsiveException 43from xdevice import DeviceState 44from xdevice import convert_serial 45from xdevice import is_proc_running 46from xdevice import convert_ip 47from xdevice import create_dir 48 49ID_OKAY = b'OKAY' 50ID_FAIL = b'FAIL' 51ID_STAT = b'STAT' 52ID_RECV = b'RECV' 53ID_DATA = b'DATA' 54ID_DONE = b'DONE' 55ID_SEND = b'SEND' 56ID_LIST = b'LIST' 57ID_DENT = b'DENT' 58 59DEFAULT_ENCODING = "ISO-8859-1" 60SYNC_DATA_MAX = 64 * 1024 61REMOTE_PATH_MAX_LENGTH = 1024 62SOCK_DATA_MAX = 256 63 64INSTALL_TIMEOUT = 2 * 60 * 1000 65DEFAULT_TIMEOUT = 40 * 1000 66 67MAX_CONNECT_ATTEMPT_COUNT = 10 68DATA_UNIT_LENGTH = 4 69HEXADECIMAL_NUMBER = 16 70SPECIAL_FILE_MODE = 41471 71FORMAT_BYTES_LENGTH = 4 72DEFAULT_OFFSET_OF_INT = 4 73 74INVALID_MODE_CODE = -1 75DEFAULT_STD_PORT = 8710 76HDC_NAME = "hdc" 77HDC_STD_NAME = "hdc_std" 78LOG = platform_logger("Hdc") 79 80 81class HdcMonitor: 82 """ 83 A Device monitor. 84 This monitor connects to the Device Connector, gets device and 85 debuggable process information from it. 86 """ 87 MONITOR_MAP = {} 88 89 def __init__(self, host="127.0.0.1", port=None, device_connector=None): 90 self.channel = dict() 91 self.channel.setdefault("host", host) 92 self.channel.setdefault("port", port) 93 self.main_hdc_connection = None 94 self.connection_attempt = 0 95 self.is_stop = False 96 self.monitoring = False 97 self.server = device_connector 98 self.devices = [] 99 100 @staticmethod 101 def get_instance(host, port=None, device_connector=None): 102 if host not in HdcMonitor.MONITOR_MAP: 103 monitor = HdcMonitor(host, port, device_connector) 104 HdcMonitor.MONITOR_MAP[host] = monitor 105 LOG.debug("HdcMonitor map add host %s, map is %s" % 106 (host, HdcMonitor.MONITOR_MAP)) 107 108 return HdcMonitor.MONITOR_MAP[host] 109 110 def start(self): 111 """ 112 Starts the monitoring. 113 """ 114 try: 115 connector_name = HDC_STD_NAME if HdcHelper.is_hdc_std() else HDC_NAME 116 self.init_hdc(connector_name) 117 server_thread = threading.Thread(target=self.loop_monitor, 118 name="HdcMonitor", args=()) 119 server_thread.setDaemon(True) 120 server_thread.start() 121 except FileNotFoundError as _: 122 LOG.error("HdcMonitor can't find connector, init device " 123 "environment failed!") 124 125 def init_hdc(self, connector_name=HDC_NAME): 126 env_hdc = shutil.which(connector_name) 127 # if not, add xdevice's own hdc path to environ path. 128 # tell if hdc has already been in the environ path. 129 if env_hdc is None: 130 LOG.error("Can not find {} or {} environment variable, " 131 "please set it first!".format(HDC_NAME, HDC_STD_NAME)) 132 if not is_proc_running(connector_name): 133 port = DEFAULT_STD_PORT 134 self.start_hdc( 135 connector=connector_name, 136 local_port=self.channel.setdefault( 137 "port", port)) 138 time.sleep(1) 139 140 def stop(self): 141 """ 142 Stops the monitoring. 143 """ 144 for host in HdcMonitor.MONITOR_MAP: 145 LOG.debug("HdcMonitor stop host %s" % host) 146 monitor = HdcMonitor.MONITOR_MAP[host] 147 try: 148 monitor.is_stop = True 149 if monitor.main_hdc_connection is not None: 150 monitor.main_hdc_connection.shutdown(2) 151 monitor.main_hdc_connection.close() 152 monitor.main_hdc_connection = None 153 except (socket.error, socket.gaierror, socket.timeout) as _: 154 LOG.error("HdcMonitor close socket exception") 155 HdcMonitor.MONITOR_MAP.clear() 156 LOG.debug("HdcMonitor hdc monitor stop!") 157 LOG.debug("HdcMonitor map is %s" % HdcMonitor.MONITOR_MAP) 158 159 def loop_monitor(self): 160 """ 161 Monitors the devices. This connects to the Debug Bridge 162 """ 163 LOG.debug("current connector name is %s" % HdcHelper.CONNECTOR_NAME) 164 while not self.is_stop: 165 try: 166 if self.main_hdc_connection is None: 167 self.main_hdc_connection = self.open_hdc_connection() 168 if self.main_hdc_connection is None: 169 self.connection_attempt += 1 170 171 if self.connection_attempt > MAX_CONNECT_ATTEMPT_COUNT: 172 self.is_stop = True 173 LOG.error( 174 "HdcMonitor attempt %s, can't connect to hdc " 175 "for Device List Monitoring" % 176 str(self.connection_attempt)) 177 raise HdcError( 178 "HdcMonitor cannot connect hdc server(%s %s)," 179 " please check!" % 180 (self.channel.get("host"), 181 str(self.channel.get("post")))) 182 183 LOG.debug( 184 "HdcMonitor Connection attempts: %s" % 185 str(self.connection_attempt)) 186 187 time.sleep(2) 188 else: 189 LOG.debug( 190 "HdcMonitor Connected to hdc for device " 191 "monitoring, main_hdc_connection is %s" % 192 self.main_hdc_connection) 193 194 self.list_targets() 195 except (HdcError, Exception) as _: 196 self.handle_exception_monitor_loop() 197 break 198 199 def handle_exception_monitor_loop(self): 200 LOG.debug("Handle exception monitor loop: %s" % 201 self.main_hdc_connection) 202 if self.main_hdc_connection is None: 203 return 204 self.main_hdc_connection.close() 205 LOG.debug("Handle exception monitor loop, main hdc connection closed, " 206 "main hdc connection: %s" % self.main_hdc_connection) 207 208 def device_list_monitoring(self): 209 request = HdcHelper.form_hdc_request("host:track-devices") 210 HdcHelper.write(self.main_hdc_connection, request) 211 resp = HdcHelper.read_hdc_response(self.main_hdc_connection) 212 if not resp.okay: 213 LOG.error("HdcMonitor hdc rejected shell command") 214 raise Exception(resp.message) 215 else: 216 LOG.debug( 217 'HdcMonitor execute command success:send device_list ' 218 'monitoring request') 219 return True 220 221 def _get_device_instance(self, items, os_type): 222 device = get_plugin(plugin_type=Plugin.DEVICE, plugin_id=os_type)[0] 223 device_instance = device.__class__() 224 device_instance.__set_serial__(items[0]) 225 device_instance.host = self.channel.get("host") 226 device_instance.port = self.channel.get("port") 227 LOG.debug("Dmlib get device instance %s %s %s" % 228 (device_instance.device_sn, 229 device_instance.host, device_instance.port)) 230 device_instance.device_state = DeviceState.get_state(items[1]) 231 return device_instance 232 233 def update_devices(self, param_array_list): 234 devices = [item for item in self.devices] 235 devices.reverse() 236 for local_device1 in devices: 237 k = 0 238 for local_device2 in param_array_list: 239 if local_device1.device_sn == local_device2.device_sn and \ 240 local_device1.device_os_type == \ 241 local_device2.device_os_type: 242 k = 1 243 if local_device1.device_state != \ 244 local_device2.device_state: 245 local_device1.device_state = local_device2.device_state 246 self.server.device_changed(local_device1) 247 param_array_list.remove(local_device2) 248 break 249 250 if k == 0: 251 self.devices.remove(local_device1) 252 self.server.device_disconnected(local_device1) 253 for local_device in param_array_list: 254 self.devices.append(local_device) 255 self.server.device_connected(local_device) 256 257 def open_hdc_connection(self): 258 """ 259 Attempts to connect to the debug bridge server. Return a connect socket 260 if success, null otherwise. 261 """ 262 sock = None 263 try: 264 LOG.debug( 265 "HdcMonitor connecting to hdc for Device List Monitoring") 266 LOG.debug("HdcMonitor socket connection host: %s, port: %s" % 267 (str(convert_ip(self.channel.get("host"))), 268 str(int(self.channel.get("port"))))) 269 270 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 271 sock.connect((self.channel.get("host"), 272 int(self.channel.get("port")))) 273 return sock 274 275 except (socket.error, socket.gaierror, socket.timeout) as exception: 276 LOG.error("HdcMonitor hdc socket connection Error: %s, " 277 "host is %s, port is %s" % (str(exception), 278 self.channel.get("host"), 279 self.channel.get("port"))) 280 return sock 281 282 @classmethod 283 def start_hdc(cls, connector=HDC_NAME, kill=False, local_port=None): 284 """Starts the hdc host side server. 285 286 Args: 287 connector: connector type, like "hdc" 288 kill: if True, kill exist host side server 289 local_port: local port to start host side server 290 291 Returns: 292 None 293 """ 294 if kill: 295 LOG.debug("HdcMonitor {} kill".format(connector)) 296 exec_cmd([connector, "kill"]) 297 LOG.debug("HdcMonitor {} start".format(connector)) 298 exec_cmd( 299 [connector, "-l5", "start"], 300 error_print=False, redirect=True) 301 302 def list_targets(self): 303 if self.main_hdc_connection and not self.monitoring: 304 self.server.monitor_lock.acquire() 305 self.monitoring_list_targets() 306 len_buf = HdcHelper.read(self.main_hdc_connection, 307 DATA_UNIT_LENGTH) 308 length = struct.unpack("!I", len_buf)[0] 309 LOG.debug("had received length is: %s" % length) 310 if length >= 8: 311 self.connection_attempt = 0 312 self.monitoring = True 313 self.process_incoming_target_data(length) 314 self.server.monitor_lock.release() 315 316 def monitoring_list_targets(self): 317 HdcHelper.handle_shake(self.main_hdc_connection) 318 request = HdcHelper.form_hdc_request('list targets') 319 HdcHelper.write(self.main_hdc_connection, request) 320 321 def process_incoming_target_data(self, length): 322 local_array_list = [] 323 data_buf = HdcHelper.read(self.main_hdc_connection, length) 324 data_str = HdcHelper.reply_to_string(data_buf) 325 if 'Empty' not in data_str: 326 lines = data_str.split('\n') 327 for line in lines: 328 items = line.strip().split('\t') 329 if not items[0]: 330 continue 331 items.append(DeviceState.ONLINE.value) 332 device_instance = self._get_device_instance( 333 items, DeviceOsType.default) 334 local_array_list.append(device_instance) 335 else: 336 LOG.debug("please check device actually.[%s]" % data_str.strip()) 337 self.update_devices(local_array_list) 338 339 @staticmethod 340 def peek_hdc(): 341 LOG.debug("Peek running process to check expect connector.") 342 # if not find hdc_std, try find hdc 343 connector_name = HDC_STD_NAME 344 env_hdc = shutil.which(connector_name) 345 # if not, add xdevice's own hdc path to environ path. 346 # tell if hdc has already been in the environ path. 347 if env_hdc is None: 348 connector_name = HDC_NAME 349 LOG.debug("Peak end") 350 return connector_name 351 352 353@dataclass 354class HdcResponse: 355 """Response from HDC.""" 356 okay = ID_OKAY # first 4 bytes in response were "OKAY"? 357 message = "" # diagnostic string if okay is false 358 359 360class SyncService: 361 """ 362 Sync service class to push/pull to/from devices/emulators, 363 through the debug bridge. 364 """ 365 366 def __init__(self, device, host=None, port=None): 367 self.device = device 368 self.host = host 369 self.port = port 370 self.sock = None 371 372 def open_sync(self, timeout=DEFAULT_TIMEOUT): 373 """ 374 Opens the sync connection. This must be called before any calls to 375 push[File] / pull[File]. 376 Return true if the connection opened, false if hdc refuse the 377 connection. This can happen device is invalid. 378 """ 379 LOG.debug("Open sync, timeout=%s" % int(timeout / 1000)) 380 self.sock = HdcHelper.socket(host=self.host, port=self.port, 381 timeout=timeout) 382 HdcHelper.set_device(self.device, self.sock) 383 384 request = HdcHelper.form_hdc_request("sync:") 385 HdcHelper.write(self.sock, request) 386 387 resp = HdcHelper.read_hdc_response(self.sock) 388 if not resp.okay: 389 self.device.log.error( 390 "Got unhappy response from HDC sync req: %s" % resp.message) 391 raise HdcError( 392 "Got unhappy response from HDC sync req: %s" % resp.message) 393 394 def close(self): 395 """ 396 Closes the connection. 397 """ 398 if self.sock is not None: 399 try: 400 self.sock.close() 401 except socket.error as error: 402 LOG.error("Socket close error: %s" % error, error_no="00420") 403 finally: 404 self.sock = None 405 406 def pull_file(self, remote, local, is_create=False): 407 """ 408 Pulls a file. 409 The top directory won't be created if is_create is False (by default) 410 and vice versa 411 """ 412 mode = self.read_mode(remote) 413 self.device.log.debug("Remote file %s mode is %d" % (remote, mode)) 414 if mode == 0: 415 raise HdcError("Remote object doesn't exist!") 416 417 if str(mode).startswith("168"): 418 if is_create: 419 remote_file_split = os.path.split(remote)[-1] \ 420 if os.path.split(remote)[-1] else os.path.split(remote)[-2] 421 remote_file_basename = os.path.basename(remote_file_split) 422 new_local = os.path.join(local, remote_file_basename) 423 create_dir(new_local) 424 else: 425 new_local = local 426 427 collect_receiver = CollectingOutputReceiver() 428 HdcHelper.execute_shell_command(self.device, "ls %s" % remote, 429 receiver=collect_receiver) 430 files = collect_receiver.output.split() 431 for file_name in files: 432 self.pull_file("%s/%s" % (remote, file_name), 433 new_local, is_create=True) 434 elif mode == SPECIAL_FILE_MODE: 435 self.device.log.info("skipping special file '%s'" % remote) 436 else: 437 if os.path.isdir(local): 438 local = os.path.join(local, os.path.basename(remote)) 439 440 self.do_pull_file(remote, local) 441 442 def do_pull_file(self, remote, local): 443 """ 444 Pulls a remote file 445 """ 446 self.device.log.info( 447 "%s pull %s to %s" % (convert_serial(self.device.device_sn), 448 remote, local)) 449 remote_path_content = remote.encode(DEFAULT_ENCODING) 450 if len(remote_path_content) > REMOTE_PATH_MAX_LENGTH: 451 raise HdcError("Remote path is too long.") 452 453 msg = self.create_file_req(ID_RECV, remote_path_content) 454 HdcHelper.write(self.sock, msg) 455 pull_result = HdcHelper.read(self.sock, DATA_UNIT_LENGTH * 2) 456 if not self.check_result(pull_result, ID_DATA) and \ 457 not self.check_result(pull_result, ID_DONE): 458 raise HdcError(self.read_error_message(pull_result)) 459 if platform.system() == "Windows": 460 flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND | os.O_BINARY 461 else: 462 flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND 463 pulled_file_open = os.open(local, flags, FilePermission.mode_755) 464 with os.fdopen(pulled_file_open, "wb") as pulled_file: 465 while True: 466 if self.check_result(pull_result, ID_DONE): 467 break 468 469 if not self.check_result(pull_result, ID_DATA): 470 raise HdcError(self.read_error_message(pull_result)) 471 472 try: 473 length = self.swap32bit_from_array( 474 pull_result, DEFAULT_OFFSET_OF_INT) 475 except IndexError as index_error: 476 self.device.log.debug("do_pull_file: %s" % 477 str(pull_result)) 478 if pull_result == ID_DATA: 479 pull_result = self.sock.recv(DATA_UNIT_LENGTH) 480 self.device.log.debug( 481 "do_pull_file: %s" % str(pull_result)) 482 length = self.swap32bit_from_array(pull_result, 0) 483 self.device.log.debug("do_pull_file: %s" % str(length)) 484 else: 485 raise IndexError(str(index_error)) 486 487 if length > SYNC_DATA_MAX: 488 raise HdcError("Receiving too much data.") 489 490 pulled_file.write(HdcHelper.read(self.sock, length)) 491 pulled_file.flush() 492 pull_result = self.sock.recv(DATA_UNIT_LENGTH * 2) 493 494 def push_file(self, local, remote, is_create=False): 495 """ 496 Push a single file. 497 The top directory won't be created if is_create is False (by default) 498 and vice versa 499 """ 500 if not os.path.exists(local): 501 raise HdcError("Local path doesn't exist.") 502 503 if os.path.isdir(local): 504 if is_create: 505 local_file_split = os.path.split(local)[-1] \ 506 if os.path.split(local)[-1] else os.path.split(local)[-2] 507 local_file_basename = os.path.basename(local_file_split) 508 remote = "{}/{}".format( 509 remote, local_file_basename) 510 HdcHelper.execute_shell_command( 511 self.device, "mkdir -p %s" % remote) 512 513 for child in os.listdir(local): 514 file_path = os.path.join(local, child) 515 if os.path.isdir(file_path): 516 self.push_file( 517 file_path, "%s/%s" % (remote, child), 518 is_create=False) 519 else: 520 self.do_push_file(file_path, "%s/%s" % (remote, child)) 521 else: 522 self.do_push_file(local, remote) 523 524 def do_push_file(self, local, remote): 525 """ 526 Push a single file 527 528 Args: 529 ------------ 530 local : string 531 the local file to push 532 remote : string 533 the remote file (length max is 1024) 534 """ 535 mode = self.read_mode(remote) 536 self.device.log.debug("Remote file %s mode is %d" % (remote, mode)) 537 self.device.log.debug("%s execute command: hdc push %s %s" % ( 538 convert_serial(self.device.device_sn), local, remote)) 539 if str(mode).startswith("168"): 540 remote = "%s/%s" % (remote, os.path.basename(local)) 541 542 try: 543 try: 544 remote_path_content = remote.encode(DEFAULT_ENCODING) 545 except UnicodeEncodeError as _: 546 remote_path_content = remote.encode("UTF-8") 547 if len(remote_path_content) > REMOTE_PATH_MAX_LENGTH: 548 raise HdcError("Remote path is too long.") 549 550 # create the header for the action 551 # and send it. We use a custom try/catch block to make the 552 # difference between file and network IO exceptions. 553 msg = self.create_send_file_req(ID_SEND, remote_path_content, 554 FilePermission.mode_644) 555 556 HdcHelper.write(self.sock, msg) 557 flags = os.O_RDONLY 558 modes = stat.S_IWUSR | stat.S_IRUSR 559 with os.fdopen(os.open(local, flags, modes), "rb") as test_file: 560 while True: 561 if platform.system() == "Linux": 562 data = test_file.read(1024 * 4) 563 else: 564 data = test_file.read(SYNC_DATA_MAX) 565 566 if not data: 567 break 568 569 buf = struct.pack( 570 "%ds%ds%ds" % (len(ID_DATA), FORMAT_BYTES_LENGTH, 571 len(data)), ID_DATA, 572 self.swap32bits_to_bytes(len(data)), data) 573 self.sock.send(buf) 574 except Exception as exception: 575 self.device.log.error("exception %s" % exception) 576 raise exception 577 578 msg = self.create_req(ID_DONE, int(time.time())) 579 HdcHelper.write(self.sock, msg) 580 result = HdcHelper.read(self.sock, DATA_UNIT_LENGTH * 2) 581 if not self.check_result(result, ID_OKAY): 582 self.device.log.error("exception %s" % result) 583 raise HdcError(self.read_error_message(result)) 584 585 def read_mode(self, path): 586 """ 587 Returns the mode of the remote file. 588 Return an Integer containing the mode if all went well or null 589 """ 590 msg = self.create_file_req(ID_STAT, path) 591 HdcHelper.write(self.sock, msg) 592 593 # read the result, in a byte array containing 4 ints 594 stat_result = HdcHelper.read(self.sock, DATA_UNIT_LENGTH * 4) 595 if not self.check_result(stat_result, ID_STAT): 596 return INVALID_MODE_CODE 597 598 return self.swap32bit_from_array(stat_result, DEFAULT_OFFSET_OF_INT) 599 600 def create_file_req(self, command, path): 601 """ 602 Creates the data array for a file request. This creates an array with a 603 4 byte command + the remote file name. 604 605 Args: 606 ------------ 607 command : 608 the 4 byte command (ID_STAT, ID_RECV, ...) 609 path : string 610 The path, as a byte array, of the remote file on which to execute 611 the command. 612 613 return: 614 ------------ 615 return the byte[] to send to the device through hdc 616 """ 617 if isinstance(path, str): 618 try: 619 path = path.encode(DEFAULT_ENCODING) 620 except UnicodeEncodeError as _: 621 path = path.encode("UTF-8") 622 623 return struct.pack( 624 "%ds%ds%ds" % (len(command), FORMAT_BYTES_LENGTH, len(path)), 625 command, self.swap32bits_to_bytes(len(path)), path) 626 627 def create_send_file_req(self, command, path, mode=0o644): 628 # make the mode into a string 629 mode_str = ",%s" % str(mode & FilePermission.mode_777) 630 mode_content = mode_str.encode(DEFAULT_ENCODING) 631 return struct.pack( 632 "%ds%ds%ds%ds" % (len(command), FORMAT_BYTES_LENGTH, len(path), 633 len(mode_content)), 634 command, self.swap32bits_to_bytes(len(path) + len(mode_content)), 635 path, mode_content) 636 637 def create_req(self, command, value): 638 """ 639 Create a command with a code and an int values 640 """ 641 return struct.pack("%ds%ds" % (len(command), FORMAT_BYTES_LENGTH), 642 command, self.swap32bits_to_bytes(value)) 643 644 @staticmethod 645 def check_result(result, code): 646 """ 647 Checks the result array starts with the provided code 648 649 Args: 650 ------------ 651 result : 652 the result array to check 653 path : string 654 the 4 byte code 655 656 return: 657 ------------ 658 bool 659 return true if the code matches 660 """ 661 return result[0:4] == code[0:4] 662 663 def read_error_message(self, result): 664 """ 665 Reads an error message from the opened Socket. 666 667 Args: 668 ------------ 669 result : 670 the current hdc result. Must contain both FAIL and the length of 671 the message. 672 """ 673 if self.check_result(result, ID_FAIL): 674 length = self.swap32bit_from_array(result, 4) 675 if length > 0: 676 return str(HdcHelper.read(self.sock, length)) 677 678 return None 679 680 @staticmethod 681 def swap32bits_to_bytes(value): 682 """ 683 Swaps an unsigned value around, and puts the result in an bytes that 684 can be sent to a device. 685 686 Args: 687 ------------ 688 value : 689 the value to swap. 690 """ 691 return bytes([value & 0x000000FF, 692 (value & 0x0000FF00) >> 8, 693 (value & 0x00FF0000) >> 16, 694 (value & 0xFF000000) >> 24]) 695 696 @staticmethod 697 def swap32bit_from_array(value, offset): 698 """ 699 Reads a signed 32 bit integer from an array coming from a device. 700 701 Args: 702 ------------ 703 value : 704 the array containing the int 705 offset: 706 the offset in the array at which the int starts 707 708 Return: 709 ------------ 710 int 711 the integer read from the array 712 """ 713 result = 0 714 result |= (int(value[offset])) & 0x000000FF 715 result |= (int(value[offset + 1]) & 0x000000FF) << 8 716 result |= (int(value[offset + 2]) & 0x000000FF) << 16 717 result |= (int(value[offset + 3]) & 0x000000FF) << 24 718 719 return result 720 721 722class HdcHelper: 723 CONNECTOR_NAME = "" 724 725 @staticmethod 726 def push_file(device, local, remote, is_create=False, 727 timeout=DEFAULT_TIMEOUT): 728 device.log.info("{} execute command: {} file send {} to {}".format(convert_serial(device.device_sn), 729 HdcHelper.CONNECTOR_NAME, 730 local, remote)) 731 HdcHelper._operator_file("file send", device, local, remote, timeout) 732 733 @staticmethod 734 def pull_file(device, remote, local, is_create=False, 735 timeout=DEFAULT_TIMEOUT): 736 device.log.info("{} execute command: {} file recv {} to {}".format(convert_serial(device.device_sn), 737 HdcHelper.CONNECTOR_NAME, 738 remote, local)) 739 HdcHelper._operator_file("file recv", device, remote, local, timeout) 740 741 @staticmethod 742 def _install_remote_package(device, remote_file_path, command): 743 receiver = CollectingOutputReceiver() 744 cmd = "bm install %s %s" % (command.strip(), remote_file_path) 745 HdcHelper.execute_shell_command(device, cmd, INSTALL_TIMEOUT, receiver) 746 return receiver.output 747 748 @staticmethod 749 def install_package(device, package_file_path, command): 750 device.log.info("%s install %s" % (convert_serial(device.device_sn), 751 package_file_path)) 752 remote_file_path = "/data/local/tmp/%s" % os.path.basename( 753 package_file_path) 754 755 service = None 756 try: 757 service = SyncService(device, host=device.host, port=device.port) 758 service.open_sync() 759 service.push_file(package_file_path, remote_file_path) 760 finally: 761 if service is not None: 762 service.close() 763 764 result = HdcHelper._install_remote_package(device, remote_file_path, 765 command) 766 HdcHelper.execute_shell_command(device, "rm %s " % remote_file_path) 767 return result 768 769 @staticmethod 770 def uninstall_package(device, package_name): 771 receiver = CollectingOutputReceiver() 772 command = "bm uninstall -n %s " % package_name 773 device.log.info("%s %s" % (convert_serial(device.device_sn), command)) 774 HdcHelper.execute_shell_command(device, command, INSTALL_TIMEOUT, 775 receiver) 776 return receiver.output 777 778 @staticmethod 779 def reboot(device, into=None): 780 device.log.info("{} execute command: {} target boot".format(convert_serial(device.device_sn), 781 HdcHelper.CONNECTOR_NAME)) 782 with HdcHelper.socket(host=device.host, port=device.port) as sock: 783 HdcHelper.handle_shake(sock, device.device_sn) 784 request = HdcHelper.form_hdc_request("target boot") 785 HdcHelper.write(sock, request) 786 787 @staticmethod 788 def execute_shell_command(device, command, timeout=DEFAULT_TIMEOUT, 789 receiver=None, **kwargs): 790 """ 791 Executes a shell command on the device and retrieve the output. 792 793 Args: 794 ------------ 795 device : IDevice 796 on which to execute the command. 797 command : string 798 the shell command to execute 799 timeout : int 800 max time between command output. If more time passes between 801 command output, the method will throw 802 ShellCommandUnresponsiveException (ms). 803 """ 804 try: 805 if not timeout: 806 timeout = DEFAULT_TIMEOUT 807 with HdcHelper.socket(host=device.host, port=device.port, 808 timeout=timeout) as sock: 809 output_flag = kwargs.get("output_flag", True) 810 timeout_msg = " with timeout %ss" % str(timeout / 1000) 811 message = "{} execute command: {} shell {}{}".format(convert_serial(device.device_sn), 812 HdcHelper.CONNECTOR_NAME, 813 command, timeout_msg) 814 if output_flag: 815 LOG.info(message) 816 else: 817 LOG.debug(message) 818 from xdevice import Scheduler 819 HdcHelper.handle_shake(sock, device.device_sn) 820 request = HdcHelper.form_hdc_request("shell {}".format(command)) 821 HdcHelper.write(sock, request) 822 resp = HdcResponse() 823 resp.okay = True 824 while True: 825 len_buf = sock.recv(DATA_UNIT_LENGTH) 826 if len_buf: 827 length = struct.unpack("!I", len_buf)[0] 828 else: 829 break 830 data = sock.recv(length) 831 ret = HdcHelper.reply_to_string(data) 832 if ret: 833 if receiver: 834 receiver.__read__(ret) 835 else: 836 LOG.debug(ret) 837 if not Scheduler.is_execute: 838 raise ExecuteTerminate() 839 return resp 840 except socket.timeout as _: 841 device.log.error("ShellCommandUnresponsiveException: %s shell %s timeout[%sS]" % ( 842 convert_serial(device.device_sn), command, str(timeout / 1000))) 843 raise ShellCommandUnresponsiveException() 844 finally: 845 if receiver: 846 receiver.__done__() 847 848 @staticmethod 849 def set_device(device, sock): 850 """ 851 Tells hdc to talk to a specific device 852 if the device is not -1, then we first tell hdc we're looking to talk 853 to a specific device 854 """ 855 msg = "host:transport:%s" % device.device_sn 856 device_query = HdcHelper.form_hdc_request(msg) 857 HdcHelper.write(sock, device_query) 858 resp = HdcHelper.read_hdc_response(sock) 859 if not resp.okay: 860 raise HdcCommandRejectedException(resp.message) 861 862 @staticmethod 863 def form_hdc_request(req): 864 """ 865 Create an ASCII string preceded by four hex digits. 866 """ 867 try: 868 if not req.endswith('\0'): 869 req = "%s\0" % req 870 req = req.encode("utf-8") 871 fmt = "!I%ss" % len(req) 872 result = struct.pack(fmt, len(req), req) 873 except UnicodeEncodeError as ex: 874 LOG.error(ex) 875 raise ex 876 return result 877 878 @staticmethod 879 def read_hdc_response(sock, read_diag_string=False): 880 """ 881 Reads the response from HDC after a command. 882 883 Args: 884 ------------ 885 read_diag_string : 886 If true, we're expecting an OKAY response to be followed by a 887 diagnostic string. Otherwise, we only expect the diagnostic string 888 to follow a FAIL. 889 """ 890 resp = HdcResponse() 891 reply = HdcHelper.read(sock, DATA_UNIT_LENGTH) 892 if HdcHelper.is_okay(reply): 893 resp.okay = True 894 else: 895 read_diag_string = True 896 resp.okay = False 897 898 while read_diag_string: 899 len_buf = HdcHelper.read(sock, DATA_UNIT_LENGTH) 900 len_str = HdcHelper.reply_to_string(len_buf) 901 msg = HdcHelper.read(sock, int(len_str, HEXADECIMAL_NUMBER)) 902 resp.message = HdcHelper.reply_to_string(msg) 903 break 904 905 return resp 906 907 @staticmethod 908 def write(sock, req, timeout=10): 909 if isinstance(req, str): 910 req = req.encode(DEFAULT_ENCODING) 911 elif isinstance(req, list): 912 req = bytes(req) 913 914 start_time = time.time() 915 while req: 916 if time.time() - start_time > timeout: 917 LOG.debug("Socket write timeout, timeout:%ss" % timeout) 918 break 919 920 size = sock.send(req) 921 if size < 0: 922 raise DeviceError("channel EOF") 923 924 req = req[size:] 925 time.sleep(5 / 1000) 926 927 @staticmethod 928 def read(sock, length, timeout=10): 929 data = b'' 930 recv_len = 0 931 start_time = time.time() 932 exc_num = 3 933 while length - recv_len > 0: 934 if time.time() - start_time > timeout: 935 LOG.debug("Socket read timeout, timout:%ss" % timeout) 936 break 937 try: 938 recv = sock.recv(length - recv_len) 939 if len(recv) > 0: 940 time.sleep(5 / 1000) 941 else: 942 break 943 except ConnectionResetError as error: 944 if exc_num <= 0: 945 raise error 946 exc_num = exc_num - 1 947 recv = b'' 948 time.sleep(1) 949 LOG.debug("ConnectionResetError occurs") 950 951 data += recv 952 recv_len += len(recv) 953 954 return data 955 956 @staticmethod 957 def is_okay(reply): 958 """ 959 Checks to see if the first four bytes in "reply" are OKAY. 960 """ 961 return reply[0:4] == ID_OKAY 962 963 @staticmethod 964 def reply_to_string(reply): 965 """ 966 Converts an HDC reply to a string. 967 """ 968 try: 969 return str(reply, encoding=DEFAULT_ENCODING) 970 except (ValueError, TypeError) as _: 971 return "" 972 973 @staticmethod 974 def socket(host=None, port=None, timeout=None): 975 end = time.time() + 10 * 60 976 sock = None 977 978 while host not in HdcMonitor.MONITOR_MAP or \ 979 HdcMonitor.MONITOR_MAP[host].main_hdc_connection is None: 980 LOG.debug("Host: %s, port: %s, HdcMonitor map is %s" % ( 981 host, port, HdcMonitor.MONITOR_MAP)) 982 if host in HdcMonitor.MONITOR_MAP: 983 LOG.debug("Monitor main hdc connection is %s" % 984 HdcMonitor.MONITOR_MAP[host].main_hdc_connection) 985 if time.time() > end: 986 raise HdcError("Cannot detect HDC monitor!") 987 time.sleep(2) 988 989 try: 990 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 991 sock.connect((host, int(port))) 992 except socket.error as exception: 993 LOG.exception("Connect hdc server error: %s" % str(exception), 994 exc_info=False) 995 raise exception 996 997 if sock is None: 998 raise HdcError("Cannot connect hdc server!") 999 1000 if timeout is not None: 1001 sock.setblocking(False) 1002 sock.settimeout(timeout / 1000) 1003 1004 return sock 1005 1006 @staticmethod 1007 def handle_shake(connection, connect_key=""): 1008 reply = HdcHelper.read(connection, 48) 1009 struct.unpack(">I12s32s", reply) 1010 banner_str = b'OHOS HDC' 1011 connect_key = connect_key.encode("utf-8") 1012 size = struct.calcsize('12s256s') 1013 fmt = "!I12s256s" 1014 pack_cmd = struct.pack(fmt, size, banner_str, connect_key) 1015 HdcHelper.write(connection, pack_cmd) 1016 return True 1017 1018 @staticmethod 1019 def _operator_file(command, device, local, remote, timeout): 1020 sock = HdcHelper.socket(host=device.host, port=device.port, 1021 timeout=timeout) 1022 HdcHelper.handle_shake(sock, device.device_sn) 1023 request = HdcHelper.form_hdc_request( 1024 "%s %s %s" % (command, local, remote)) 1025 HdcHelper.write(sock, request) 1026 reply = HdcHelper.read(sock, DATA_UNIT_LENGTH) 1027 length = struct.unpack("!I", reply)[0] 1028 data_buf = HdcHelper.read(sock, length) 1029 HdcHelper.reply_to_string(data_buf) 1030 LOG.debug("result is {}".format(data_buf)) 1031 1032 @staticmethod 1033 def is_hdc_std(): 1034 return HDC_STD_NAME in HdcHelper.CONNECTOR_NAME 1035 1036 1037class DeviceConnector(object): 1038 __instance = None 1039 __init_flag = False 1040 1041 def __init__(self, host=None, port=None, usb_type=None): 1042 if DeviceConnector.__init_flag: 1043 return 1044 self.device_listeners = [] 1045 self.device_monitor = None 1046 self.monitor_lock = threading.Condition() 1047 self.host = host if host else "127.0.0.1" 1048 self.usb_type = usb_type 1049 connector_name = HdcMonitor.peek_hdc() 1050 HdcHelper.CONNECTOR_NAME = connector_name 1051 if port: 1052 self.port = int(port) 1053 else: 1054 self.port = int(os.getenv("HDC_SERVER_PORT", DEFAULT_STD_PORT)) 1055 1056 def start(self): 1057 self.device_monitor = HdcMonitor.get_instance( 1058 self.host, self.port, device_connector=self) 1059 self.device_monitor.start() 1060 1061 def terminate(self): 1062 if self.device_monitor: 1063 self.device_monitor.stop() 1064 self.device_monitor = None 1065 1066 def add_device_change_listener(self, device_change_listener): 1067 self.device_listeners.append(device_change_listener) 1068 1069 def remove_device_change_listener(self, device_change_listener): 1070 if device_change_listener in self.device_listeners: 1071 self.device_listeners.remove(device_change_listener) 1072 1073 def device_connected(self, device): 1074 LOG.debug("DeviceConnector device connected:host %s, port %s, " 1075 "device sn %s " % (self.host, self.port, device.device_sn)) 1076 if device.host != self.host or device.port != self.port: 1077 LOG.debug("DeviceConnector device error") 1078 for listener in self.device_listeners: 1079 listener.device_connected(device) 1080 1081 def device_disconnected(self, device): 1082 LOG.debug("DeviceConnector device disconnected:host %s, port %s, " 1083 "device sn %s" % (self.host, self.port, device.device_sn)) 1084 if device.host != self.host or device.port != self.port: 1085 LOG.debug("DeviceConnector device error") 1086 for listener in self.device_listeners: 1087 listener.device_disconnected(device) 1088 1089 def device_changed(self, device): 1090 LOG.debug("DeviceConnector device changed:host %s, port %s, " 1091 "device sn %s" % (self.host, self.port, device.device_sn)) 1092 if device.host != self.host or device.port != self.port: 1093 LOG.debug("DeviceConnector device error") 1094 for listener in self.device_listeners: 1095 listener.device_changed(device) 1096 1097 1098class CollectingOutputReceiver(IShellReceiver): 1099 def __init__(self): 1100 self.output = "" 1101 1102 def __read__(self, output): 1103 self.output = "%s%s" % (self.output, output) 1104 1105 def __error__(self, message): 1106 pass 1107 1108 def __done__(self, result_code="", message=""): 1109 pass 1110 1111 1112class DisplayOutputReceiver(IShellReceiver): 1113 def __init__(self): 1114 self.output = "" 1115 self.unfinished_line = "" 1116 1117 def _process_output(self, output, end_mark="\n"): 1118 content = output 1119 if self.unfinished_line: 1120 content = "".join((self.unfinished_line, content)) 1121 self.unfinished_line = "" 1122 lines = content.split(end_mark) 1123 if content.endswith(end_mark): 1124 # get rid of the tail element of this list contains empty str 1125 return lines[:-1] 1126 else: 1127 self.unfinished_line = lines[-1] 1128 # not return the tail element of this list contains unfinished str, 1129 # so we set position -1 1130 return lines[:-1] 1131 1132 def __read__(self, output): 1133 self.output = "%s%s" % (self.output, output) 1134 lines = self._process_output(output) 1135 for line in lines: 1136 line = line.strip() 1137 if line: 1138 LOG.info(line) 1139 1140 def __error__(self, message): 1141 pass 1142 1143 def __done__(self, result_code="", message=""): 1144 pass 1145 1146 1147def process_command_ret(ret, receiver): 1148 try: 1149 if ret != "" and receiver: 1150 receiver.__read__(ret) 1151 receiver.__done__() 1152 except Exception as _: 1153 LOG.exception("Error generating log report.", exc_info=False) 1154 raise ReportException() 1155 1156 if ret != "" and not receiver: 1157 lines = ret.split("\n") 1158 for line in lines: 1159 line = line.strip() 1160 if line: 1161 LOG.debug(line) 1162