pytest_advanced_ota.py 30 KB

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