4. 差分补丁生成工具开发
任务目标
开发一个PC端Python工具,输入旧固件与新版固件二进制文件,输出差分补丁文件(.patch)。该补丁文件包含版本信息、数据块操作指令(写入/跳过)及新增数据,供后续Bootloader端解析并完成OTA升级。
环境依赖
仅需Python 3.8+标准库,无第三方依赖。工具采用bsdiff风格的分块差分算法,以固定块大小(默认256字节)对比新旧固件,生成紧凑的补丁结构。
核心实现
import struct
import hashlib
import os
import sys
BLOCK_SIZE = 256
MAGIC = b'DFOTA'
VERSION = 1
def generate_patch(old_path, new_path, patch_path):
with open(old_path, 'rb') as f:
old_data = f.read()
with open(new_path, 'rb') as f:
new_data = f.read()
# 对齐到块大小
old_len = (len(old_data) + BLOCK_SIZE - 1) // BLOCK_SIZE * BLOCK_SIZE
new_len = (len(new_data) + BLOCK_SIZE - 1) // BLOCK_SIZE * BLOCK_SIZE
old_data = old_data.ljust(old_len, b'\xff')
new_data = new_data.ljust(new_len, b'\xff')
old_blocks = [old_data[i:i+BLOCK_SIZE] for i in range(0, old_len, BLOCK_SIZE)]
new_blocks = [new_data[i:i+BLOCK_SIZE] for i in range(0, new_len, BLOCK_SIZE)]
# 生成操作指令列表
ops = [] # (block_index, type, data) type: 0=skip, 1=write
max_blocks = max(len(old_blocks), len(new_blocks))
for i in range(max_blocks):
old_blk = old_blocks[i] if i < len(old_blocks) else b'\xff' * BLOCK_SIZE
new_blk = new_blocks[i] if i < len(new_blocks) else b'\xff' * BLOCK_SIZE
if old_blk == new_blk:
ops.append((i, 0, b''))
else:
ops.append((i, 1, new_blk))
# 序列化补丁文件
with open(patch_path, 'wb') as f:
f.write(MAGIC)
f.write(struct.pack('<H', VERSION))
f.write(struct.pack('<I', len(old_data)))
f.write(struct.pack('<I', len(new_data)))
f.write(hashlib.md5(old_data).digest())
f.write(hashlib.md5(new_data).digest())
# 压缩连续skip操作
compressed_ops = []
skip_count = 0
for idx, op_type, data in ops:
if op_type == 0:
skip_count += 1
else:
if skip_count > 0:
compressed_ops.append((0, skip_count, b''))
skip_count = 0
compressed_ops.append((1, 1, data))
if skip_count > 0:
compressed_ops.append((0, skip_count, b''))
f.write(struct.pack('<I', len(compressed_ops)))
for op_type, count, data in compressed_ops:
f.write(struct.pack('<B', op_type))
f.write(struct.pack('<I', count))
if op_type == 1:
f.write(data)
def verify_patch(patch_path, old_path, new_path):
"""验证补丁可正确还原新固件"""
with open(patch_path, 'rb') as f:
assert f.read(5) == MAGIC
ver = struct.unpack('<H', f.read(2))[0]
old_len = struct.unpack('<I', f.read(4))[0]
new_len = struct.unpack('<I', f.read(4))[0]
old_md5 = f.read(16)
new_md5 = f.read(16)
op_count = struct.unpack('<I', f.read(4))[0]
with open(old_path, 'rb') as of:
old_data = of.read().ljust(old_len, b'\xff')
new_data = bytearray(new_len)
pos = 0
for _ in range(op_count):
op_type = struct.unpack('<B', f.read(1))[0]
count = struct.unpack('<I', f.read(4))[0]
if op_type == 0:
pos += count * BLOCK_SIZE
else:
for _ in range(count):
block = f.read(BLOCK_SIZE)
new_data[pos:pos+BLOCK_SIZE] = block
pos += BLOCK_SIZE
new_data = bytes(new_data[:new_len])
assert hashlib.md5(new_data).digest() == new_md5, 'MD5 mismatch'
with open(new_path, 'rb') as nf:
assert new_data == nf.read(), 'Data mismatch'
print('验证通过:补丁可正确还原新固件')
if __name__ == '__main__':
if len(sys.argv) != 4:
print('用法: python diff_patch.py <旧固件> <新固件> <输出补丁>')
sys.exit(1)
generate_patch(sys.argv[1], sys.argv[2], sys.argv[3])
print(f'补丁已生成: {sys.argv[3]}')
# 自动验证
verify_patch(sys.argv[3], sys.argv[1], sys.argv[2])
运行与验证
- 准备两个测试固件文件(如
old.bin和new.bin),其中new.bin相对old.bin有部分字节修改。 - 执行命令:
python diff_patch.py old.bin new.bin upgrade.patch - 程序自动完成补丁生成与回验,输出
验证通过:补丁可正确还原新固件即表示成功。
补丁文件结构:文件头(魔数+版本+长度+MD5)+操作指令序列。操作类型0表示跳过count个块(旧数据不变),类型1表示写入count个新数据块。连续跳过操作已合并压缩,减少补丁体积。
判定标准
- 生成补丁后自动验证通过,无异常输出。
- 补丁文件大小远小于新固件(当新旧固件差异较小时)。
- 使用不同内容的新旧固件重复测试,验证逻辑均通过。