Add support for ssdiff patches compressed using Brotli algorithm 04/270404/8
authorMateusz Moscicki <m.moscicki2@samsung.com>
Tue, 1 Feb 2022 10:37:09 +0000 (11:37 +0100)
committerMateusz Moscicki <m.moscicki2@partner.samsung.com>
Mon, 21 Feb 2022 14:59:47 +0000 (15:59 +0100)
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

CMakeLists.txt
bsdiff/ss_brotli_patch.c [new file with mode: 0644]
bsdiff/ss_brotli_patch.h [new file with mode: 0644]
packaging/libtota.spec
ss_engine/SS_Common.c
ss_engine/SS_Common.h
ss_engine/SS_PatchDelta.c
ss_engine/SS_UPI.c
ss_engine/SS_UPI.h
ss_engine/ua_types.h

index 325ee98..f63f3c4 100755 (executable)
@@ -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 (file)
index 0000000..dffad65
--- /dev/null
@@ -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 <assert.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <brotli/decode.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#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 (file)
index 0000000..47694b9
--- /dev/null
@@ -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 <unistd.h>
+
+extern int apply_patch_brotli(char *source_file, size_t source_size, char *dest_file, size_t dest_size, char *patch_file);
index 9e34d36..424ed84 100755 (executable)
@@ -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
index 0dc38af..6e31854 100755 (executable)
@@ -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]);
+       }
+}
index 50868b7..6e75a46 100755 (executable)
@@ -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);
index ce78f69..2c85344 100755 (executable)
@@ -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;
+}
index b2baf5e..4c6b19c 100755 (executable)
@@ -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);
index e24c2f5..9e6fa1d 100755 (executable)
@@ -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);
index c7d70b1..859f49d 100755 (executable)
@@ -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;