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