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.
340 lines
14 KiB
340 lines
14 KiB
// Пачки RS: независимый эталон, пропуски, размеры, заголовки и внешняя сборка потока. |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
|
|
#include "reed_solomon.h" |
|
#include "mem.h" |
|
#include "debug_config.h" |
|
|
|
#define CHECK(expr) do { if (!(expr)) { \ |
|
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "test_reed_solomon: failed line=%d: %s", __LINE__, #expr); exit(1); \ |
|
} } while (0) |
|
#define GUARD 8 |
|
#define MARKER 0xcd |
|
|
|
struct fixture { |
|
struct rs_codec *ctx; |
|
unsigned n; |
|
unsigned k; |
|
size_t block_size; |
|
size_t input_size; |
|
size_t storage_size; |
|
uint8_t *input_storage; |
|
uint8_t *output_storage; |
|
uint8_t *storage; |
|
uint8_t *saved; |
|
uint8_t *input; |
|
uint8_t *output; |
|
uint8_t *blocks[RS_MAX_BLOCKS]; |
|
const uint8_t *received[RS_MAX_BLOCKS]; |
|
}; |
|
|
|
/* Независимая арифметика поля без таблиц, для эталона через интерполяцию Лагранжа. */ |
|
static uint8_t reference_multiply(uint8_t a, uint8_t b) { |
|
unsigned value = a; |
|
unsigned result = 0; |
|
while (b) { |
|
if (b & 1) result ^= value; |
|
b >>= 1; |
|
value <<= 1; |
|
if (value & 256) value ^= 0x11d; |
|
} |
|
return (uint8_t)result; |
|
} |
|
|
|
static uint8_t reference_power(uint8_t a, unsigned power) { |
|
uint8_t result = 1; |
|
while (power--) result = reference_multiply(result, a); |
|
return result; |
|
} |
|
|
|
/* Проверить защитные байты вокруг всех предоставленных кодеку буферов. */ |
|
static void check_guards(const struct fixture *f) { |
|
for (unsigned i = 0; i < GUARD; i++) { |
|
CHECK(f->input_storage[i] == MARKER && f->input[f->input_size + i] == MARKER); |
|
CHECK(f->output_storage[i] == MARKER && f->output[f->input_size + i] == MARKER); |
|
for (unsigned j = 0; j < f->n; j++) { |
|
CHECK(f->blocks[j][(int)i - GUARD] == MARKER); |
|
CHECK(f->blocks[j][f->block_size + i] == MARKER); |
|
} |
|
} |
|
} |
|
|
|
/* Выделить буферы с защитными байтами и закодировать детерминированные исходные данные. */ |
|
static void fixture_init(struct fixture *f, unsigned n, unsigned r, size_t block_size, uint64_t seq) { |
|
memset(f, 0, sizeof(*f)); |
|
f->n = n; |
|
f->k = n - r; |
|
f->block_size = block_size; |
|
CHECK(rs_init(&f->ctx, n, r, block_size) == RS_OK); |
|
f->input_size = rs_input_size(f->ctx); |
|
CHECK(f->input_size == f->k * (block_size - RS_HEADER_SIZE)); |
|
f->storage_size = n * (block_size + 2 * GUARD); |
|
f->input_storage = u_malloc((uint32_t)(f->input_size + 2 * GUARD)); |
|
f->output_storage = u_malloc((uint32_t)(f->input_size + 2 * GUARD)); |
|
f->storage = u_malloc((uint32_t)f->storage_size); |
|
f->saved = u_malloc((uint32_t)f->storage_size); |
|
CHECK(f->input_storage && f->output_storage && f->storage && f->saved); |
|
memset(f->input_storage, MARKER, f->input_size + 2 * GUARD); |
|
memset(f->output_storage, MARKER, f->input_size + 2 * GUARD); |
|
memset(f->storage, MARKER, f->storage_size); |
|
f->input = f->input_storage + GUARD; |
|
f->output = f->output_storage + GUARD; |
|
uint32_t random = 0x5eed1234; |
|
for (size_t i = 0; i < f->input_size; i++) { |
|
random ^= random << 13; |
|
random ^= random >> 17; |
|
random ^= random << 5; |
|
f->input[i] = (uint8_t)random; |
|
} |
|
for (unsigned i = 0; i < n; i++) { |
|
f->blocks[i] = f->storage + i * (block_size + 2 * GUARD) + GUARD; |
|
f->received[i] = f->blocks[i]; |
|
} |
|
size_t allocations = u_get_allocated_count(); |
|
CHECK(rs_encode(f->ctx, seq, f->input, f->blocks) == RS_OK); |
|
CHECK(u_get_allocated_count() == allocations); |
|
memcpy(f->saved, f->storage, f->storage_size); |
|
for (unsigned i = 0; i < n; i++) { |
|
for (unsigned j = 0; j < 8; j++) CHECK(f->blocks[i][j] == (uint8_t)(seq >> (56 - 8 * j))); |
|
CHECK(f->blocks[i][8] == i); |
|
if (i < f->k) CHECK(memcmp(f->blocks[i] + RS_HEADER_SIZE, f->input + i * (block_size - RS_HEADER_SIZE), |
|
block_size - RS_HEADER_SIZE) == 0); |
|
} |
|
check_guards(f); |
|
} |
|
|
|
static void fixture_free(struct fixture *f) { |
|
check_guards(f); |
|
u_free(f->input_storage); |
|
u_free(f->output_storage); |
|
u_free(f->storage); |
|
u_free(f->saved); |
|
rs_free(f->ctx); |
|
} |
|
|
|
/* Кодограммы decode остаются неизменными, выход совпадает побайтово, аллокаций нет. */ |
|
static void check_decode(struct fixture *f) { |
|
size_t allocations = u_get_allocated_count(); |
|
memset(f->output, MARKER, f->input_size); |
|
CHECK(rs_decode(f->ctx, f->received, f->output) == RS_OK); |
|
CHECK(u_get_allocated_count() == allocations); |
|
CHECK(memcmp(f->input, f->output, f->input_size) == 0); |
|
CHECK(memcmp(f->storage, f->saved, f->storage_size) == 0); |
|
check_guards(f); |
|
} |
|
|
|
/* Сравнить проверочные субблоки с независимой интерполяцией по исходным значениям. */ |
|
static void check_reference(const struct fixture *f) { |
|
size_t payload_size = f->block_size - RS_HEADER_SIZE; |
|
uint8_t coefficients[RS_MAX_BLOCKS]; |
|
for (unsigned i = f->k; i < f->n; i++) { |
|
uint8_t point = reference_power(2, i); |
|
for (unsigned j = 0; j < f->k; j++) { |
|
uint8_t source_point = reference_power(2, j); |
|
uint8_t coefficient = 1; |
|
for (unsigned t = 0; t < f->k; t++) { |
|
if (t == j) continue; |
|
uint8_t other = reference_power(2, t); |
|
uint8_t inverse = reference_power(source_point ^ other, 254); |
|
coefficient = reference_multiply(coefficient, reference_multiply(point ^ other, inverse)); |
|
} |
|
coefficients[j] = coefficient; |
|
} |
|
for (size_t b = 0; b < payload_size; b++) { |
|
uint8_t expected = 0; |
|
for (unsigned j = 0; j < f->k; j++) expected ^= reference_multiply(coefficients[j], f->input[j * payload_size + b]); |
|
CHECK(f->blocks[i][RS_HEADER_SIZE + b] == expected); |
|
} |
|
} |
|
} |
|
|
|
/* Для 16/2 перебрать отсутствие потерь, все одиночные потери и все 120 пар. */ |
|
static void test_all_pairs(void) { |
|
const size_t sizes[] = {10, 11, 16, 255, 1000, 4095, 4096}; |
|
for (size_t s = 0; s < sizeof(sizes) / sizeof(sizes[0]); s++) { |
|
struct fixture f; |
|
fixture_init(&f, 16, 2, sizes[s], UINT64_C(0x0123456789abcdef)); |
|
check_reference(&f); |
|
check_decode(&f); |
|
for (unsigned i = 0; i < 16; i++) { |
|
f.received[i] = NULL; |
|
check_decode(&f); |
|
for (unsigned j = i + 1; j < 16; j++) { |
|
f.received[j] = NULL; |
|
check_decode(&f); |
|
f.received[j] = f.blocks[j]; |
|
} |
|
f.received[i] = f.blocks[i]; |
|
} |
|
fixture_free(&f); |
|
} |
|
puts("[PASS] reed_solomon: 16/2 all single losses and 120 pairs, seven block sizes, independent parity reference"); |
|
} |
|
|
|
/* Каждый полный размер кодограммы от 10 до 4096, seq=0 и seq=UINT64_MAX. */ |
|
static void test_all_sizes(void) { |
|
for (size_t size = RS_HEADER_SIZE + 1; size <= RS_MAX_BLOCK_SIZE; size++) { |
|
struct fixture f; |
|
fixture_init(&f, 3, 1, size, size & 1 ? UINT64_MAX : 0); |
|
f.received[size % 2] = NULL; |
|
check_decode(&f); |
|
fixture_free(&f); |
|
} |
|
puts("[PASS] reed_solomon: all block sizes 10..4096, seq zero and maximum"); |
|
} |
|
|
|
/* Независимый фиксированный вектор RS(4,2): P0=2*D0+3*D1, P1=6*D0+7*D1. */ |
|
static void test_known_vector(void) { |
|
struct fixture f; |
|
fixture_init(&f, 4, 2, 10, 7); |
|
f.input[0] = 1; |
|
f.input[1] = 2; |
|
CHECK(rs_encode(f.ctx, 7, f.input, f.blocks) == RS_OK); |
|
CHECK(f.blocks[2][9] == 4 && f.blocks[3][9] == 8); |
|
memcpy(f.saved, f.storage, f.storage_size); |
|
f.received[0] = NULL; |
|
f.received[1] = NULL; |
|
check_decode(&f); |
|
fixture_free(&f); |
|
puts("[PASS] reed_solomon: fixed parity vector and recovery from parity alone"); |
|
} |
|
|
|
/* Перебрать все достаточные наборы для небольших схем с разным числом резервных потоков. */ |
|
static void test_configurations(void) { |
|
const unsigned configs[][2] = {{1, 0}, {4, 0}, {4, 1}, {6, 3}, {8, 6}, {8, 7}}; |
|
for (unsigned c = 0; c < sizeof(configs) / sizeof(configs[0]); c++) { |
|
struct fixture f; |
|
fixture_init(&f, configs[c][0], configs[c][1], 31, c); |
|
check_reference(&f); |
|
for (unsigned mask = 0; mask < (1u << f.n); mask++) { |
|
unsigned count = 0; |
|
for (unsigned i = 0; i < f.n; i++) { |
|
f.received[i] = mask & (1u << i) ? f.blocks[i] : NULL; |
|
if (f.received[i]) count++; |
|
} |
|
if (count >= f.k) check_decode(&f); |
|
} |
|
fixture_free(&f); |
|
} |
|
const unsigned limits[][2] = {{255, 0}, {255, 1}, {255, 253}, {255, 254}}; |
|
for (unsigned c = 0; c < sizeof(limits) / sizeof(limits[0]); c++) { |
|
struct fixture f; |
|
fixture_init(&f, limits[c][0], limits[c][1], 10, UINT64_MAX); |
|
for (unsigned i = 0; i < limits[c][1]; i++) f.received[i] = NULL; |
|
check_decode(&f); |
|
fixture_free(&f); |
|
} |
|
puts("[PASS] reed_solomon: configurable redundancy, exhaustive small sets and 255-block limits"); |
|
} |
|
|
|
static void check_unchanged_output(const struct fixture *f) { |
|
for (size_t i = 0; i < f->input_size; i++) CHECK(f->output[i] == MARKER); |
|
check_guards(f); |
|
} |
|
|
|
/* Невалидные аргументы и заголовки не меняют выход, даже при наличии всех исходных субблоков. */ |
|
static void test_errors(void) { |
|
struct rs_codec *ctx = NULL; |
|
CHECK(rs_init(NULL, 16, 2, 4096) == RS_ERR_INVALID_ARG); |
|
const unsigned configs[][2] = {{0, 0}, {256, 1}, {16, 16}, {16, 17}}; |
|
for (unsigned i = 0; i < sizeof(configs) / sizeof(configs[0]); i++) { |
|
CHECK(rs_init(&ctx, configs[i][0], configs[i][1], 10) == RS_ERR_INVALID_ARG && !ctx); |
|
} |
|
const size_t sizes[] = {0, 9, 4097, SIZE_MAX}; |
|
for (unsigned i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) { |
|
CHECK(rs_init(&ctx, 16, 2, sizes[i]) == RS_ERR_INVALID_ARG && !ctx); |
|
} |
|
CHECK(rs_input_size(NULL) == 0); |
|
rs_free(NULL); |
|
struct fixture f; |
|
fixture_init(&f, 16, 2, 23, 100); |
|
CHECK(rs_encode(NULL, 100, f.input, f.blocks) == RS_ERR_INVALID_ARG); |
|
CHECK(rs_encode(f.ctx, 100, NULL, f.blocks) == RS_ERR_INVALID_ARG); |
|
CHECK(rs_encode(f.ctx, 100, f.input, NULL) == RS_ERR_INVALID_ARG); |
|
f.blocks[15] = NULL; |
|
CHECK(rs_encode(f.ctx, 101, f.input, f.blocks) == RS_ERR_INVALID_ARG); |
|
CHECK(memcmp(f.storage, f.saved, f.storage_size) == 0); |
|
f.blocks[15] = (uint8_t *)f.received[15]; |
|
CHECK(rs_decode(NULL, f.received, f.output) == RS_ERR_INVALID_ARG); |
|
CHECK(rs_decode(f.ctx, NULL, f.output) == RS_ERR_INVALID_ARG); |
|
CHECK(rs_decode(f.ctx, f.received, NULL) == RS_ERR_INVALID_ARG); |
|
f.received[0] = f.received[1] = f.received[2] = NULL; |
|
CHECK(rs_decode(f.ctx, f.received, f.output) == RS_ERR_INSUFFICIENT); |
|
check_unchanged_output(&f); |
|
for (unsigned i = 0; i < 16; i++) f.received[i] = NULL; |
|
CHECK(rs_decode(f.ctx, f.received, f.output) == RS_ERR_INSUFFICIENT); |
|
check_unchanged_output(&f); |
|
for (unsigned i = 0; i < 16; i++) f.received[i] = f.blocks[i]; |
|
f.blocks[15][0] ^= 1; |
|
CHECK(rs_decode(f.ctx, f.received, f.output) == RS_ERR_HEADER); |
|
check_unchanged_output(&f); |
|
f.blocks[15][0] ^= 1; |
|
f.blocks[15][8] = 0; |
|
CHECK(rs_decode(f.ctx, f.received, f.output) == RS_ERR_HEADER); |
|
check_unchanged_output(&f); |
|
f.blocks[15][8] = 255; |
|
CHECK(rs_decode(f.ctx, f.received, f.output) == RS_ERR_HEADER); |
|
f.blocks[15][8] = 15; |
|
f.received[0] = f.blocks[1]; |
|
f.received[1] = f.blocks[0]; |
|
CHECK(rs_decode(f.ctx, f.received, f.output) == RS_ERR_HEADER); |
|
check_unchanged_output(&f); |
|
f.received[0] = f.blocks[0]; |
|
f.received[1] = f.blocks[1]; |
|
check_decode(&f); |
|
fixture_free(&f); |
|
puts("[PASS] reed_solomon: invalid parameters, missing buffers, insufficient blocks, mixed seq and invalid indices"); |
|
} |
|
|
|
/* Приложение само разбивает поток, дополняет хвост и собирает пачки с учётом seq. */ |
|
static void test_stream(void) { |
|
struct fixture f; |
|
fixture_init(&f, 16, 2, 101, 0); |
|
size_t length = 3 * f.input_size + 17; |
|
uint8_t *source = u_malloc((uint32_t)length); |
|
uint8_t *result = u_malloc((uint32_t)length); |
|
CHECK(source && result); |
|
for (size_t i = 0; i < length; i++) source[i] = (uint8_t)(i * 17 + i / 251); |
|
size_t offset = 0; |
|
for (uint64_t seq = 0; offset < length; seq++) { |
|
size_t valid = length - offset; |
|
if (valid > f.input_size) valid = f.input_size; |
|
memset(f.input, 0, f.input_size); |
|
memcpy(f.input, source + offset, valid); |
|
CHECK(rs_encode(f.ctx, seq, f.input, f.blocks) == RS_OK); |
|
memcpy(f.saved, f.storage, f.storage_size); |
|
for (unsigned i = 0; i < f.n; i++) f.received[i] = NULL; |
|
for (unsigned i = f.n; i > 0; i--) { |
|
unsigned index = f.blocks[i - 1][8]; |
|
if (index == seq % f.n || index == (seq + 1) % f.n) continue; |
|
f.received[index] = f.blocks[i - 1]; |
|
} |
|
check_decode(&f); |
|
memcpy(result + offset, f.output, valid); |
|
offset += valid; |
|
} |
|
CHECK(memcmp(source, result, length) == 0); |
|
u_free(source); |
|
u_free(result); |
|
fixture_free(&f); |
|
puts("[PASS] reed_solomon: external stream splitting and exact reassembly with two losses per batch and padded tail"); |
|
} |
|
|
|
int main(void) { |
|
debug_config_init(); |
|
debug_set_level(DEBUG_LEVEL_WARN); |
|
debug_set_category_level(DEBUG_CATEGORY_GENERAL, DEBUG_LEVEL_WARN); |
|
size_t baseline = u_get_allocated_count(); |
|
test_known_vector(); |
|
test_all_pairs(); |
|
test_all_sizes(); |
|
test_configurations(); |
|
test_errors(); |
|
debug_set_category_level(DEBUG_CATEGORY_GENERAL, DEBUG_LEVEL_TRACE); |
|
test_stream(); |
|
CHECK(u_get_allocated_count() == baseline); |
|
puts("[PASS] reed_solomon: all tests passed, no memory leaks"); |
|
return 0; |
|
}
|
|
|