diff options
author | Sami Tolvanen <samitolvanen@google.com> | 2015-06-26 14:28:31 +0100 |
---|---|---|
committer | Sami Tolvanen <samitolvanen@google.com> | 2015-09-25 13:06:07 +0100 |
commit | c54a33db7505976a3530aa76ebd5602f12923c4d (patch) | |
tree | cf56942b160fa5b443a332cfd86c3814ba4e1f76 /libfec | |
parent | 8ed1c5101b2081784dcece041cf47f765896ef58 (diff) | |
download | extras-c54a33db7505976a3530aa76ebd5602f12923c4d.tar.gz |
Error correction: Add libfec to read encoded data
Add libfec to read files or partitions with error-correcting codes
appended to them. Uses verity metadata to speed up I/O and improve
error correction effectiveness.
Bug: 21893453
Change-Id: I94b95058b084418019fc96595bb6055df36e2c2b
Diffstat (limited to 'libfec')
-rw-r--r-- | libfec/Android.mk | 56 | ||||
-rw-r--r-- | libfec/fec_open.cpp | 563 | ||||
-rw-r--r-- | libfec/fec_private.h | 176 | ||||
-rw-r--r-- | libfec/fec_process.cpp | 137 | ||||
-rw-r--r-- | libfec/fec_read.cpp | 554 | ||||
-rw-r--r-- | libfec/fec_verity.cpp | 604 | ||||
-rw-r--r-- | libfec/include/fec/ecc.h | 70 | ||||
-rw-r--r-- | libfec/include/fec/io.h | 186 | ||||
-rw-r--r-- | libfec/test/Android.mk | 28 | ||||
-rw-r--r-- | libfec/test/test_read.cpp | 71 | ||||
-rw-r--r-- | libfec/test/test_rs.c | 93 |
11 files changed, 2538 insertions, 0 deletions
diff --git a/libfec/Android.mk b/libfec/Android.mk new file mode 100644 index 00000000..cccf6c06 --- /dev/null +++ b/libfec/Android.mk @@ -0,0 +1,56 @@ +# Copyright 2015 The Android Open Source Project +# +LOCAL_PATH := $(call my-dir) + +common_cflags := -Wall -Werror -O3 + +common_c_includes := \ + $(LOCAL_PATH)/include \ + external/fec \ + system/extras/ext4_utils \ + system/extras/squashfs_utils + +common_src_files := \ + fec_open.cpp \ + fec_read.cpp \ + fec_verity.cpp \ + fec_process.cpp + +common_static_libraries := \ + libmincrypt \ + libcrypto_static \ + libcutils \ + libbase + +include $(CLEAR_VARS) +LOCAL_CFLAGS := $(common_cflags) +LOCAL_C_INCLUDES := $(common_c_includes) +LOCAL_CLANG := true +LOCAL_SANITIZE := integer +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +LOCAL_MODULE := libfec +LOCAL_SRC_FILES := $(common_src_files) +LOCAL_STATIC_LIBRARIES := \ + libfec_rs \ + libext4_utils_static \ + libsquashfs_utils \ + libcutils \ + $(common_static_libraries) +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_CFLAGS := $(common_cflags) -D_GNU_SOURCE -DFEC_NO_KLOG +LOCAL_C_INCLUDES := $(common_c_includes) +LOCAL_CLANG := true +LOCAL_SANITIZE := integer +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +LOCAL_MODULE := libfec_host +LOCAL_SRC_FILES := $(common_src_files) +LOCAL_STATIC_LIBRARIES := \ + libfec_rs_host \ + libext4_utils_host \ + libsquashfs_utils_host \ + $(common_static_libraries) +include $(BUILD_HOST_STATIC_LIBRARY) + +include $(LOCAL_PATH)/test/Android.mk diff --git a/libfec/fec_open.cpp b/libfec/fec_open.cpp new file mode 100644 index 00000000..f25aa7fb --- /dev/null +++ b/libfec/fec_open.cpp @@ -0,0 +1,563 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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 <linux/fs.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/stat.h> + +extern "C" { + #include <squashfs_utils.h> + #include <ext4_sb.h> +} + +#include "fec_private.h" + +/* used by `find_offset'; returns metadata size for a file size `size' and + `roots' Reed-Solomon parity bytes */ +using size_func = uint64_t (*)(uint64_t size, int roots); + +/* performs a binary search to find a metadata offset from a file so that + the metadata size matches function `get_real_size(size, roots)', using + the approximate size returned by `get_appr_size' as a starting point */ +static int find_offset(uint64_t file_size, int roots, uint64_t *offset, + size_func get_appr_size, size_func get_real_size) +{ + check(offset); + check(get_appr_size); + check(get_real_size); + + if (file_size % FEC_BLOCKSIZE) { + /* must be a multiple of block size */ + error("file size not multiple of " stringify(FEC_BLOCKSIZE)); + errno = EINVAL; + return -1; + } + + uint64_t mi = get_appr_size(file_size, roots); + uint64_t lo = file_size - mi * 2; + uint64_t hi = file_size - mi / 2; + + while (lo < hi) { + mi = ((hi + lo) / (2 * FEC_BLOCKSIZE)) * FEC_BLOCKSIZE; + uint64_t total = mi + get_real_size(mi, roots); + + if (total < file_size) { + lo = mi + FEC_BLOCKSIZE; + } else if (total > file_size) { + hi = mi; + } else { + *offset = mi; + debug("file_size = %" PRIu64 " -> offset = %" PRIu64, file_size, + mi); + return 0; + } + } + + warn("could not determine offset"); + errno = ERANGE; + return -1; +} + +/* returns verity metadata size for a `size' byte file */ +static uint64_t get_verity_size(uint64_t size, int) +{ + return VERITY_METADATA_SIZE + verity_get_size(size, NULL, NULL); +} + +/* computes the verity metadata offset for a file with size `f->size' */ +static int find_verity_offset(fec_handle *f, uint64_t *offset) +{ + check(f); + check(offset); + + return find_offset(f->data_size, 0, offset, get_verity_size, + get_verity_size); +} + +/* attempts to read and validate an ecc header from file position `offset' */ +static int parse_ecc_header(fec_handle *f, uint64_t offset) +{ + check(f); + check(f->ecc.rsn > 0 && f->ecc.rsn < FEC_RSM); + check(f->size > sizeof(fec_header)); + + debug("offset = %" PRIu64, offset); + + if (offset > f->size - sizeof(fec_header)) { + return -1; + } + + fec_header header; + + /* there's obviously no ecc data at this point, so there is no need to + call fec_pread to access this data */ + if (!raw_pread(f, &header, sizeof(fec_header), offset)) { + error("failed to read: %s", strerror(errno)); + return -1; + } + + if (header.magic != FEC_MAGIC) { + return -1; + } + if (header.version != FEC_VERSION) { + error("unsupported ecc version: %u", header.version); + return -1; + } + if (header.size != sizeof(fec_header)) { + error("unexpected ecc header size: %u", header.size); + return -1; + } + if (header.roots == 0 || header.roots >= FEC_RSM) { + error("invalid ecc roots: %u", header.roots); + return -1; + } + if (f->ecc.roots != (int)header.roots) { + error("unexpected number of roots: %d vs %u", f->ecc.roots, + header.roots); + return -1; + } + if (header.fec_size % header.roots || + header.fec_size % FEC_BLOCKSIZE) { + error("inconsistent ecc size %u", header.fec_size); + return -1; + } + /* structure: data | ecc | header */ + if (offset < header.fec_size || + offset - header.fec_size != header.inp_size) { + error("unexpected input size: %" PRIu64 " vs %" PRIu64, offset, + header.inp_size); + return -1; + } + + f->data_size = header.inp_size; + f->ecc.blocks = fec_div_round_up(f->data_size, FEC_BLOCKSIZE); + f->ecc.rounds = fec_div_round_up(f->ecc.blocks, f->ecc.rsn); + + if (header.fec_size != + (uint32_t)f->ecc.rounds * f->ecc.roots * FEC_BLOCKSIZE) { + error("inconsistent ecc size %u", header.fec_size); + return -1; + } + + f->ecc.size = header.fec_size; + f->ecc.start = header.inp_size; + + /* validate encoding data; caller may opt not to use it if invalid */ + SHA256_CTX ctx; + SHA256_Init(&ctx); + + uint8_t buf[FEC_BLOCKSIZE]; + uint32_t n = 0; + uint32_t len = FEC_BLOCKSIZE; + + while (n < f->ecc.size) { + if (len > f->ecc.size - n) { + len = f->ecc.size - n; + } + + if (!raw_pread(f, buf, len, f->ecc.start + n)) { + error("failed to read ecc: %s", strerror(errno)); + return -1; + } + + SHA256_Update(&ctx, buf, len); + n += len; + } + + uint8_t hash[SHA256_DIGEST_LENGTH]; + SHA256_Final(hash, &ctx); + + f->ecc.valid = !memcmp(hash, header.hash, SHA256_DIGEST_LENGTH); + + if (!f->ecc.valid) { + warn("ecc data not valid"); + } + + return 0; +} + +/* attempts to read an ecc header from `offset', and checks for a backup copy + at the end of the block if the primary header is not valid */ +static int parse_ecc(fec_handle *f, uint64_t offset) +{ + check(f); + check(offset % FEC_BLOCKSIZE == 0); + check(offset < UINT64_MAX - FEC_BLOCKSIZE); + + /* check the primary header at the beginning of the block */ + if (parse_ecc_header(f, offset) == 0) { + return 0; + } + + /* check the backup header at the end of the block */ + if (parse_ecc_header(f, offset + FEC_BLOCKSIZE - sizeof(fec_header)) == 0) { + warn("using backup ecc header"); + return 0; + } + + return -1; +} + +/* reads the squashfs superblock and returns the size of the file system in + `offset' */ +static int get_squashfs_size(fec_handle *f, uint64_t *offset) +{ + check(f); + check(offset); + + size_t sb_size = squashfs_get_sb_size(); + check(sb_size <= SSIZE_MAX); + + uint8_t buffer[sb_size]; + + if (fec_pread(f, buffer, sizeof(buffer), 0) != (ssize_t)sb_size) { + error("failed to read superblock: %s", strerror(errno)); + return -1; + } + + squashfs_info sq; + + if (squashfs_parse_sb_buffer(buffer, &sq) < 0) { + error("failed to parse superblock: %s", strerror(errno)); + return -1; + } + + *offset = sq.bytes_used_4K_padded; + return 0; +} + +/* reads the ext4 superblock and returns the size of the file system in + `offset' */ +static int get_ext4_size(fec_handle *f, uint64_t *offset) +{ + check(f); + check(f->size > 1024 + sizeof(ext4_super_block)); + check(offset); + + ext4_super_block sb; + + if (fec_pread(f, &sb, sizeof(sb), 1024) != sizeof(sb)) { + error("failed to read superblock: %s", strerror(errno)); + return -1; + } + + fs_info info; + info.len = 0; /* only len is set to 0 to ask the device for real size. */ + + if (ext4_parse_sb(&sb, &info) != 0) { + errno = EINVAL; + return -1; + } + + *offset = info.len; + return 0; +} + +/* attempts to determine file system size, if no fs type is specified in + `f->flags', tries all supported types, and returns the size in `offset' */ +static int get_fs_size(fec_handle *f, uint64_t *offset) +{ + check(f); + check(offset); + + if (f->flags & FEC_FS_EXT4) { + return get_ext4_size(f, offset); + } else if (f->flags & FEC_FS_SQUASH) { + return get_squashfs_size(f, offset); + } else { + /* try all alternatives */ + int rc = get_ext4_size(f, offset); + + if (rc == 0) { + debug("found ext4fs"); + return rc; + } + + rc = get_squashfs_size(f, offset); + + if (rc == 0) { + debug("found squashfs"); + } + + return rc; + } +} + +/* locates, validates, and loads verity metadata from `f->fd' */ +static int load_verity(fec_handle *f) +{ + check(f); + debug("size = %" PRIu64 ", flags = %d", f->data_size, f->flags); + + uint64_t offset = f->data_size - VERITY_METADATA_SIZE; + + /* verity header is at the end of the data area */ + if (verity_parse_header(f, offset) == 0) { + debug("found at %" PRIu64 " (start %" PRIu64 ")", offset, + f->verity.hash_start); + return 0; + } + + debug("trying legacy formats"); + + /* legacy format at the end of the partition */ + if (find_verity_offset(f, &offset) == 0 && + verity_parse_header(f, offset) == 0) { + debug("found at %" PRIu64 " (start %" PRIu64 ")", offset, + f->verity.hash_start); + return 0; + } + + /* legacy format after the file system, but not at the end */ + int rc = get_fs_size(f, &offset); + + if (rc == 0) { + debug("file system size = %" PRIu64, offset); + rc = verity_parse_header(f, offset); + + if (rc == 0) { + debug("found at %" PRIu64 " (start %" PRIu64 ")", offset, + f->verity.hash_start); + } + } + + return rc; +} + +/* locates, validates, and loads ecc data from `f->fd' */ +static int load_ecc(fec_handle *f) +{ + check(f); + debug("size = %" PRIu64, f->data_size); + + uint64_t offset = f->data_size - FEC_BLOCKSIZE; + + if (parse_ecc(f, offset) == 0) { + debug("found at %" PRIu64 " (start %" PRIu64 ")", offset, + f->ecc.start); + return 0; + } + + return -1; +} + +/* sets `f->size' to the size of the file or block device */ +static int get_size(fec_handle *f) +{ + check(f); + + struct stat st; + + if (fstat(f->fd, &st) == -1) { + error("fstat failed: %s", strerror(errno)); + return -1; + } + + if (S_ISBLK(st.st_mode)) { + debug("block device"); + + if (ioctl(f->fd, BLKGETSIZE64, &f->size) == -1) { + error("ioctl failed: %s", strerror(errno)); + return -1; + } + } else if (S_ISREG(st.st_mode)) { + debug("file"); + f->size = st.st_size; + } else { + error("unsupported type %d", (int)st.st_mode); + errno = EACCES; + return -1; + } + + return 0; +} + +/* clears fec_handle fiels to safe values */ +static void reset_handle(fec_handle *f) +{ + f->fd = -1; + f->flags = 0; + f->mode = 0; + f->errors = 0; + f->data_size = 0; + f->pos = 0; + f->size = 0; + + memset(&f->ecc, 0, sizeof(f->ecc)); + memset(&f->verity, 0, sizeof(f->verity)); +} + +/* closes and flushes `f->fd' and releases any memory allocated for `f' */ +int fec_close(struct fec_handle *f) +{ + check(f); + + if (f->fd != -1) { + if (f->mode & O_RDWR && fdatasync(f->fd) == -1) { + warn("fdatasync failed: %s", strerror(errno)); + } + + TEMP_FAILURE_RETRY(close(f->fd)); + } + + if (f->verity.hash) { + delete[] f->verity.hash; + } + if (f->verity.salt) { + delete[] f->verity.salt; + } + if (f->verity.table) { + delete[] f->verity.table; + } + + pthread_mutex_destroy(&f->mutex); + + reset_handle(f); + delete f; + + return 0; +} + +/* populates `data' from the internal data in `f', returns a value <0 if verity + metadata is not available in `f->fd' */ +int fec_verity_get_metadata(struct fec_handle *f, struct fec_verity_metadata *data) +{ + check(f); + check(data); + + if (!f->verity.metadata_start) { + return -1; + } + + check(f->data_size < f->size); + check(f->data_size <= f->verity.hash_start); + check(f->data_size <= f->verity.metadata_start); + check(f->verity.table); + + data->disabled = f->verity.disabled; + data->data_size = f->data_size; + memcpy(data->signature, f->verity.header.signature, sizeof(data->signature)); + data->table = f->verity.table; + data->table_length = f->verity.header.length; + + return 0; +} + +/* populates `data' from the internal data in `f', returns a value <0 if ecc + metadata is not available in `f->fd' */ +int fec_ecc_get_metadata(struct fec_handle *f, struct fec_ecc_metadata *data) +{ + check(f); + check(data); + + if (!f->ecc.start) { + return -1; + } + + check(f->data_size < f->size); + check(f->ecc.start >= f->data_size); + check(f->ecc.start < f->size); + check(f->ecc.start % FEC_BLOCKSIZE == 0) + + data->valid = f->ecc.valid; + data->roots = f->ecc.roots; + data->blocks = f->ecc.blocks; + data->rounds = f->ecc.rounds; + data->start = f->ecc.start; + + return 0; +} + +/* populates `data' from the internal status in `f' */ +int fec_get_status(struct fec_handle *f, struct fec_status *s) +{ + check(f); + check(s); + + s->flags = f->flags; + s->mode = f->mode; + s->errors = f->errors; + s->data_size = f->data_size; + s->size = f->size; + + return 0; +} + +/* opens `path' using given options and returns a fec_handle in `handle' if + successful */ +int fec_open(struct fec_handle **handle, const char *path, int mode, int flags, + int roots) +{ + check(path); + check(handle); + check(roots > 0 && roots < FEC_RSM); + + debug("path = %s, mode = %d, flags = %d, roots = %d", path, mode, flags, + roots); + + if (mode & (O_CREAT | O_TRUNC | O_EXCL | O_WRONLY)) { + /* only reading and updating existing files is supported */ + error("failed to open '%s': (unsupported mode %d)", path, mode); + errno = EACCES; + return -1; + } + + fec::handle f(new (std::nothrow) fec_handle, fec_close); + + if (unlikely(!f)) { + error("failed to allocate file handle"); + errno = ENOMEM; + return -1; + } + + reset_handle(f.get()); + + f->mode = mode; + f->ecc.roots = roots; + f->ecc.rsn = FEC_RSM - roots; + f->flags = flags; + + if (unlikely(pthread_mutex_init(&f->mutex, NULL) != 0)) { + error("failed to create a mutex: %s", strerror(errno)); + return -1; + } + + f->fd = TEMP_FAILURE_RETRY(open(path, mode | O_CLOEXEC)); + + if (f->fd == -1) { + error("failed to open '%s': %s", path, strerror(errno)); + return -1; + } + + if (get_size(f.get()) == -1) { + error("failed to get size for '%s': %s", path, strerror(errno)); + return -1; + } + + f->data_size = f->size; /* until ecc and/or verity are loaded */ + + if (load_ecc(f.get()) == -1) { + debug("error-correcting codes not found from '%s'", path); + } + + if (load_verity(f.get()) == -1) { + debug("verity metadata not found from '%s'", path); + } + + *handle = f.release(); + return 0; +} diff --git a/libfec/fec_private.h b/libfec/fec_private.h new file mode 100644 index 00000000..fab81cfe --- /dev/null +++ b/libfec/fec_private.h @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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 __FEC_PRIVATE_H__ +#define __FEC_PRIVATE_H__ + +#include <errno.h> +#include <fcntl.h> +#include <list> +#include <memory> +#include <new> +#include <pthread.h> +#include <stdio.h> +#include <string> +#include <string.h> +#include <sys/syscall.h> +#include <unistd.h> +#include <unordered_map> +#include <vector> + +#include <mincrypt/rsa.h> +#include <openssl/sha.h> +#include <fec/io.h> +#include <fec/ecc.h> + +/* processing parameters */ +#define WORK_MIN_THREADS 1 +#define WORK_MAX_THREADS 64 + +/* verity parameters */ +#define VERITY_CACHE_BLOCKS 4096 +#define VERITY_NO_CACHE UINT64_MAX + +/* verity definitions */ +#define VERITY_METADATA_SIZE (8 * FEC_BLOCKSIZE) +#define VERITY_TABLE_ARGS 10 /* mandatory arguments */ +#define VERITY_MIN_TABLE_SIZE (VERITY_TABLE_ARGS * 2) /* for a sanity check */ +#define VERITY_MAX_TABLE_SIZE (VERITY_METADATA_SIZE - sizeof(verity_header)) + +/* verity header and metadata */ +#define VERITY_MAGIC 0xB001B001 +#define VERITY_MAGIC_DISABLE 0x46464F56 +#define VERITY_VERSION 0 +#define VERITY_TABLE_FIELDS 10 +#define VERITY_TABLE_VERSION 1 + +struct verity_header { + uint32_t magic; + uint32_t version; + uint8_t signature[RSANUMBYTES]; + uint32_t length; +}; + +/* file handle */ +struct ecc_info { + bool valid; + int roots; + int rsn; + uint32_t size; + uint64_t blocks; + uint64_t rounds; + uint64_t start; /* offset in file */ +}; + +struct verity_info { + bool disabled; + char *table; + uint32_t hash_data_blocks; + uint32_t hash_size; + uint64_t hash_data_offset; + uint64_t hash_start; + uint8_t *hash; + uint32_t salt_size; + uint8_t *salt; + uint64_t data_blocks; + uint64_t metadata_start; /* offset in file */ + uint8_t zero_hash[SHA256_DIGEST_LENGTH]; + verity_header header; +}; + +struct verity_block_info { + uint64_t index; + bool valid; +}; + +struct fec_handle { + ecc_info ecc; + int fd; + int flags; /* additional flags passed to fec_open */ + int mode; /* mode for open(2) */ + pthread_mutex_t mutex; + std::list<verity_block_info> lru; + std::unordered_map<uint64_t, std::list<verity_block_info>::iterator> cache; + uint64_t errors; + uint64_t data_size; + uint64_t pos; + uint64_t size; + verity_info verity; +}; + +/* I/O helpers */ +extern bool raw_pread(fec_handle *f, void *buf, size_t count, + uint64_t offset); +extern bool raw_pwrite(fec_handle *f, const void *buf, size_t count, + uint64_t offset); + +/* processing functions */ +typedef ssize_t (*read_func)(fec_handle *f, uint8_t *dest, size_t count, + uint64_t offset, size_t *errors); + +extern ssize_t process(fec_handle *f, uint8_t *buf, size_t count, + uint64_t offset, read_func func); + +/* verity functions */ +extern uint64_t verity_get_size(uint64_t file_size, uint32_t *verity_levels, + uint32_t *level_hashes); + +extern int verity_parse_header(fec_handle *f, uint64_t offset); + +extern bool verity_check_block(fec_handle *f, uint64_t index, + const uint8_t *expected, const uint8_t *block); + +/* helper macros */ +#ifndef unlikely + #define unlikely(x) __builtin_expect(!!(x), 0) + #define likely(x) __builtin_expect(!!(x), 1) +#endif + +#ifndef stringify + #define __stringify(x) #x + #define stringify(x) __stringify(x) +#endif + +/* warnings, errors, debug output */ +#ifdef FEC_NO_KLOG + #define __log(func, type, format, args...) \ + fprintf(stderr, "fec: <%d> " type ": %s: " format "\n", \ + (int)syscall(SYS_gettid), __FUNCTION__, ##args) +#else + #include <cutils/klog.h> + + #define __log(func, type, format, args...) \ + KLOG_##func("fec", "<%d> " type ": %s: " format "\n", \ + (int)syscall(SYS_gettid), __FUNCTION__, ##args) +#endif + +#ifdef NDEBUG + #define debug(format, args...) +#else + #define debug(format, args...) __log(DEBUG, "debug", format, ##args) +#endif + +#define warn(format, args...) __log(WARNING, "warning", format, ##args) +#define error(format, args...) __log(ERROR, "error", format, ##args) + +#define check(p) \ + if (unlikely(!(p))) { \ + error("`%s' failed", #p); \ + errno = EFAULT; \ + return -1; \ + } + +#endif /* __FEC_PRIVATE_H__ */ diff --git a/libfec/fec_process.cpp b/libfec/fec_process.cpp new file mode 100644 index 00000000..3b2846c6 --- /dev/null +++ b/libfec/fec_process.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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 "fec_private.h" + +struct process_info { + int id; + fec_handle *f; + uint8_t *buf; + size_t count; + uint64_t offset; + read_func func; + ssize_t rc; + size_t errors; +}; + +/* thread function */ +static void * __process(void *cookie) +{ + process_info *p = static_cast<process_info *>(cookie); + + debug("thread %d: [%" PRIu64 ", %" PRIu64 ")", p->id, p->offset, + p->offset + p->count); + + p->rc = p->func(p->f, p->buf, p->count, p->offset, &p->errors); + return p; +} + +/* launches a maximum number of threads to process a read */ +ssize_t process(fec_handle *f, uint8_t *buf, size_t count, uint64_t offset, + read_func func) +{ + check(f); + check(buf) + check(func); + + if (count == 0) { + return 0; + } + + int threads = sysconf(_SC_NPROCESSORS_ONLN); + + if (threads < WORK_MIN_THREADS) { + threads = WORK_MIN_THREADS; + } else if (threads > WORK_MAX_THREADS) { + threads = WORK_MAX_THREADS; + } + + uint64_t start = (offset / FEC_BLOCKSIZE) * FEC_BLOCKSIZE; + size_t blocks = fec_div_round_up(count, FEC_BLOCKSIZE); + + if ((size_t)threads > blocks) { + threads = (int)blocks; + } + + size_t count_per_thread = fec_div_round_up(blocks, threads) * FEC_BLOCKSIZE; + size_t left = count; + uint64_t pos = offset; + uint64_t end = start + count_per_thread; + + debug("%d threads, %zu bytes per thread (total %zu)", threads, + count_per_thread, count); + + std::vector<pthread_t> handles; + process_info info[threads]; + ssize_t rc = 0; + + /* start threads to process queue */ + for (int i = 0; i < threads; ++i) { + check(left > 0); + + info[i].id = i; + info[i].f = f; + info[i].buf = &buf[pos - offset]; + info[i].count = (size_t)(end - pos); + info[i].offset = pos; + info[i].func = func; + info[i].rc = -1; + info[i].errors = 0; + + if (info[i].count > left) { + info[i].count = left; + } + + pthread_t thread; + + if (pthread_create(&thread, NULL, __process, &info[i]) != 0) { + error("failed to create thread: %s", strerror(errno)); + rc = -1; + } else { + handles.push_back(thread); + } + + pos = end; + end += count_per_thread; + left -= info[i].count; + } + + check(left == 0); + + ssize_t nread = 0; + + /* wait for all threads to complete */ + for (auto thread : handles) { + process_info *p = NULL; + + if (pthread_join(thread, (void **)&p) != 0) { + error("failed to join thread: %s", strerror(errno)); + rc = -1; + } else if (!p || p->rc == -1) { + rc = -1; + } else { + nread += p->rc; + f->errors += p->errors; + } + } + + if (rc == -1) { + errno = EIO; + return -1; + } + + return nread; +} diff --git a/libfec/fec_read.cpp b/libfec/fec_read.cpp new file mode 100644 index 00000000..68ea410e --- /dev/null +++ b/libfec/fec_read.cpp @@ -0,0 +1,554 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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 <fcntl.h> +#include <stdlib.h> +#include <sys/mman.h> + +extern "C" { + #include <fec.h> +} + +#include "fec_private.h" + +using rs_unique_ptr = std::unique_ptr<void, decltype(&free_rs_char)>; + +/* prints a hexdump of `data' using warn(...) */ +static void dump(const char *name, uint64_t value, const uint8_t *data, + size_t size) +{ + const int bytes_per_line = 16; + char hex[bytes_per_line * 3 + 1]; + char prn[bytes_per_line + 1]; + + warn("%s (%" PRIu64 ") (%zu bytes):", name ? name : "", value, size); + + if (!data) { + warn(" (null)"); + return; + } + + for (size_t n = 0; n < size; n += bytes_per_line) { + memset(hex, 0, sizeof(hex)); + memset(prn, 0, sizeof(prn)); + + for (size_t m = 0; m < bytes_per_line; ++m) { + if (n + m < size) { + sprintf(&hex[m * 3], "%02x ", data[n + m]); + + if (isprint(data[n + m])) { + prn[m] = data[n + m]; + } else { + prn[m] = '.'; + } + } else { + strcpy(&hex[m * 3], " "); + } + } + + warn(" %04zu %s %s", n, hex, prn); + } +} + +/* checks if `offset' is within a corrupted block */ +static inline bool is_erasure(fec_handle *f, uint64_t offset, + const uint8_t *data) +{ + if (unlikely(offset >= f->data_size)) { + return false; + } + + /* ideally, we would like to know if a specific byte on this block has + been corrupted, but knowing whether any of them is can be useful as + well, because often the entire block is corrupted */ + + uint64_t n = offset / FEC_BLOCKSIZE; + + return !verity_check_block(f, n, &f->verity.hash[n * SHA256_DIGEST_LENGTH], + data); +} + +/* check if `offset' is within a block expected to contain zeros */ +static inline bool is_zero(fec_handle *f, uint64_t offset) +{ + verity_info *v = &f->verity; + + if (!v->hash || unlikely(offset >= f->data_size)) { + return false; + } + + uint64_t hash_offset = (offset / FEC_BLOCKSIZE) * SHA256_DIGEST_LENGTH; + + if (unlikely(hash_offset > + v->hash_data_blocks * FEC_BLOCKSIZE - SHA256_DIGEST_LENGTH)) { + return false; + } + + return !memcmp(v->zero_hash, &v->hash[hash_offset], SHA256_DIGEST_LENGTH); +} + +/* reads and decodes a single block starting from `offset', returns the number + of bytes corrected in `errors' */ +static int __ecc_read(fec_handle *f, void *rs, uint8_t *dest, uint64_t offset, + bool use_erasures, uint8_t *ecc_data, size_t *errors) +{ + check(offset % FEC_BLOCKSIZE == 0); + ecc_info *e = &f->ecc; + + /* reverse interleaving: calculate the RS block that includes the requested + offset */ + uint64_t rsb = offset - (offset / (e->rounds * FEC_BLOCKSIZE)) * + e->rounds * FEC_BLOCKSIZE; + int data_index = -1; + int erasures[e->rsn]; + int neras = 0; + + /* verity is required to check for erasures */ + check(!use_erasures || f->verity.hash); + + for (int i = 0; i < e->rsn; ++i) { + uint64_t interleaved = fec_ecc_interleave(rsb * e->rsn + i, e->rsn, + e->rounds); + + if (interleaved == offset) { + data_index = i; + } + + /* copy raw data to reconstruct the RS block */ + uint8_t bbuf[FEC_BLOCKSIZE]; + + if (unlikely(interleaved >= e->start) || + is_zero(f, interleaved)) { + memset(bbuf, 0, FEC_BLOCKSIZE); + } else { + if (!raw_pread(f, bbuf, FEC_BLOCKSIZE, interleaved)) { + error("failed to read: %s", strerror(errno)); + return -1; + } + + if (use_erasures && neras <= e->roots && + is_erasure(f, interleaved, bbuf)) { + erasures[neras++] = i; + } + } + + for (int j = 0; j < FEC_BLOCKSIZE; ++j) { + ecc_data[j * FEC_RSM + i] = bbuf[j]; + } + } + + check(data_index >= 0); + + size_t nerrs = 0; + uint8_t copy[FEC_RSM]; + + for (int i = 0; i < FEC_BLOCKSIZE; ++i) { + /* copy parity data */ + if (!raw_pread(f, &ecc_data[i * FEC_RSM + e->rsn], e->roots, + e->start + (i + rsb) * e->roots)) { + error("failed to read ecc data: %s", strerror(errno)); + return -1; + } + + /* for debugging decoding failures, because decode_rs_char can mangle + ecc_data */ + if (unlikely(use_erasures)) { + memcpy(copy, &ecc_data[i * FEC_RSM], FEC_RSM); + } + + /* decode */ + int rc = decode_rs_char(rs, &ecc_data[i * FEC_RSM], erasures, neras); + + if (unlikely(rc < 0)) { + if (use_erasures) { + error("RS block %" PRIu64 ": decoding failed (%d erasures)", + rsb, neras); + dump("raw RS block", rsb, copy, FEC_RSM); + } else if (!f->verity.hash) { + warn("RS block %" PRIu64 ": decoding failed", rsb); + } else { + debug("RS block %" PRIu64 ": decoding failed", rsb); + } + + errno = EIO; + return -1; + } else if (unlikely(rc > 0)) { + check(rc <= (use_erasures ? e->roots : e->roots / 2)); + nerrs += rc; + } + + dest[i] = ecc_data[i * FEC_RSM + data_index]; + } + + if (nerrs) { + warn("RS block %" PRIu64 ": corrected %zu errors", rsb, nerrs); + *errors += nerrs; + } + + return FEC_BLOCKSIZE; +} + +/* initializes RS decoder and allocates memory for interleaving */ +static int ecc_init(fec_handle *f, rs_unique_ptr& rs, + std::unique_ptr<uint8_t[]>& ecc_data) +{ + check(f); + + rs.reset(init_rs_char(FEC_PARAMS(f->ecc.roots))); + + if (unlikely(!rs)) { + error("failed to initialize RS"); + errno = ENOMEM; + return -1; + } + + ecc_data.reset(new (std::nothrow) uint8_t[FEC_RSM * FEC_BLOCKSIZE]); + + if (unlikely(!ecc_data)) { + error("failed to allocate ecc buffer"); + errno = ENOMEM; + return -1; + } + + return 0; +} + +/* reads `count' bytes from `offset' and corrects possible errors without + erasure detection, returning the number of corrected bytes in `errors' */ +static ssize_t ecc_read(fec_handle *f, uint8_t *dest, size_t count, + uint64_t offset, size_t *errors) +{ + check(f); + check(dest); + check(offset < f->data_size); + check(offset + count <= f->data_size); + check(errors); + + debug("[%" PRIu64 ", %" PRIu64 ")", offset, offset + count); + + rs_unique_ptr rs(NULL, free_rs_char); + std::unique_ptr<uint8_t[]> ecc_data; + + if (ecc_init(f, rs, ecc_data) == -1) { + return -1; + } + + uint64_t curr = offset / FEC_BLOCKSIZE; + size_t coff = (size_t)(offset - curr * FEC_BLOCKSIZE); + size_t left = count; + + uint8_t data[FEC_BLOCKSIZE]; + + while (left > 0) { + /* there's no erasure detection without verity metadata */ + if (__ecc_read(f, rs.get(), data, curr * FEC_BLOCKSIZE, false, + ecc_data.get(), errors) == -1) { + return -1; + } + + size_t copy = FEC_BLOCKSIZE - coff; + + if (copy > left) { + copy = left; + } + + memcpy(dest, &data[coff], copy); + + dest += copy; + left -= copy; + coff = 0; + ++curr; + } + + return count; +} + +/* reads `count' bytes from `offset', corrects possible errors with + erasure detection, and verifies the integrity of read data using + verity hash tree; returns the number of corrections in `errors' */ +static ssize_t verity_read(fec_handle *f, uint8_t *dest, size_t count, + uint64_t offset, size_t *errors) +{ + check(f); + check(dest); + check(offset < f->data_size); + check(offset + count <= f->data_size); + check(f->verity.hash); + check(errors); + + debug("[%" PRIu64 ", %" PRIu64 ")", offset, offset + count); + + rs_unique_ptr rs(NULL, free_rs_char); + std::unique_ptr<uint8_t[]> ecc_data; + + if (f->ecc.start && ecc_init(f, rs, ecc_data) == -1) { + return -1; + } + + uint64_t curr = offset / FEC_BLOCKSIZE; + size_t coff = (size_t)(offset - curr * FEC_BLOCKSIZE); + size_t left = count; + uint8_t data[FEC_BLOCKSIZE]; + + uint64_t max_hash_block = (f->verity.hash_data_blocks * FEC_BLOCKSIZE - + SHA256_DIGEST_LENGTH) / SHA256_DIGEST_LENGTH; + + while (left > 0) { + check(curr <= max_hash_block); + + uint8_t *hash = &f->verity.hash[curr * SHA256_DIGEST_LENGTH]; + uint64_t curr_offset = curr * FEC_BLOCKSIZE; + + bool expect_zeros = is_zero(f, curr_offset); + + /* if we are in read-only mode and expect to read a zero block, + skip reading and just return zeros */ + if (f->mode & O_RDONLY && expect_zeros) { + memset(data, 0, FEC_BLOCKSIZE); + goto valid; + } + + /* copy raw data without error correction */ + if (!raw_pread(f, data, FEC_BLOCKSIZE, curr_offset)) { + error("failed to read: %s", strerror(errno)); + return -1; + } + + if (likely(verity_check_block(f, curr, hash, data))) { + goto valid; + } + + /* we know the block is supposed to contain zeros, so return zeros + instead of trying to correct it */ + if (expect_zeros) { + memset(data, 0, FEC_BLOCKSIZE); + goto corrected; + } + + if (!f->ecc.start) { + /* fatal error without ecc */ + error("[%" PRIu64 ", %" PRIu64 "): corrupted block %" PRIu64, + offset, offset + count, curr); + return -1; + } else { + debug("[%" PRIu64 ", %" PRIu64 "): corrupted block %" PRIu64, + offset, offset + count, curr); + } + + /* try to correct without erasures first, because checking for + erasure locations is slower */ + if (__ecc_read(f, rs.get(), data, curr_offset, false, ecc_data.get(), + errors) == FEC_BLOCKSIZE && + verity_check_block(f, VERITY_NO_CACHE, hash, data)) { + goto corrected; + } + + /* try to correct with erasures */ + if (__ecc_read(f, rs.get(), data, curr_offset, true, ecc_data.get(), + errors) == FEC_BLOCKSIZE && + verity_check_block(f, VERITY_NO_CACHE, hash, data)) { + goto corrected; + } + + error("[%" PRIu64 ", %" PRIu64 "): corrupted block %" PRIu64 + " (offset %" PRIu64 ") cannot be recovered", + offset, offset + count, curr, curr_offset); + dump("decoded block", curr, data, FEC_BLOCKSIZE); + + errno = EIO; + return -1; + +corrected: + /* update the corrected block to the file if we are in r/w mode */ + if (f->mode & O_RDWR && + !raw_pwrite(f, data, FEC_BLOCKSIZE, curr_offset)) { + error("failed to write: %s", strerror(errno)); + return -1; + } + +valid: + size_t copy = FEC_BLOCKSIZE - coff; + + if (copy > left) { + copy = left; + } + + memcpy(dest, &data[coff], copy); + + dest += copy; + left -= copy; + coff = 0; + ++curr; + } + + return count; +} + +/* sets the internal file position to `offset' relative to `whence' */ +int fec_seek(struct fec_handle *f, int64_t offset, int whence) +{ + check(f); + + if (whence == SEEK_SET) { + if (offset < 0) { + errno = EOVERFLOW; + return -1; + } + + f->pos = offset; + } else if (whence == SEEK_CUR) { + if (offset < 0 && f->pos < (uint64_t)-offset) { + errno = EOVERFLOW; + return -1; + } else if (offset > 0 && (uint64_t)offset > UINT64_MAX - f->pos) { + errno = EOVERFLOW; + return -1; + } + + f->pos += offset; + } else if (whence == SEEK_END) { + if (offset >= 0) { + errno = ENXIO; + return -1; + } else if ((uint64_t)-offset > f->size) { + errno = EOVERFLOW; + return -1; + } + + f->pos = f->size + offset; + } else { + errno = EINVAL; + return -1; + } + + return 0; +} + +/* reads up to `count' bytes starting from the internal file position using + error correction and integrity validation, if available */ +ssize_t fec_read(struct fec_handle *f, void *buf, size_t count) +{ + ssize_t rc = fec_pread(f, buf, count, f->pos); + + if (rc > 0) { + check(f->pos < UINT64_MAX - rc); + f->pos += rc; + } + + return rc; +} + +/* for a file with size `max', returns the number of bytes we can read starting + from `offset', up to `count' bytes */ +static inline size_t get_max_count(uint64_t offset, size_t count, uint64_t max) +{ + if (offset >= max) { + return 0; + } else if (offset > max - count) { + return (size_t)(max - offset); + } + + return count; +} + +/* reads `count' bytes from `f->fd' starting from `offset', and copies the + data to `buf' */ +bool raw_pread(fec_handle *f, void *buf, size_t count, uint64_t offset) +{ + check(f); + check(buf); + + uint8_t *p = (uint8_t *)buf; + size_t remaining = count; + + while (remaining > 0) { + ssize_t n = TEMP_FAILURE_RETRY(pread64(f->fd, p, remaining, offset)); + + if (n <= 0) { + return false; + } + + p += n; + remaining -= n; + offset += n; + } + + return true; +} + +/* writes `count' bytes from `buf' to `f->fd' to a file position `offset' */ +bool raw_pwrite(fec_handle *f, const void *buf, size_t count, uint64_t offset) +{ + check(f); + check(buf); + + const uint8_t *p = (const uint8_t *)buf; + size_t remaining = count; + + while (remaining > 0) { + ssize_t n = TEMP_FAILURE_RETRY(pwrite64(f->fd, p, remaining, offset)); + + if (n <= 0) { + return false; + } + + p += n; + remaining -= n; + offset += n; + } + + return true; +} + +/* reads up to `count' bytes starting from `offset' using error correction and + integrity validation, if available */ +ssize_t fec_pread(struct fec_handle *f, void *buf, size_t count, + uint64_t offset) +{ + check(f); + check(buf); + + if (unlikely(offset > UINT64_MAX - count)) { + errno = EOVERFLOW; + return -1; + } + + if (f->verity.hash) { + return process(f, (uint8_t *)buf, + get_max_count(offset, count, f->data_size), offset, + verity_read); + } else if (f->ecc.start) { + check(f->ecc.start < f->size); + + count = get_max_count(offset, count, f->data_size); + ssize_t rc = process(f, (uint8_t *)buf, count, offset, ecc_read); + + if (rc >= 0) { + return rc; + } + + /* return raw data if pure ecc read fails; due to interleaving + the specific blocks the caller wants may still be fine */ + } else { + count = get_max_count(offset, count, f->size); + } + + if (raw_pread(f, buf, count, offset)) { + return count; + } + + return -1; +} diff --git a/libfec/fec_verity.cpp b/libfec/fec_verity.cpp new file mode 100644 index 00000000..eaf56b4b --- /dev/null +++ b/libfec/fec_verity.cpp @@ -0,0 +1,604 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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 <ctype.h> +#include <stdlib.h> +#include <base/strings.h> +#include "fec_private.h" + +/* converts a hex nibble into an int */ +static inline int hextobin(char c) +{ + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } else { + errno = EINVAL; + return -1; + } +} + +/* converts a hex string `src' of `size' characters to binary and copies the + the result into `dst' */ +static int parse_hex(uint8_t *dst, uint32_t size, const char *src) +{ + int l, h; + + check(dst); + check(src); + check(2 * size == strlen(src)); + + while (size) { + h = hextobin(tolower(*src++)); + l = hextobin(tolower(*src++)); + + check(l >= 0); + check(h >= 0); + + *dst++ = (h << 4) | l; + --size; + } + + return 0; +} + +/* parses a 64-bit unsigned integer from string `src' into `dst' and if + `maxval' is >0, checks that `dst' <= `maxval' */ +static int parse_uint64(const char *src, uint64_t maxval, uint64_t *dst) +{ + char *end; + unsigned long long int value; + + check(src); + check(dst); + + errno = 0; + value = strtoull(src, &end, 0); + + if (*src == '\0' || *end != '\0' || + (errno == ERANGE && value == ULLONG_MAX)) { + errno = EINVAL; + return -1; + } + + if (maxval && value > maxval) { + errno = EINVAL; + return -1; + } + + *dst = (uint64_t)value; + return 0; +} + +/* computes the size of verity hash tree for `file_size' bytes and returns the + number of hash tree levels in `verity_levels,' and the number of hashes per + level in `level_hashes', if the parameters are non-NULL */ +uint64_t verity_get_size(uint64_t file_size, uint32_t *verity_levels, + uint32_t *level_hashes) +{ + /* we assume a known metadata size, 4 KiB block size, and SHA-256 to avoid + relying on disk content */ + + uint32_t level = 0; + uint64_t total = 0; + uint64_t hashes = file_size / FEC_BLOCKSIZE; + + do { + if (level_hashes) { + level_hashes[level] = hashes; + } + + hashes = fec_div_round_up(hashes * SHA256_DIGEST_LENGTH, FEC_BLOCKSIZE); + total += hashes; + + ++level; + } while (hashes > 1); + + if (verity_levels) { + *verity_levels = level; + } + + return total * FEC_BLOCKSIZE; +} + +/* computes a SHA-256 salted with `f->verity.salt' from a FEC_BLOCKSIZE byte + buffer `block', and copies the hash to `hash' */ +static inline int verity_hash(fec_handle *f, const uint8_t *block, + uint8_t *hash) +{ + SHA256_CTX ctx; + SHA256_Init(&ctx); + + check(f); + check(f->verity.salt); + SHA256_Update(&ctx, f->verity.salt, f->verity.salt_size); + + check(block); + SHA256_Update(&ctx, block, FEC_BLOCKSIZE); + + check(hash); + SHA256_Final(hash, &ctx); + return 0; +} + +/* computes a verity hash for FEC_BLOCKSIZE bytes from buffer `block' and + compres it to the expected value in `expected'; if `index' has a value + different from `VERITY_NO_CACHE', uses `f->cache' to cache the results */ +bool verity_check_block(fec_handle *f, uint64_t index, const uint8_t *expected, + const uint8_t *block) +{ + check(f); + + if (index != VERITY_NO_CACHE) { + pthread_mutex_lock(&f->mutex); + auto cached = f->cache.find(index); + + if (cached != f->cache.end()) { + verity_block_info vbi = *(cached->second); + + f->lru.erase(cached->second); + f->lru.push_front(vbi); + f->cache[index] = f->lru.begin(); + + pthread_mutex_unlock(&f->mutex); + return vbi.valid; + } + + pthread_mutex_unlock(&f->mutex); + } + + uint8_t hash[SHA256_DIGEST_LENGTH]; + + if (unlikely(verity_hash(f, block, hash) == -1)) { + error("failed to hash"); + return false; + } + + check(expected); + bool valid = !memcmp(expected, hash, SHA256_DIGEST_LENGTH); + + if (index != VERITY_NO_CACHE) { + pthread_mutex_lock(&f->mutex); + + verity_block_info vbi; + vbi.index = index; + vbi.valid = valid; + + if (f->lru.size() >= VERITY_CACHE_BLOCKS) { + f->cache.erase(f->lru.rbegin()->index); + f->lru.pop_back(); + } + + f->lru.push_front(vbi); + f->cache[index] = f->lru.begin(); + pthread_mutex_unlock(&f->mutex); + } + + return valid; +} + +/* reads a verity hash and the corresponding data block using error correction, + if available */ +static bool ecc_read_hashes(fec_handle *f, uint64_t hash_offset, + uint8_t *hash, uint64_t data_offset, uint8_t *data) +{ + check(f); + + if (hash && fec_pread(f, hash, SHA256_DIGEST_LENGTH, hash_offset) != + SHA256_DIGEST_LENGTH) { + error("failed to read hash tree: offset %" PRIu64 ": %s", hash_offset, + strerror(errno)); + return false; + } + + check(data); + + if (fec_pread(f, data, FEC_BLOCKSIZE, data_offset) != FEC_BLOCKSIZE) { + error("failed to read hash tree: data_offset %" PRIu64 ": %s", + data_offset, strerror(errno)); + return false; + } + + return true; +} + +/* reads the verity hash tree, validates it against the root hash in `root', + corrects errors if necessary, and copies valid data blocks for later use + to `f->verity.hash' */ +static int verify_tree(fec_handle *f, const uint8_t *root) +{ + uint8_t data[FEC_BLOCKSIZE]; + uint8_t hash[SHA256_DIGEST_LENGTH]; + + check(f); + check(root); + + verity_info *v = &f->verity; + uint32_t levels = 0; + + /* calculate the size and the number of levels in the hash tree */ + v->hash_size = + verity_get_size(v->data_blocks * FEC_BLOCKSIZE, &levels, NULL); + + check(v->hash_start < UINT64_MAX - v->hash_size); + check(v->hash_start + v->hash_size <= f->data_size); + + uint64_t hash_offset = v->hash_start; + uint64_t data_offset = hash_offset + FEC_BLOCKSIZE; + + v->hash_data_offset = data_offset; + + /* validate the root hash */ + if (!raw_pread(f, data, FEC_BLOCKSIZE, hash_offset) || + !verity_check_block(f, VERITY_NO_CACHE, root, data)) { + /* try to correct */ + if (!ecc_read_hashes(f, 0, NULL, hash_offset, data) || + !verity_check_block(f, VERITY_NO_CACHE, root, data)) { + error("root hash invalid"); + return -1; + } else if (f->mode & O_RDWR && + !raw_pwrite(f, data, FEC_BLOCKSIZE, hash_offset)) { + error("failed to rewrite the root block: %s", strerror(errno)); + return -1; + } + } + + debug("root hash valid"); + + /* calculate the number of hashes on each level */ + uint32_t hashes[levels]; + + verity_get_size(v->data_blocks * FEC_BLOCKSIZE, NULL, hashes); + + /* calculate the size and offset for the data hashes */ + for (uint32_t i = 1; i < levels; ++i) { + uint32_t blocks = hashes[levels - i]; + debug("%u hash blocks on level %u", blocks, levels - i); + + v->hash_data_offset = data_offset; + v->hash_data_blocks = blocks; + + data_offset += blocks * FEC_BLOCKSIZE; + } + + check(v->hash_data_blocks); + check(v->hash_data_blocks <= v->hash_size / FEC_BLOCKSIZE); + + check(v->hash_data_offset); + check(v->hash_data_offset <= + UINT64_MAX - (v->hash_data_blocks * FEC_BLOCKSIZE)); + check(v->hash_data_offset < f->data_size); + check(v->hash_data_offset + v->hash_data_blocks * FEC_BLOCKSIZE <= + f->data_size); + + /* copy data hashes to memory in case they are corrupted, so we don't + have to correct them every time they are needed */ + std::unique_ptr<uint8_t[]> data_hashes( + new (std::nothrow) uint8_t[f->verity.hash_data_blocks * FEC_BLOCKSIZE]); + + if (!data_hashes) { + errno = ENOMEM; + return -1; + } + + /* validate the rest of the hash tree */ + data_offset = hash_offset + FEC_BLOCKSIZE; + + for (uint32_t i = 1; i < levels; ++i) { + uint32_t blocks = hashes[levels - i]; + + for (uint32_t j = 0; j < blocks; ++j) { + /* ecc reads are very I/O intensive, so read raw hash tree and do + error correcting only if it doesn't validate */ + if (!raw_pread(f, hash, SHA256_DIGEST_LENGTH, + hash_offset + j * SHA256_DIGEST_LENGTH) || + !raw_pread(f, data, FEC_BLOCKSIZE, + data_offset + j * FEC_BLOCKSIZE)) { + error("failed to read hashes: %s", strerror(errno)); + return -1; + } + + if (!verity_check_block(f, VERITY_NO_CACHE, hash, data)) { + /* try to correct */ + if (!ecc_read_hashes(f, + hash_offset + j * SHA256_DIGEST_LENGTH, hash, + data_offset + j * FEC_BLOCKSIZE, data) || + !verity_check_block(f, VERITY_NO_CACHE, hash, data)) { + error("invalid hash tree: hash_offset %" PRIu64 ", " + "data_offset %" PRIu64 ", block %u", + hash_offset, data_offset, j); + return -1; + } + + /* update the corrected blocks to the file if we are in r/w + mode */ + if (f->mode & O_RDWR) { + if (!raw_pwrite(f, hash, SHA256_DIGEST_LENGTH, + hash_offset + j * SHA256_DIGEST_LENGTH) || + !raw_pwrite(f, data, FEC_BLOCKSIZE, + data_offset + j * FEC_BLOCKSIZE)) { + error("failed to write hashes: %s", strerror(errno)); + return -1; + } + } + } + + if (blocks == v->hash_data_blocks) { + memcpy(data_hashes.get() + j * FEC_BLOCKSIZE, data, + FEC_BLOCKSIZE); + } + } + + hash_offset = data_offset; + data_offset += blocks * FEC_BLOCKSIZE; + } + + debug("valid"); + + v->hash = data_hashes.release(); + return 0; +} + +/* reads, corrects and parses the verity table, validates parameters, and if + `f->flags' does not have `FEC_VERITY_DISABLE' set, calls `verify_tree' to + load and validate the hash tree */ +static int parse_table(fec_handle *f, uint64_t offset, uint32_t size) +{ + check(f); + check(size >= VERITY_MIN_TABLE_SIZE); + check(size <= VERITY_MAX_TABLE_SIZE); + + debug("offset = %" PRIu64 ", size = %u", offset, size); + + verity_info *v = &f->verity; + std::unique_ptr<char[]> table(new (std::nothrow) char[size + 1]); + + if (!table) { + errno = ENOMEM; + return -1; + } + + if (fec_pread(f, table.get(), size, offset) != (ssize_t)size) { + error("failed to read verity table: %s", strerror(errno)); + return -1; + } + + table[size] = '\0'; + debug("verity table: '%s'", table.get()); + + int i = 0; + std::unique_ptr<uint8_t[]> salt; + uint8_t root[SHA256_DIGEST_LENGTH]; + + auto tokens = android::base::Split(table.get(), " "); + + for (const auto token : tokens) { + switch (i++) { + case 0: /* version */ + if (token != stringify(VERITY_TABLE_VERSION)) { + error("unsupported verity table version: %s", token.c_str()); + return -1; + } + break; + case 3: /* data_block_size */ + case 4: /* hash_block_size */ + /* assume 4 KiB block sizes for everything */ + if (token != stringify(FEC_BLOCKSIZE)) { + error("unsupported verity block size: %s", token.c_str()); + return -1; + } + break; + case 5: /* num_data_blocks */ + if (parse_uint64(token.c_str(), f->data_size / FEC_BLOCKSIZE, + &v->data_blocks) == -1) { + error("invalid number of verity data blocks: %s", + token.c_str()); + return -1; + } + break; + case 6: /* hash_start_block */ + if (parse_uint64(token.c_str(), f->data_size / FEC_BLOCKSIZE, + &v->hash_start) == -1) { + error("invalid verity hash start block: %s", token.c_str()); + return -1; + } + + v->hash_start *= FEC_BLOCKSIZE; + break; + case 7: /* algorithm */ + if (token != "sha256") { + error("unsupported verity hash algorithm: %s", token.c_str()); + return -1; + } + break; + case 8: /* digest */ + if (parse_hex(root, sizeof(root), token.c_str()) == -1) { + error("invalid verity root hash: %s", token.c_str()); + return -1; + } + break; + case 9: /* salt */ + v->salt_size = token.size(); + check(v->salt_size % 2 == 0); + v->salt_size /= 2; + + salt.reset(new (std::nothrow) uint8_t[v->salt_size]); + + if (!salt) { + errno = ENOMEM; + return -1; + } + + if (parse_hex(salt.get(), v->salt_size, token.c_str()) == -1) { + error("invalid verity salt: %s", token.c_str()); + return -1; + } + break; + default: + break; + } + } + + if (i < VERITY_TABLE_ARGS) { + error("not enough arguments in verity table: %d; expected at least " + stringify(VERITY_TABLE_ARGS), i); + return -1; + } + + check(v->hash_start < f->data_size); + + if (v->metadata_start < v->hash_start) { + check(v->data_blocks == v->metadata_start / FEC_BLOCKSIZE); + } else { + check(v->data_blocks == v->hash_start / FEC_BLOCKSIZE); + } + + v->salt = salt.release(); + v->table = table.release(); + + if (!(f->flags & FEC_VERITY_DISABLE)) { + if (verify_tree(f, root) == -1) { + return -1; + } + + check(v->hash); + + uint8_t zero_block[FEC_BLOCKSIZE]; + memset(zero_block, 0, FEC_BLOCKSIZE); + + if (verity_hash(f, zero_block, v->zero_hash) == -1) { + error("failed to hash"); + return -1; + } + } + + return 0; +} + +/* rewrites verity metadata block using error corrected data in `f->verity' */ +static int rewrite_metadata(fec_handle *f, uint64_t offset) +{ + check(f); + check(f->data_size > VERITY_METADATA_SIZE); + check(offset <= f->data_size - VERITY_METADATA_SIZE); + + std::unique_ptr<uint8_t[]> metadata( + new (std::nothrow) uint8_t[VERITY_METADATA_SIZE]); + + if (!metadata) { + errno = ENOMEM; + return -1; + } + + memset(metadata.get(), 0, VERITY_METADATA_SIZE); + + verity_info *v = &f->verity; + memcpy(metadata.get(), &v->header, sizeof(v->header)); + + check(v->table); + size_t len = strlen(v->table); + + check(sizeof(v->header) + len <= VERITY_METADATA_SIZE); + memcpy(metadata.get() + sizeof(v->header), v->table, len); + + return raw_pwrite(f, metadata.get(), VERITY_METADATA_SIZE, offset); +} + +/* attempts to read verity metadata from `f->fd' position `offset'; if in r/w + mode, rewrites the metadata if it had errors */ +int verity_parse_header(fec_handle *f, uint64_t offset) +{ + check(f); + check(f->data_size > VERITY_METADATA_SIZE); + + if (offset > f->data_size - VERITY_METADATA_SIZE) { + debug("failed to read verity header: offset %" PRIu64 " is too far", + offset); + return -1; + } + + verity_info *v = &f->verity; + uint64_t errors = f->errors; + + if (fec_pread(f, &v->header, sizeof(v->header), offset) != + sizeof(v->header)) { + error("failed to read verity header: %s", strerror(errno)); + return -1; + } + + verity_header raw_header; + + if (!raw_pread(f, &raw_header, sizeof(raw_header), offset)) { + error("failed to read verity header: %s", strerror(errno)); + return -1; + } + /* use raw data to check for the alternative magic, because it will + be error corrected to VERITY_MAGIC otherwise */ + if (raw_header.magic == VERITY_MAGIC_DISABLE) { + /* this value is not used by us, but can be used by a caller to + decide whether dm-verity should be enabled */ + v->disabled = true; + } else if (v->header.magic != VERITY_MAGIC) { + return -1; + } + + if (v->header.version != VERITY_VERSION) { + error("unsupported verity version %u", v->header.version); + return -1; + } + + if (v->header.length < VERITY_MIN_TABLE_SIZE || + v->header.length > VERITY_MAX_TABLE_SIZE) { + error("invalid verity table size: %u; expected [" + stringify(VERITY_MIN_TABLE_SIZE) ", " + stringify(VERITY_MAX_TABLE_SIZE) ")", v->header.length); + return -1; + } + + v->metadata_start = offset; + + /* signature is skipped, because for our purposes it won't matter from + where the data originates; the caller of the library is responsible + for signature verification */ + + if (offset > UINT64_MAX - v->header.length) { + error("invalid verity table length: %u", v->header.length); + return -1; + } else if (offset + v->header.length >= f->data_size) { + error("invalid verity table length: %u", v->header.length); + return -1; + } + + if (parse_table(f, offset + sizeof(v->header), v->header.length) == -1) { + return -1; + } + + /* if we corrected something while parsing metadata and we are in r/w + mode, rewrite the corrected metadata */ + if (f->mode & O_RDWR && f->errors > errors && + rewrite_metadata(f, offset) < 0) { + warn("failed to rewrite verity metadata: %s", strerror(errno)); + } + + if (v->metadata_start < v->hash_start) { + f->data_size = v->metadata_start; + } else { + f->data_size = v->hash_start; + } + + return 0; +} diff --git a/libfec/include/fec/ecc.h b/libfec/include/fec/ecc.h new file mode 100644 index 00000000..c0fd9bae --- /dev/null +++ b/libfec/include/fec/ecc.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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 ___FEC_ECC_H___ +#define ___FEC_ECC_H___ + +#include <fec/io.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* ecc parameters */ +#define FEC_RSM 255 + +/* parameters to init_rs_char */ +#define FEC_PARAMS(roots) \ + 8, /* symbol size in bits */ \ + 0x11d, /* field generator polynomial coefficients */ \ + 0, /* first root of the generator */ \ + 1, /* primitive element to generate polynomial roots */ \ + (roots), /* polynomial degree (number of roots) */ \ + 0 /* padding bytes at the front of shortened block */ + +/* computes ceil(x / y) */ +inline uint64_t fec_div_round_up(uint64_t x, uint64_t y) +{ + return (x / y) + (x % y > 0 ? 1 : 0); +} + +/* rounds up x to the nearest multiple of y */ +inline uint64_t fec_round_up(uint64_t x, uint64_t y) +{ + return fec_div_round_up(x, y) * y; +} + +/* returns a physical offset for a byte in an RS block */ +inline uint64_t fec_ecc_interleave(uint64_t offset, int rsn, uint64_t rounds) +{ + return (offset / rsn) + (offset % rsn) * rounds * FEC_BLOCKSIZE; +} + +/* returns the size of ecc data given a file size and the number of roots */ +inline uint64_t fec_ecc_get_size(uint64_t file_size, int roots) +{ + return fec_div_round_up(fec_div_round_up(file_size, FEC_BLOCKSIZE), + FEC_RSM - roots) + * roots * FEC_BLOCKSIZE + + FEC_BLOCKSIZE; +} + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ___FEC_ECC_H___ */ diff --git a/libfec/include/fec/io.h b/libfec/include/fec/io.h new file mode 100644 index 00000000..5a9decb5 --- /dev/null +++ b/libfec/include/fec/io.h @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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 ___FEC_IO_H___ +#define ___FEC_IO_H___ + +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> +#include <mincrypt/rsa.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef SHA256_DIGEST_LENGTH +#define SHA256_DIGEST_LENGTH 32 +#endif + +#define FEC_BLOCKSIZE 4096 +#define FEC_DEFAULT_ROOTS 2 + +#define FEC_MAGIC 0xFECFECFE +#define FEC_VERSION 0 + +/* disk format for the header */ +struct fec_header { + uint32_t magic; + uint32_t version; + uint32_t size; + uint32_t roots; + uint32_t fec_size; + uint64_t inp_size; + uint8_t hash[SHA256_DIGEST_LENGTH]; +}; + +struct fec_status { + int flags; + int mode; + uint64_t errors; + uint64_t data_size; + uint64_t size; +}; + +struct fec_ecc_metadata { + bool valid; + uint32_t roots; + uint64_t blocks; + uint64_t rounds; + uint64_t start; +}; + +struct fec_verity_metadata { + bool disabled; + uint64_t data_size; + uint8_t signature[RSANUMBYTES]; + const char *table; + uint32_t table_length; +}; + +/* flags for fec_open */ +enum { + FEC_FS_EXT4 = 1 << 0, + FEC_FS_SQUASH = 1 << 1, + FEC_VERITY_DISABLE = 1 << 8 +}; + +struct fec_handle; + +/* file access */ +extern int fec_open(struct fec_handle **f, const char *path, int mode, + int flags, int roots); + +extern int fec_close(struct fec_handle *f); + +extern int fec_verity_get_metadata(struct fec_handle *f, + struct fec_verity_metadata *data); + +extern int fec_ecc_get_metadata(struct fec_handle *f, + struct fec_ecc_metadata *data); + +extern int fec_get_status(struct fec_handle *f, struct fec_status *s); + +extern int fec_seek(struct fec_handle *f, int64_t offset, int whence); + +extern ssize_t fec_read(struct fec_handle *f, void *buf, size_t count); + +extern ssize_t fec_pread(struct fec_handle *f, void *buf, size_t count, + uint64_t offset); + +#ifdef __cplusplus +} /* extern "C" */ + +#include <memory> +#include <string> + +/* C++ wrappers for fec_handle and operations */ +namespace fec { + using handle = std::unique_ptr<fec_handle, decltype(&fec_close)>; + + class io { + public: + io() : handle_(nullptr, fec_close) {} + + io(const std::string& fn, int mode = O_RDONLY, int flags = 0, + int roots = FEC_DEFAULT_ROOTS) : handle_(nullptr, fec_close) { + open(fn, mode, flags, roots); + } + + explicit operator bool() const { + return !!handle_; + } + + bool open(const std::string& fn, int mode = O_RDONLY, int flags = 0, + int roots = FEC_DEFAULT_ROOTS) + { + fec_handle *fh = nullptr; + int rc = fec_open(&fh, fn.c_str(), mode, flags, roots); + if (!rc) { + handle_.reset(fh); + } + return !rc; + } + + bool close() { + return !fec_close(handle_.release()); + } + + bool seek(int64_t offset, int whence) { + return !fec_seek(handle_.get(), offset, whence); + } + + ssize_t read(void *buf, size_t count) { + return fec_read(handle_.get(), buf, count); + } + + ssize_t pread(void *buf, size_t count, uint64_t offset) { + return fec_pread(handle_.get(), buf, count, offset); + } + + bool get_status(fec_status& status) { + return !fec_get_status(handle_.get(), &status); + } + + bool get_verity_metadata(fec_verity_metadata& data) { + return !fec_verity_get_metadata(handle_.get(), &data); + } + + bool has_verity() { + fec_verity_metadata data; + return get_verity_metadata(data); + } + + bool get_ecc_metadata(fec_ecc_metadata& data) { + return !fec_ecc_get_metadata(handle_.get(), &data); + } + + bool has_ecc() { + fec_ecc_metadata data; + return get_ecc_metadata(data) && data.valid; + } + + private: + handle handle_; + }; +} +#endif + +#endif /* ___FEC_IO_H___ */ diff --git a/libfec/test/Android.mk b/libfec/test/Android.mk new file mode 100644 index 00000000..4b38310f --- /dev/null +++ b/libfec/test/Android.mk @@ -0,0 +1,28 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_CLANG := true +LOCAL_SANITIZE := integer +LOCAL_MODULE := fec_test_read +LOCAL_SRC_FILES := test_read.cpp +LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_LIBRARIES := \ + libfec_host \ + libfec_rs_host \ + libcrypto_static \ + libext4_utils_host \ + libsquashfs_utils_host \ + libbase +LOCAL_CFLAGS := -Wall -Werror -D_GNU_SOURCE +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_CLANG := true +LOCAL_SANITIZE := integer +LOCAL_MODULE := fec_test_rs +LOCAL_SRC_FILES := test_rs.c +LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_LIBRARIES := libfec_rs_host +LOCAL_CFLAGS := -Wall -Werror -D_GNU_SOURCE +LOCAL_C_INCLUDES += external/fec +include $(BUILD_HOST_EXECUTABLE) diff --git a/libfec/test/test_read.cpp b/libfec/test/test_read.cpp new file mode 100644 index 00000000..060c727d --- /dev/null +++ b/libfec/test/test_read.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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 <new> +#include <memory> +#include <fstream> +#include <iostream> +#include <fec/io.h> + +using namespace std; +const unsigned bufsize = 2 * 1024 * FEC_BLOCKSIZE; + +int main(int argc, char **argv) +{ + if (argc != 3) { + cerr << "usage: " << argv[0] << " input output" << endl; + return 1; + } + + unique_ptr<uint8_t[]> buffer(new (nothrow) uint8_t[bufsize]); + + if (!buffer) { + cerr << "failed to allocate buffer" << endl; + return 1; + } + + fec::io input(argv[1]); + + if (!input) { + return 1; + } + + ofstream output(argv[2], ios::binary | ios::trunc); + + if (!output) { + cerr << "failed to open " << argv[2] << endl; + return 1; + } + + ssize_t count; + + do { + count = input.read(buffer.get(), bufsize); + + if (count == -1) { + return 1; + } else if (count > 0) { + output.write(reinterpret_cast<const char *>(buffer.get()), count); + + if (!output) { + cerr << "write" << endl; + return 1; + } + } + } while (count > 0); + + return 0; +} diff --git a/libfec/test/test_rs.c b/libfec/test/test_rs.c new file mode 100644 index 00000000..61ac12d0 --- /dev/null +++ b/libfec/test/test_rs.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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 <inttypes.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <fec.h> + +#define FEC_RSM 255 +#define FEC_ROOTS 16 +#define FEC_RSN (FEC_RSM - FEC_ROOTS) +#define FEC_PARAMS(roots) \ + 8, 0x11d, 0, 1, (roots), 0 + +int main() +{ + uint8_t data[FEC_RSM]; + uint8_t dupl[FEC_RSM]; + uint8_t corr[FEC_RSM]; + int i, rc, neras, errors; + int erasures[FEC_RSM]; + void *rs; + + memset(data, 0x00, sizeof(data)); + memset(corr, 0x00, sizeof(corr)); + + rs = init_rs_char(FEC_PARAMS(FEC_ROOTS)); + + if (!rs) { + perror("init_rs_char"); + exit(1); + } + + encode_rs_char(rs, data, &corr[FEC_RSN]); + + for (neras = 1; neras <= FEC_ROOTS; ++neras) { + printf("%d errors\n", neras); + + for (i = 0; i < neras; ++i) { + corr[i] = 0xFD; + erasures[i] = i; + } + + memcpy(dupl, corr, sizeof(corr)); + + rc = decode_rs_char(rs, corr, NULL, 0); + + printf("\tno erasures: %d\n", rc); + + errors = 0; + for (i = 0; i < FEC_RSN; ++i) { + if (corr[i] != 0x00) { + printf("\t\terror at %d (%02x)\n", i, corr[i]); + ++errors; + } + } + printf("\t\t%d errors in output\n", errors); + + rc = decode_rs_char(rs, dupl, erasures, neras); + + printf("\terasures: %d\n", rc); + + errors = 0; + for (i = 0; i < FEC_RSN; ++i) { + if (dupl[i] != 0x00) { + printf("\t\terror at %d (%02x)\n", i, dupl[i]); + ++errors; + } + } + printf("\t\t%d errors in output\n", errors); + } + + exit(0); +} |