pytest_advanced_ota.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Unlicense OR CC0-1.0
  3. import http.server
  4. import multiprocessing
  5. import os
  6. import random
  7. import socket
  8. import ssl
  9. import struct
  10. import subprocess
  11. from typing import Callable
  12. import pexpect
  13. import pytest
  14. from pytest_embedded import Dut
  15. from RangeHTTPServer import RangeRequestHandler
  16. server_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_certs/server_cert.pem')
  17. key_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_certs/server_key.pem')
  18. def get_my_ip() -> str:
  19. s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  20. s1.connect(('8.8.8.8', 80))
  21. my_ip = ''
  22. my_ip = s1.getsockname()[0]
  23. s1.close()
  24. return my_ip
  25. def https_request_handler() -> Callable[...,http.server.BaseHTTPRequestHandler]:
  26. """
  27. Returns a request handler class that handles broken pipe exception
  28. """
  29. class RequestHandler(RangeRequestHandler):
  30. def finish(self) -> None:
  31. try:
  32. if not self.wfile.closed:
  33. self.wfile.flush()
  34. self.wfile.close()
  35. except socket.error:
  36. pass
  37. self.rfile.close()
  38. def handle(self) -> None:
  39. try:
  40. RangeRequestHandler.handle(self)
  41. except socket.error:
  42. pass
  43. return RequestHandler
  44. def start_https_server(ota_image_dir: str, server_ip: str, server_port: int) -> None:
  45. os.chdir(ota_image_dir)
  46. requestHandler = https_request_handler()
  47. httpd = http.server.HTTPServer((server_ip, server_port), requestHandler)
  48. httpd.socket = ssl.wrap_socket(httpd.socket,
  49. keyfile=key_file,
  50. certfile=server_file, server_side=True)
  51. httpd.serve_forever()
  52. def start_chunked_server(ota_image_dir: str, server_port: int) -> subprocess.Popen:
  53. os.chdir(ota_image_dir)
  54. chunked_server = subprocess.Popen(['openssl', 's_server', '-WWW', '-key', key_file, '-cert', server_file, '-port', str(server_port)])
  55. return chunked_server
  56. def redirect_handler_factory(url: str) -> Callable[...,http.server.BaseHTTPRequestHandler]:
  57. """
  58. Returns a request handler class that redirects to supplied `url`
  59. """
  60. class RedirectHandler(http.server.SimpleHTTPRequestHandler):
  61. def do_GET(self) -> None:
  62. print('Sending resp, URL: ' + url)
  63. self.send_response(301)
  64. self.send_header('Location', url)
  65. self.end_headers()
  66. def handle(self) -> None:
  67. try:
  68. http.server.BaseHTTPRequestHandler.handle(self)
  69. except socket.error:
  70. pass
  71. return RedirectHandler
  72. def start_redirect_server(ota_image_dir: str, server_ip: str, server_port: int, redirection_port: int) -> None:
  73. os.chdir(ota_image_dir)
  74. redirectHandler = redirect_handler_factory('https://' + server_ip + ':' + str(redirection_port) + '/advanced_https_ota.bin')
  75. httpd = http.server.HTTPServer((server_ip, server_port), redirectHandler)
  76. httpd.socket = ssl.wrap_socket(httpd.socket,
  77. keyfile=key_file,
  78. certfile=server_file, server_side=True)
  79. httpd.serve_forever()
  80. @pytest.mark.esp32
  81. @pytest.mark.esp32c3
  82. @pytest.mark.esp32s2
  83. @pytest.mark.esp32s3
  84. @pytest.mark.ethernet_ota
  85. def test_examples_protocol_advanced_https_ota_example(dut: Dut) -> None:
  86. """
  87. This is a positive test case, which downloads complete binary file multiple number of times.
  88. Number of iterations can be specified in variable iterations.
  89. steps: |
  90. 1. join AP
  91. 2. Fetch OTA image over HTTPS
  92. 3. Reboot with the new OTA image
  93. """
  94. try:
  95. # Number of iterations to validate OTA
  96. iterations = 3
  97. server_port = 8001
  98. bin_name = 'advanced_https_ota.bin'
  99. # start test
  100. host_ip = get_my_ip()
  101. thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, host_ip, server_port))
  102. thread1.daemon = True
  103. thread1.start()
  104. for i in range(iterations):
  105. dut.expect('Loaded app from partition at offset', timeout=60)
  106. try:
  107. ip_address = dut.expect(r' (sta|eth) ip: ([^,]+),', timeout=30)
  108. print('Connected to AP with IP: {}'.format(ip_address))
  109. except pexpect.exceptions.TIMEOUT:
  110. thread1.terminate()
  111. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  112. dut.expect('Starting Advanced OTA example', timeout=30)
  113. print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + bin_name))
  114. dut.write('https://' + host_ip + ':' + str(server_port) + '/' + bin_name)
  115. finally:
  116. thread1.terminate()
  117. @pytest.mark.esp32
  118. @pytest.mark.esp32c3
  119. @pytest.mark.esp32s2
  120. @pytest.mark.esp32s3
  121. @pytest.mark.ethernet_ota
  122. def test_examples_protocol_advanced_https_ota_example_truncated_bin(dut: Dut) -> None:
  123. """
  124. Working of OTA if binary file is truncated is validated in this test case.
  125. Application should return with error message in this case.
  126. steps: |
  127. 1. join AP
  128. 2. Generate truncated binary file
  129. 3. Fetch OTA image over HTTPS
  130. 4. Check working of code if bin is truncated
  131. """
  132. try:
  133. server_port = 8001
  134. # Original binary file generated after compilation
  135. bin_name = 'advanced_https_ota.bin'
  136. # Truncated binary file to be generated from original binary file
  137. truncated_bin_name = 'truncated.bin'
  138. # Size of truncated file to be grnerated. This value can range from 288 bytes (Image header size) to size of original binary file
  139. # truncated_bin_size is set to 64000 to reduce consumed by the test case
  140. truncated_bin_size = 64000
  141. binary_file = os.path.join(dut.app.binary_path, bin_name)
  142. with open(binary_file, 'rb+') as f:
  143. with open(os.path.join(dut.app.binary_path, truncated_bin_name), 'wb+') as fo:
  144. fo.write(f.read(truncated_bin_size))
  145. binary_file = os.path.join(dut.app.binary_path, truncated_bin_name)
  146. # start test
  147. host_ip = get_my_ip()
  148. thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, host_ip, server_port))
  149. thread1.daemon = True
  150. thread1.start()
  151. dut.expect('Loaded app from partition at offset', timeout=30)
  152. try:
  153. ip_address = dut.expect(r' (sta|eth) ip: ([^,]+),', timeout=30)
  154. print('Connected to AP with IP: {}'.format(ip_address))
  155. except pexpect.exceptions.TIMEOUT:
  156. thread1.terminate()
  157. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  158. dut.expect('Starting Advanced OTA example', timeout=30)
  159. print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + truncated_bin_name))
  160. dut.write('https://' + host_ip + ':' + str(server_port) + '/' + truncated_bin_name)
  161. dut.expect('Image validation failed, image is corrupted', timeout=30)
  162. try:
  163. os.remove(binary_file)
  164. except OSError:
  165. pass
  166. finally:
  167. thread1.terminate()
  168. @pytest.mark.esp32
  169. @pytest.mark.esp32c3
  170. @pytest.mark.esp32s2
  171. @pytest.mark.esp32s3
  172. @pytest.mark.ethernet_ota
  173. def test_examples_protocol_advanced_https_ota_example_truncated_header(dut: Dut) -> None:
  174. """
  175. Working of OTA if headers of binary file are truncated is vaildated in this test case.
  176. Application should return with error message in this case.
  177. steps: |
  178. 1. join AP
  179. 2. Generate binary file with truncated headers
  180. 3. Fetch OTA image over HTTPS
  181. 4. Check working of code if headers are not sent completely
  182. """
  183. try:
  184. server_port = 8001
  185. # Original binary file generated after compilation
  186. bin_name = 'advanced_https_ota.bin'
  187. # Truncated binary file to be generated from original binary file
  188. truncated_bin_name = 'truncated_header.bin'
  189. # Size of truncated file to be generated. This value should be less than 288 bytes (Image header size)
  190. truncated_bin_size = 180
  191. # check and log bin size
  192. binary_file = os.path.join(dut.app.binary_path, bin_name)
  193. with open(binary_file, 'rb+') as f:
  194. with open(os.path.join(dut.app.binary_path, truncated_bin_name), 'wb+') as fo:
  195. fo.write(f.read(truncated_bin_size))
  196. binary_file = os.path.join(dut.app.binary_path, truncated_bin_name)
  197. # start test
  198. host_ip = get_my_ip()
  199. thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, host_ip, server_port))
  200. thread1.daemon = True
  201. thread1.start()
  202. dut.expect('Loaded app from partition at offset', timeout=30)
  203. try:
  204. ip_address = dut.expect(r' (sta|eth) ip: ([^,]+),', timeout=30)
  205. print('Connected to AP with IP: {}'.format(ip_address))
  206. except pexpect.exceptions.TIMEOUT:
  207. thread1.terminate()
  208. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  209. dut.expect('Starting Advanced OTA example', timeout=30)
  210. print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + truncated_bin_name))
  211. dut.write('https://' + host_ip + ':' + str(server_port) + '/' + truncated_bin_name)
  212. dut.expect('advanced_https_ota_example: esp_https_ota_read_img_desc failed', timeout=30)
  213. try:
  214. os.remove(binary_file)
  215. except OSError:
  216. pass
  217. finally:
  218. thread1.terminate()
  219. @pytest.mark.esp32
  220. @pytest.mark.esp32c3
  221. @pytest.mark.esp32s2
  222. @pytest.mark.esp32s3
  223. @pytest.mark.ethernet_ota
  224. def test_examples_protocol_advanced_https_ota_example_random(dut: Dut) -> None:
  225. """
  226. Working of OTA if random data is added in binary file are validated in this test case.
  227. Magic byte verification should fail in this case.
  228. steps: |
  229. 1. join AP
  230. 2. Generate random binary image
  231. 3. Fetch OTA image over HTTPS
  232. 4. Check working of code for random binary file
  233. """
  234. try:
  235. server_port = 8001
  236. # Random binary file to be generated
  237. random_bin_name = 'random.bin'
  238. # Size of random binary file. 32000 is choosen, to reduce the time required to run the test-case
  239. random_bin_size = 32000
  240. # check and log bin size
  241. binary_file = os.path.join(dut.app.binary_path, random_bin_name)
  242. with open(binary_file, 'wb+') as fo:
  243. # First byte of binary file is always set to zero. If first byte is generated randomly,
  244. # in some cases it may generate 0xE9 which will result in failure of testcase.
  245. fo.write(struct.pack('B', 0))
  246. for i in range(random_bin_size - 1):
  247. fo.write(struct.pack('B', random.randrange(0,255,1)))
  248. # start test
  249. host_ip = get_my_ip()
  250. thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, host_ip, server_port))
  251. thread1.daemon = True
  252. thread1.start()
  253. dut.expect('Loaded app from partition at offset', timeout=30)
  254. try:
  255. ip_address = dut.expect(r' (sta|eth) ip: ([^,]+),', timeout=30)
  256. print('Connected to AP with IP: {}'.format(ip_address))
  257. except pexpect.exceptions.TIMEOUT:
  258. thread1.terminate()
  259. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  260. dut.expect('Starting Advanced OTA example', timeout=30)
  261. print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + random_bin_name))
  262. dut.write('https://' + host_ip + ':' + str(server_port) + '/' + random_bin_name)
  263. dut.expect(r'esp_https_ota: Incorrect app descriptor magic', timeout=10)
  264. try:
  265. os.remove(binary_file)
  266. except OSError:
  267. pass
  268. finally:
  269. thread1.terminate()
  270. @pytest.mark.esp32
  271. @pytest.mark.esp32c3
  272. @pytest.mark.esp32s2
  273. @pytest.mark.esp32s3
  274. @pytest.mark.ethernet_ota
  275. def test_examples_protocol_advanced_https_ota_example_invalid_chip_id(dut: Dut) -> None:
  276. """
  277. Working of OTA if binary file have invalid chip id is validated in this test case.
  278. Chip id verification should fail in this case.
  279. steps: |
  280. 1. join AP
  281. 2. Generate binary image with invalid chip id
  282. 3. Fetch OTA image over HTTPS
  283. 4. Check working of code for random binary file
  284. """
  285. try:
  286. server_port = 8001
  287. bin_name = 'advanced_https_ota.bin'
  288. # Random binary file to be generated
  289. random_bin_name = 'random.bin'
  290. random_binary_file = os.path.join(dut.app.binary_path, random_bin_name)
  291. # Size of random binary file. 2000 is choosen, to reduce the time required to run the test-case
  292. random_bin_size = 2000
  293. binary_file = os.path.join(dut.app.binary_path, bin_name)
  294. with open(binary_file, 'rb+') as f:
  295. data = list(f.read(random_bin_size))
  296. # Changing Chip id
  297. data[13] = 0xfe
  298. with open(random_binary_file, 'wb+') as fo:
  299. fo.write(bytearray(data))
  300. # start test
  301. host_ip = get_my_ip()
  302. thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, host_ip, server_port))
  303. thread1.daemon = True
  304. thread1.start()
  305. dut.expect('Loaded app from partition at offset', timeout=30)
  306. try:
  307. ip_address = dut.expect(r' (sta|eth) ip: ([^,]+),', timeout=30)
  308. print('Connected to AP with IP: {}'.format(ip_address))
  309. except pexpect.exceptions.TIMEOUT:
  310. thread1.terminate()
  311. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  312. dut.expect('Starting Advanced OTA example', timeout=30)
  313. print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + random_bin_name))
  314. dut.write('https://' + host_ip + ':' + str(server_port) + '/' + random_bin_name)
  315. dut.expect(r'esp_https_ota: Mismatch chip id, expected 0, found \d', timeout=10)
  316. try:
  317. os.remove(random_binary_file)
  318. except OSError:
  319. pass
  320. finally:
  321. thread1.terminate()
  322. @pytest.mark.esp32
  323. @pytest.mark.esp32c3
  324. @pytest.mark.esp32s2
  325. @pytest.mark.esp32s3
  326. @pytest.mark.ethernet_ota
  327. def test_examples_protocol_advanced_https_ota_example_chunked(dut: Dut) -> None:
  328. """
  329. This is a positive test case, which downloads complete binary file multiple number of times.
  330. Number of iterations can be specified in variable iterations.
  331. steps: |
  332. 1. join AP
  333. 2. Fetch OTA image over HTTPS
  334. 3. Reboot with the new OTA image
  335. """
  336. # File to be downloaded. This file is generated after compilation
  337. bin_name = 'advanced_https_ota.bin'
  338. # start test
  339. host_ip = get_my_ip()
  340. chunked_server = start_chunked_server(dut.app.binary_path, 8070)
  341. try:
  342. dut.expect('Loaded app from partition at offset', timeout=30)
  343. try:
  344. ip_address = dut.expect(r' (sta|eth) ip: ([^,]+),', timeout=30)
  345. print('Connected to AP with IP: {}'.format(ip_address))
  346. except pexpect.exceptions.TIMEOUT:
  347. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  348. dut.expect('Starting Advanced OTA example', timeout=30)
  349. print('writing to device: {}'.format('https://' + host_ip + ':8070/' + bin_name))
  350. dut.write('https://' + host_ip + ':8070/' + bin_name)
  351. dut.expect('Loaded app from partition at offset', timeout=60)
  352. dut.expect('Starting Advanced OTA example', timeout=30)
  353. finally:
  354. chunked_server.kill()
  355. @pytest.mark.esp32
  356. @pytest.mark.esp32c3
  357. @pytest.mark.esp32s2
  358. @pytest.mark.esp32s3
  359. @pytest.mark.ethernet_ota
  360. def test_examples_protocol_advanced_https_ota_example_redirect_url(dut: Dut) -> None:
  361. """
  362. This is a positive test case, which starts a server and a redirection server.
  363. Redirection server redirects http_request to different port
  364. Number of iterations can be specified in variable iterations.
  365. steps: |
  366. 1. join AP
  367. 2. Fetch OTA image over HTTPS
  368. 3. Reboot with the new OTA image
  369. """
  370. try:
  371. server_port = 8001
  372. # Port to which the request should be redirected
  373. redirection_server_port = 8081
  374. redirection_server_port1 = 8082
  375. # File to be downloaded. This file is generated after compilation
  376. bin_name = 'advanced_https_ota.bin'
  377. # start test
  378. host_ip = get_my_ip()
  379. thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, host_ip, server_port))
  380. thread1.daemon = True
  381. thread1.start()
  382. thread2 = multiprocessing.Process(target=start_redirect_server, args=(dut.app.binary_path, host_ip, redirection_server_port, redirection_server_port1))
  383. thread2.daemon = True
  384. thread2.start()
  385. thread3 = multiprocessing.Process(target=start_redirect_server, args=(dut.app.binary_path, host_ip, redirection_server_port1, server_port))
  386. thread3.daemon = True
  387. thread3.start()
  388. dut.expect('Loaded app from partition at offset', timeout=30)
  389. try:
  390. ip_address = dut.expect(r' (sta|eth) ip: ([^,]+),', timeout=30)
  391. print('Connected to AP with IP: {}'.format(ip_address))
  392. except pexpect.exceptions.TIMEOUT:
  393. thread1.terminate()
  394. thread2.terminate()
  395. thread3.terminate()
  396. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  397. dut.expect('Starting Advanced OTA example', timeout=30)
  398. print('writing to device: {}'.format('https://' + host_ip + ':' + str(redirection_server_port) + '/' + bin_name))
  399. dut.write('https://' + host_ip + ':' + str(redirection_server_port) + '/' + bin_name)
  400. dut.expect('Loaded app from partition at offset', timeout=60)
  401. dut.expect('Starting Advanced OTA example', timeout=30)
  402. finally:
  403. thread1.terminate()
  404. thread2.terminate()
  405. thread3.terminate()
  406. @pytest.mark.esp32
  407. @pytest.mark.esp32c3
  408. @pytest.mark.esp32s2
  409. @pytest.mark.esp32s3
  410. @pytest.mark.ethernet_flash_8m
  411. @pytest.mark.parametrize('config', ['anti_rollback',], indirect=True)
  412. @pytest.mark.parametrize('skip_autoflash', ['y'], indirect=True)
  413. def test_examples_protocol_advanced_https_ota_example_anti_rollback(dut: Dut) -> None:
  414. """
  415. Working of OTA when anti_rollback is enabled and security version of new image is less than current one.
  416. Application should return with error message in this case.
  417. steps: |
  418. 1. join AP
  419. 2. Generate binary file with lower security version
  420. 3. Fetch OTA image over HTTPS
  421. 4. Check working of anti_rollback feature
  422. """
  423. try:
  424. dut.serial.erase_flash()
  425. dut.serial.flash()
  426. server_port = 8001
  427. # Original binary file generated after compilation
  428. bin_name = 'advanced_https_ota.bin'
  429. # Modified firmware image to lower security version in its header. This is to enable negative test case
  430. anti_rollback_bin_name = 'advanced_https_ota_lower_sec_version.bin'
  431. # check and log bin size
  432. binary_file = os.path.join(dut.app.binary_path, bin_name)
  433. file_size = os.path.getsize(binary_file)
  434. with open(binary_file, 'rb+') as f:
  435. with open(os.path.join(dut.app.binary_path, anti_rollback_bin_name), 'wb+') as fo:
  436. fo.write(f.read(file_size))
  437. # Change security_version to 0 for negative test case
  438. fo.seek(36)
  439. fo.write(b'\x00')
  440. binary_file = os.path.join(dut.app.binary_path, anti_rollback_bin_name)
  441. # start test
  442. host_ip = get_my_ip()
  443. thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, host_ip, server_port))
  444. thread1.daemon = True
  445. thread1.start()
  446. # Positive Case
  447. dut.expect('Loaded app from partition at offset', timeout=30)
  448. try:
  449. ip_address = dut.expect(r' (sta|eth) ip: ([^,]+),', timeout=30)
  450. print('Connected to AP with IP: {}'.format(ip_address))
  451. except pexpect.exceptions.TIMEOUT:
  452. thread1.terminate()
  453. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  454. dut.expect('Starting Advanced OTA example', timeout=30)
  455. # Use originally generated image with secure_version=1
  456. print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + bin_name))
  457. dut.write('https://' + host_ip + ':' + str(server_port) + '/' + bin_name)
  458. dut.expect('Loaded app from partition at offset', timeout=60)
  459. dut.expect(r' (sta|eth) ip: ([^,]+),', timeout=30)
  460. dut.expect(r'App is valid, rollback cancelled successfully', timeout=30)
  461. # Negative Case
  462. dut.expect('Starting Advanced OTA example', timeout=30)
  463. # Use modified image with secure_version=0
  464. print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + anti_rollback_bin_name))
  465. dut.write('https://' + host_ip + ':' + str(server_port) + '/' + anti_rollback_bin_name)
  466. dut.expect('New firmware security version is less than eFuse programmed, 0 < 1', timeout=30)
  467. try:
  468. os.remove(binary_file)
  469. except OSError:
  470. pass
  471. finally:
  472. thread1.terminate()
  473. @pytest.mark.esp32
  474. @pytest.mark.esp32c3
  475. @pytest.mark.esp32s2
  476. @pytest.mark.esp32s3
  477. @pytest.mark.ethernet_ota
  478. @pytest.mark.parametrize('config', ['partial_download',], indirect=True)
  479. def test_examples_protocol_advanced_https_ota_example_partial_request(dut: Dut) -> None:
  480. """
  481. This is a positive test case, to test OTA workflow with Range HTTP header.
  482. steps: |
  483. 1. join AP
  484. 2. Fetch OTA image over HTTPS
  485. 3. Reboot with the new OTA image
  486. """
  487. try:
  488. server_port = 8001
  489. # Size of partial HTTP request
  490. request_size = 16384
  491. # File to be downloaded. This file is generated after compilation
  492. bin_name = 'advanced_https_ota.bin'
  493. binary_file = os.path.join(dut.app.binary_path, bin_name)
  494. bin_size = os.path.getsize(binary_file)
  495. http_requests = int((bin_size / request_size) - 1)
  496. # start test
  497. host_ip = get_my_ip()
  498. thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, host_ip, server_port))
  499. thread1.daemon = True
  500. thread1.start()
  501. dut.expect('Loaded app from partition at offset', timeout=30)
  502. try:
  503. ip_address = dut.expect(r' (sta|eth) ip: ([^,]+),', timeout=30)
  504. print('Connected to AP with IP: {}'.format(ip_address))
  505. except pexpect.exceptions.TIMEOUT:
  506. print('ENV_TEST_FAILURE: Cannot connect to AP')
  507. thread1.terminate()
  508. raise
  509. dut.expect('Starting Advanced OTA example', timeout=30)
  510. print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + bin_name))
  511. dut.write('https://' + host_ip + ':' + str(server_port) + '/' + bin_name)
  512. for _ in range(http_requests):
  513. dut.expect('Connection closed', timeout=60)
  514. dut.expect('Loaded app from partition at offset', timeout=60)
  515. dut.expect('Starting Advanced OTA example', timeout=30)
  516. finally:
  517. thread1.terminate()
  518. @pytest.mark.esp32
  519. @pytest.mark.esp32c3
  520. @pytest.mark.esp32s2
  521. @pytest.mark.esp32s3
  522. @pytest.mark.wifi_ota
  523. @pytest.mark.parametrize('config', ['nimble',], indirect=True)
  524. @pytest.mark.xfail(run=False)
  525. def test_examples_protocol_advanced_https_ota_example_nimble_gatts(dut: Dut) -> None:
  526. """
  527. Run an OTA image update while a BLE GATT Server is running in background. This GATT server will be using NimBLE Host stack.
  528. steps: |
  529. 1. join AP
  530. 2. Run BLE advertise and then GATT server.
  531. 3. Fetch OTA image over HTTPS
  532. 4. Reboot with the new OTA image
  533. """
  534. try:
  535. server_port = 8001
  536. # File to be downloaded. This file is generated after compilation
  537. bin_name = 'advanced_https_ota.bin'
  538. # start test
  539. host_ip = get_my_ip()
  540. thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, host_ip, server_port))
  541. thread1.daemon = True
  542. thread1.start()
  543. dut.expect('Loaded app from partition at offset', timeout=30)
  544. try:
  545. ip_address = dut.expect(r' sta ip: ([^,]+),', timeout=30)
  546. print('Connected to AP with IP: {}'.format(ip_address))
  547. except pexpect.exceptions.TIMEOUT:
  548. thread1.terminate()
  549. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  550. dut.expect('Starting Advanced OTA example', timeout=30)
  551. print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + bin_name))
  552. print('Started GAP advertising.')
  553. dut.write('https://' + host_ip + ':' + str(server_port) + '/' + bin_name)
  554. dut.expect('Loaded app from partition at offset', timeout=60)
  555. dut.expect('Starting Advanced OTA example', timeout=30)
  556. finally:
  557. thread1.terminate()
  558. @pytest.mark.esp32
  559. @pytest.mark.esp32c3
  560. @pytest.mark.esp32s2
  561. @pytest.mark.esp32s3
  562. @pytest.mark.wifi_ota
  563. @pytest.mark.parametrize('config', ['bluedroid',], indirect=True)
  564. @pytest.mark.xfail(run=False)
  565. def test_examples_protocol_advanced_https_ota_example_bluedroid_gatts(dut: Dut) -> None:
  566. """
  567. Run an OTA image update while a BLE GATT Server is running in background. This GATT server will be using Bluedroid Host stack.
  568. steps: |
  569. 1. join AP
  570. 2. Run BLE advertise and then GATT server.
  571. 3. Fetch OTA image over HTTPS
  572. 4. Reboot with the new OTA image
  573. """
  574. try:
  575. server_port = 8001
  576. # File to be downloaded. This file is generated after compilation
  577. bin_name = 'advanced_https_ota.bin'
  578. # start test
  579. host_ip = get_my_ip()
  580. thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, host_ip, server_port))
  581. thread1.daemon = True
  582. thread1.start()
  583. dut.expect('Loaded app from partition at offset', timeout=30)
  584. try:
  585. ip_address = dut.expect(r' sta ip: ([^,]+),', timeout=30)
  586. print('Connected to AP with IP: {}'.format(ip_address))
  587. except pexpect.exceptions.TIMEOUT:
  588. thread1.terminate()
  589. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  590. dut.expect('Starting Advanced OTA example', timeout=30)
  591. print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + bin_name))
  592. dut.expect('Started advertising.', timeout=30)
  593. print('Started GAP advertising.')
  594. dut.write('https://' + host_ip + ':' + str(server_port) + '/' + bin_name)
  595. dut.expect('Loaded app from partition at offset', timeout=60)
  596. dut.expect('Starting Advanced OTA example', timeout=30)
  597. finally:
  598. thread1.terminate()
  599. @pytest.mark.esp32
  600. @pytest.mark.esp32c3
  601. @pytest.mark.esp32s2
  602. @pytest.mark.esp32s3
  603. @pytest.mark.ethernet_ota
  604. def test_examples_protocol_advanced_https_ota_example_openssl_aligned_bin(dut: Dut) -> None:
  605. """
  606. This is a test case for esp_http_client_read with binary size multiple of 289 bytes
  607. steps: |
  608. 1. join AP
  609. 2. Fetch OTA image over HTTPS
  610. 3. Reboot with the new OTA image
  611. """
  612. # Original binary file generated after compilation
  613. bin_name = 'advanced_https_ota.bin'
  614. # Binary file aligned to DEFAULT_OTA_BUF_SIZE(289 bytes) boundary
  615. aligned_bin_name = 'aligned.bin'
  616. # check and log bin size
  617. binary_file = os.path.join(dut.app.binary_path, bin_name)
  618. # Original binary size
  619. bin_size = os.path.getsize(binary_file)
  620. # Dummy data required to align binary size to 289 bytes boundary
  621. dummy_data_size = 289 - (bin_size % 289)
  622. with open(binary_file, 'rb+') as f:
  623. with open(os.path.join(dut.app.binary_path, aligned_bin_name), 'wb+') as fo:
  624. fo.write(f.read(bin_size))
  625. for _ in range(dummy_data_size):
  626. fo.write(struct.pack('B', random.randrange(0,255,1)))
  627. # start test
  628. host_ip = get_my_ip()
  629. chunked_server = start_chunked_server(dut.app.binary_path, 8070)
  630. try:
  631. dut.expect('Loaded app from partition at offset', timeout=30)
  632. try:
  633. ip_address = dut.expect(r' (sta|eth) ip: ([^,]+),', timeout=30)
  634. print('Connected to AP with IP: {}'.format(ip_address))
  635. except pexpect.exceptions.TIMEOUT:
  636. raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
  637. dut.expect('Starting Advanced OTA example', timeout=30)
  638. print('writing to device: {}'.format('https://' + host_ip + ':8070/' + aligned_bin_name))
  639. dut.write('https://' + host_ip + ':8070/' + aligned_bin_name)
  640. dut.expect('Loaded app from partition at offset', timeout=60)
  641. dut.expect('Starting Advanced OTA example', timeout=30)
  642. try:
  643. os.remove(aligned_bin_name)
  644. except OSError:
  645. pass
  646. finally:
  647. chunked_server.kill()