Skip to content

Commit a2c1d48

Browse files
committed
Merge branch 'fix/adding_tools_and_patch_script' into 'master'
Added tools folder in ble_ota example See merge request ae_group/esp-iot-solution!1364
2 parents cb40f88 + 835d0d6 commit a2c1d48

File tree

4 files changed

+149
-1
lines changed

4 files changed

+149
-1
lines changed

examples/bluetooth/ble_ota/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,17 @@ pip install -r tools/requirements.txt
145145

146146
To create a patch file, use the [python tool](./tools/esp_delta_ota_patch_gen.py)
147147
```
148-
python esp_delta_ota_patch_gen.py --chip <target> --base_binary <base_binary> --new_binary <new_binary> --patch_file_name <patch_file_name>
148+
python esp_delta_ota_patch_gen.py create_patch --chip <target> --base_binary <base_binary> --new_binary <new_binary> --patch_file_name <patch_file_name>
149149
```
150150

151151
This will generate the patch file for the new binary which needs to be hosted on the OTA update server.
152152

153153
> **_NOTE:_** Make sure that the firmware present in the device is used as `base_binary` while creating the patch file. For this purpose, user should keep backup
154154
of the firmware running in the device as it is required for creating the patch file.
155+
156+
### Verifying Patch
157+
158+
To verify that the patch file is correctly created from the provided base binary and new binary, use the following command:
159+
```
160+
python esp_delta_ota_patch_gen.py verify_patch --chip <target> --base_binary <base_binary> --new_binary <new_binary> --patch_file_name <patch_file_name>
161+
```
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/usr/bin/env python
2+
#
3+
# ESP Delta OTA Patch Generator Tool. This tool helps in generating the compressed patch file
4+
# using BSDiff and Heatshrink algorithms
5+
#
6+
# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
7+
# SPDX-License-Identifier: Apache-2.0
8+
9+
import argparse
10+
import os
11+
import re
12+
import tempfile
13+
import hashlib
14+
import sys
15+
import esptool
16+
17+
try:
18+
import detools
19+
except ImportError:
20+
print("Please install 'detools'. Use command `pip install -r tools/requirements.txt`")
21+
sys.exit(1)
22+
23+
# Magic Byte is created using command: echo -n "esp_delta_ota" | sha256sum
24+
esp_delta_ota_magic = 0xfccdde10
25+
26+
MAGIC_SIZE = 4 # This is the size of the magic byte
27+
DIGEST_SIZE = 32 # This is the SHA256 of the base binary
28+
HEADER_SIZE = 64
29+
RESERVED_HEADER = HEADER_SIZE - (MAGIC_SIZE + DIGEST_SIZE) # This is the reserved header size
30+
31+
def calculate_sha256(file_path: str) -> str:
32+
"""Calculate the SHA-256 hash of a file."""
33+
sha256_hash = hashlib.sha256()
34+
35+
with open(file_path, 'rb') as f:
36+
# Read the file in chunks to avoid memory issues for large files
37+
for byte_block in iter(lambda: f.read(4096), b''):
38+
sha256_hash.update(byte_block)
39+
40+
# Return the hex representation of the hash
41+
return sha256_hash.hexdigest()
42+
43+
def create_patch(chip: str, base_binary: str, new_binary: str, patch_file_name: str) -> None:
44+
command = ['--chip', chip, 'image_info', base_binary]
45+
output = sys.stdout
46+
sys.stdout = tempfile.TemporaryFile(mode='w+')
47+
try:
48+
esptool.main(command)
49+
sys.stdout.seek(0)
50+
content = sys.stdout.read()
51+
except Exception as e:
52+
print(f'Error during esptool command execution: {e}')
53+
finally:
54+
sys.stdout.close()
55+
sys.stdout = output
56+
57+
x = re.search(r'Validation Hash: ([A-Za-z0-9]+) \(valid\)', content)
58+
59+
if x is None:
60+
print('Failed to find validation hash in base binary.')
61+
return
62+
patch_file_without_header = 'patch_file_temp.bin'
63+
try:
64+
with open(base_binary, 'rb') as b_binary, open(new_binary, 'rb') as n_binary, open(patch_file_without_header, 'wb') as p_binary:
65+
detools.create_patch(b_binary, n_binary, p_binary, compression='heatshrink') # b_binary is the base binary, n_binary is the new binary, p_binary is the patch file without header
66+
67+
with open(patch_file_without_header, 'rb') as p_binary, open(patch_file_name, 'wb') as patch_file:
68+
patch_file.write(esp_delta_ota_magic.to_bytes(MAGIC_SIZE, 'little'))
69+
patch_file.write(bytes.fromhex(x[1]))
70+
patch_file.write(bytearray(RESERVED_HEADER))
71+
patch_file.write(p_binary.read())
72+
except Exception as e:
73+
print(f'Error during patch creation: {e}')
74+
finally:
75+
if os.path.exists(patch_file_without_header):
76+
os.remove(patch_file_without_header)
77+
78+
print('Patch created successfully.')
79+
# Verifying the created patch file
80+
verify_patch(base_binary, patch_file_name, new_binary)
81+
82+
# This API applies the patch file over the base_binary file and generates the binary.new file. Then it compares
83+
# the hash of new_binary and binary.new, if they are the same then the verification is successful, otherwise it fails.
84+
def verify_patch(base_binary: str, patch_to_verify: str, new_binary: str) -> None:
85+
86+
with open(patch_to_verify, 'rb') as original_file:
87+
original_file.seek(HEADER_SIZE)
88+
patch_content = original_file.read()
89+
90+
temp_file_name = None
91+
try:
92+
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
93+
temp_file.write(patch_content)
94+
temp_file.flush()
95+
temp_file_name = temp_file.name
96+
97+
detools.apply_patch_filenames(base_binary, temp_file_name, 'binary.new')
98+
except Exception as e:
99+
print(f'Failed to apply patch: {e}')
100+
finally:
101+
if temp_file_name and os.path.exists(temp_file_name):
102+
os.remove(temp_file_name)
103+
104+
sha_of_new_created_binary = calculate_sha256('binary.new')
105+
sha_of_new_binary = calculate_sha256(new_binary)
106+
107+
if sha_of_new_created_binary == sha_of_new_binary:
108+
print('Patch file verified successfully')
109+
else:
110+
print('Failed to verify the patch')
111+
os.remove('binary.new')
112+
113+
def main() -> None:
114+
if len(sys.argv) < 2:
115+
print('Usage: python esp_delta_ota_patch_gen.py create_patch/verify_patch [arguments]')
116+
sys.exit(1)
117+
118+
command = sys.argv[1]
119+
parser = argparse.ArgumentParser('Delta OTA Patch Generator Tool')
120+
121+
if command == 'create_patch':
122+
parser.add_argument('--chip', help='Target', default='esp32')
123+
parser.add_argument('--base_binary', help='Path of Base Binary for creating the patch', required=True)
124+
parser.add_argument('--new_binary', help='Path of New Binary for which patch has to be created', required=True)
125+
parser.add_argument('--patch_file_name', help='Patch file path', default='patch.bin')
126+
args = parser.parse_args(sys.argv[2:])
127+
create_patch(args.chip, args.base_binary, args.new_binary, args.patch_file_name)
128+
elif command == 'verify_patch':
129+
parser.add_argument('--base_binary', help='Path of Base Binary for verifying the patch', required=True)
130+
parser.add_argument('--patch_file_name', help='Patch file path', required=True)
131+
parser.add_argument('--new_binary', help='Path of New Binary for verifying the patch', required=True)
132+
args = parser.parse_args(sys.argv[2:])
133+
verify_patch(args.base_binary, args.patch_file_name, args.new_binary)
134+
else:
135+
print("Invalid command. Use 'create_patch' or 'verify_patch'.")
136+
sys.exit(1)
137+
138+
if __name__ == '__main__':
139+
main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
detools>=0.49.0

tools/ci/executable-list.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ components/utilities/xz/xz-embedded/linux/scripts/xz_wrap.sh
55
docs/check_lang_folder_sync.sh
66
docs/zh_CN/conf.py
77
docs/zh_CN/conf.py
8+
examples/bluetooth/ble_ota/tools/esp_delta_ota_patch_gen.py
89
examples/elf_loader/elf_loader_example/main/test_riscv.elf
910
examples/elf_loader/elf_loader_example/main/test_xtensa.elf
1011
examples/usb/device/bootloader_uf2/utils/redirect.sh

0 commit comments

Comments
 (0)