From: Mateusz Moscicki Date: Tue, 1 Feb 2022 10:37:09 +0000 (+0100) Subject: Add support for ssdiff patches compressed using Brotli algorithm X-Git-Tag: accepted/tizen/6.5/unified/20220222.132801~2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=refs%2Fchanges%2F04%2F270404%2F8;p=platform%2Fcore%2Fsystem%2Flibtota.git Add support for ssdiff patches compressed using Brotli algorithm Add new partition upgrade type - DELTA_IMG_AB A patch is a binary diff between two partition that is compressed with the Brotli algorithm. Brotli has a good compression ratio and allows decompression using a small amount of memory (during tests, it did not exceed 17MB). Change-Id: I7f251054f07f7860749dd95a2b90ca018ca5a899 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 325ee98..f63f3c4 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ SET(SRCS ss_engine/fota_log.c ss_engine/fota_tar.c bsdiff/ss_bspatch_common.c + bsdiff/ss_brotli_patch.c ) SET(HEADERS ss_engine/fota_common.h diff --git a/bsdiff/ss_brotli_patch.c b/bsdiff/ss_brotli_patch.c new file mode 100644 index 0000000..dffad65 --- /dev/null +++ b/bsdiff/ss_brotli_patch.c @@ -0,0 +1,347 @@ +/* + * libtota + * + * Copyright (c) 2022 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fota_log.h" + +#define PF_OK 0 +#define PF_ERROR_OPEN_FILE 1 +#define PF_ERROR_MMAP 2 +#define PF_ERROR_INVALID_PATCH_FILE 3 +#define PF_ERROR_DECOMPRESSION 4 + +#define BUFF_IN_LEN 4096 +#define BUFF_OUT_LEN 4096 +#define SSINT_LEN 8 + +const char SSDIFF_MAGIC[] = "SSDIFF40"; + +struct bs_data { + int src_fd, dest_fd, patch_fd; + void *src_ptr, *dest_ptr, *patch_ptr; + size_t src_len, dest_len, patch_len; + unsigned char buff_in[BUFF_IN_LEN]; + unsigned char buff_out[BUFF_IN_LEN]; + uint8_t *dest_pos; + uint8_t *src_pos; + size_t available_in, available_out; + const uint8_t *compressed_pos; + uint8_t *decompressed_pos; + size_t total_size; + BrotliDecoderState *bstate; +}; + +static void free_data(struct bs_data *data) +{ + if (data == NULL) + return; + + if (data->src_ptr) munmap(data->src_ptr, data->src_len); + if (data->dest_ptr) munmap(data->dest_ptr, data->dest_len); + if (data->patch_ptr) munmap(data->patch_ptr, data->patch_len); + + if (data->src_fd) close(data->src_fd); + if (data->patch_fd) close(data->patch_fd); + if (data->dest_fd) close(data->dest_fd); +} + +static int open_file(char *file_name, int mode) +{ + assert(file_name); + int fd = open(file_name, mode, S_IWUSR | S_IRUSR); + if (fd < 0) + LOGE("Open file %s error: %m (%d)\n", file_name, errno); + return fd; +} + +static size_t get_file_len(int fd) +{ + assert(fd >= 0); + size_t result = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + return result; +} + + +static size_t decompress_bytes(struct bs_data *data, size_t keep_offset) +{ + assert(data); + if (keep_offset > 0) { + memcpy(data->buff_out, data->buff_out + sizeof(data->buff_out) - keep_offset, keep_offset); + } + data->decompressed_pos = data->buff_out + keep_offset; + data->available_out = sizeof(data->buff_out) - keep_offset; + + BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + result = BrotliDecoderDecompressStream(data->bstate, + &data->available_in, + &data->compressed_pos, + &data->available_out, + &data->decompressed_pos, + &data->total_size); + + if (result == BROTLI_DECODER_RESULT_ERROR) { + LOGE("Decoder error\n"); + return PF_ERROR_DECOMPRESSION; + } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) { + LOGE("Invalid source file\n"); + return PF_ERROR_DECOMPRESSION; + } + + return PF_OK; +} + +static int open_files(struct bs_data *data, char *source_file, size_t source_size, char *dest_file, size_t dest_size, char *patch_file) +{ + assert(data); + assert(source_file); + assert(dest_file); + assert(patch_file); + + data->src_fd = open_file(source_file, O_RDONLY); + data->patch_fd = open_file(patch_file, O_RDONLY); + data->dest_fd = open_file(dest_file, O_RDWR); + if (data->src_fd < 0 || + data->patch_fd < 0 || + data->dest_fd < 0) + return PF_ERROR_OPEN_FILE; + + data->src_len = source_size; + data->patch_len = get_file_len(data->patch_fd); + data->dest_len = dest_size; + + data->src_ptr = mmap(NULL, data->src_len, PROT_READ, MAP_PRIVATE, data->src_fd, 0); + if (data->src_ptr == MAP_FAILED) { + LOGE("mmap source file error: %m (%d)", errno); + return PF_ERROR_MMAP; + } + + data->patch_ptr = mmap(NULL, data->patch_len, PROT_READ, MAP_PRIVATE, data->patch_fd, 0); + if (data->patch_ptr == MAP_FAILED) { + LOGE("mmap patch file error: %m (%d)", errno); + return PF_ERROR_MMAP; + } + + data->dest_ptr = mmap(NULL, data->dest_len, PROT_WRITE, MAP_SHARED, data->dest_fd, 0); + if (data->dest_ptr == MAP_FAILED) { + LOGE("mmap destination error: %m (%d)\n", errno); + return PF_ERROR_MMAP; + } + + data->compressed_pos = data->patch_ptr; + data->available_in = data->patch_len; + + return PF_OK; +} + +static void init_data(struct bs_data *data) +{ + assert(data); + + data->src_fd = -1; + data->patch_fd = -1; + data->dest_fd = -1; + data->src_ptr = NULL; + data->dest_ptr = NULL; + data->patch_ptr = NULL; + data->src_len = 0; + data->dest_len = 0; + data->patch_len = 0; + data->available_in = 0; + data->compressed_pos = 0; + data->available_out = 0; + data->decompressed_pos = 0; + data->bstate = BrotliDecoderCreateInstance(NULL, NULL, NULL); +} + +static int64_t parse_ssint(unsigned char *buff) +{ + assert(buff); + /* + * From bsdiff 4.0 documentation: + * + * INTEGER type: + * + * offset size data type value + * 0 1 byte x0 + * 1 1 byte x1 + * 2 1 byte x2 + * 3 1 byte x3 + * 4 1 byte x4 + * 5 1 byte x5 + * 6 1 byte x6 + * 7 1 byte x7 + 128 * s + * + * The values x0, x2, x2, x3, x4, x5, x6 are between 0 and 255 (inclusive). + * The value x7 is between 0 and 127 (inclusive). The value s is 0 or 1. + * + * The INTEGER is parsed as: + * (x0 + x1 * 256 + x2 * 256^2 + x3 * 256^3 + x4 * 256^4 + + * x5 * 256^5 + x6 * 256^6 + x7 * 256^7) * (-1)^s + * + * (In other words, an INTEGER is a 64-byte signed integer in sign-magnitude + * format, stored in little-endian byte order.) + */ + int64_t result = *(int64_t*)buff & 0x7fffffff; + if ((buff[7] & 0x80) != 0) + result = -result; + + return result; +} + +int read_header(struct bs_data *data, uint8_t **buff_out_pos) +{ + assert(data); + assert(buff_out_pos); + + *buff_out_pos = data->buff_out; + + if (*buff_out_pos + sizeof(SSDIFF_MAGIC) > data->decompressed_pos || + memcmp(data->buff_out, SSDIFF_MAGIC, sizeof(SSDIFF_MAGIC) - 1) != 0) { + LOGE("Invalid patch file\n"); + return PF_ERROR_INVALID_PATCH_FILE; + } else { + LOGL(LOG_SSENGINE, "Looks like SSDIFF\n"); + } + + *buff_out_pos += sizeof(SSDIFF_MAGIC) - 1; + + if (*buff_out_pos + SSINT_LEN > data->decompressed_pos) { + decompress_bytes(data, data->decompressed_pos - *buff_out_pos); + *buff_out_pos = data->buff_out; + } + + size_t target_size = parse_ssint(*buff_out_pos); + LOGL(LOG_SSENGINE, "target_size: 0x%lx (%ld)\n", target_size, target_size); + + if (target_size != data->dest_len) { + LOGE("Declared target size differs from that read from the patch\n"); + return PF_ERROR_INVALID_PATCH_FILE; + } + + *buff_out_pos += SSINT_LEN; + + return PF_OK; +} + +int apply_patch_brotli(char *source_file, size_t source_size, char *dest_file, size_t dest_size, char *patch_file) +{ + assert(source_file); + assert(dest_file); + assert(patch_file); + + int result; + struct bs_data data; + + init_data(&data); + + if ((result = open_files(&data, source_file, source_size, dest_file, dest_size, patch_file)) != PF_OK) + goto exit; + + if ((result = decompress_bytes(&data, 0)) != PF_OK) + goto exit; + + uint8_t *buff_out_pos; + + if ((result = read_header(&data, &buff_out_pos)) != PF_OK) + goto exit; + + uint64_t total_write = 0; + + while (total_write < data.dest_len) { + /* + * Make sure we can read the block header + */ + if (buff_out_pos + 4*8 > data.decompressed_pos) { + if ((result = decompress_bytes(&data, data.decompressed_pos - buff_out_pos)) != PF_OK) + goto exit; + buff_out_pos = data.buff_out; + } + + /* + * Read the block header + */ + int64_t diff_len = parse_ssint(buff_out_pos+0*8); + int64_t extra_len = parse_ssint(buff_out_pos+1*8); + int64_t old_pos = parse_ssint(buff_out_pos+2*8); + int64_t new_pos = parse_ssint(buff_out_pos+3*8); + buff_out_pos += 4*8; + + /* + * Prepare pointers + */ + data.dest_pos = data.dest_ptr + new_pos; + data.src_pos = data.src_ptr + old_pos; + /* + * Read diff data + */ + int64_t write = 0; + while (write < diff_len) { + if (buff_out_pos >= data.decompressed_pos) { + if ((result = decompress_bytes(&data, 0)) != PF_OK) + goto exit; + buff_out_pos = data.buff_out; + } + while (write < diff_len && buff_out_pos < data.decompressed_pos) { + *data.dest_pos = *(uint8_t*)(data.src_pos) + *(uint8_t*)buff_out_pos; + data.dest_pos++; + data.src_pos++; + buff_out_pos++; + write++; + } + } + total_write += write; + /* + * Read extra data + */ + write = 0; + while (write < extra_len) { + if (buff_out_pos >= data.decompressed_pos) { + if ((result = decompress_bytes(&data, 0)) != PF_OK) + goto exit; + buff_out_pos = data.buff_out; + } + int64_t chunk_size = extra_len - write; + if (buff_out_pos + chunk_size > data.decompressed_pos) { + chunk_size = data.decompressed_pos - buff_out_pos; + } + memcpy(data.dest_pos, buff_out_pos, chunk_size); + data.dest_pos += chunk_size; + buff_out_pos += chunk_size; + write += chunk_size; + } + total_write += write; + } + + result = PF_OK; + +exit: + free_data(&data); + return result; +} diff --git a/bsdiff/ss_brotli_patch.h b/bsdiff/ss_brotli_patch.h new file mode 100644 index 0000000..47694b9 --- /dev/null +++ b/bsdiff/ss_brotli_patch.h @@ -0,0 +1,22 @@ +/* + * libtota + * + * Copyright (c) 2022 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +extern int apply_patch_brotli(char *source_file, size_t source_size, char *dest_file, size_t dest_size, char *patch_file); diff --git a/packaging/libtota.spec b/packaging/libtota.spec index 9e34d36..424ed84 100755 --- a/packaging/libtota.spec +++ b/packaging/libtota.spec @@ -8,6 +8,7 @@ Source0: %{name}-%{version}.tar.gz BuildRequires: cmake BuildRequires: pkgconfig(liblzma-tool) +BuildRequires: libbrotli-devel %description Fota update agent which update firmware using delta files diff --git a/ss_engine/SS_Common.c b/ss_engine/SS_Common.c index 0dc38af..6e31854 100755 --- a/ss_engine/SS_Common.c +++ b/ss_engine/SS_Common.c @@ -178,3 +178,9 @@ Cleanup: return ulResult; } +void hex_digest(char * sha1, char *buffer, int size) +{ + for (int i = 0; i < size; i++) { + snprintf(&buffer[i * 2], (size * 2) - (i * 2) + 1, "%02x", sha1[i]); + } +} diff --git a/ss_engine/SS_Common.h b/ss_engine/SS_Common.h index 50868b7..6e75a46 100755 --- a/ss_engine/SS_Common.h +++ b/ss_engine/SS_Common.h @@ -68,3 +68,4 @@ void SS_unicode_to_char(const char *src, char *dest, int size); #define SHA_DIGEST_SIZE 20 // To avoid creating dependencies on sha1.h #endif int SS_CalculateFileSha(char *filename, long int filesize, unsigned char calculated_sha1[SHA_DIGEST_SIZE]); +void hex_digest(char * sha1, char *buffer, int size); diff --git a/ss_engine/SS_PatchDelta.c b/ss_engine/SS_PatchDelta.c index ce78f69..2c85344 100755 --- a/ss_engine/SS_PatchDelta.c +++ b/ss_engine/SS_PatchDelta.c @@ -31,6 +31,8 @@ #include "SS_PatchDelta.h" #include "fota_common.h" #include "SS_Engine_Errors.h" +#include "ss_brotli_patch.h" +#include "SS_Common.h" extern void *SS_Malloc(unsigned int size); @@ -980,3 +982,52 @@ Cleanup: return result; } + +int SS_UpdateDeltaIMGAB(ua_dataSS_t * ua_dataSS, int (*write_to_blkdev) (char *, int, int, char *)) +{ + int result = S_SS_SUCCESS; + uint8_t target_sha1[SHA_DIGEST_SIZE]; + uint8_t source_sha1[SHA_DIGEST_SIZE]; + uint8_t current_target_sha1[SHA_DIGEST_SIZE]; + + if (ParseSha1(ua_dataSS->update_cfg->target_sha1, target_sha1) != 0) { + LOGE("failed to parse tgt-sha1 \"%s\"\n", ua_dataSS->update_cfg->target_sha1); + return E_SS_FAILURE; + } + + if (ParseSha1(ua_dataSS->update_cfg->soure_sha1, source_sha1) != 0) { + LOGE("failed to parse Src-sha1 \"%s\"\n", ua_dataSS->update_cfg->soure_sha1); + return E_SS_FAILURE; + } + + SS_CalculateFileSha(ua_dataSS->parti_info->ua_blk_name, + ua_dataSS->update_cfg->target_img_size, + current_target_sha1); + + /* source_file.size = ua_dataSS->update_cfg->soure_img_size; */ + /* source_file.data = NULL; */ + if (memcmp(target_sha1, current_target_sha1, SHA_DIGEST_SIZE) == 0) { + LOGL(LOG_SSENGINE, "SS_UpdateDeltaIMGAB - Patch already applied\n"); + return S_SS_SUCCESS; + } + + SS_CalculateFileSha(ua_dataSS->parti_info->ua_blk_name_previous, + ua_dataSS->update_cfg->soure_img_size, + current_target_sha1); + + if (memcmp(source_sha1, current_target_sha1, SHA_DIGEST_SIZE) != 0) { + unsigned char actualShaBuffer[41] = { 0, }; + hex_digest(current_target_sha1, actualShaBuffer, SHA_DIGEST_SIZE); + LOGL(LOG_SSENGINE, "SS_UpdateDeltaIMGAB - Source partition was corrupted. SRC: [%s] Expected [%s] Actual [%s]\n", + ua_dataSS->parti_info->ua_blk_name_previous, ua_dataSS->update_cfg->soure_sha1, actualShaBuffer); + return E_SS_FAILURE; + } + + apply_patch_brotli(ua_dataSS->parti_info->ua_blk_name_previous, + ua_dataSS->update_cfg->soure_img_size, + ua_dataSS->parti_info->ua_blk_name, + ua_dataSS->update_cfg->target_img_size, + SS_PATCHFILE_SOURCE); + + return result; +} diff --git a/ss_engine/SS_UPI.c b/ss_engine/SS_UPI.c index b2baf5e..4c6b19c 100755 --- a/ss_engine/SS_UPI.c +++ b/ss_engine/SS_UPI.c @@ -41,6 +41,7 @@ Function Prototypes Mandatory #include "SS_PatchDelta.h" #include "SS_Engine_Errors.h" #include "SS_FSUpdate.h" +#include "ss_bspatch_common.h" int gtotalFSCnt = 0; int FS_UpgradeState = E_SS_FAILURE; @@ -96,13 +97,6 @@ int SS_Do_Memory_Profiling() } } #endif -static void hex_digest(char * sha1, char *buffer, int size) -{ - int i = 0; - for ( i = 0; i < size; i++){ - snprintf(&buffer[i * 2], (size * 2) - (i * 2) + 1, "%02x", sha1[i]); - } -} #ifdef TIME_PROFILING static char ts1[256]; static double ts2; @@ -2320,7 +2314,7 @@ int SS_IMGUpdatemain(ua_dataSS_t * ua_dataSS, int update_type) //SS_FSUpdatePar if (update_type == FULL_IMG && ua_dataSS->update_data->ua_temp_path) ulResult = SS_MoveFile(SS_PATCHFILE_SOURCE, ua_dataSS->update_data->ua_temp_path); else if ((ua_dataSS->update_cfg->update_type == DELTA_IMG && ua_dataSS->write_data_to_blkdev) - || ua_dataSS->update_cfg->update_type == EXTRA) { + || ua_dataSS->update_cfg->update_type == EXTRA || ua_dataSS->update_cfg->update_type == DELTA_IMG_AB) { FILE *fp = NULL; char buf[14] = { 0, }; //to store zImage-delta magic keyword @@ -2338,7 +2332,9 @@ int SS_IMGUpdatemain(ua_dataSS_t * ua_dataSS, int update_type) //SS_FSUpdatePar LOGL(LOG_SSENGINE, "short read of \"%s\" (%ld bytes of %ld)\n", SS_PATCHFILE_SOURCE, (long)bytes_read, (long)13); fclose(fp); - if (strncmp(buf, SS_KERNEL_MAGIC, sizeof(buf) / sizeof(char)) == 0) + if (update_type == DELTA_IMG_AB) + ulResult = SS_UpdateDeltaIMGAB(ua_dataSS); + else if (strncmp(buf, SS_KERNEL_MAGIC, sizeof(buf) / sizeof(char)) == 0) ulResult = SS_UpdateDeltaKernel(ua_dataSS, ua_dataSS->write_data_to_blkdev); else ulResult = SS_UpdateDeltaIMG(ua_dataSS, ua_dataSS->write_data_to_blkdev); diff --git a/ss_engine/SS_UPI.h b/ss_engine/SS_UPI.h index e24c2f5..9e6fa1d 100755 --- a/ss_engine/SS_UPI.h +++ b/ss_engine/SS_UPI.h @@ -66,6 +66,7 @@ extern int SS_FSUpdatemain(ua_dataSS_t * ua_dataSS, int part_idx); extern int SS_FSVerifyPartition(ua_dataSS_t * ua_dataSS, int part_idx); extern int SS_UpdateDeltaIMG(ua_dataSS_t * ua_dataSS, int (*write_to_blkdev) (char *, int, int, char *)); extern int SS_UpdateDeltaKernel(ua_dataSS_t * ua_dataSS, int (*write_to_blkdev) (char *, int, int, char *)); +extern int SS_UpdateDeltaIMGAB(ua_dataSS_t * ua_dataSS); //extra functions extern void *SS_Malloc(unsigned int size); diff --git a/ss_engine/ua_types.h b/ss_engine/ua_types.h index c7d70b1..859f49d 100755 --- a/ss_engine/ua_types.h +++ b/ss_engine/ua_types.h @@ -36,6 +36,7 @@ typedef enum { FULL_IMG, DELTA_IMG, + DELTA_IMG_AB, DELTA_FS, EXTRA } UA_DATA_FORMAT; @@ -73,6 +74,7 @@ typedef struct _ua_part_info_t { char *ua_parti_name; char *ua_subject_name; char *ua_blk_name; + char *ua_blk_name_previous; int ua_blk_offset; } ua_part_info_t;