Add support for ssdiff patches compressed using Brotli algorithm 04/270404/4
authorMateusz Moscicki <m.moscicki2@samsung.com>
Tue, 1 Feb 2022 10:37:09 +0000 (11:37 +0100)
committerMateusz Moscicki <m.moscicki2@partner.samsung.com>
Tue, 1 Feb 2022 15:51:25 +0000 (16:51 +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 325ee98d1bd0981f38613ffe8a8e15df33131b67..f63f3c4047458513a185fd7265e63bcac8314149 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..5b7f81c
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * 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 <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 BUFF_IN_LEN 4096
+#define BUFF_OUT_LEN 4096
+
+const char SSDIFF_MAGIC[] = "SSDIFF40";
+
+struct bs_data {
+    int s_fd, d_fd, p_fd;
+    void *s_ptr, *d_ptr, *p_ptr;
+    size_t s_len, d_len, p_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)
+{
+    munmap(data->s_ptr, data->s_len);
+    munmap(data->p_ptr, data->p_len);
+    munmap(data->d_ptr, data->d_len);
+    close(data->s_fd);
+    close(data->p_fd);
+    close(data->d_fd);
+}
+
+static int open_file(char *file_name, int mode)
+{
+    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)
+{
+    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)
+{
+    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 0;
+    } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
+        LOGE("Invalid source file\n");
+        return 0;
+    }
+
+    return data->decompressed_pos - data->buff_out;
+}
+
+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)
+{
+    data->s_fd = open_file(source_file, O_RDONLY);
+    data->p_fd = open_file(patch_file, O_RDONLY);
+    data->d_fd = open_file(dest_file, O_RDWR);
+    if (data->s_fd < 0 ||
+        data->p_fd < 0 ||
+        data->d_fd < 0) 
+        return PF_ERROR_OPEN_FILE;
+
+    data->s_len = source_size;
+    data->p_len = get_file_len(data->p_fd);
+    data->d_len = dest_size;
+
+    data->s_ptr = mmap(NULL, data->s_len, PROT_READ, MAP_PRIVATE, data->s_fd, 0);
+    if (data->s_ptr == MAP_FAILED) {
+        LOGE("mmap source file error: %m (%d)", errno);
+        return PF_ERROR_MMAP;
+    }
+
+    data->p_ptr = mmap(NULL, data->p_len, PROT_READ, MAP_PRIVATE, data->p_fd, 0);
+    if (data->p_ptr == MAP_FAILED) {
+        LOGE("mmap patch file error: %m (%d)", errno);
+        return PF_ERROR_MMAP;
+    }
+
+    data->d_ptr = mmap(NULL, data->d_len, PROT_WRITE, MAP_SHARED, data->d_fd, 0);
+    if (data->d_ptr == MAP_FAILED) {
+        LOGE("mmap destination error: %m (%d)\n", errno);
+        return PF_ERROR_MMAP;
+    }
+
+    data->compressed_pos = data->p_ptr;
+    data->available_in = data->p_len;
+
+    return PF_OK;
+}
+
+static void init_data(struct bs_data *data)
+{
+    data->s_fd = -1;
+    data->p_fd = -1;
+    data->d_fd = -1;
+    data->s_ptr = NULL;
+    data->d_ptr = NULL;
+    data->p_ptr = NULL;
+    data->s_len = 0;
+    data->d_len = 0;
+    data->p_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)
+{
+    /*
+     * 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 apply_patch_brotli(char *source_file, size_t source_size, char *dest_file, size_t dest_size, char *patch_file)
+{
+    int result = PF_OK;
+    struct bs_data data;
+    init_data(&data);
+
+    if (open_files(&data, source_file, source_size, dest_file, dest_size, patch_file) != PF_OK)
+        goto exit;
+
+    if (decompress_bytes(&data, 0) == 0)
+        goto exit;
+
+    uint8_t *buff_out_pos = data.buff_out;
+
+    if (memcmp(data.buff_out, SSDIFF_MAGIC, sizeof(SSDIFF_MAGIC)) != 0) {
+        LOGE("Invalid patch file\n");
+        goto exit;
+    } else {
+        LOGL(LOG_SSENGINE, "Looks like SSDIFF\n");
+    }
+
+    buff_out_pos += 8;
+
+    if (buff_out_pos + 8 > 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.d_len) {
+        LOGE("Declared target size differs from that read from the patch\n");
+        goto exit;
+    }
+
+    buff_out_pos += 8;
+
+    uint64_t total_write = 0;
+
+    while (total_write < data.d_len) {
+        /*
+         * Make sure we can read the block header
+         */
+        if (buff_out_pos + 4*8 > data.decompressed_pos) {
+            decompress_bytes(&data, data.decompressed_pos - buff_out_pos);
+            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.d_ptr + new_pos;
+        data.src_pos = data.s_ptr + old_pos;
+        /*
+         * Read diff data
+         */
+        int64_t write = 0;
+        while (write < diff_len) { 
+            if (buff_out_pos >= data.decompressed_pos) {
+                decompress_bytes(&data, 0);
+                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) {
+                decompress_bytes(&data, 0);
+                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;
+    }
+
+
+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..1966266
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+#ifndef _SS_BROTLI_PATCH_H
+#define _SS_BROTLI_PATCH_H
+#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);
+#endif
index 9e34d3652a3a33c70b8d0668bbe12099acd1a5d6..424ed84eca6f1f7ed599bd3a39d6c186ea1b790c 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 0dc38afe6146e8c9b7f5c93a1417234e23cbcdea..6f9765c6a564534c29fb9f581dccb198cfe3a045 100755 (executable)
@@ -178,3 +178,10 @@ Cleanup:
        return ulResult;
 }
 
+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]);
+       }
+}
index 50868b746b2d4e58f5eddb062ed0f9971034d5c4..6e75a46d7c1b684196dc092a39d98d55e0679474 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 ce78f696230760eba2fa8659942f5fe229cda33d..2c85344288d48d1b305517fff5c59108f66ed254 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 7237c09cb38977e16e1b5257813992ebadb5b79a..2a7d68b03fef0e45c1f24bafefee17c2a7f36a22 100755 (executable)
@@ -40,6 +40,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;
@@ -95,13 +96,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;
@@ -2318,7 +2312,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
@@ -2336,7 +2330,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 d82c8e64c245545525b6171194b42a0667fbd763..b1bfa231c63552178f469c052984a0100e565f4d 100755 (executable)
@@ -65,6 +65,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 c7d70b1af73fb91754c547fc6304c77473853753..859f49d0a44a567a5386f67ca78174fa1533cd20 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;