import socket import time import os import threading from NativeLog import NativeLog RETRANSMIT_COUNT = 5 RETRANSMIT_TIMEOUT = 0.5 ABORT_TIMEOUT = 120 BEACON_SEND_RATE = 30 VALUE_NAME = {"type": 0x00, "session id": 0x01, "result code": 0x02, "ap ssid": 0x03, "ap password": 0x04, "start SC time": 0x05, "recv UDP time": 0x06, "SP model": 0x07, "SP mac": 0x08, "ET version": 0x09, "ap bssid": 0x0A, "ssid hidden": 0x0B, "ap encryption": 0x0C, } TYPE_VAL = {"Init new test": 0x00, "test request": 0x01, "result": 0x02, "query phone": 0x03, "ACK": 0x80, "phone report": 0x81, "Not support": 0xFF, "invalid session": 0xFE, } RESULT_CODE_VAL = {"OK": 0x80, "JAP fail": 0x81, # SP join AP fail, should disqualify this result "recv UDP fail": 0x82, # SP did not receive UDP sent by target } AP_ENCRYPTION_VAL = {"OPEN": 0x00, "WEP": 0x01, "WPA": 0x02, } AP_PROPERTY = ("ssid", "password", "bssid", "is_hidden", "encryption", "ht") PHONE_PROPERTY = ("ip", "mac", "model") SERIAL_PORT_NUM = 3 LOG_FILE_PREFIX = "SC_IOT" LOG_FOLDER = os.path.join("AT_LOG", "TEMP") LOG_FILE_NAME = os.path.join(LOG_FOLDER, "%s_%s.log" % (LOG_FILE_PREFIX, time.strftime("%d%H%M%S", time.localtime()))) REQUEST_LOCK = threading.Lock() HANDLER_LOCK = threading.Lock() def sync_request_list(func): def handle_args(*args, **kwargs): with REQUEST_LOCK: ret = func(*args, **kwargs) return ret return handle_args def sync_handler_list(func): def handle_args(*args, **kwargs): with HANDLER_LOCK: ret = func(*args, **kwargs) return ret return handle_args def _process_one_tlv_pair(data): typ = ord(data[0]) length = ord(data[1]) value = data[2:2+length] processed_data = data[2+length:] return (typ, value), processed_data pass def bytes_to_msg(data): data_to_process = data msg = [] while True: one_pair, data_to_process = _process_one_tlv_pair(data_to_process) msg.append(one_pair) if len(data_to_process) == 0: break return msg pass def msg_to_bytes(msg): byte_str = "" for pair in msg: byte_str += chr(pair[0]) if isinstance(pair[1], list) is True: byte_str += chr(len(pair[1])) byte_str.join([chr(m) for m in pair[1]]) elif isinstance(pair[1], str) is True: byte_str += chr(len(pair[1])) byte_str += pair[1] elif isinstance(pair[1], int) is True: byte_str += chr(1) byte_str += chr(pair[1]) else: raise TypeError("msg content only support list and string type") return byte_str def get_value_from_msg(type_list, msg): if isinstance(type_list, str) is True: type_list = [type_list] ret = [""] * len(type_list) for pair in msg: for i in range(len(type_list)): if pair[0] == VALUE_NAME[type_list[i]]: ret[i] = pair[1] if "" not in ret: # all type value found break else: NativeLog.add_prompt_trace("missing required type in msg") return ret def bytes_to_time(bytes_in): if len(bytes_in) != 4: return 0 t = float(ord(bytes_in[0])*256*256*256 + ord(bytes_in[1])*256*256 + ord(bytes_in[2])*256 + ord(bytes_in[2]))/1000 return t pass def mac_to_bytes(mac): tmp = mac.split(':') return "".join([chr(int(m[:2], base=16)) for m in tmp]) pass def bytes_to_mac(bytes_in): mac = "".join(["%x:" % ord(m) for m in bytes_in] ) return mac[:-1] class RetransmitHandler(threading.Thread): def __init__(self, udp_server): threading.Thread.__init__(self) self.setDaemon(True) self.udp_server = udp_server self.exit_event = threading.Event() pass @sync_request_list def find_required_retransmit_msg(self): time_now = time.time() aborted_sessions = [] retransmit_msg = [] msgs = filter(lambda x: time_now - x[4] >= RETRANSMIT_TIMEOUT, self.udp_server.unconfirmed_request) for msg in msgs: if msg[3] == 0: aborted_sessions.append(msg[0]) self.udp_server.unconfirmed_request.remove(msg) else: msg[3] -= 1 msg[4] = time_now retransmit_msg.append(msg) pass return aborted_sessions, retransmit_msg pass def run(self): while True: self.exit_event.wait(0.1) if self.exit_event.isSet() is True: break aborted_sessions, retransmit_msg = self.find_required_retransmit_msg() for msg in retransmit_msg: self.udp_server.udp_socket.sendto(msg[1], msg[2]) for session_id in aborted_sessions: self.udp_server.session_aborted(session_id) def exit(self): self.exit_event.set() pass class SendBeacon(threading.Thread): def __init__(self, sock, udp_port): threading.Thread.__init__(self) self.setDaemon(True) self.udp_sock = sock self.udp_port = udp_port self.exit_event = threading.Event() def run(self): while True: msg = [[VALUE_NAME["type"], TYPE_VAL["query phone"]]] data = msg_to_bytes(msg) self.udp_sock.sendto(data, ("", self.udp_port)) for i in range(BEACON_SEND_RATE): self.exit_event.wait(1) if self.exit_event.isSet() is True: return pass def exit(self): self.exit_event.set() pass class UDPServer(threading.Thread): def __init__(self, pc_ip, udp_port, update_phone_handler): threading.Thread.__init__(self) self.setDaemon(True) sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) sock.bind((pc_ip, udp_port)) sock.settimeout(1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) self.udp_socket = sock self.unconfirmed_request = [] self.test_handler_list = [] self.pc_ip = pc_ip self.udp_port = udp_port self.update_phone_handler = update_phone_handler self.retransmit_thread = RetransmitHandler(self) self.beacon_thread = SendBeacon(self.udp_socket, self.udp_port) self.retransmit_thread.start() self.beacon_thread.start() self.exit_event = threading.Event() pass @sync_handler_list def register_test_handler(self, session_id, test_handler): tmp = filter(lambda x: x[0] == session_id, self.test_handler_list) if len(tmp) > 0: NativeLog.add_prompt_trace("handler with same session id exist") else: self.test_handler_list.append([session_id, test_handler]) pass @sync_handler_list def deregister_test_handler(self, session_id): tmp = filter(lambda x: x[0] == session_id, self.test_handler_list) if len(tmp) > 1: NativeLog.add_prompt_trace("deregister test handler fail") elif len(tmp) == 1: self.test_handler_list.remove(tmp[0]) pass @sync_handler_list def get_test_handler(self, session_id): ret = None tmp = filter(lambda x: x[0] == session_id, self.test_handler_list) if len(tmp) != 1: NativeLog.add_prompt_trace("failed to get test handler, " "%d handler found, session id %s" % (len(tmp), session_id)) elif len(tmp) == 1: ret = tmp[0][1] return ret pass def session_aborted(self, session_id): test_handler = self.get_test_handler(session_id) if test_handler is not None: test_handler.abort_handler() pass def confirm_request(self, session_id, msg, address): test_handler = self.get_test_handler(session_id) if test_handler is not None: test_handler.res_receiver(msg, address) self.remove_pending_request(session_id) pass def receive_request(self, msg, address): result = get_value_from_msg(["type", "session id"], msg) msg_type = ord(result[0]) session_id = result[1] if msg_type != TYPE_VAL["result"]: self.send_response([[VALUE_NAME["type"], TYPE_VAL["Not support"]]], address) else: test_handler = self.get_test_handler(session_id) if test_handler is None: self.send_response([[VALUE_NAME["type"], TYPE_VAL["invalid session"]], [VALUE_NAME["session id"], session_id]], address) pass else: test_handler.req_receiver(msg, address) pass @sync_request_list def add_request_to_queue(self, dest_addr, session_id, data): tmp = filter(lambda x: x[0] == session_id, self.unconfirmed_request) if len(tmp) != 0: NativeLog.add_prompt_trace("One pending request belong to same session id %s" % session_id) pass else: self.unconfirmed_request.append([session_id, data, dest_addr, RETRANSMIT_COUNT-1, time.time()]) def send_request(self, dest_addr, session_id, msg): data = msg_to_bytes(msg) self.add_request_to_queue(dest_addr, session_id, data) self.udp_socket.sendto(data, dest_addr) pass def send_response(self, msg, address): self.udp_socket.sendto(msg_to_bytes(msg), address) @sync_request_list def remove_pending_request(self, session_id): tmp = filter(lambda x: x[0] == session_id, self.unconfirmed_request) if len(tmp) > 0: self.unconfirmed_request.remove(tmp[0]) pass pass def handle_response(self, msg, address): result = get_value_from_msg(["type", "session id"], msg) msg_type = ord(result[0]) session_id = result[1] if msg_type == TYPE_VAL["ACK"]: self.confirm_request(session_id, msg, address) elif msg_type == TYPE_VAL["phone report"]: # add new available phone tmp = get_value_from_msg(["SP model", "SP mac"], msg) phone = dict(zip(PHONE_PROPERTY, [address[0], bytes_to_mac(tmp[1]), tmp[0]])) self.update_phone_handler(phone) pass elif msg_type == TYPE_VAL["Not support"] or msg_type == TYPE_VAL["invalid session"]: self.session_aborted(session_id) pass def run(self): while self.exit_event.isSet() is False: try: data, address = self.udp_socket.recvfrom(65535) except socket.error, e: continue if address[0] == self.pc_ip: continue msg = bytes_to_msg(data) msg_type = get_value_from_msg(["type"], msg)[0] if msg_type is None: NativeLog.add_prompt_trace("invalid incoming msg: %s" % "".join(["0x%X, " % m for m in data])) else: msg_type = ord(msg_type) # check if request or reply if (msg_type & 0x80) != 0: self.handle_response(msg, address) else: self.receive_request(msg, address) pass self.retransmit_thread.exit() self.beacon_thread.exit() self.retransmit_thread.join() self.beacon_thread.join() pass def exit(self): self.exit_event.set() pass