upload android base code part6

This commit is contained in:
August 2018-08-08 17:48:24 +08:00
parent 421e214c7d
commit 4e516ec6ed
35396 changed files with 9188716 additions and 0 deletions

View file

@ -0,0 +1,39 @@
// Copyright 2015 The Android Open Source Project
cc_library {
name: "libfec",
host_supported: true,
srcs: [
"fec_open.cpp",
"fec_read.cpp",
"fec_verity.cpp",
"fec_process.cpp",
],
cflags: ["-Wall", "-Werror", "-O3", "-D_LARGEFILE64_SOURCE"],
export_include_dirs: ["include"],
// Exported header include/fec/io.h includes crypto_utils headers.
export_static_lib_headers: ["libcrypto_utils"],
static_libs: [
"libext4_utils",
"libfec_rs",
"libsquashfs_utils",
"libcrypto_utils",
"libcrypto",
"libcutils",
"libbase",
],
target: {
host: {
cflags: ["-D_GNU_SOURCE", "-DFEC_NO_KLOG"]
},
linux: {
sanitize: {
misc_undefined: ["integer"],
},
},
},
clang: true,
}

View file

@ -0,0 +1,5 @@
# Copyright 2015 The Android Open Source Project
#
LOCAL_PATH := $(call my-dir)
include $(LOCAL_PATH)/test/Android.mk

View file

@ -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

View file

@ -0,0 +1,570 @@
/*
* 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>
#include <ext4_utils/ext4_sb.h>
extern "C" {
#include <squashfs_utils.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;
}
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;
}

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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;
}

View file

@ -0,0 +1,671 @@
/*
* 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");
if (v->hash) {
delete[] v->hash;
v->hash = NULL;
}
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, bool useecc)
{
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 (!useecc) {
if (!raw_pread(f, table.get(), size, offset)) {
error("failed to read verity table: %s", strerror(errno));
return -1;
}
} else if (fec_pread(f, table.get(), size, offset) != (ssize_t)size) {
error("failed to ecc 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);
}
if (v->salt) {
delete[] v->salt;
v->salt = NULL;
}
v->salt = salt.release();
if (v->table) {
delete[] v->table;
v->table = NULL;
}
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,
false) == -1 &&
parse_table(f, offset + sizeof(v->header), v->header.length,
true) == -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;
}

View file

@ -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___ */

View file

@ -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___ */

View file

@ -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 \
libfec_rs \
libcrypto_utils \
libcrypto \
libext4_utils \
libsquashfs_utils \
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
LOCAL_CFLAGS := -Wall -Werror -D_GNU_SOURCE
LOCAL_C_INCLUDES += external/fec
include $(BUILD_HOST_EXECUTABLE)
endif # HOST_OS == linux

View file

@ -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;
}

View file

@ -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);
}