wss_server_example_test.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2021 Espressif Systems (Shanghai) CO LTD
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. from __future__ import division, print_function, unicode_literals
  17. import os
  18. import re
  19. import threading
  20. import time
  21. from types import TracebackType
  22. from typing import Any, Optional
  23. import tiny_test_fw
  24. import ttfw_idf
  25. import websocket
  26. from tiny_test_fw import Utility
  27. OPCODE_TEXT = 0x1
  28. OPCODE_BIN = 0x2
  29. OPCODE_PING = 0x9
  30. OPCODE_PONG = 0xa
  31. CORRECT_ASYNC_DATA = 'Hello client'
  32. class WsClient:
  33. def __init__(self, ip, port, ca_file): # type: (str, int, str) -> None
  34. self.port = port
  35. self.ip = ip
  36. sslopt = {'ca_certs':ca_file, 'check_hostname': False}
  37. self.ws = websocket.WebSocket(sslopt=sslopt)
  38. # Set timeout to 10 seconds to avoid conection failure at the time of handshake
  39. self.ws.settimeout(10)
  40. def __enter__(self): # type: ignore
  41. self.ws.connect('wss://{}:{}/ws'.format(self.ip, self.port))
  42. return self
  43. def __exit__(self, exc_type, exc_value, traceback): # type: (type, RuntimeError, TracebackType) -> None
  44. self.ws.close()
  45. def read(self): # type: () -> Any
  46. return self.ws.recv_data(control_frame=True)
  47. def write(self, data, opcode=OPCODE_TEXT): # type: (str, int) -> Any
  48. if opcode == OPCODE_PING:
  49. return self.ws.ping(data)
  50. if opcode == OPCODE_PONG:
  51. return self.ws.pong(data)
  52. return self.ws.send(data)
  53. class wss_client_thread(threading.Thread):
  54. def __init__(self, ip, port, ca_file): # type: (str, int, str) -> None
  55. threading.Thread.__init__(self)
  56. self.ip = ip
  57. self.port = port
  58. self.ca_file = ca_file
  59. self.start_time = time.time()
  60. self.exc = None
  61. self.data = 'Espressif'
  62. self.async_response = False
  63. def run(self): # type: () -> None
  64. with WsClient(self.ip, self.port, self.ca_file) as ws:
  65. while True:
  66. try:
  67. opcode, data = ws.read()
  68. data = data.decode('UTF-8')
  69. if opcode == OPCODE_PING:
  70. ws.write(data=self.data, opcode=OPCODE_PONG)
  71. if opcode == OPCODE_TEXT:
  72. if data == CORRECT_ASYNC_DATA:
  73. self.async_response = True
  74. Utility.console_log('Thread {} obtained correct async message'.format(self.name))
  75. # Keep sending pong to update the keepalive in the server
  76. if (time.time() - self.start_time) > 20:
  77. break
  78. except Exception as e:
  79. Utility.console_log('Failed to connect to the client and read async data')
  80. self.exc = e # type: ignore
  81. if self.async_response is not True:
  82. self.exc = RuntimeError('Failed to obtain correct async data') # type: ignore
  83. def join(self, timeout=0): # type:(Optional[float]) -> None
  84. threading.Thread.join(self)
  85. if self.exc:
  86. raise self.exc
  87. def test_multiple_client_keep_alive_and_async_response(ip, port, ca_file): # type: (str, int, str) -> None
  88. threads = []
  89. for _ in range(3):
  90. try:
  91. thread = wss_client_thread(ip, port, ca_file)
  92. thread.start()
  93. threads.append(thread)
  94. except OSError:
  95. Utility.console_log('Error: unable to start thread')
  96. # keep delay of 5 seconds between two connections to avoid handshake timeout
  97. time.sleep(5)
  98. for t in threads:
  99. t.join()
  100. @ttfw_idf.idf_example_test(env_tag='Example_WIFI_Protocols')
  101. def test_examples_protocol_https_wss_server(env, extra_data): # type: (tiny_test_fw.Env.Env, None) -> None # pylint: disable=unused-argument
  102. # Acquire DUT
  103. dut1 = env.get_dut('https_server', 'examples/protocols/https_server/wss_server', dut_class=ttfw_idf.ESP32DUT)
  104. # Get binary file
  105. binary_file = os.path.join(dut1.app.binary_path, 'wss_server.bin')
  106. bin_size = os.path.getsize(binary_file)
  107. ttfw_idf.log_performance('https_wss_server_bin_size', '{}KB'.format(bin_size // 1024))
  108. # Upload binary and start testing
  109. Utility.console_log('Starting wss_server test app')
  110. dut1.start_app()
  111. # Parse IP address of STA
  112. got_port = dut1.expect(re.compile(r'Server listening on port (\d+)'), timeout=60)[0]
  113. Utility.console_log('Waiting to connect with AP')
  114. got_ip = dut1.expect(re.compile(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)'), timeout=60)[0]
  115. Utility.console_log('Got IP : ' + got_ip)
  116. Utility.console_log('Got Port : ' + got_port)
  117. ca_file = os.path.join(os.path.dirname(__file__), 'main', 'certs', 'cacert.pem')
  118. # Start ws server test
  119. with WsClient(got_ip, int(got_port), ca_file) as ws:
  120. # Check for echo
  121. DATA = 'Espressif'
  122. dut1.expect('performing session handshake')
  123. client_fd = dut1.expect(re.compile(r'New client connected (\d+)'), timeout=20)[0]
  124. ws.write(data=DATA, opcode=OPCODE_TEXT)
  125. dut1.expect(re.compile(r'Received packet with message: {}'.format(DATA)))
  126. opcode, data = ws.read()
  127. data = data.decode('UTF-8')
  128. if data != DATA:
  129. raise RuntimeError('Failed to receive the correct echo response')
  130. Utility.console_log('Correct echo response obtained from the wss server')
  131. # Test for keepalive
  132. Utility.console_log('Testing for keep alive (approx time = 20s)')
  133. start_time = time.time()
  134. while True:
  135. try:
  136. opcode, data = ws.read()
  137. if opcode == OPCODE_PING:
  138. ws.write(data='Espressif', opcode=OPCODE_PONG)
  139. Utility.console_log('Received PING, replying PONG (to update the keepalive)')
  140. # Keep sending pong to update the keepalive in the server
  141. if (time.time() - start_time) > 20:
  142. break
  143. except Exception:
  144. Utility.console_log('Failed the test for keep alive,\nthe client got abruptly disconnected')
  145. raise
  146. # keepalive timeout is 10 seconds so do not respond for (10 + 1) senconds
  147. Utility.console_log('Testing if client is disconnected if it does not respond for 10s i.e. keep_alive timeout (approx time = 11s)')
  148. try:
  149. dut1.expect('Client not alive, closing fd {}'.format(client_fd), timeout=20)
  150. dut1.expect('Client disconnected {}'.format(client_fd))
  151. except Exception:
  152. Utility.console_log('ENV_ERROR:Failed the test for keep alive,\nthe connection was not closed after timeout')
  153. time.sleep(11)
  154. Utility.console_log('Passed the test for keep alive')
  155. # Test keep alive and async response for multiple simultaneous client connections
  156. Utility.console_log('Testing for multiple simultaneous client connections (approx time = 30s)')
  157. test_multiple_client_keep_alive_and_async_response(got_ip, int(got_port), ca_file)
  158. Utility.console_log('Passed the test for multiple simultaneous client connections')
  159. if __name__ == '__main__':
  160. test_examples_protocol_https_wss_server() # pylint: disable=no-value-for-parameter