--- /dev/null
+/*
+ * 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;
+}
#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);
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;
+}