You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
412 lines
11 KiB
412 lines
11 KiB
/*
|
|
* 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.
|
|
*/
|
|
|
|
extern "C" {
|
|
#include <fec.h>
|
|
}
|
|
|
|
#undef NDEBUG
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <getopt.h>
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <android-base/file.h>
|
|
#include "image.h"
|
|
|
|
enum {
|
|
MODE_ENCODE,
|
|
MODE_DECODE,
|
|
MODE_PRINTSIZE,
|
|
MODE_GETECCSTART,
|
|
MODE_GETVERITYSTART
|
|
};
|
|
|
|
static void encode_rs(struct image_proc_ctx *ctx)
|
|
{
|
|
struct image *fcx = ctx->ctx;
|
|
int j;
|
|
uint8_t data[fcx->rs_n];
|
|
uint64_t i;
|
|
|
|
for (i = ctx->start; i < ctx->end; i += fcx->rs_n) {
|
|
for (j = 0; j < fcx->rs_n; ++j) {
|
|
data[j] = image_get_interleaved_byte(i + j, fcx);
|
|
}
|
|
|
|
encode_rs_char(ctx->rs, data, &fcx->fec[ctx->fec_pos]);
|
|
ctx->fec_pos += fcx->roots;
|
|
}
|
|
}
|
|
|
|
static void decode_rs(struct image_proc_ctx *ctx)
|
|
{
|
|
struct image *fcx = ctx->ctx;
|
|
int j, rv;
|
|
uint8_t data[fcx->rs_n + fcx->roots];
|
|
uint64_t i;
|
|
|
|
assert(sizeof(data) == FEC_RSM);
|
|
|
|
for (i = ctx->start; i < ctx->end; i += fcx->rs_n) {
|
|
for (j = 0; j < fcx->rs_n; ++j) {
|
|
data[j] = image_get_interleaved_byte(i + j, fcx);
|
|
}
|
|
|
|
memcpy(&data[fcx->rs_n], &fcx->fec[ctx->fec_pos], fcx->roots);
|
|
rv = decode_rs_char(ctx->rs, data, nullptr, 0);
|
|
|
|
if (rv < 0) {
|
|
FATAL("failed to recover [%" PRIu64 ", %" PRIu64 ")\n",
|
|
i, i + fcx->rs_n);
|
|
} else if (rv > 0) {
|
|
/* copy corrected data to output */
|
|
for (j = 0; j < fcx->rs_n; ++j) {
|
|
image_set_interleaved_byte(i + j, fcx, data[j]);
|
|
}
|
|
|
|
ctx->rv += rv;
|
|
}
|
|
|
|
ctx->fec_pos += fcx->roots;
|
|
}
|
|
}
|
|
|
|
static int usage()
|
|
{
|
|
printf("fec: a tool for encoding and decoding files using RS(255, N).\n"
|
|
"\n"
|
|
"usage: fec <mode> [ <options> ] [ <data> <fec> [ <output> ] ]\n"
|
|
"mode:\n"
|
|
" -e --encode encode (default)\n"
|
|
" -d --decode decode\n"
|
|
" -s, --print-fec-size=<data size> print FEC size\n"
|
|
" -E, --get-ecc-start=data print ECC offset in data\n"
|
|
" -V, --get-verity-start=data print verity offset\n"
|
|
"options:\n"
|
|
" -h show this help\n"
|
|
" -v enable verbose logging\n"
|
|
" -r, --roots=<bytes> number of parity bytes\n"
|
|
" -j, --threads=<threads> number of threads to use\n"
|
|
" -S treat data as a sparse file\n"
|
|
"encoding options:\n"
|
|
" -p, --padding=<bytes> add padding after ECC data\n"
|
|
"decoding options:\n"
|
|
" -i, --inplace correct <data> in place\n"
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static uint64_t parse_arg(const char *arg, const char *name, uint64_t maxval)
|
|
{
|
|
char* endptr;
|
|
errno = 0;
|
|
|
|
unsigned long long int value = strtoull(arg, &endptr, 0);
|
|
|
|
if (arg[0] == '\0' || *endptr != '\0' ||
|
|
(errno == ERANGE && value == ULLONG_MAX)) {
|
|
FATAL("invalid value of %s\n", name);
|
|
}
|
|
if (value > maxval) {
|
|
FATAL("value of roots too large (max. %" PRIu64 ")\n", maxval);
|
|
}
|
|
|
|
return (uint64_t)value;
|
|
}
|
|
|
|
static int print_size(image& ctx)
|
|
{
|
|
/* output size including header */
|
|
printf("%" PRIu64 "\n", fec_ecc_get_size(ctx.inp_size, ctx.roots));
|
|
return 0;
|
|
}
|
|
|
|
static int get_start(int mode, const std::string& filename)
|
|
{
|
|
fec::io fh(filename, O_RDONLY, FEC_VERITY_DISABLE);
|
|
|
|
if (!fh) {
|
|
FATAL("failed to open input\n");
|
|
}
|
|
|
|
if (mode == MODE_GETECCSTART) {
|
|
fec_ecc_metadata data;
|
|
|
|
if (!fh.get_ecc_metadata(data)) {
|
|
FATAL("no ecc data\n");
|
|
}
|
|
|
|
printf("%" PRIu64 "\n", data.start);
|
|
} else {
|
|
fec_verity_metadata data;
|
|
|
|
if (!fh.get_verity_metadata(data)) {
|
|
FATAL("no verity data\n");
|
|
}
|
|
|
|
printf("%" PRIu64 "\n", data.data_size);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int encode(image& ctx, const std::vector<std::string>& inp_filenames,
|
|
const std::string& fec_filename)
|
|
{
|
|
if (ctx.inplace) {
|
|
FATAL("invalid parameters: inplace can only used when decoding\n");
|
|
}
|
|
|
|
if (!image_load(inp_filenames, &ctx)) {
|
|
FATAL("failed to read input\n");
|
|
}
|
|
|
|
if (!image_ecc_new(fec_filename, &ctx)) {
|
|
FATAL("failed to allocate ecc\n");
|
|
}
|
|
|
|
INFO("encoding RS(255, %d) to '%s' for input files:\n", ctx.rs_n,
|
|
fec_filename.c_str());
|
|
|
|
size_t n = 1;
|
|
|
|
for (const auto& fn : inp_filenames) {
|
|
INFO("\t%zu: '%s'\n", n++, fn.c_str());
|
|
}
|
|
|
|
if (ctx.verbose) {
|
|
INFO("\traw fec size: %u\n", ctx.fec_size);
|
|
INFO("\tblocks: %" PRIu64 "\n", ctx.blocks);
|
|
INFO("\trounds: %" PRIu64 "\n", ctx.rounds);
|
|
}
|
|
|
|
if (!image_process(encode_rs, &ctx)) {
|
|
FATAL("failed to process input\n");
|
|
}
|
|
|
|
if (!image_ecc_save(&ctx)) {
|
|
FATAL("failed to write output\n");
|
|
}
|
|
|
|
image_free(&ctx);
|
|
return 0;
|
|
}
|
|
|
|
static int decode(image& ctx, const std::vector<std::string>& inp_filenames,
|
|
const std::string& fec_filename, std::string& out_filename)
|
|
{
|
|
const std::string& inp_filename = inp_filenames.front();
|
|
|
|
if (ctx.inplace && ctx.sparse) {
|
|
FATAL("invalid parameters: inplace cannot be used with sparse "
|
|
"files\n");
|
|
}
|
|
|
|
if (ctx.padding) {
|
|
FATAL("invalid parameters: padding is only relevant when encoding\n");
|
|
}
|
|
|
|
if (!image_ecc_load(fec_filename, &ctx) ||
|
|
!image_load(inp_filenames, &ctx)) {
|
|
FATAL("failed to read input\n");
|
|
}
|
|
|
|
if (ctx.inplace) {
|
|
INFO("correcting '%s' using RS(255, %d) from '%s'\n",
|
|
inp_filename.c_str(), ctx.rs_n, fec_filename.c_str());
|
|
|
|
out_filename = inp_filename;
|
|
} else {
|
|
INFO("decoding '%s' to '%s' using RS(255, %d) from '%s'\n",
|
|
inp_filename.c_str(),
|
|
out_filename.empty() ? out_filename.c_str() : "<none>", ctx.rs_n,
|
|
fec_filename.c_str());
|
|
}
|
|
|
|
if (ctx.verbose) {
|
|
INFO("\traw fec size: %u\n", ctx.fec_size);
|
|
INFO("\tblocks: %" PRIu64 "\n", ctx.blocks);
|
|
INFO("\trounds: %" PRIu64 "\n", ctx.rounds);
|
|
}
|
|
|
|
if (!image_process(decode_rs, &ctx)) {
|
|
FATAL("failed to process input\n");
|
|
}
|
|
|
|
if (ctx.rv) {
|
|
INFO("corrected %" PRIu64 " errors\n", ctx.rv);
|
|
} else {
|
|
INFO("no errors found\n");
|
|
}
|
|
|
|
if (!out_filename.empty() && !image_save(out_filename, &ctx)) {
|
|
FATAL("failed to write output\n");
|
|
}
|
|
|
|
image_free(&ctx);
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
std::string fec_filename;
|
|
std::string out_filename;
|
|
std::vector<std::string> inp_filenames;
|
|
int mode = MODE_ENCODE;
|
|
image ctx;
|
|
|
|
image_init(&ctx);
|
|
ctx.roots = FEC_DEFAULT_ROOTS;
|
|
|
|
while (1) {
|
|
const static struct option long_options[] = {
|
|
{"help", no_argument, nullptr, 'h'},
|
|
{"encode", no_argument, nullptr, 'e'},
|
|
{"decode", no_argument, nullptr, 'd'},
|
|
{"sparse", no_argument, nullptr, 'S'},
|
|
{"roots", required_argument, nullptr, 'r'},
|
|
{"inplace", no_argument, nullptr, 'i'},
|
|
{"threads", required_argument, nullptr, 'j'},
|
|
{"print-fec-size", required_argument, nullptr, 's'},
|
|
{"get-ecc-start", required_argument, nullptr, 'E'},
|
|
{"get-verity-start", required_argument, nullptr, 'V'},
|
|
{"padding", required_argument, nullptr, 'p'},
|
|
{"verbose", no_argument, nullptr, 'v'},
|
|
{nullptr, 0, nullptr, 0}
|
|
};
|
|
int c = getopt_long(argc, argv, "hedSr:ij:s:E:V:p:v", long_options, nullptr);
|
|
if (c < 0) {
|
|
break;
|
|
}
|
|
|
|
switch (c) {
|
|
case 'h':
|
|
return usage();
|
|
case 'S':
|
|
ctx.sparse = true;
|
|
break;
|
|
case 'e':
|
|
if (mode != MODE_ENCODE) {
|
|
return usage();
|
|
}
|
|
break;
|
|
case 'd':
|
|
if (mode != MODE_ENCODE) {
|
|
return usage();
|
|
}
|
|
mode = MODE_DECODE;
|
|
break;
|
|
case 'r':
|
|
ctx.roots = (int)parse_arg(optarg, "roots", FEC_RSM);
|
|
break;
|
|
case 'i':
|
|
ctx.inplace = true;
|
|
break;
|
|
case 'j':
|
|
ctx.threads = (int)parse_arg(optarg, "threads", IMAGE_MAX_THREADS);
|
|
break;
|
|
case 's':
|
|
if (mode != MODE_ENCODE) {
|
|
return usage();
|
|
}
|
|
mode = MODE_PRINTSIZE;
|
|
ctx.inp_size = parse_arg(optarg, "print-fec-size", UINT64_MAX);
|
|
break;
|
|
case 'E':
|
|
if (mode != MODE_ENCODE) {
|
|
return usage();
|
|
}
|
|
mode = MODE_GETECCSTART;
|
|
inp_filenames.push_back(optarg);
|
|
break;
|
|
case 'V':
|
|
if (mode != MODE_ENCODE) {
|
|
return usage();
|
|
}
|
|
mode = MODE_GETVERITYSTART;
|
|
inp_filenames.push_back(optarg);
|
|
break;
|
|
case 'p':
|
|
ctx.padding = (uint32_t)parse_arg(optarg, "padding", UINT32_MAX);
|
|
if (ctx.padding % FEC_BLOCKSIZE) {
|
|
FATAL("padding must be multiple of %u\n", FEC_BLOCKSIZE);
|
|
}
|
|
break;
|
|
case 'v':
|
|
ctx.verbose = true;
|
|
break;
|
|
case '?':
|
|
return usage();
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
assert(ctx.roots > 0 && ctx.roots < FEC_RSM);
|
|
|
|
/* check for input / output parameters */
|
|
if (mode == MODE_ENCODE) {
|
|
/* allow multiple input files */
|
|
for (int i = 0; i < (argc - 1); ++i) {
|
|
inp_filenames.push_back(argv[i]);
|
|
}
|
|
|
|
if (inp_filenames.empty()) {
|
|
return usage();
|
|
}
|
|
|
|
/* the last one is the output file */
|
|
fec_filename = argv[argc - 1];
|
|
} else if (mode == MODE_DECODE) {
|
|
if (argc < 2 || argc > 3) {
|
|
return usage();
|
|
} else if (argc == 3) {
|
|
if (ctx.inplace) {
|
|
return usage();
|
|
}
|
|
out_filename = argv[2];
|
|
}
|
|
|
|
inp_filenames.push_back(argv[0]);
|
|
fec_filename = argv[1];
|
|
}
|
|
|
|
switch (mode) {
|
|
case MODE_PRINTSIZE:
|
|
return print_size(ctx);
|
|
case MODE_GETECCSTART:
|
|
case MODE_GETVERITYSTART:
|
|
return get_start(mode, inp_filenames.front());
|
|
case MODE_ENCODE:
|
|
return encode(ctx, inp_filenames, fec_filename);
|
|
case MODE_DECODE:
|
|
return decode(ctx, inp_filenames, fec_filename, out_filename);
|
|
default:
|
|
abort();
|
|
}
|
|
|
|
return 1;
|
|
}
|