def test_tunnelling_request(self): """Test string representation of KNX/IP TunnellingRequest.""" xknx = XKNX(loop=self.loop) tunnelling_request = TunnellingRequest(xknx) tunnelling_request.communication_channel_id = 23 tunnelling_request.sequence_counter = 42 self.assertEqual( str(tunnelling_request), '<TunnellingRequest communication_channel_id="23" sequence_counter="42" cemi="<CEMIFrame SourceAddress="GroupAddress("0/0/0")" Destina' 'tionAddress="GroupAddress("0/0/0")" Flags=" 0" Command="APCICommand.GROUP_READ" payload="None" />" />')
def test_tunnelling_request(self): """Test string representation of KNX/IP TunnellingRequest.""" tunnelling_request = TunnellingRequest() tunnelling_request.communication_channel_id = 23 tunnelling_request.sequence_counter = 42 assert ( str(tunnelling_request) == '<TunnellingRequest communication_channel_id="23" sequence_counter="42" ' 'cemi="<CEMIFrame SourceAddress="IndividualAddress("0.0.0")" DestinationAddress="GroupAddress("0/0/0")" ' 'Flags=" 0" code="L_DATA_REQ" payload="None" />" />')
def test_tunnelling_request(self): """Test string representation of KNX/IP TunnellingRequest.""" xknx = XKNX() tunnelling_request = TunnellingRequest(xknx) tunnelling_request.communication_channel_id = 23 tunnelling_request.sequence_counter = 42 self.assertEqual( str(tunnelling_request), '<TunnellingRequest communication_channel_id="23" sequence_counter="42" cemi="<CEMIFrame SourceAddress="IndividualAddress("0.0.0")"' ' DestinationAddress="GroupAddress("0/0/0")" Flags=" 0" payload="None" />" />', )
async def test_tunnel_wait_for_l2_confirmation(self, time_travel): """Test tunnel waits for L_DATA.con before sending another L_DATA.req.""" self.tunnel.transport.send = Mock() self.tunnel.communication_channel = 1 test_telegram = Telegram(payload=GroupValueWrite(DPTArray((1,)))) test_ack = KNXIPFrame.init_from_body(TunnellingAck(sequence_counter=23)) confirmation = KNXIPFrame.init_from_body( TunnellingRequest( communication_channel_id=1, sequence_counter=23, cemi=CEMIFrame.init_from_telegram( test_telegram, code=CEMIMessageCode.L_DATA_CON ), ) ) task = asyncio.create_task(self.tunnel.send_telegram(test_telegram)) await time_travel(0) self.tunnel.transport.handle_knxipframe(test_ack, HPAI()) await time_travel(0) assert not task.done() assert self.tunnel.transport.send.call_count == 1 self.tunnel.transport.handle_knxipframe(confirmation, HPAI()) await time_travel(0) assert task.done() # one call for the outgoing request and one for the ACK for the confirmation assert self.tunnel.transport.send.call_count == 2 await task
async def _tunnelling_request(self, cemi: CEMIFrame) -> bool: """Send Telegram to tunnelling device.""" if self.communication_channel is None: raise CommunicationError( "Sending telegram failed. No active communication channel.") tunnelling_request = TunnellingRequest( communication_channel_id=self.communication_channel, sequence_counter=self.sequence_number, cemi=cemi, ) if cemi.code is CEMIMessageCode.L_DATA_REQ: async def _async_wrapper() -> None: self.transport.send( KNXIPFrame.init_from_body(tunnelling_request)) await self._wait_for_tunnelling_request_confirmation( send_tunneling_request_aw=_async_wrapper(), cemi=cemi, ) else: self.transport.send(KNXIPFrame.init_from_body(tunnelling_request)) return True
def test_connect_request(self): """Test parsing and streaming connection tunneling request KNX/IP packet.""" raw = bytes.fromhex( "06 10 04 20 00 15 04 01 17 00 11 00 BC E0 00 00 48 08 01 00 81") knxipframe = KNXIPFrame() knxipframe.from_knx(raw) assert isinstance(knxipframe.body, TunnellingRequest) assert knxipframe.body.communication_channel_id == 1 assert knxipframe.body.sequence_counter == 23 assert isinstance(knxipframe.body.cemi, CEMIFrame) assert knxipframe.body.cemi.telegram == Telegram( destination_address=GroupAddress("9/0/8"), payload=GroupValueWrite(DPTBinary(1)), ) cemi = CEMIFrame(code=CEMIMessageCode.L_DATA_REQ) cemi.telegram = Telegram( destination_address=GroupAddress("9/0/8"), payload=GroupValueWrite(DPTBinary(1)), ) tunnelling_request = TunnellingRequest( communication_channel_id=1, sequence_counter=23, cemi=cemi, ) knxipframe2 = KNXIPFrame.init_from_body(tunnelling_request) assert knxipframe2.to_knx() == raw
def create_knxipframe(self) -> KNXIPFrame: """Create KNX/IP Frame object to be sent to device.""" tunnelling_request = TunnellingRequest( communication_channel_id=self.communication_channel_id, sequence_counter=self.sequence_counter, cemi=self.cemi_frame, ) return KNXIPFrame.init_from_body(tunnelling_request)
def test_connect_request(self): """Test parsing and streaming connection tunneling request KNX/IP packet.""" raw = ( 0x06, 0x10, 0x04, 0x20, 0x00, 0x15, 0x04, 0x01, 0x17, 0x00, 0x11, 0x00, 0xBC, 0xE0, 0x00, 0x00, 0x48, 0x08, 0x01, 0x00, 0x81, ) xknx = XKNX() knxipframe = KNXIPFrame(xknx) knxipframe.from_knx(raw) self.assertTrue(isinstance(knxipframe.body, TunnellingRequest)) self.assertEqual(knxipframe.body.communication_channel_id, 1) self.assertEqual(knxipframe.body.sequence_counter, 23) self.assertTrue(isinstance(knxipframe.body.cemi, CEMIFrame)) self.assertEqual( knxipframe.body.cemi.telegram, Telegram( destination_address=GroupAddress("9/0/8"), payload=GroupValueWrite(DPTBinary(1)), ), ) cemi = CEMIFrame(xknx, code=CEMIMessageCode.L_Data_REQ) cemi.telegram = Telegram( destination_address=GroupAddress("9/0/8"), payload=GroupValueWrite(DPTBinary(1)), ) tunnelling_request = TunnellingRequest(xknx, communication_channel_id=1, sequence_counter=23, cemi=cemi) knxipframe2 = KNXIPFrame.init_from_body(tunnelling_request) self.assertEqual(knxipframe2.to_knx(), list(raw))
def create_knxipframe(self) -> KNXIPFrame: """Create KNX/IP Frame object to be sent to device.""" cemi = CEMIFrame.init_from_telegram( telegram=self.telegram, code=CEMIMessageCode.L_DATA_REQ, src_addr=self.src_address, ) tunnelling_request = TunnellingRequest( communication_channel_id=self.communication_channel_id, sequence_counter=self.sequence_counter, cemi=cemi, ) return KNXIPFrame.init_from_body(tunnelling_request)
async def test_tunnelling(self): """Test tunnelling from KNX bus.""" communication_channel_id = 23 data_endpoint = ("192.168.1.2", 4567) udp_transport = UDPTransport(("192.168.1.1", 0), ("192.168.1.2", 1234)) cemi = CEMIFrame.init_from_telegram( Telegram( destination_address=GroupAddress("1/2/3"), payload=GroupValueWrite(DPTArray((0x1, 0x2, 0x3))), ) ) sequence_counter = 42 tunnelling = Tunnelling( udp_transport, data_endpoint, cemi, sequence_counter, communication_channel_id, ) tunnelling.timeout_in_seconds = 0 assert tunnelling.awaited_response_class == TunnellingAck assert tunnelling.communication_channel_id == communication_channel_id # Expected KNX/IP-Frame: tunnelling_request = TunnellingRequest( communication_channel_id=communication_channel_id, sequence_counter=sequence_counter, ) tunnelling_request.cemi = cemi exp_knxipframe = KNXIPFrame.init_from_body(tunnelling_request) with patch("xknx.io.transport.UDPTransport.send") as mock_udp_send, patch( "xknx.io.transport.UDPTransport.getsockname" ) as mock_udp_getsockname: mock_udp_getsockname.return_value = ("192.168.1.3", 4321) await tunnelling.start() mock_udp_send.assert_called_with(exp_knxipframe, addr=data_endpoint) # Response KNX/IP-Frame with wrong type wrong_knxipframe = KNXIPFrame() wrong_knxipframe.init(KNXIPServiceType.CONNECTIONSTATE_REQUEST) with patch("logging.Logger.warning") as mock_warning: tunnelling.response_rec_callback(wrong_knxipframe, HPAI(), None) mock_warning.assert_called_with("Could not understand knxipframe") # Response KNX/IP-Frame with error: err_knxipframe = KNXIPFrame() err_knxipframe.init(KNXIPServiceType.TUNNELLING_ACK) err_knxipframe.body.status_code = ErrorCode.E_CONNECTION_ID with patch("logging.Logger.debug") as mock_warning: tunnelling.response_rec_callback(err_knxipframe, HPAI(), None) mock_warning.assert_called_with( "Error: KNX bus responded to request of type '%s' with error in '%s': %s", type(tunnelling).__name__, type(err_knxipframe.body).__name__, ErrorCode.E_CONNECTION_ID, ) # Correct Response KNX/IP-Frame: res_knxipframe = KNXIPFrame() res_knxipframe.init(KNXIPServiceType.TUNNELLING_ACK) tunnelling.response_rec_callback(res_knxipframe, HPAI(), None) assert tunnelling.success
def test_tunnelling(self): """Test tunnelling from KNX bus.""" # pylint: disable=too-many-locals xknx = XKNX() communication_channel_id = 23 udp_client = UDPClient(xknx, ("192.168.1.1", 0), ("192.168.1.2", 1234)) telegram = Telegram( destination_address=GroupAddress("1/2/3"), payload=GroupValueWrite(DPTArray((0x1, 0x2, 0x3))), ) sequence_counter = 42 src_address = IndividualAddress("2.2.2") tunnelling = Tunnelling( xknx, udp_client, telegram, src_address, sequence_counter, communication_channel_id, ) tunnelling.timeout_in_seconds = 0 self.assertEqual(tunnelling.awaited_response_class, TunnellingAck) self.assertEqual(tunnelling.communication_channel_id, communication_channel_id) # Expected KNX/IP-Frame: tunnelling_request = TunnellingRequest( xknx, communication_channel_id=communication_channel_id, sequence_counter=sequence_counter, ) tunnelling_request.cemi.telegram = telegram tunnelling_request.cemi.src_addr = src_address exp_knxipframe = KNXIPFrame.init_from_body(tunnelling_request) with patch("xknx.io.UDPClient.send") as mock_udp_send, patch( "xknx.io.UDPClient.getsockname") as mock_udp_getsockname: mock_udp_getsockname.return_value = ("192.168.1.3", 4321) self.loop.run_until_complete(tunnelling.start()) mock_udp_send.assert_called_with(exp_knxipframe) # Response KNX/IP-Frame with wrong type wrong_knxipframe = KNXIPFrame(xknx) wrong_knxipframe.init(KNXIPServiceType.CONNECTIONSTATE_REQUEST) with patch("logging.Logger.warning") as mock_warning: tunnelling.response_rec_callback(wrong_knxipframe, None) mock_warning.assert_called_with("Could not understand knxipframe") # Response KNX/IP-Frame with error: err_knxipframe = KNXIPFrame(xknx) err_knxipframe.init(KNXIPServiceType.TUNNELLING_ACK) err_knxipframe.body.status_code = ErrorCode.E_CONNECTION_ID with patch("logging.Logger.debug") as mock_warning: tunnelling.response_rec_callback(err_knxipframe, None) mock_warning.assert_called_with( "Error: KNX bus responded to request of type '%s' with error in '%s': %s", type(tunnelling).__name__, type(err_knxipframe.body).__name__, ErrorCode.E_CONNECTION_ID, ) # Correct Response KNX/IP-Frame: res_knxipframe = KNXIPFrame(xknx) res_knxipframe.init(KNXIPServiceType.TUNNELLING_ACK) tunnelling.response_rec_callback(res_knxipframe, None) self.assertTrue(tunnelling.success)
async def test_tunnel_connect_send_disconnect( self, time_travel, route_back, data_endpoint_addr, local_endpoint ): """Test initiating a tunnelling connection.""" local_addr = ("192.168.1.1", 12345) remote_addr = ("192.168.1.2", 3671) self.tunnel.route_back = route_back gateway_data_endpoint = ( HPAI(*data_endpoint_addr) if data_endpoint_addr else HPAI() ) self.tunnel.transport.connect = AsyncMock() self.tunnel.transport.getsockname = Mock(return_value=local_addr) self.tunnel.transport.send = Mock() self.tunnel.transport.stop = Mock() # Connect connect_request = ConnectRequest( control_endpoint=local_endpoint, data_endpoint=local_endpoint, ) connect_frame = KNXIPFrame.init_from_body(connect_request) connection_task = asyncio.create_task(self.tunnel.connect()) await time_travel(0) self.tunnel.transport.connect.assert_called_once() self.tunnel.transport.send.assert_called_once_with(connect_frame) connect_response_frame = KNXIPFrame.init_from_body( ConnectResponse( communication_channel=23, data_endpoint=gateway_data_endpoint, identifier=7, ) ) self.tunnel.transport.handle_knxipframe(connect_response_frame, remote_addr) await connection_task assert self.tunnel._data_endpoint_addr == data_endpoint_addr assert self.tunnel._src_address == IndividualAddress(7) # Send - use data endpoint self.tunnel.transport.send.reset_mock() test_telegram = Telegram(payload=GroupValueWrite(DPTArray((1,)))) test_telegram_frame = KNXIPFrame.init_from_body( TunnellingRequest( communication_channel_id=23, sequence_counter=0, cemi=CEMIFrame.init_from_telegram( test_telegram, code=CEMIMessageCode.L_DATA_REQ, src_addr=IndividualAddress(7), ), ) ) asyncio.create_task(self.tunnel.send_telegram(test_telegram)) await time_travel(0) self.tunnel.transport.send.assert_called_once_with( test_telegram_frame, addr=data_endpoint_addr ) # skip ack and confirmation # Disconnect self.tunnel.transport.send.reset_mock() disconnect_request = DisconnectRequest( communication_channel_id=23, control_endpoint=local_endpoint, ) disconnect_frame = KNXIPFrame.init_from_body(disconnect_request) disconnection_task = asyncio.create_task(self.tunnel.disconnect()) await time_travel(0) self.tunnel.transport.send.assert_called_once_with(disconnect_frame) disconnect_response_frame = KNXIPFrame.init_from_body( DisconnectResponse(communication_channel_id=23) ) self.tunnel.transport.handle_knxipframe(disconnect_response_frame, remote_addr) await disconnection_task assert self.tunnel._data_endpoint_addr is None self.tunnel.transport.stop.assert_called_once()