PageRenderTime 26ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/test_noncoap_client.py

https://gitlab.com/aiocoap/aiocoap
Python | 174 lines | 118 code | 31 blank | 25 comment | 7 complexity | 72d139bb8e3af426f8d0fa068d303f15 MD5 | raw file
  1. # This file is part of the Python aiocoap library project.
  2. #
  3. # Copyright (c) 2012-2014 Maciej Wasilak <http://sixpinetrees.blogspot.com/>,
  4. # 2013-2014 Christian Amsüss <c.amsuess@energyharvesting.at>
  5. #
  6. # aiocoap is free software, this file is published under the MIT license as
  7. # described in the accompanying LICENSE file.
  8. """Confront an aiocoap server with a client that speaks so bad protocol it is
  9. easier to mock with sending byte sequences than with aiocoap"""
  10. import sys
  11. import socket
  12. import asyncio
  13. from asyncio import wait_for, TimeoutError
  14. import signal
  15. import contextlib
  16. import os
  17. import unittest
  18. import aiocoap
  19. from .test_server import WithTestServer, precise_warnings, no_warnings, asynctest, WithAsyncLoop
  20. # For some reasons site-local requests do not work on my test setup, resorting
  21. # to link-local; that means a link needs to be given, and while we never need
  22. # to find the default multicast interface to join MC groups, we need to know it
  23. # to address them. This needs support from outside the test suite right now.
  24. _skip_unless_defaultmcif = unittest.skipIf(
  25. "AIOCOAP_TEST_MCIF" not in os.environ,
  26. "Multicast tests require AIOCOAP_TEST_MCIF environment variable to tell"
  27. " the default multicast interface")
  28. class MockSockProtocol:
  29. def __init__(self, remote_addr):
  30. # It should be pointed out here that this whole mocksock thing is not
  31. # terribly well thought out, and just hacked together to replace the
  32. # blocking sockets that used to be there (which were equally hacked
  33. # together)
  34. self.incoming_queue = asyncio.Queue()
  35. self.remote_addr = remote_addr
  36. def connection_made(self, transport):
  37. self.transport = transport
  38. def datagram_received(self, data, addr):
  39. self.incoming_queue.put_nowait((data, addr))
  40. async def close(self):
  41. pass
  42. # emulating the possibly connected socket.socket this once was
  43. def send(self, data):
  44. self.transport.sendto(data, self.remote_addr)
  45. def sendto(self, data, addr):
  46. self.transport.sendto(data, addr)
  47. async def recv(self):
  48. return (await self.incoming_queue.get())[0]
  49. class WithMockSock(WithAsyncLoop):
  50. def setUp(self):
  51. super().setUp()
  52. _, self.mocksock = self.loop.run_until_complete(
  53. self.loop.create_datagram_endpoint(
  54. lambda: MockSockProtocol(self.mocksock_remote_addr),
  55. family=socket.AF_INET6,
  56. ))
  57. def tearDown(self):
  58. self.loop.run_until_complete(self.mocksock.close())
  59. super().tearDown()
  60. class TestNoncoapClient(WithTestServer, WithMockSock):
  61. def setUp(self):
  62. self.mocksock_remote_addr = (self.serveraddress, aiocoap.COAP_PORT)
  63. super().setUp()
  64. @precise_warnings(["Ignoring unparsable message from ..."])
  65. @asynctest
  66. async def test_veryshort(self):
  67. self.mocksock.send(b'\x40')
  68. await asyncio.sleep(0.1)
  69. @precise_warnings(["Ignoring unparsable message from ..."])
  70. @asynctest
  71. async def test_short_mid(self):
  72. self.mocksock.send(b'\x40\x01\x97')
  73. await asyncio.sleep(0.1)
  74. @precise_warnings(["Ignoring unparsable message from ..."])
  75. @asynctest
  76. async def test_version2(self):
  77. self.mocksock.send(b'\x80\x01\x99\x98')
  78. await asyncio.sleep(0.1)
  79. @no_warnings
  80. @asynctest
  81. async def test_duplicate(self):
  82. self.mocksock.send(b'\x40\x01\x99\x99') # that's a GET /
  83. await asyncio.sleep(0.1)
  84. self.mocksock.send(b'\x40\x01\x99\x99') # that's a GET /
  85. await asyncio.sleep(0.1)
  86. r1 = r2 = None
  87. try:
  88. r1 = await wait_for(self.mocksock.recv(), timeout=1)
  89. r2 = await wait_for(self.mocksock.recv(), timeout=1)
  90. except TimeoutError:
  91. pass
  92. self.assertEqual(r1, r2, "Duplicate GETs gave different responses")
  93. self.assertTrue(r1 is not None, "No responses received to duplicate GET")
  94. @no_warnings
  95. @asynctest
  96. async def test_ping(self):
  97. self.mocksock.send(b'\x40\x00\x99\x9a') # CoAP ping -- should this test be doable in aiocoap?
  98. response = await asyncio.wait_for(self.mocksock.recv(), timeout=1)
  99. assert response == b'\x70\x00\x99\x9a'
  100. @no_warnings
  101. @asynctest
  102. async def test_noresponse(self):
  103. self.mocksock.send(b'\x50\x01\x99\x9b\xd1\xf5\x02') # CoAP NON GET / with no-response on 2.xx
  104. try:
  105. response = await wait_for(self.mocksock.recv(), timeout=1)
  106. self.assertTrue(False, "Response was sent when No-Response should have suppressed it")
  107. except TimeoutError:
  108. pass
  109. @no_warnings
  110. @asynctest
  111. async def test_unknownresponse_reset(self):
  112. self.mocksock.send(bytes.fromhex("4040ffff"))
  113. response = await wait_for(self.mocksock.recv(), timeout=1)
  114. self.assertEqual(response, bytes.fromhex("7000ffff"), "Unknown CON Response did not trigger RST")
  115. # Skipping the whole class when no multicast address was given (as otherwise
  116. # it'd try binding :: which is bound to fail with a simplesocketserver setting)
  117. @_skip_unless_defaultmcif
  118. class TestNoncoapMulticastClient(WithTestServer, WithMockSock):
  119. # This exposes the test server to traffic from the environment system for
  120. # some time; it's only run if a default multicast inteface is given
  121. # explicitly, though.
  122. serveraddress = '::'
  123. def setUp(self):
  124. # always used with sendto
  125. self.mocksock_remote_addr = None
  126. super().setUp()
  127. @no_warnings
  128. @asynctest
  129. async def test_mutlicast_ping(self):
  130. # exactly like the unicast case -- just to verify we're actually reaching our server
  131. self.mocksock.sendto(b'\x40\x00\x99\x9a', (aiocoap.numbers.constants.MCAST_IPV6_LINKLOCAL_ALLCOAPNODES, aiocoap.COAP_PORT, 0, socket.if_nametoindex(os.environ['AIOCOAP_TEST_MCIF'])))
  132. response = await wait_for(self.mocksock.recv(), timeout=1)
  133. assert response == b'\x70\x00\x99\x9a'
  134. @no_warnings
  135. @asynctest
  136. async def test_multicast_unknownresponse_noreset(self):
  137. self.mocksock.sendto(bytes.fromhex("4040ffff"), (aiocoap.numbers.constants.MCAST_IPV6_LINKLOCAL_ALLCOAPNODES, aiocoap.COAP_PORT, 0, socket.if_nametoindex(os.environ['AIOCOAP_TEST_MCIF'])))
  138. try:
  139. response = await wait_for(self.mocksock.recv(), timeout=1)
  140. except TimeoutError:
  141. pass
  142. else:
  143. self.assertEqual(False, "Message was sent back responding to CON response to multicast address")