summaryrefslogtreecommitdiff
path: root/libfec
diff options
context:
space:
mode:
authorSami Tolvanen <samitolvanen@google.com>2015-06-26 14:28:31 +0100
committerSami Tolvanen <samitolvanen@google.com>2015-09-25 13:06:07 +0100
commitc54a33db7505976a3530aa76ebd5602f12923c4d (patch)
treecf56942b160fa5b443a332cfd86c3814ba4e1f76 /libfec
parent8ed1c5101b2081784dcece041cf47f765896ef58 (diff)
downloadextras-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.mk56
-rw-r--r--libfec/fec_open.cpp563
-rw-r--r--libfec/fec_private.h176
-rw-r--r--libfec/fec_process.cpp137
-rw-r--r--libfec/fec_read.cpp554
-rw-r--r--libfec/fec_verity.cpp604
-rw-r--r--libfec/include/fec/ecc.h70
-rw-r--r--libfec/include/fec/io.h186
-rw-r--r--libfec/test/Android.mk28
-rw-r--r--libfec/test/test_read.cpp71
-rw-r--r--libfec/test/test_rs.c93
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);
+}