Project import
diff --git a/libfec/Android.mk b/libfec/Android.mk
new file mode 100644
index 0000000..b92dd4e
--- /dev/null
+++ b/libfec/Android.mk
@@ -0,0 +1,58 @@
+# 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 := \
+    libcrypto_utils \
+    libcrypto \
+    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
+ifeq ($(HOST_OS),linux)
+LOCAL_SANITIZE := integer
+endif
+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/NOTICE b/libfec/NOTICE
new file mode 100644
index 0000000..8530865
--- /dev/null
+++ b/libfec/NOTICE
@@ -0,0 +1,190 @@
+
+   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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/libfec/fec_open.cpp b/libfec/fec_open.cpp
new file mode 100644
index 0000000..0e41bf4
--- /dev/null
+++ b/libfec/fec_open.cpp
@@ -0,0 +1,576 @@
+/*
+ * 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 <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+
+extern "C" {
+    #include <squashfs_utils.h>
+    #include <ext4_sb.h>
+}
+
+#if defined(__linux__)
+    #include <linux/fs.h>
+#elif defined(__APPLE__)
+    #include <sys/disk.h>
+    #define BLKGETSIZE64 DKIOCGETBLOCKCOUNT
+    #define fdatasync(fd) fcntl((fd), F_FULLFSYNC)
+#endif
+
+#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;
+    }
+
+    /* move offset back to the beginning of the block for validating header */
+    offset -= offset % FEC_BLOCKSIZE;
+
+    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));
+    memcpy(data->ecc_signature, f->verity.ecc_header.signature,
+        sizeof(data->ecc_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 0000000..0d94228
--- /dev/null
+++ b/libfec/fec_private.h
@@ -0,0 +1,174 @@
+/*
+ * 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 <memory>
+#include <new>
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <vector>
+
+#include <crypto_utils/android_pubkey.h>
+#include <fec/ecc.h>
+#include <fec/io.h>
+#include <openssl/sha.h>
+#include <utils/Compat.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[ANDROID_PUBKEY_MODULUS_SIZE];
+    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;
+    verity_header ecc_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;
+    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, 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 0000000..6e0ddd1
--- /dev/null
+++ b/libfec/fec_process.cpp
@@ -0,0 +1,139 @@
+/*
+ * 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);
+
+    size_t count_per_thread = fec_div_round_up(blocks, threads) * FEC_BLOCKSIZE;
+    size_t max_threads = fec_div_round_up(count, count_per_thread);
+
+    if ((size_t)threads > max_threads) {
+        threads = (int)max_threads;
+    }
+
+    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 0000000..0f5ec99
--- /dev/null
+++ b/libfec/fec_read.cpp
@@ -0,0 +1,557 @@
+/*
+ * 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) {
+                ptrdiff_t offset = &hex[m * 3] - hex;
+                snprintf(hex + offset, sizeof(hex) - offset, "%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, &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;
+        }
+
+        /* to improve our chances of correcting IO errors, initialize the
+           buffer to zeros even if we are going to read to it later */
+        uint8_t bbuf[FEC_BLOCKSIZE] = {0};
+
+        if (likely(interleaved < e->start) && !is_zero(f, interleaved)) {
+            /* copy raw data to reconstruct the RS block */
+            if (!raw_pread(f, bbuf, FEC_BLOCKSIZE, interleaved)) {
+                warn("failed to read: %s", strerror(errno));
+
+                /* treat errors as corruption */
+                if (use_erasures && neras <= e->roots) {
+                    erasures[neras++] = i;
+                }
+            } else 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, 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, 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, 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 0000000..5dea53d
--- /dev/null
+++ b/libfec/fec_verity.cpp
@@ -0,0 +1,647 @@
+/*
+ * 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 <android-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
+   compares it to the expected value in `expected' */
+bool verity_check_block(fec_handle *f, const uint8_t *expected,
+        const uint8_t *block)
+{
+    check(f);
+    check(block);
+
+    uint8_t hash[SHA256_DIGEST_LENGTH];
+
+    if (unlikely(verity_hash(f, block, hash) == -1)) {
+        error("failed to hash");
+        return false;
+    }
+
+    check(expected);
+    return !memcmp(expected, hash, SHA256_DIGEST_LENGTH);
+}
+
+/* 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, root, data)) {
+        /* try to correct */
+        if (!ecc_read_hashes(f, 0, NULL, hash_offset, data) ||
+                !verity_check_block(f, 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, 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, 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);
+}
+
+static int validate_header(const fec_handle *f, const verity_header *header,
+        uint64_t offset)
+{
+    check(f);
+    check(header);
+
+    if (header->magic != VERITY_MAGIC &&
+        header->magic != VERITY_MAGIC_DISABLE) {
+        return -1;
+    }
+
+    if (header->version != VERITY_VERSION) {
+        error("unsupported verity version %u", header->version);
+        return -1;
+    }
+
+    if (header->length < VERITY_MIN_TABLE_SIZE ||
+        header->length > VERITY_MAX_TABLE_SIZE) {
+        error("invalid verity table size: %u; expected ["
+            stringify(VERITY_MIN_TABLE_SIZE) ", "
+            stringify(VERITY_MAX_TABLE_SIZE) ")", header->length);
+        return -1;
+    }
+
+    /* 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 - header->length) {
+        error("invalid verity table length: %u", header->length);
+        return -1;
+    } else if (offset + header->length >= f->data_size) {
+        error("invalid verity table length: %u", header->length);
+        return -1;
+    }
+
+    return 0;
+}
+
+/* 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 (!raw_pread(f, &v->header, sizeof(v->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 (v->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;
+    }
+
+    if (fec_pread(f, &v->ecc_header, sizeof(v->ecc_header), offset) !=
+            sizeof(v->ecc_header)) {
+        warn("failed to read verity header: %s", strerror(errno));
+        return -1;
+    }
+
+    if (validate_header(f, &v->header, offset)) {
+        /* raw verity header is invalid; this could be due to corruption, or
+           due to missing verity metadata */
+
+        if (validate_header(f, &v->ecc_header, offset)) {
+            return -1; /* either way, we cannot recover */
+        }
+
+        /* report mismatching fields */
+        if (!v->disabled && v->header.magic != v->ecc_header.magic) {
+            warn("corrected verity header magic");
+            v->header.magic = v->ecc_header.magic;
+        }
+
+        if (v->header.version != v->ecc_header.version) {
+            warn("corrected verity header version");
+            v->header.version = v->ecc_header.version;
+        }
+
+        if (v->header.length != v->ecc_header.length) {
+            warn("corrected verity header length");
+            v->header.length = v->ecc_header.length;
+        }
+
+        if (memcmp(v->header.signature, v->ecc_header.signature,
+                sizeof(v->header.signature))) {
+            warn("corrected verity header signature");
+            /* we have no way of knowing which signature is correct, if either
+               of them is */
+        }
+    }
+
+    v->metadata_start = offset;
+
+    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;
+}
+
+int fec_verity_set_status(struct fec_handle *f, bool enabled)
+{
+    check(f);
+
+    if (!(f->mode & O_RDWR)) {
+        error("cannot update verity magic: read-only handle");
+        errno = EBADF;
+        return -1;
+    }
+
+    verity_info *v = &f->verity;
+
+    if (!v->metadata_start) {
+        error("cannot update verity magic: no metadata found");
+        errno = EINVAL;
+        return -1;
+    }
+
+    if (v->disabled == !enabled) {
+        return 0; /* nothing to do */
+    }
+
+    uint32_t magic = enabled ? VERITY_MAGIC : VERITY_MAGIC_DISABLE;
+
+    if (!raw_pwrite(f, &magic, sizeof(magic), v->metadata_start)) {
+        error("failed to update verity magic to %08x: %s", magic,
+            strerror(errno));
+        return -1;
+    }
+
+    warn("updated verity magic to %08x (%s)", magic,
+        enabled ? "enabled" : "disabled");
+    v->disabled = !enabled;
+
+    return 0;
+}
diff --git a/libfec/include/fec/ecc.h b/libfec/include/fec/ecc.h
new file mode 100644
index 0000000..c0fd9ba
--- /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 0000000..5e597ac
--- /dev/null
+++ b/libfec/include/fec/io.h
@@ -0,0 +1,194 @@
+/*
+ * 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 <crypto_utils/android_pubkey.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];
+} __attribute__ ((packed));
+
+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[ANDROID_PUBKEY_MODULUS_SIZE];
+    uint8_t ecc_signature[ANDROID_PUBKEY_MODULUS_SIZE];
+    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_set_status(struct fec_handle *f, bool enabled);
+
+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) {}
+
+        explicit 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;
+        }
+
+        bool set_verity_status(bool enabled) {
+            return !fec_verity_set_status(handle_.get(), enabled);
+        }
+
+    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 0000000..fce07cf
--- /dev/null
+++ b/libfec/test/Android.mk
@@ -0,0 +1,33 @@
+LOCAL_PATH:= $(call my-dir)
+
+ifeq ($(HOST_OS),linux)
+
+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_utils \
+    libcrypto \
+    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)
+
+endif # HOST_OS == linux
diff --git a/libfec/test/test_read.cpp b/libfec/test/test_read.cpp
new file mode 100644
index 0000000..060c727
--- /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 0000000..61ac12d
--- /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);
+}