Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3430f509e | ||
|
|
32a669d0d1 | ||
|
|
4d9f00221c | ||
|
|
27c723b0c8 | ||
|
|
4bd59b91b3 | ||
|
|
3a3c6142b8 | ||
|
|
5d36397f2f |
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.5.3] - 2023-11-12
|
||||||
|
|
||||||
|
- remove apk packet manager from the final image
|
||||||
|
- send contact info every time a client connection is established
|
||||||
|
- change timestamp from local time to utc
|
||||||
|
|
||||||
## [0.5.2] - 2023-11-09
|
## [0.5.2] - 2023-11-09
|
||||||
|
|
||||||
- add int64 data type to info parser
|
- add int64 data type to info parser
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
tests/
|
tests/
|
||||||
**/__pycache__
|
**/__pycache__
|
||||||
*.pyc
|
*.pyc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
build.sh
|
||||||
@@ -52,6 +52,8 @@ COPY --from=builder /root/wheels /root/wheels
|
|||||||
RUN python -m pip install --no-cache --no-index /root/wheels/* && \
|
RUN python -m pip install --no-cache --no-index /root/wheels/* && \
|
||||||
rm -rf /root/wheels
|
rm -rf /root/wheels
|
||||||
|
|
||||||
|
RUN apk --purge del apk-tools
|
||||||
|
|
||||||
# copy the content of the local src and config directory to the working directory
|
# copy the content of the local src and config directory to the working directory
|
||||||
COPY --chmod=0700 entrypoint.sh /root/entrypoint.sh
|
COPY --chmod=0700 entrypoint.sh /root/entrypoint.sh
|
||||||
COPY config .
|
COPY config .
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ logger = logging.getLogger('conn')
|
|||||||
|
|
||||||
class AsyncStream(Message):
|
class AsyncStream(Message):
|
||||||
|
|
||||||
def __init__(self, reader, writer, addr, remote_stream, server_side: bool
|
def __init__(self, reader, writer, addr, remote_stream, server_side: bool,
|
||||||
) -> None:
|
id_str=b'') -> None:
|
||||||
super().__init__(server_side)
|
super().__init__(server_side, id_str)
|
||||||
self.reader = reader
|
self.reader = reader
|
||||||
self.writer = writer
|
self.writer = writer
|
||||||
self.remoteStream = remote_stream
|
self.remoteStream = remote_stream
|
||||||
@@ -84,6 +84,10 @@ class AsyncStream(Message):
|
|||||||
if self._forward_buffer:
|
if self._forward_buffer:
|
||||||
if not self.remoteStream:
|
if not self.remoteStream:
|
||||||
await self.async_create_remote()
|
await self.async_create_remote()
|
||||||
|
if self.remoteStream:
|
||||||
|
self.remoteStream._init_new_client_conn(self.contact_name,
|
||||||
|
self.contact_mail)
|
||||||
|
await self.remoteStream.__async_write()
|
||||||
|
|
||||||
if self.remoteStream:
|
if self.remoteStream:
|
||||||
hex_dump_memory(logging.INFO,
|
hex_dump_memory(logging.INFO,
|
||||||
|
|||||||
@@ -149,7 +149,8 @@ class Inverter(AsyncStream):
|
|||||||
logging.info(f'Connected to {addr}')
|
logging.info(f'Connected to {addr}')
|
||||||
connect = asyncio.open_connection(host, port)
|
connect = asyncio.open_connection(host, port)
|
||||||
reader, writer = await connect
|
reader, writer = await connect
|
||||||
self.remoteStream = AsyncStream(reader, writer, addr, self, False)
|
self.remoteStream = AsyncStream(reader, writer, addr, self,
|
||||||
|
False, self.id_str)
|
||||||
asyncio.create_task(self.client_loop(addr))
|
asyncio.create_task(self.client_loop(addr))
|
||||||
|
|
||||||
except ConnectionRefusedError as error:
|
except ConnectionRefusedError as error:
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class Message(metaclass=IterRegistry):
|
|||||||
_registry = []
|
_registry = []
|
||||||
new_stat_data = {}
|
new_stat_data = {}
|
||||||
|
|
||||||
def __init__(self, server_side: bool):
|
def __init__(self, server_side: bool, id_str=b''):
|
||||||
self._registry.append(weakref.ref(self))
|
self._registry.append(weakref.ref(self))
|
||||||
self.server_side = server_side
|
self.server_side = server_side
|
||||||
self.header_valid = False
|
self.header_valid = False
|
||||||
@@ -83,6 +83,9 @@ class Message(metaclass=IterRegistry):
|
|||||||
self.unique_id = 0
|
self.unique_id = 0
|
||||||
self.node_id = ''
|
self.node_id = ''
|
||||||
self.sug_area = ''
|
self.sug_area = ''
|
||||||
|
self.id_str = id_str
|
||||||
|
self.contact_name = b''
|
||||||
|
self.contact_mail = b''
|
||||||
self._recv_buffer = b''
|
self._recv_buffer = b''
|
||||||
self._send_buffer = bytearray(0)
|
self._send_buffer = bytearray(0)
|
||||||
self._forward_buffer = bytearray(0)
|
self._forward_buffer = bytearray(0)
|
||||||
@@ -175,6 +178,16 @@ class Message(metaclass=IterRegistry):
|
|||||||
f' Ctl: {int(self.ctrl):#02x} Msg: {fnc.__name__!r}')
|
f' Ctl: {int(self.ctrl):#02x} Msg: {fnc.__name__!r}')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _init_new_client_conn(self, contact_name, contact_mail) -> None:
|
||||||
|
logger.info(f'name: {contact_name} mail: {contact_mail}')
|
||||||
|
self.msg_id = 0
|
||||||
|
self.__build_header(0x91)
|
||||||
|
self._send_buffer += struct.pack(f'!{len(contact_name)+1}p'
|
||||||
|
f'{len(contact_mail)+1}p',
|
||||||
|
contact_name, contact_mail)
|
||||||
|
|
||||||
|
self.__finish_send_msg()
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Our private methods
|
Our private methods
|
||||||
'''
|
'''
|
||||||
@@ -271,29 +284,56 @@ class Message(metaclass=IterRegistry):
|
|||||||
self.__build_header(0x99)
|
self.__build_header(0x99)
|
||||||
self._send_buffer += b'\x01'
|
self._send_buffer += b'\x01'
|
||||||
self.__finish_send_msg()
|
self.__finish_send_msg()
|
||||||
|
self.__process_contact_info()
|
||||||
|
# don't forward this contact info here, we will build one
|
||||||
|
# when the remote connection is established
|
||||||
|
return
|
||||||
elif self.ctrl.is_resp():
|
elif self.ctrl.is_resp():
|
||||||
return # ignore received response from tsun
|
return # ignore received response from tsun
|
||||||
else:
|
else:
|
||||||
self.inc_counter('Unknown_Ctrl')
|
self.inc_counter('Unknown_Ctrl')
|
||||||
|
self.forward(self._recv_buffer, self.header_len+self.data_len)
|
||||||
|
|
||||||
self.forward(self._recv_buffer, self.header_len+self.data_len)
|
def __process_contact_info(self):
|
||||||
|
result = struct.unpack_from('!B', self._recv_buffer, self.header_len)
|
||||||
|
name_len = result[0]
|
||||||
|
|
||||||
|
result = struct.unpack_from(f'!{name_len+1}pB', self._recv_buffer,
|
||||||
|
self.header_len)
|
||||||
|
self.contact_name = result[0]
|
||||||
|
mail_len = result[1]
|
||||||
|
logger.info(f'name: {self.contact_name}')
|
||||||
|
|
||||||
|
result = struct.unpack_from(f'!{mail_len+1}p', self._recv_buffer,
|
||||||
|
self.header_len+name_len+1)
|
||||||
|
self.contact_mail = result[0]
|
||||||
|
logger.info(f'mail: {self.contact_mail}')
|
||||||
|
|
||||||
def msg_get_time(self):
|
def msg_get_time(self):
|
||||||
if self.ctrl.is_ind():
|
tsun = Config.get('tsun')
|
||||||
ts = self.__timestamp()
|
if tsun['enabled']:
|
||||||
logger.debug(f'time: {ts:08x}')
|
if self.ctrl.is_resp():
|
||||||
|
ts = self.__timestamp()
|
||||||
self.__build_header(0x99)
|
result = struct.unpack_from('!q', self._recv_buffer,
|
||||||
self._send_buffer += struct.pack('!q', ts)
|
self.header_len)
|
||||||
self.__finish_send_msg()
|
logger.debug(f'tsun-time: {result[0]:08x}'
|
||||||
|
f' proxy-time: {ts:08x}')
|
||||||
elif self.ctrl.is_resp():
|
|
||||||
result = struct.unpack_from('!q', self._recv_buffer,
|
|
||||||
self.header_len)
|
|
||||||
logger.debug(f'tsun-time: {result[0]:08x}')
|
|
||||||
return # ignore received response from tsun
|
|
||||||
else:
|
else:
|
||||||
self.inc_counter('Unknown_Ctrl')
|
if self.ctrl.is_ind():
|
||||||
|
ts = self.__timestamp()
|
||||||
|
logger.debug(f'time: {ts:08x}')
|
||||||
|
|
||||||
|
self.__build_header(0x99)
|
||||||
|
self._send_buffer += struct.pack('!q', ts)
|
||||||
|
self.__finish_send_msg()
|
||||||
|
|
||||||
|
elif self.ctrl.is_resp():
|
||||||
|
result = struct.unpack_from('!q', self._recv_buffer,
|
||||||
|
self.header_len)
|
||||||
|
logger.debug(f'tsun-time: {result[0]:08x}')
|
||||||
|
return # ignore received response from tsun
|
||||||
|
else:
|
||||||
|
self.inc_counter('Unknown_Ctrl')
|
||||||
self.forward(self._recv_buffer, self.header_len+self.data_len)
|
self.forward(self._recv_buffer, self.header_len+self.data_len)
|
||||||
|
|
||||||
def parse_msg_header(self):
|
def parse_msg_header(self):
|
||||||
|
|||||||
@@ -67,6 +67,10 @@ def Msg2ContactInfo(): # two Contact Info messages
|
|||||||
def MsgContactResp(): # Contact Response message
|
def MsgContactResp(): # Contact Response message
|
||||||
return b'\x00\x00\x00\x14\x10R170000000000001\x99\x00\x01'
|
return b'\x00\x00\x00\x14\x10R170000000000001\x99\x00\x01'
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def MsgContactResp2(): # Contact Response message
|
||||||
|
return b'\x00\x00\x00\x14\x10R170000000000002\x99\x00\x01'
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def MsgContactInvalid(): # Contact Response message
|
def MsgContactInvalid(): # Contact Response message
|
||||||
return b'\x00\x00\x00\x14\x10R170000000000001\x93\x00\x01'
|
return b'\x00\x00\x00\x14\x10R170000000000001\x93\x00\x01'
|
||||||
@@ -246,7 +250,7 @@ def test_read_message_in_chunks2(MsgContactInfo):
|
|||||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||||
m.close()
|
m.close()
|
||||||
|
|
||||||
def test_read_two_messages(ConfigTsunAllowAll, Msg2ContactInfo):
|
def test_read_two_messages(ConfigTsunAllowAll, Msg2ContactInfo,MsgContactResp,MsgContactResp2):
|
||||||
ConfigTsunAllowAll
|
ConfigTsunAllowAll
|
||||||
m = MemoryStream(Msg2ContactInfo, (0,))
|
m = MemoryStream(Msg2ContactInfo, (0,))
|
||||||
m.db.stat['proxy']['Unknown_Ctrl'] = 0
|
m.db.stat['proxy']['Unknown_Ctrl'] = 0
|
||||||
@@ -259,9 +263,15 @@ def test_read_two_messages(ConfigTsunAllowAll, Msg2ContactInfo):
|
|||||||
assert m.msg_id==0
|
assert m.msg_id==0
|
||||||
assert m.header_len==23
|
assert m.header_len==23
|
||||||
assert m.data_len==25
|
assert m.data_len==25
|
||||||
assert m._forward_buffer==b'\x00\x00\x00,\x10R170000000000001\x91\x00\x08solarhub\x0fsolarhub@123456'
|
assert m._forward_buffer==b''
|
||||||
|
assert m._send_buffer==MsgContactResp
|
||||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||||
|
|
||||||
|
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||||
|
m._init_new_client_conn(b'solarhub', b'solarhub@123456')
|
||||||
|
assert m._send_buffer==b'\x00\x00\x00,\x10R170000000000001\x91\x00\x08solarhub\x0fsolarhub@123456'
|
||||||
|
|
||||||
|
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||||
m.read() # read complete msg, and dispatch msg
|
m.read() # read complete msg, and dispatch msg
|
||||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||||
assert m.msg_count == 2
|
assert m.msg_count == 2
|
||||||
@@ -271,8 +281,13 @@ def test_read_two_messages(ConfigTsunAllowAll, Msg2ContactInfo):
|
|||||||
assert m.msg_id==0
|
assert m.msg_id==0
|
||||||
assert m.header_len==23
|
assert m.header_len==23
|
||||||
assert m.data_len==25
|
assert m.data_len==25
|
||||||
assert m._forward_buffer==b'\x00\x00\x00,\x10R170000000000002\x91\x00\x08solarhub\x0fsolarhub@123456'
|
assert m._forward_buffer==b''
|
||||||
|
assert m._send_buffer==MsgContactResp2
|
||||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||||
|
|
||||||
|
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||||
|
m._init_new_client_conn(b'solarhub', b'solarhub@123456')
|
||||||
|
assert m._send_buffer==b'\x00\x00\x00,\x10R170000000000002\x91\x00\x08solarhub\x0fsolarhub@123456'
|
||||||
m.close()
|
m.close()
|
||||||
|
|
||||||
def test_msg_contact_resp(ConfigTsunInv1, MsgContactResp):
|
def test_msg_contact_resp(ConfigTsunInv1, MsgContactResp):
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ def test_send_contact_info1(ClientConnection, MsgContactInfo, MsgContactResp):
|
|||||||
pass
|
pass
|
||||||
assert data == MsgContactResp
|
assert data == MsgContactResp
|
||||||
|
|
||||||
|
|
||||||
def test_send_contact_info2(ClientConnection, MsgContactInfo2, MsgContactInfo, MsgContactResp):
|
def test_send_contact_info2(ClientConnection, MsgContactInfo2, MsgContactInfo, MsgContactResp):
|
||||||
s = ClientConnection
|
s = ClientConnection
|
||||||
try:
|
try:
|
||||||
@@ -154,7 +155,20 @@ def test_send_contact_info2(ClientConnection, MsgContactInfo2, MsgContactInfo, M
|
|||||||
pass
|
pass
|
||||||
assert data == MsgContactResp
|
assert data == MsgContactResp
|
||||||
|
|
||||||
|
def test_send_contact_info3(ClientConnection, MsgContactInfo, MsgContactResp, MsgTimeStampReq):
|
||||||
|
s = ClientConnection
|
||||||
|
try:
|
||||||
|
s.sendall(MsgContactInfo)
|
||||||
|
data = s.recv(1024)
|
||||||
|
except TimeoutError:
|
||||||
|
pass
|
||||||
|
assert data == MsgContactResp
|
||||||
|
try:
|
||||||
|
s.sendall(MsgTimeStampReq)
|
||||||
|
data = s.recv(1024)
|
||||||
|
except TimeoutError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_send_contact_resp(ClientConnection, MsgContactResp):
|
def test_send_contact_resp(ClientConnection, MsgContactResp):
|
||||||
s = ClientConnection
|
s = ClientConnection
|
||||||
|
|||||||
Reference in New Issue
Block a user