diff --git a/CHANGELOG.md b/CHANGELOG.md index 3730dbc..60ebd53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- 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 - add int64 data type to info parser diff --git a/app/src/async_stream.py b/app/src/async_stream.py index 98c8678..b1b5bf7 100644 --- a/app/src/async_stream.py +++ b/app/src/async_stream.py @@ -9,9 +9,9 @@ logger = logging.getLogger('conn') class AsyncStream(Message): - def __init__(self, reader, writer, addr, remote_stream, server_side: bool - ) -> None: - super().__init__(server_side) + def __init__(self, reader, writer, addr, remote_stream, server_side: bool, + id_str=b'') -> None: + super().__init__(server_side, id_str) self.reader = reader self.writer = writer self.remoteStream = remote_stream @@ -84,6 +84,10 @@ class AsyncStream(Message): if self._forward_buffer: if not self.remoteStream: 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: hex_dump_memory(logging.INFO, diff --git a/app/src/inverter.py b/app/src/inverter.py index 01b67e0..2c16631 100644 --- a/app/src/inverter.py +++ b/app/src/inverter.py @@ -149,7 +149,8 @@ class Inverter(AsyncStream): logging.info(f'Connected to {addr}') connect = asyncio.open_connection(host, port) 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)) except ConnectionRefusedError as error: diff --git a/app/src/messages.py b/app/src/messages.py index 96574c7..493b016 100644 --- a/app/src/messages.py +++ b/app/src/messages.py @@ -74,7 +74,7 @@ class Message(metaclass=IterRegistry): _registry = [] 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.server_side = server_side self.header_valid = False @@ -83,6 +83,9 @@ class Message(metaclass=IterRegistry): self.unique_id = 0 self.node_id = '' self.sug_area = '' + self.id_str = id_str + self.contact_name = b'' + self.contact_mail = b'' self._recv_buffer = b'' self._send_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}') 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 ''' @@ -271,29 +284,56 @@ class Message(metaclass=IterRegistry): self.__build_header(0x99) self._send_buffer += b'\x01' 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(): 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 __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): - 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 + tsun = Config.get('tsun') + if tsun['enabled']: + if self.ctrl.is_resp(): + ts = self.__timestamp() + result = struct.unpack_from('!q', self._recv_buffer, + self.header_len) + logger.debug(f'tsun-time: {result[0]:08x}' + f' proxy-time: {ts:08x}') 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) def parse_msg_header(self): diff --git a/app/tests/test_messages.py b/app/tests/test_messages.py index b3cf4dd..30bdd3a 100644 --- a/app/tests/test_messages.py +++ b/app/tests/test_messages.py @@ -67,6 +67,10 @@ def Msg2ContactInfo(): # two Contact Info messages def MsgContactResp(): # Contact Response message 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 def MsgContactInvalid(): # Contact Response message 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 m.close() -def test_read_two_messages(ConfigTsunAllowAll, Msg2ContactInfo): +def test_read_two_messages(ConfigTsunAllowAll, Msg2ContactInfo,MsgContactResp,MsgContactResp2): ConfigTsunAllowAll m = MemoryStream(Msg2ContactInfo, (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.header_len==23 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 + 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 assert not m.header_valid # must be invalid, since msg was handled and buffer flushed assert m.msg_count == 2 @@ -271,8 +281,13 @@ def test_read_two_messages(ConfigTsunAllowAll, Msg2ContactInfo): assert m.msg_id==0 assert m.header_len==23 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 + + 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() def test_msg_contact_resp(ConfigTsunInv1, MsgContactResp): diff --git a/system_tests/test_tcp_socket.py b/system_tests/test_tcp_socket.py index a50bb98..e04193c 100644 --- a/system_tests/test_tcp_socket.py +++ b/system_tests/test_tcp_socket.py @@ -137,6 +137,7 @@ def test_send_contact_info1(ClientConnection, MsgContactInfo, MsgContactResp): pass assert data == MsgContactResp + def test_send_contact_info2(ClientConnection, MsgContactInfo2, MsgContactInfo, MsgContactResp): s = ClientConnection try: @@ -154,7 +155,20 @@ def test_send_contact_info2(ClientConnection, MsgContactInfo2, MsgContactInfo, M pass 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): s = ClientConnection