diff --git a/const.py b/const.py index 79d7010..db16983 100644 --- a/const.py +++ b/const.py @@ -2,7 +2,7 @@ Constants used across package """ -from enum import Enum +from enum import IntEnum MINIMUM_BLINK_RATE = 20 @@ -15,9 +15,36 @@ PACKETHEADER_LINKBIT = 0x80 -GatewayCmdSendToSerial = 0x30 +class GatewayCmd(IntEnum): + KEEP_ALIVE = 0x10 + SET_DATE_TIME = 0x20 + GET_DATE_TIME = 0x22 + SET_SCHEDULE = 0x28 + GET_SCHEDULE = 0x2a + SEND_TO_SERIAL = 0x30 + FILE_WRITE_OPEN = 0x50 + FILE_WRITE = 0x52 + FILE_WRITE_SIZE = 0x54 + FILE_WRITE_CLOSE = 0x56 + FILE_READ_OPEN = 0x60 + FILE_READ = 0x62 + FILE_READ_SIZE = 0x64 + FILE_READ_CLOSE = 0x66 + FILE_READ_DELETE = 0x70 + DIR_LISTING = 0x80 + START_PULSE_MODE = 0x90 + EXIT_PULSE_MODE = 0x92 + SERIAL_MESSAGE = 0xe0 + DEVICE_STATE_CHANGE = 0xe2 + DISCONNECT = 0xf0 + PULSE_MODE_APP_CONNECTED = 0xf2 + CMD_NAK = 0xff -class UpbTransmission(Enum): + @classmethod + def has_value(cls, value): + return value in cls._value2member_map_ + +class UpbTransmission(IntEnum): UPB_MESSAGE = 0x55 # U UPB_PIM_ACCEPT = 0x41 # A UPB_PIM_BUSY = 0x42 # B @@ -26,7 +53,7 @@ class UpbTransmission(Enum): UPB_TRANSMISSION_ACK = 0x4b # K UPB_TRANSMISSION_NAK = 0x4e # N -class UpbMessage(Enum): +class UpbMessage(IntEnum): UPB_MESSAGE_PIMREPORT = 0x50 # P UPB_MESSAGE_START = 0x58 # X UPB_MESSAGE_SYNC = 0x52 # R @@ -54,12 +81,12 @@ def is_message_data(cls, value): return False -class PimCommand(Enum): +class PimCommand(IntEnum): UPB_NETWORK_TRANSMIT = 0x14 UPB_PIM_READ = 0x12 UPB_PIM_WRITE = 0x17 -class UpbReg(Enum): +class UpbReg(IntEnum): UPB_REG_NETWORKID = 0x00 UPB_REG_MODULEID = 0x01 UPB_REG_PASSWORD = 0x02 @@ -79,7 +106,7 @@ class UpbReg(Enum): UPB_REG_NOISEFLOOR = 0xfa UPB_REG_NOISECOUNTS = 0xfb -class UpbDeviceId(Enum): +class UpbDeviceId(IntEnum): BROADCAST_DEVICEID = 0x00 RESERVED1_DEVICEID = 0xfb RESERVED2_DEVICEID = 0xfc @@ -91,19 +118,19 @@ class UpbDeviceId(Enum): def has_value(cls, value): return value in cls._value2member_map_ -class UpbReqAck(Enum): +class UpbReqAck(IntEnum): REQ_ACKDEFAULT = 0x00 REQ_ACKMESSAGE = 0x01 REQ_NOACK = 0x02 REQ_ACKNOREQUEUEONNAK = 0x04 -class UpbReqRepeater(Enum): +class UpbReqRepeater(IntEnum): REP_NONREPEATER = 0x00 REP_LOWREPEAT = 0x01 REP_MEDIUMREPEAT = 0x02 REP_HIGHREPEAT = 0x03 -class MdidSet(Enum): +class MdidSet(IntEnum): MDID_CORE_COMMANDS = 0x00 MDID_DEVICE_CONTROL_COMMANDS = 0x20 MDID_RESERVED_COMMAND_SET1 = 0x40 @@ -113,7 +140,7 @@ class MdidSet(Enum): MDID_RESERVED_REPORT_SET2 = 0xc0 MDID_EXTENDED_MESSAGE_SET = 0xe0 -class MdidCoreCmd(Enum): +class MdidCoreCmd(IntEnum): MDID_CORE_COMMAND_NULL = 0x00 MDID_CORE_COMMAND_WRITEENABLE = 0x01 MDID_CORE_COMMAND_WRITEPROTECT = 0x02 @@ -133,7 +160,7 @@ class MdidCoreCmd(Enum): MDID_CORE_COMMAND_GETREGISTERVALUES = 0x10 MDID_CORE_COMMAND_SETREGISTERVALUES = 0x11 -class MdidDeviceControlCmd(Enum): +class MdidDeviceControlCmd(IntEnum): MDID_DEVICE_CONTROL_COMMAND_ACTIVATELINK = 0x00 MDID_DEVICE_CONTROL_COMMAND_DEACTIVATELINK = 0x01 MDID_DEVICE_CONTROL_COMMAND_GOTO = 0x02 @@ -143,7 +170,7 @@ class MdidDeviceControlCmd(Enum): MDID_DEVICE_CONTROL_COMMAND_REPORTSTATE = 0x10 MDID_DEVICE_CONTROL_COMMAND_STORESTATE = 0x11 -class MdidCoreReport(Enum): +class MdidCoreReport(IntEnum): MDID_DEVICE_CORE_REPORT_SETUPTIME = 0x05 MDID_DEVICE_CORE_REPORT_DEVICESTATE = 0x06 MDID_DEVICE_CORE_REPORT_DEVICESTATUS = 0x07 diff --git a/proxy.py b/proxy.py index 10100b8..81a4968 100644 --- a/proxy.py +++ b/proxy.py @@ -3,11 +3,12 @@ import hmac from pprint import pprint, pformat from binascii import unhexlify +from struct import unpack from const import PimCommand, UpbMessage, UpbDeviceId, UpbTransmission, UpbReqAck, UpbReqRepeater, \ -MdidSet, MdidCoreCmd, MdidDeviceControlCmd, MdidCoreReport, GatewayCmdSendToSerial, \ +MdidSet, MdidCoreCmd, MdidDeviceControlCmd, MdidCoreReport, GatewayCmd, \ UPB_MESSAGE_TYPE, UPB_MESSAGE_PIMREPORT_TYPE, INITIAL_PIM_REG_QUERY_BASE, PACKETHEADER_LINKBIT, PULSE_REPORT_BYTES -from util import cksum +from util import cksum, hexdump class PIM(asyncio.Protocol): @@ -83,8 +84,7 @@ def process_packet(self, packet): pprint(response) def nt_line_received(self, line): - print(f'pim null terminated line: {line}') - self.wrapped = True + print(f'pim null terminated line: {line}, hex: {hexdump(line)}') errors = { 'MAX CONNECTIONS REACHED', 'PULSE MODE ACTIVE', @@ -98,6 +98,7 @@ def nt_line_received(self, line): elif result == b'AUTH SUCCEEDED': print("auth succeded") self.initial = False + self.wrapped = True else: print(f"unexpected auth result: {result}") elif self.initial: @@ -235,17 +236,39 @@ def line_received(self, line): def data_received(self, data): self.buffer += data - print(f'pim buffer: {self.buffer}') - while b'\x00' in self.buffer: - line, self.buffer = self.buffer.split(b'\x00', 1) - if len(line) >= 1: - self.nt_line_received(line) - while b'\r' in self.buffer and not self.wrapped: - line, self.buffer = self.buffer.split(b'\r', 1) - if len(line) > 1: - self.line_received(line) + #print(f'pim buffer: {self.buffer}, hex: {hexdump(self.buffer)}') if self.server_transport: self.server_transport.write(data) + if self.wrapped: + while len(self.buffer) > 0 and GatewayCmd.has_value(self.buffer[0]-1): + if len(self.buffer) >= 8: + rcCommand = self.buffer[1] + assert(rcCommand == 0x00) + length = unpack('>H', self.buffer[6:8])[0] + #print(f"pim length: {length}") + if len(self.buffer) >= length + 9: + cmd = GatewayCmd(self.buffer[0]-1) + #print(f"pim cmd: {cmd.name}") + if cmd == GatewayCmd.SEND_TO_SERIAL: + line = self.buffer[8:length+7] + self.buffer = self.buffer[length+10:] + #print(f"pim gateway response: {line}, hex: {hexdump(line)} length: {length}") + self.line_received(line) + elif cmd == GatewayCmd.KEEP_ALIVE: + self.buffer = self.buffer[length+4:] + else: + return + else: + return + else: + while b'\x00' in self.buffer: + line, self.buffer = self.buffer.split(b'\x00', 1) + if len(line) >= 1: + self.nt_line_received(line) + while b'\r' in self.buffer and not self.wrapped: + line, self.buffer = self.buffer.split(b'\r', 1) + if len(line) > 1: + self.line_received(line) def connection_lost(self, *args): print("pim connection lost") @@ -274,7 +297,7 @@ def send_data(self, data): self.client.transport.write(data) def nt_line_received(self, line): - print(f'upstart null terminated line: {line}') + print(f'upstart null terminated line: {line}, hex: {hexdump(line)}') if self.initial: if self.client.pim_info.get('auth', None) == b'AUTH REQUIRED' and \ self.client.challenge is not None and self.client.authenticated is not True: @@ -299,14 +322,9 @@ def nt_line_received(self, line): } self.client.protocol = protocol print(f'self.client_info:\n{pformat(self.client_info)}') - else: - if line[0] == GatewayCmdSendToSerial: - length_hi = line[1] - length_lo = line[2] - print(f"gateway to serial line: {line}, length_hi: {length_hi}, length_lo: {length_lo}") def line_received(self, line): - print(f'upstart line: {line}') + #print(f'upstart line: {line}') command = PimCommand(line[0]) data = unhexlify(line[1:-2]) crc = int(line[-2:], 16) @@ -365,16 +383,36 @@ def line_received(self, line): def data_received(self, data): self.buffer += data - print(f'buffer: {self.buffer}') - while b'\x00' in self.buffer: - line, self.buffer = self.buffer.split(b'\x00', 1) - if len(line) > 0: - self.nt_line_received(line) - while b'\r' in self.buffer and not self.client.wrapped: - line, self.buffer = self.buffer.split(b'\r', 1) - if len(line) >= 1: - self.line_received(line) + #print(f'upstart buffer: {self.buffer}, hex: {hexdump(self.buffer)}') self.send_data(data) + if self.client.wrapped: + while len(self.buffer) > 0 and GatewayCmd.has_value(self.buffer[0]): + if len(self.buffer) >= 4: + length = unpack('>H', self.buffer[1:3])[0] + if len(self.buffer) > length + 4: + #print(f"length: {length}") + cmd = GatewayCmd(self.buffer[0]) + #print(f"cmd: {cmd.name}") + if cmd == GatewayCmd.SEND_TO_SERIAL: + line = self.buffer[3:length+2] + self.buffer = self.buffer[length+4:] + #print(f"gateway to serial line: {line}, hex: {hexdump(line)} length: {length}") + self.line_received(line) + elif cmd == GatewayCmd.KEEP_ALIVE: + self.buffer = self.buffer[length+4:] + else: + return + else: + return + else: + while b'\x00' in self.buffer: + line, self.buffer = self.buffer.split(b'\x00', 1) + if len(line) > 0: + self.nt_line_received(line) + while b'\r' in self.buffer and not self.client.wrapped: + line, self.buffer = self.buffer.split(b'\r', 1) + if len(line) >= 1: + self.line_received(line) def connection_lost(self, *args): print("upstart connection lost") diff --git a/util.py b/util.py index ee7f53f..c9b1862 100644 --- a/util.py +++ b/util.py @@ -4,3 +4,12 @@ def cksum(data): return (256 - reduce(lambda x, y: x + y, data)) % 256 +def hexdump(data, length=None, sep=':'): + if length is not None: + lines = "" + for seq in range(0, len(data), 16): + line = data[seq: seq + 16] + lines += sep.join("{:02x}".format(c) for c in line) + "\n" + else: + lines = sep.join("{:02x}".format(c) for c in data) + return lines