需求
OTA(Over-The-Air)升級是一種至關重要的技術,用于更新嵌入式設備的固件或軟件,以確保設備具備最新功能和修復漏洞。在OTA升級過程中,使用差異算法工具(如bsdiff、hdiffpatch和xdelta3)能夠有效減小升級包的大小,從而降低帶寬消耗并提高升級效率。以下是對差異算法在OTA升級中的重要性的更詳細說明:
-
帶寬節省:通過使用差異算法,云端可以提取新舊升級包之間的差異數據,生成相對較小的差異包。這意味著只需傳輸差異包,而不是整個新升級包。這極大地降低了數據傳輸所需的帶寬,尤其對于大型升級包而言。
-
成本降低:減少傳輸數據量不僅有助于帶寬節省,還能減少數據傳輸費用。這對于云端提供OTA服務的成本管理非常關鍵。
-
快速升級:較小的差異包能更快速地下載到設備上,且設備更容易處理和應用差異數據。這減少了升級過程所需的時間,有助于確保用戶設備迅速獲取最新功能和修復。
-
存儲空間節省:設備的存儲空間是有限的,較小的差異包需要更少的存儲空間,對設備而言非常有益。
-
版本控制:差異算法還能輕松進行版本控制,確保設備獲取正確的升級。
升級流程
OTA升級流程:
-
計算差分包:云端根據升級需求,調用差分服務,輸入文件 V1(當前版本)和文件 V2(新版本)來計算差分包 PATCH(差異數據)。hdiffpatch 將生成一個相對較小的 PATCH 差分包,其中包含了新版本相對于當前版本的差異信息。
-
通知設備下載差分包:云端通知設備有可用的升級。設備收到通知后,開始下載差分包 PATCH。
-
應用差分包:設備成功下載差分包 PATCH 后,它使用文件 V1(當前版本)和差分包 PATCH 來還原文件 V2(新版本)。設備內部的 hdiffpatch 工具將應用 PATCH 差異數據到當前版本文件 V1 上,生成新版本文件 V2。這個過程是快速的,因為 PATCH 包是相對較小的,只包含了需要修改的數據。
-
安全性檢查:設備在應用差分包后,可以進行安全性檢查來確保新版本文件 V2 完整和正確。這可以包括校驗文件的哈希值或數字簽名,以防止潛在的數據損壞或惡意修改。
-
更新完成通知:設備在成功應用差分包且通過安全性檢查后,通知云端升級完成。云端記錄設備已完成升級,以便進行版本跟蹤和管理。
通過這一流程,我們能夠充分利用 hdiffpatch 差異算法,將升級包的大小減小,降低了升級過程中的帶寬需求,同時保持了升級的效率。這對于嵌入式設備的OTA升級是一個優化的、可行的方案。
方案設計
技術路線
-
BsDiff:BsDiff(Binary Software Differential)是一種用于生成二進制文件差異(差異數據)的算法。通常用于比較原始文件和新文件,生成差異文件,然后將差異文件應用到原始文件,生成新文件。BsDiff的核心思想是將二進制文件劃分成塊,計算塊之間的差異,然后將這些差異編碼成差異文件。BsDiff算法的目標是生成最小的差異數據,以在生成和應用差異時盡可能減小文件大小并提高效率。它在軟件更新、版本控制等領域非常有用,因為它減小升級文件的大小,降低下載和存儲成本。
-
hdiffpatch:hdiffpatch 是另一個差異生成和應用工具,專門用于減小文件大小,特別是用于固件和軟件的更新。它被設計為高性能、低內存占用的工具,可以生成小差異文件以減小升級包的大小,從而節省帶寬和提高升級效率。hdiffpatch的基本原理是將文件劃分成塊,找到相匹配的塊,計算不匹配塊之間的差異,將差異數據編碼成差異文件,然后使用差異文件來應用差異數據以生成新文件。hdiffpatch強調高性能和低內存占用,適用于嵌入式設備和固件更新等場景。它的主要優勢在于生成較小的差異文件,減小升級包的大小,降低帶寬消耗,以及節省存儲空間。
技術對比
BsDiff 和 hdiffpatch 都是用于生成和應用二進制文件差異的算法,用于減小升級包的大小,從而節省帶寬和降低成本。它們有各自的特點和優勢,下面對它們進行比較:
BsDiff:
-
基本原理:BsDiff 將文件劃分成塊,找到匹配的塊,計算不匹配塊之間的差異,然后編碼差異文件。差異文件可以應用到原始文件上,生成新文件。
-
優勢:
- 生成的差異文件通常較小,節省帶寬。
- 被廣泛用于軟件更新、版本控制等領域。
- 相對成熟的開源實現可供使用。
-
劣勢:
- 生成和應用差異的性能可能不如 hdiffpatch。
- 對于某些文件類型,可能生成較大的差異文件。
hdiffpatch:
-
基本原理:hdiffpatch 也將文件劃分成塊,找到匹配的塊,計算不匹配塊之間的差異,然后編碼差異文件。差異文件可以應用到原始文件上,生成新文件。
-
優勢:
- 高性能,生成和應用差異的速度較快。
- 低內存占用,適合嵌入式設備等資源受限的環境。
- 生成的差異文件通常較小,節省帶寬和存儲空間。
- 支持多平臺,可在不同操作系統上使用。
-
劣勢:
- 可能不如 BsDiff 在某些文件類型上表現出色。
- 開源實現相對較新,可能需要更多的定制和集成工作。
技術選型
考慮到本次方案將面向多種嵌入式設備arm和x86架構,我們決定采用 hdiffpatch 作為差異算法的核心。主要原因如下:
-
通用性:hdiffpatch 是一種通用性較強的差異算法,適用于各種嵌入式設備。這意味著我們可以在不同設備上實現統一的OTA升級方案,而不需要為每種設備單獨開發和維護不同的差異算法。
-
高性能:hdiffpatch 被設計為高性能工具,特別是在生成和應用差異數據時表現出色。對于無人車等對性能要求較高的設備,這一特點尤為重要,因為它可以加速升級過程,降低用戶設備的升級時間。
-
低內存占用:嵌入式設備通常擁有有限的內存資源,hdiffpatch 的低內存占用使其非常適合這些資源受限的環境。這將有助于確保升級過程不會占用過多內存,從而不影響設備的正常運行。
-
小差異文件:hdiffpatch 生成的差異文件通常較小,減小了升級包的大小。這對于需要在有限帶寬下進行OTA升級的設備(例如無人車)非常重要。
-
多平臺支持:hdiffpatch 支持多種操作系統和平臺,這意味著可以在不同嵌入式設備上使用,無論其運行的操作系統是什么。這種靈活性將簡化方案的部署和維護。
-
版本控制:差異算法允許進行版本控制,確保設備得到正確的升級。這在嵌入式設備的OTA升級中至關重要,因為需要確保安全性和正確性。
因此,通過采用 hdiffpatch 差異算法,我們可以實現一種通用、高性能、低內存占用的OTA升級方案,適用于多種嵌入式設備。這將有助于確保設備快速、安全地獲得最新的功能和修復,同時減小了部署和維護成本,提高了升級效率。
基本測試
升級示意圖
下圖展示了通過兩種方式,將圖1升級到圖片2升級的過程
極端情況測試
-
文件完全不一致
在文件完全不一致的情況下,采用2張完全不一樣的圖片進行測試,
1.jpg
和2.jpg
內容完全不一致,普通升級需要傳輸 596KB 數據,差分升級需要傳輸 488KB 數據。文件類型 普通升級 差分升級 1.jpg 629 KB 629 KB 2.jpg 596 KB 0 KB PATCHA文件 0 KB 488 KB -
文件增量
在文件增量情況下,采用2份txt文件進行測試,
2.txt
在1.txt
基礎上進行了字符串增加,普通升級需要傳輸 119KB 數據,差分升級僅需要傳輸 0.11KB 數據。文件類型 普通升級 差分升級 1.txt 11.9 KB 11.9 KB 2.txt 119 KB 0 KB PATCHA文件 0 KB 0.11KB
參考資料
-
項目地址
https://github.com/sisong/HDiffPatch/releases/tag/v4.6.7
-
SDK
linux64
arm64
android
windows64
source code
-
java參考代碼
HDiffPatch 的官方實現是基于 C/C++ 的庫。雖然它沒有官方的 Java 實現,但你可以通過 Java 的 JNI(Java Native Interface)機制來調用 C/C++ 庫中的函數。以下是一個簡單的示例,演示如何使用 JNI 在 Java 中調用 HDiffPatch 的 C/C++ 函數。
首先,需要編寫一個 Java 類,用于加載 HDiffPatch 動態鏈接庫并定義與 C/C++ 函數的映射。以下是一個示例 Java 類:
import java.io.*; import java.nio.file.*; import java.nio.ByteBuffer;public class HDiffPatch {static {System.loadLibrary("hdiff"); // 請根據你的庫名稱進行修改}// JNI 聲明:生成差異數據private static native int createDiff(byte[] oldData, byte[] newData, byte[] diffData);// JNI 聲明:應用差異數據private static native int applyDiff(byte[] oldData, byte[] diffData, byte[] newData);public static void generateDiffData(String oldFilePath, String newFilePath, String diffFilePath) {try {byte[] oldData = Files.readAllBytes(Paths.get(oldFilePath));byte[] newData = Files.readAllBytes(Paths.get(newFilePath));byte[] diffData = new byte[Math.max(oldData.length, newData.length)]; // 設置差異數據的大小int result = createDiff(oldData, newData, diffData);if (result == 0) {Files.write(Paths.get(diffFilePath), diffData);System.out.println("差異數據生成成功!");} else {System.out.println("差異數據生成失敗。");}} catch (IOException e) {e.printStackTrace();}}public static void applyDiffData(String oldFilePath, String diffFilePath, String newFilePath) {try {byte[] oldData = Files.readAllBytes(Paths.get(oldFilePath));byte[] diffData = Files.readAllBytes(Paths.get(diffFilePath));byte[] newData = new byte[oldData.length + diffData.length]; // 設置新文件的最大可能大小int result = applyDiff(oldData, diffData, newData);if (result == 0) {Files.write(Paths.get(newFilePath), newData);System.out.println("差異數據應用成功!");} else {System.out.println("差異數據應用失敗。");}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {String oldFile = "old.bin";String newFile = "new.bin";String diffFile = "diff.bin";String restoredFile = "restored.bin";// 生成差異數據generateDiffData(oldFile, newFile, diffFile);// 應用差異數據applyDiffData(oldFile, diffFile, restoredFile);} }
上述示例代碼假定你已經編譯了 HDiffPatch 的 C/C++ 動態鏈接庫(.so 文件),并將其放在正確的位置。你需要根據你的庫文件名和路徑進行修改。請注意,這是一個簡單的示例,你可能需要根據你的項目需求和環境進行更復雜的設置和錯誤處理。
-
c++參考代碼
下面是一個基本的 C++ 示例,展示如何使用 HDiffPatch 來生成差異數據和應用差異數據以還原文件。請注意,HDiffPatch 需要在項目中添加相關的頭文件和鏈接到庫文件。
#include <iostream> #include <vector> #include "HDiff/diff.h" // 請根據你的項目實際情況包含正確的頭文件int main() {// 原始文件數據std::vector<uint8_t> oldData = {1, 2, 3, 4, 5};// 新文件數據std::vector<uint8_t> newData = {1, 2, 3, 9, 5}; // 更改第四個字節的值// 差異數據存儲std::vector<uint8_t> diffData;// 生成差異數據int result = create_diff(oldData.data(), oldData.size(), newData.data(), newData.size(), diffData);if (result == 0) {// 差異數據生成成功,你可以保存它或傳輸給其他設備std::cout << "差異數據生成成功!" << std::endl;} else {std::cout << "差異數據生成失敗。" << std::endl;return 1;}// 還原新文件std::vector<uint8_t> restoredData;restoredData.resize(oldData.size() + diffData.size());result = apply_diff(oldData.data(), oldData.size(), diffData.data(), diffData.size(), restoredData.data(), restoredData.size());if (result == 0) {// 新文件已還原,你可以保存它std::cout << "新文件還原成功!" << std::endl;} else {std::cout << "新文件還原失敗。" << std::endl;return 1;}return 0; }