379 lines
12 KiB
Python
379 lines
12 KiB
Python
|
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, ("<broadcast>", 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
|