Skip to content

Commit

Permalink
Add initial gateway serial command unwrapping
Browse files Browse the repository at this point in the history
  • Loading branch information
jameshilliard committed Jul 31, 2020
1 parent 78b5556 commit fc5000e
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 42 deletions.
53 changes: 40 additions & 13 deletions const.py
Expand Up @@ -2,7 +2,7 @@
Constants used across package
"""

from enum import Enum
from enum import IntEnum

MINIMUM_BLINK_RATE = 20

Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
96 changes: 67 additions & 29 deletions proxy.py
Expand Up @@ -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):

Expand Down Expand Up @@ -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',
Expand All @@ -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:
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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")
Expand Down
9 changes: 9 additions & 0 deletions util.py
Expand Up @@ -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

0 comments on commit fc5000e

Please sign in to comment.