example_test.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. import multiprocessing
  2. import os
  3. import re
  4. import socket
  5. import ssl
  6. import struct
  7. from tiny_test_fw import DUT
  8. import ttfw_idf
  9. import random
  10. import subprocess
  11. try:
  12. import BaseHTTPServer
  13. from SimpleHTTPServer import SimpleHTTPRequestHandler
  14. except ImportError:
  15. import http.server as BaseHTTPServer
  16. from http.server import SimpleHTTPRequestHandler
  17. server_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_certs/server_cert.pem')
  18. key_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_certs/server_key.pem')
  19. def get_my_ip():
  20. s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  21. s1.connect(("8.8.8.8", 80))
  22. my_ip = s1.getsockname()[0]
  23. s1.close()
  24. return my_ip
  25. def get_server_status(host_ip, port):
  26. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  27. server_status = sock.connect_ex((host_ip, port))
  28. sock.close()
  29. if server_status == 0:
  30. return True
  31. return False
  32. def https_request_handler():
  33. """
  34. Returns a request handler class that handles broken pipe exception
  35. """
  36. class RequestHandler(SimpleHTTPRequestHandler):
  37. def finish(self):
  38. try:
  39. if not self.wfile.closed:
  40. self.wfile.flush()
  41. self.wfile.close()
  42. except socket.error:
  43. pass
  44. self.rfile.close()
  45. def handle(self):
  46. try:
  47. BaseHTTPServer.BaseHTTPRequestHandler.handle(self)
  48. except socket.error:
  49. pass
  50. return RequestHandler
  51. def start_https_server(ota_image_dir, server_ip, server_port):
  52. os.chdir(ota_image_dir)
  53. requestHandler = https_request_handler()
  54. httpd = BaseHTTPServer.HTTPServer((server_ip, server_port), requestHandler)
  55. httpd.socket = ssl.wrap_socket(httpd.socket,
  56. keyfile=key_file,
  57. certfile=server_file, server_side=True)
  58. httpd.serve_forever()
  59. def start_chunked_server(ota_image_dir, server_port):
  60. os.chdir(ota_image_dir)
  61. chunked_server = subprocess.Popen(['openssl', 's_server', '-WWW', '-key', key_file, '-cert', server_file, '-port', str(server_port)])
  62. return chunked_server
  63. def redirect_handler_factory(url):
  64. """
  65. Returns a request handler class that redirects to supplied `url`
  66. """
  67. class RedirectHandler(SimpleHTTPRequestHandler):
  68. def do_GET(self):
  69. print("Sending resp, URL: " + url)
  70. self.send_response(301)
  71. self.send_header('Location', url)
  72. self.end_headers()
  73. return RedirectHandler
  74. def start_redirect_server(ota_image_dir, server_ip, server_port, redirection_port):
  75. os.chdir(ota_image_dir)
  76. redirectHandler = redirect_handler_factory('https://' + server_ip + ':' + str(redirection_port) + '/advanced_https_ota.bin')
  77. httpd = BaseHTTPServer.HTTPServer((server_ip, server_port),
  78. redirectHandler)
  79. httpd.socket = ssl.wrap_socket(httpd.socket,
  80. keyfile=key_file,
  81. certfile=server_file, server_side=True)
  82. httpd.serve_forever()
  83. @ttfw_idf.idf_example_test(env_tag="Example_WIFI")
  84. def test_examples_protocol_advanced_https_ota_example(env, extra_data):
  85. """
  86. This is a positive test case, which downloads complete binary file multiple number of times.
  87. Number of iterations can be specified in variable iterations.
  88. steps: |
  89. 1. join AP
  90. 2. Fetch OTA image over HTTPS
  91. 3. Reboot with the new OTA image
  92. """
  93. dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota", dut_class=ttfw_idf.ESP32DUT)
  94. # Number of iterations to validate OTA
  95. iterations = 3
  96. server_port = 8001
  97. # File to be downloaded. This file is generated after compilation
  98. bin_name = "advanced_https_ota.bin"
  99. # check and log bin size
  100. binary_file = os.path.join(dut1.app.binary_path, bin_name)
  101. bin_size = os.path.getsize(binary_file)
  102. ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
  103. ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024)
  104. # start test
  105. host_ip = get_my_ip()
  106. if (get_server_status(host_ip, server_port) is False):
  107. thread1 = multiprocessing.Process(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
  108. thread1.daemon = True
  109. thread1.start()
  110. dut1.start_app()
  111. for i in range(iterations):
  112. dut1.expect("Loaded app from partition at offset", timeout=30)
  113. try:
  114. ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
  115. print("Connected to AP with IP: {}".format(ip_address))
  116. except DUT.ExpectTimeout:
  117. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  118. thread1.terminate()
  119. dut1.expect('Starting Advanced OTA example', timeout=30)
  120. print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + bin_name))
  121. dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + bin_name)
  122. dut1.expect("Loaded app from partition at offset", timeout=60)
  123. dut1.expect("Starting Advanced OTA example", timeout=30)
  124. dut1.reset()
  125. thread1.terminate()
  126. @ttfw_idf.idf_example_test(env_tag="Example_WIFI")
  127. def test_examples_protocol_advanced_https_ota_example_truncated_bin(env, extra_data):
  128. """
  129. Working of OTA if binary file is truncated is validated in this test case.
  130. Application should return with error message in this case.
  131. steps: |
  132. 1. join AP
  133. 2. Generate truncated binary file
  134. 3. Fetch OTA image over HTTPS
  135. 4. Check working of code if bin is truncated
  136. """
  137. dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota", dut_class=ttfw_idf.ESP32DUT)
  138. server_port = 8001
  139. # Original binary file generated after compilation
  140. bin_name = "advanced_https_ota.bin"
  141. # Truncated binary file to be generated from original binary file
  142. truncated_bin_name = "truncated.bin"
  143. # Size of truncated file to be grnerated. This value can range from 288 bytes (Image header size) to size of original binary file
  144. # truncated_bin_size is set to 64000 to reduce consumed by the test case
  145. truncated_bin_size = 64000
  146. # check and log bin size
  147. binary_file = os.path.join(dut1.app.binary_path, bin_name)
  148. f = open(binary_file, "rb+")
  149. fo = open(os.path.join(dut1.app.binary_path, truncated_bin_name), "wb+")
  150. fo.write(f.read(truncated_bin_size))
  151. fo.close()
  152. f.close()
  153. binary_file = os.path.join(dut1.app.binary_path, truncated_bin_name)
  154. bin_size = os.path.getsize(binary_file)
  155. ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
  156. ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024)
  157. # start test
  158. host_ip = get_my_ip()
  159. if (get_server_status(host_ip, server_port) is False):
  160. thread1 = multiprocessing.Process(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
  161. thread1.daemon = True
  162. thread1.start()
  163. dut1.start_app()
  164. dut1.expect("Loaded app from partition at offset", timeout=30)
  165. try:
  166. ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
  167. print("Connected to AP with IP: {}".format(ip_address))
  168. except DUT.ExpectTimeout:
  169. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  170. thread1.terminate()
  171. dut1.expect('Starting Advanced OTA example', timeout=30)
  172. print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name))
  173. dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name)
  174. dut1.expect("Image validation failed, image is corrupted", timeout=30)
  175. os.remove(binary_file)
  176. thread1.terminate()
  177. @ttfw_idf.idf_example_test(env_tag="Example_WIFI")
  178. def test_examples_protocol_advanced_https_ota_example_truncated_header(env, extra_data):
  179. """
  180. Working of OTA if headers of binary file are truncated is vaildated in this test case.
  181. Application should return with error message in this case.
  182. steps: |
  183. 1. join AP
  184. 2. Generate binary file with truncated headers
  185. 3. Fetch OTA image over HTTPS
  186. 4. Check working of code if headers are not sent completely
  187. """
  188. dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota", dut_class=ttfw_idf.ESP32DUT)
  189. server_port = 8001
  190. # Original binary file generated after compilation
  191. bin_name = "advanced_https_ota.bin"
  192. # Truncated binary file to be generated from original binary file
  193. truncated_bin_name = "truncated_header.bin"
  194. # Size of truncated file to be grnerated. This value should be less than 288 bytes (Image header size)
  195. truncated_bin_size = 180
  196. # check and log bin size
  197. binary_file = os.path.join(dut1.app.binary_path, bin_name)
  198. f = open(binary_file, "rb+")
  199. fo = open(os.path.join(dut1.app.binary_path, truncated_bin_name), "wb+")
  200. fo.write(f.read(truncated_bin_size))
  201. fo.close()
  202. f.close()
  203. binary_file = os.path.join(dut1.app.binary_path, truncated_bin_name)
  204. bin_size = os.path.getsize(binary_file)
  205. ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
  206. ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024)
  207. # start test
  208. host_ip = get_my_ip()
  209. if (get_server_status(host_ip, server_port) is False):
  210. thread1 = multiprocessing.Process(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
  211. thread1.daemon = True
  212. thread1.start()
  213. dut1.start_app()
  214. dut1.expect("Loaded app from partition at offset", timeout=30)
  215. try:
  216. ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
  217. print("Connected to AP with IP: {}".format(ip_address))
  218. except DUT.ExpectTimeout:
  219. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  220. thread1.terminate()
  221. dut1.expect('Starting Advanced OTA example', timeout=30)
  222. print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name))
  223. dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name)
  224. dut1.expect("advanced_https_ota_example: esp_https_ota_read_img_desc failed", timeout=30)
  225. os.remove(binary_file)
  226. thread1.terminate()
  227. @ttfw_idf.idf_example_test(env_tag="Example_WIFI")
  228. def test_examples_protocol_advanced_https_ota_example_random(env, extra_data):
  229. """
  230. Working of OTA if random data is added in binary file are validated in this test case.
  231. Magic byte verification should fail in this case.
  232. steps: |
  233. 1. join AP
  234. 2. Generate random binary image
  235. 3. Fetch OTA image over HTTPS
  236. 4. Check working of code for random binary file
  237. """
  238. dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota", dut_class=ttfw_idf.ESP32DUT)
  239. server_port = 8001
  240. # Random binary file to be generated
  241. random_bin_name = "random.bin"
  242. # Size of random binary file. 32000 is choosen, to reduce the time required to run the test-case
  243. random_bin_size = 32000
  244. # check and log bin size
  245. binary_file = os.path.join(dut1.app.binary_path, random_bin_name)
  246. fo = open(binary_file, "wb+")
  247. # First byte of binary file is always set to zero. If first byte is generated randomly,
  248. # in some cases it may generate 0xE9 which will result in failure of testcase.
  249. fo.write(struct.pack("B", 0))
  250. for i in range(random_bin_size - 1):
  251. fo.write(struct.pack("B", random.randrange(0,255,1)))
  252. fo.close()
  253. bin_size = os.path.getsize(binary_file)
  254. ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
  255. ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024)
  256. # start test
  257. host_ip = get_my_ip()
  258. if (get_server_status(host_ip, server_port) is False):
  259. thread1 = multiprocessing.Process(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
  260. thread1.daemon = True
  261. thread1.start()
  262. dut1.start_app()
  263. dut1.expect("Loaded app from partition at offset", timeout=30)
  264. try:
  265. ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
  266. print("Connected to AP with IP: {}".format(ip_address))
  267. except DUT.ExpectTimeout:
  268. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  269. thread1.terminate()
  270. dut1.expect('Starting Advanced OTA example', timeout=30)
  271. print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + random_bin_name))
  272. dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + random_bin_name)
  273. dut1.expect("esp_ota_ops: OTA image has invalid magic byte", timeout=10)
  274. os.remove(binary_file)
  275. thread1.terminate()
  276. @ttfw_idf.idf_example_test(env_tag="Example_WIFI")
  277. def test_examples_protocol_advanced_https_ota_example_chunked(env, extra_data):
  278. """
  279. This is a positive test case, which downloads complete binary file multiple number of times.
  280. Number of iterations can be specified in variable iterations.
  281. steps: |
  282. 1. join AP
  283. 2. Fetch OTA image over HTTPS
  284. 3. Reboot with the new OTA image
  285. """
  286. dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota", dut_class=ttfw_idf.ESP32DUT)
  287. # File to be downloaded. This file is generated after compilation
  288. bin_name = "advanced_https_ota.bin"
  289. # check and log bin size
  290. binary_file = os.path.join(dut1.app.binary_path, bin_name)
  291. bin_size = os.path.getsize(binary_file)
  292. ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
  293. ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024)
  294. # start test
  295. host_ip = get_my_ip()
  296. chunked_server = start_chunked_server(dut1.app.binary_path, 8070)
  297. dut1.start_app()
  298. dut1.expect("Loaded app from partition at offset", timeout=30)
  299. try:
  300. ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
  301. print("Connected to AP with IP: {}".format(ip_address))
  302. except DUT.ExpectTimeout:
  303. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  304. dut1.expect("Starting Advanced OTA example", timeout=30)
  305. print("writing to device: {}".format("https://" + host_ip + ":8070/" + bin_name))
  306. dut1.write("https://" + host_ip + ":8070/" + bin_name)
  307. dut1.expect("Loaded app from partition at offset", timeout=60)
  308. dut1.expect("Starting Advanced OTA example", timeout=30)
  309. chunked_server.kill()
  310. @ttfw_idf.idf_example_test(env_tag="Example_WIFI")
  311. def test_examples_protocol_advanced_https_ota_example_redirect_url(env, extra_data):
  312. """
  313. This is a positive test case, which starts a server and a redirection server.
  314. Redirection server redirects http_request to different port
  315. Number of iterations can be specified in variable iterations.
  316. steps: |
  317. 1. join AP
  318. 2. Fetch OTA image over HTTPS
  319. 3. Reboot with the new OTA image
  320. """
  321. dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota", dut_class=ttfw_idf.ESP32DUT)
  322. server_port = 8001
  323. # Port to which the request should be redirecetd
  324. redirection_server_port = 8081
  325. # File to be downloaded. This file is generated after compilation
  326. bin_name = "advanced_https_ota.bin"
  327. # check and log bin size
  328. binary_file = os.path.join(dut1.app.binary_path, bin_name)
  329. bin_size = os.path.getsize(binary_file)
  330. ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
  331. ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024)
  332. # start test
  333. host_ip = get_my_ip()
  334. if (get_server_status(host_ip, server_port) is False):
  335. thread1 = multiprocessing.Process(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
  336. thread1.daemon = True
  337. thread1.start()
  338. thread2 = multiprocessing.Process(target=start_redirect_server, args=(dut1.app.binary_path, host_ip, redirection_server_port, server_port))
  339. thread2.daemon = True
  340. thread2.start()
  341. dut1.start_app()
  342. dut1.expect("Loaded app from partition at offset", timeout=30)
  343. try:
  344. ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
  345. print("Connected to AP with IP: {}".format(ip_address))
  346. except DUT.ExpectTimeout:
  347. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  348. thread1.terminate()
  349. thread2.terminate()
  350. dut1.expect('Starting Advanced OTA example', timeout=30)
  351. print("writing to device: {}".format("https://" + host_ip + ":" + str(redirection_server_port) + "/" + bin_name))
  352. dut1.write("https://" + host_ip + ":" + str(redirection_server_port) + "/" + bin_name)
  353. dut1.expect("Loaded app from partition at offset", timeout=60)
  354. dut1.expect("Starting Advanced OTA example", timeout=30)
  355. dut1.reset()
  356. thread1.terminate()
  357. thread2.terminate()
  358. if __name__ == '__main__':
  359. test_examples_protocol_advanced_https_ota_example()
  360. test_examples_protocol_advanced_https_ota_example_chunked()
  361. test_examples_protocol_advanced_https_ota_example_redirect_url()
  362. test_examples_protocol_advanced_https_ota_example_truncated_bin()
  363. test_examples_protocol_advanced_https_ota_example_truncated_header()
  364. test_examples_protocol_advanced_https_ota_example_random()