pytest_advanced_ota.py 28 KB

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