diff --git a/lib/reed_solomon.c b/lib/reed_solomon.c index a7fee4c1..1f577d22 100644 --- a/lib/reed_solomon.c +++ b/lib/reed_solomon.c @@ -7,8 +7,7 @@ struct rs_codec { unsigned n; unsigned k; - size_t block_size; - size_t payload_size; + size_t max_block_size; uint8_t gf_log[256]; uint8_t gf_exp[510]; uint8_t *generator; @@ -108,8 +107,7 @@ int rs_init(struct rs_codec **out, unsigned n, unsigned r, size_t block_size) { } ctx->n = n; ctx->k = k; - ctx->block_size = block_size; - ctx->payload_size = block_size - RS_HEADER_SIZE; + ctx->max_block_size = block_size; ctx->generator = ctx->storage; ctx->work = ctx->storage + n * k; unsigned value = 1; @@ -127,32 +125,34 @@ int rs_init(struct rs_codec **out, unsigned n, unsigned r, size_t block_size) { return rc; } *out = ctx; - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "reed_solomon: initialized n=%u r=%u block_size=%zu input_size=%zu memory=%zu", - n, r, block_size, k * ctx->payload_size, bytes); + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "reed_solomon: initialized n=%u r=%u max_block_size=%zu input_capacity=%zu memory=%zu", + n, r, block_size, k * (block_size - RS_HEADER_SIZE), bytes); return RS_OK; } void rs_free(struct rs_codec *ctx) { if (!ctx) return; - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "reed_solomon: free n=%u r=%u block_size=%zu", ctx->n, ctx->n - ctx->k, ctx->block_size); + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "reed_solomon: free n=%u r=%u max_block_size=%zu", ctx->n, ctx->n - ctx->k, ctx->max_block_size); u_free(ctx); } -size_t rs_input_size(const struct rs_codec *ctx) { +size_t rs_input_capacity(const struct rs_codec *ctx) { if (!ctx) { - DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: input_size requires a context"); + DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: input_capacity requires a context"); return 0; } - return ctx->k * ctx->payload_size; + return ctx->k * (ctx->max_block_size - RS_HEADER_SIZE); } /* Записать заголовки и исходные субблоки, затем вычислить r проверочных субблоков. */ -int rs_encode(struct rs_codec *ctx, uint64_t seq, const uint8_t *input, uint8_t *const blocks[]) { - if (!ctx || !input || !blocks) { - DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: invalid encode ctx=%p input=%p blocks=%p", - (void *)ctx, (const void *)input, (const void *)blocks); +int rs_encode(struct rs_codec *ctx, uint64_t seq, const uint8_t *input, size_t length, + uint8_t *const blocks[], size_t *block_size) { + if (!ctx || !input || !blocks || !block_size || !length || length > rs_input_capacity(ctx)) { + DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: invalid encode ctx=%p input=%p blocks=%p length=%zu size_out=%p", + (void *)ctx, (const void *)input, (const void *)blocks, length, (void *)block_size); return RS_ERR_INVALID_ARG; } + size_t payload_size = (length + ctx->k - 1) / ctx->k; for (unsigned i = 0; i < ctx->n; i++) { if (!blocks[i]) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: missing encode buffer seq=%llu subblock=%u", (unsigned long long)seq, i); @@ -163,30 +163,38 @@ int rs_encode(struct rs_codec *ctx, uint64_t seq, const uint8_t *input, uint8_t for (unsigned j = 0; j < 8; j++) blocks[i][j] = (uint8_t)(seq >> (56 - 8 * j)); blocks[i][8] = (uint8_t)i; uint8_t *payload = blocks[i] + RS_HEADER_SIZE; + memset(payload, 0, payload_size); if (i < ctx->k) { - memcpy(payload, input + i * ctx->payload_size, ctx->payload_size); + size_t offset = i * payload_size; + size_t take = offset < length ? length - offset : 0; + if (take > payload_size) take = payload_size; + if (take) memcpy(payload, input + offset, take); continue; } - memset(payload, 0, ctx->payload_size); for (unsigned j = 0; j < ctx->k; j++) { uint8_t factor = ctx->generator[i * ctx->k + j]; if (!factor) continue; - const uint8_t *source = input + j * ctx->payload_size; - for (size_t b = 0; b < ctx->payload_size; b++) payload[b] ^= rs_multiply(ctx, factor, source[b]); + const uint8_t *source = blocks[j] + RS_HEADER_SIZE; + for (size_t b = 0; b < payload_size; b++) payload[b] ^= rs_multiply(ctx, factor, source[b]); } } - DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "reed_solomon: encoded seq=%llu n=%u r=%u input_size=%zu block_size=%zu", - (unsigned long long)seq, ctx->n, ctx->n - ctx->k, ctx->k * ctx->payload_size, ctx->block_size); + *block_size = RS_HEADER_SIZE + payload_size; + DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "reed_solomon: encoded seq=%llu n=%u r=%u length=%zu padding=%zu block_size=%zu max=%zu", + (unsigned long long)seq, ctx->n, ctx->n - ctx->k, length, ctx->k * payload_size - length, + *block_size, ctx->max_block_size); return RS_OK; } /* Проверить всю пачку и восстановить только отсутствующие исходные субблоки. */ -int rs_decode(struct rs_codec *ctx, const uint8_t *const blocks[], uint8_t *output) { - if (!ctx || !blocks || !output) { - DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: invalid decode ctx=%p blocks=%p output=%p", - (void *)ctx, (const void *)blocks, (void *)output); +int rs_decode(struct rs_codec *ctx, const uint8_t *const blocks[], size_t block_size, + uint8_t *output, size_t output_capacity) { + if (!ctx || !blocks || !output || block_size <= RS_HEADER_SIZE || block_size > ctx->max_block_size + || output_capacity < ctx->k * (block_size - RS_HEADER_SIZE)) { + DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: invalid decode ctx=%p blocks=%p output=%p block_size=%zu capacity=%zu", + (void *)ctx, (const void *)blocks, (void *)output, block_size, output_capacity); return RS_ERR_INVALID_ARG; } + size_t payload_size = block_size - RS_HEADER_SIZE; const uint8_t *rows[RS_MAX_BLOCKS]; unsigned selected[RS_MAX_BLOCKS]; unsigned count = 0; @@ -221,21 +229,21 @@ int rs_decode(struct rs_codec *ctx, const uint8_t *const blocks[], uint8_t *outp if (rc != RS_OK) return rc; } for (unsigned i = 0; i < ctx->k; i++) { - uint8_t *target = output + i * ctx->payload_size; + uint8_t *target = output + i * payload_size; if (blocks[i]) { - memcpy(target, blocks[i] + RS_HEADER_SIZE, ctx->payload_size); + memcpy(target, blocks[i] + RS_HEADER_SIZE, payload_size); continue; } - memset(target, 0, ctx->payload_size); + memset(target, 0, payload_size); for (unsigned j = 0; j < ctx->k; j++) { uint8_t factor = ctx->work[i * 2 * ctx->k + ctx->k + j]; if (!factor) continue; const uint8_t *source = blocks[selected[j]] + RS_HEADER_SIZE; - for (size_t b = 0; b < ctx->payload_size; b++) target[b] ^= rs_multiply(ctx, factor, source[b]); + for (size_t b = 0; b < payload_size; b++) target[b] ^= rs_multiply(ctx, factor, source[b]); } DEBUG_TRACE(DEBUG_CATEGORY_GENERAL, "reed_solomon: recovered seq=%llu subblock=%u", (unsigned long long)seq, i); } DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "reed_solomon: decoded seq=%llu received=%u recovered=%u output_size=%zu", - (unsigned long long)seq, count, missing, ctx->k * ctx->payload_size); + (unsigned long long)seq, count, missing, ctx->k * payload_size); return RS_OK; } diff --git a/lib/reed_solomon.h b/lib/reed_solomon.h index 9b05ec0e..9ee17fd0 100644 --- a/lib/reed_solomon.h +++ b/lib/reed_solomon.h @@ -1,6 +1,6 @@ /** * Reed–Solomon: кодирование и восстановление одной пачки по известным пропускам. - * rs_init задаёт n кодограмм, r проверочных и полный размер каждой кодограммы. + * rs_init задаёт n кодограмм, r проверочных и максимальный размер каждой кодограммы. * Первые n-r кодограмм содержат исходные данные, остальные — проверочные. * Формат: seq (8 байт, big-endian), subblock (1 байт), полезные данные. * blocks[i] всегда соответствует subblock=i; при decode NULL обозначает пропуск. @@ -8,7 +8,8 @@ * Полученные данные и заголовки должны быть проверены транспортом на повреждения. * Все входные/выходные буферы принадлежат вызывающему коду и не перекрываются. * Кодек выделяет рабочую память только при init; один контекст используют последовательно. - * Неполную пачку вызывающий код дополняет нулями и отдельно сохраняет её настоящую длину. + * encode принимает действительную длину, выбирает минимальный одинаковый размер частей + * и дополняет хвост максимум n-r-1 нулями. Размер и исходную длину хранит вызывающий код. */ #ifndef REED_SOLOMON_H #define REED_SOLOMON_H @@ -35,32 +36,36 @@ enum rs_result { struct rs_codec; -/* Создать кодек: 1 <= n <= 255, 0 <= r < n, 10 <= block_size <= 4096. +/* Создать кодек: 1 <= n <= 255, 0 <= r < n, 10 <= max_block_size <= 4096. * ctx — выходной указатель; при ошибке *ctx=NULL. Старый контекст сначала освобождают. * Возвращает RS_OK или отрицательный rs_result. */ -int rs_init(struct rs_codec **ctx, unsigned n, unsigned r, size_t block_size); +int rs_init(struct rs_codec **ctx, unsigned n, unsigned r, size_t max_block_size); /* Освободить контекст и рабочую память; NULL допустим. */ void rs_free(struct rs_codec *ctx); -/* Размер входа encode и выхода decode: (n-r)*(block_size-RS_HEADER_SIZE). +/* Максимальная длина входа encode: (n-r)*(max_block_size-RS_HEADER_SIZE). * Для NULL возвращает 0 и логирует ошибку. */ -size_t rs_input_size(const struct rs_codec *ctx); +size_t rs_input_capacity(const struct rs_codec *ctx); -/* Читать rs_input_size(ctx) байт input, записать n кодограмм с заданным seq. - * blocks — n ненулевых указателей, каждый буфер содержит block_size байт. - * При ошибке аргументов выходные буферы не изменяются. +/* Читать 0 < length <= rs_input_capacity(ctx) байт input, записать n кодограмм. + * blocks — n ненулевых указателей на буферы ёмкостью max_block_size каждый. + * block_size получает RS_HEADER_SIZE+ceil(length/(n-r)); хвост за этим размером не меняется. + * При ошибке аргументов выходные буферы и block_size не изменяются. */ -int rs_encode(struct rs_codec *ctx, uint64_t seq, const uint8_t *input, uint8_t *const blocks[]); +int rs_encode(struct rs_codec *ctx, uint64_t seq, const uint8_t *input, size_t length, + uint8_t *const blocks[], size_t *block_size); -/* Восстановить rs_input_size(ctx) байт в output из массива n указателей blocks. - * Ненулевые буферы содержат block_size байт; NULL обозначает пропуск. +/* Восстановить (n-r)*(block_size-RS_HEADER_SIZE) байт, включая нулевой хвост. + * Ненулевые буферы содержат block_size байт; NULL обозначает пропуск. Размер общий для пачки. + * output_capacity проверяется до записи; block_size не превышает максимум контекста. * Проверяет одинаковый seq и subblock=i у всех присутствующих кодограмм. * Входные буферы не изменяются. При ошибке output не изменяется. */ -int rs_decode(struct rs_codec *ctx, const uint8_t *const blocks[], uint8_t *output); +int rs_decode(struct rs_codec *ctx, const uint8_t *const blocks[], size_t block_size, + uint8_t *output, size_t output_capacity); #ifdef __cplusplus }