1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

sys/coding: add XOR based coding module

This implements the XOR based error-correction code described by
Jürgen Fitschen (@jue89) at the RIOT Summit.

A parity byte is generated for each 3 payload bytes, then the payload array
is transposed by interpreting it as a 2D matrix with height of 3.

This is to reduce the chance of consecutive bytes ending up in the same
packet.

This allows to recover one in 3 lost data packets (if parity packets are received).

[0] https://summit.riot-os.org/2021/wp-content/uploads/sites/16/2021/09/s02-01.pdf
This commit is contained in:
Benjamin Valentin 2021-10-21 13:32:09 +02:00 committed by Benjamin Valentin
parent 19021d618e
commit d18a304267
5 changed files with 317 additions and 0 deletions

View File

@ -33,6 +33,10 @@ ifneq (,$(filter auto_init_sock_dns,$(USEMODULE)))
endif
endif
ifneq (,$(filter coding,$(USEMODULE)))
USEMODULE += bitfield
endif
ifneq (,$(filter congure_%,$(USEMODULE)))
USEMODULE += congure
endif

1
sys/coding/Makefile Normal file
View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

16
sys/coding/doc.txt Normal file
View File

@ -0,0 +1,16 @@
/*
* Copyright (C) 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup sys_coding Error correction codes
* @ingroup sys
* @brief Error correction function libraries
*
* This module provides functions to generate redundancy data for error
* correction.
*/

203
sys/coding/xor.c Normal file
View File

@ -0,0 +1,203 @@
/*
* Copyright (C) 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup sys_coding_xor
* @brief XOR coding algorithm
*
* @{
*
* @file
* @brief XOR coding implementation
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*/
#include <string.h>
#include "bitfield.h"
#include "coding/xor.h"
#define ENABLE_DEBUG 0
#include "debug.h"
static void _gen_parity(const void *data, size_t len, uint8_t *out)
{
const uint8_t *in = data;
memset(out, 0, CODING_XOR_PARITY_LEN(len));
for (unsigned i = 0; i < len; ++i) {
out[i / CONFIG_CODING_XOR_CHECK_BYTES] ^= in[i];
}
}
static inline size_t _transpose_idx(size_t i, size_t width, size_t height)
{
size_t x = i % width;
size_t y = i / width;
return x * height + y;
}
static inline void _swap(uint8_t *a, uint8_t *b)
{
uint8_t tmp = *a;
*a = *b;
*b = tmp;
}
/* https://www.geeksforgeeks.org/inplace-m-x-n-size-matrix-transpose/ */
static void _transpose(uint8_t *data, size_t len, size_t height)
{
BITFIELD(visited, len);
memset(visited, 0, sizeof(visited));
/* A[0] and A[size-1] won't move */
size_t i = 1;
size_t last = len -1;
bf_set(visited, 0);
bf_set(visited, last);
while (i < last) {
size_t cycle_begin = i;
uint8_t tmp = data[i];
do {
/* Input matrix [r x c]
* Output matrix
* i_new = (i*r)%(N-1)
*/
size_t next = (i * height) % last;
_swap(&data[next], &tmp);
bf_set(visited, i);
i = next;
} while (i != cycle_begin);
/* Get Next Move (what about querying random location?) */
i = bf_find_first_unset(visited, len);
}
}
static inline void _mix(void *data, size_t len)
{
_transpose(data, len, len / CONFIG_CODING_XOR_CHECK_BYTES);
}
static inline void _unmix(void *data, size_t len)
{
_transpose(data, len, CONFIG_CODING_XOR_CHECK_BYTES);
}
static bool _recover_byte(const uint8_t *in, size_t width, uint8_t height,
const uint8_t *parity, size_t idx, uint8_t *bitfield,
size_t block_size, uint8_t *out)
{
uint8_t res = parity[idx / CONFIG_CODING_XOR_CHECK_BYTES];
size_t start = idx - idx % CONFIG_CODING_XOR_CHECK_BYTES;
for (unsigned i = start; i < start + CONFIG_CODING_XOR_CHECK_BYTES; ++i) {
/* skip the lost byte */
if (i == idx) {
continue;
}
/* get index of neighbor byte in transposed matrix */
size_t idx_in = _transpose_idx(i, height, width);
if (!bf_isset(bitfield, idx_in / block_size)) {
DEBUG("missing chunk %u\n", idx_in / block_size);
return false;
}
res ^= in[idx_in];
}
*out = res;
return true;
}
static bool _recover_blocks(void *data, size_t len, const uint8_t *parity,
uint8_t *bitfield, size_t block_size)
{
uint8_t *in = data;
const uint8_t height = CONFIG_CODING_XOR_CHECK_BYTES;
const size_t width = len / height;
const uint8_t num_data_blocks = len / block_size;
bool success = true;
for (size_t i = 0; i < len; i += block_size) {
/* block is present, nothing to do */
if (bf_isset(bitfield, i / block_size)) {
continue;
}
DEBUG("try to recover chunk %u / %u\n", i / block_size, num_data_blocks);
for (size_t j = i; j < i + block_size; ++j) {
/* get original byte position */
size_t idx = _transpose_idx(j, width, height);
/* we can only recover the byte if we have the matching parity block */
size_t parity_block = idx / (CONFIG_CODING_XOR_CHECK_BYTES * block_size);
if (!bf_isset(bitfield, num_data_blocks + parity_block)) {
DEBUG("missing parity block %u\n", parity_block);
success = false;
goto next_block;
}
/* try to recover lost byte from parity */
if (!_recover_byte(in, width, height, parity, idx,
bitfield, block_size, &in[j])) {
success = false;
goto next_block;
}
}
bf_set(bitfield, i / block_size);
next_block:
/* try to recover another block */ ;
}
return success;
}
void coding_xor_generate(void *data, size_t len, uint8_t *parity)
{
_gen_parity(data, len, parity);
_mix(data, len);
}
bool coding_xor_recover(void *data, size_t len, uint8_t *parity,
uint8_t *bitfield, size_t block_size, bool recover_parity)
{
size_t num_data_chunks = len / block_size;
size_t num_parity_chunks = CODING_XOR_PARITY_LEN(len) / block_size;
if (!_recover_blocks(data, len, parity, bitfield, block_size)) {
return false;
}
_unmix(data, len);
if (!recover_parity) {
return true;
}
/* recover lost parity blocks */
for (size_t i = 0; i < num_parity_chunks; ++i) {
if (bf_isset(bitfield, num_data_chunks + i)) {
continue;
}
DEBUG("regenerate parity block %u\n", i);
size_t data_len = block_size * CONFIG_CODING_XOR_CHECK_BYTES;
_gen_parity((uint8_t *)data + i * data_len,
data_len, parity + i * block_size);
}
return true;
}
/** @} */

93
sys/include/coding/xor.h Normal file
View File

@ -0,0 +1,93 @@
/*
* Copyright (C) 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup sys_coding_xor XOR code
* @ingroup sys_coding
* @brief Simple XOR based coding algorithms
*
* @experimental This is a very basic implementation, it can only recover
* 1 lost block in 3 and only has a 33% chance of recovering
* two consecutive lost blocks.
* API / Algorithm might change if that means we can do better.
*
* @{
*
* @file
* @brief XOR coding definitions
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*/
#ifndef CODING_XOR_H
#define CODING_XOR_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Number of payload bytes per parity byte
*/
#ifndef CONFIG_CODING_XOR_CHECK_BYTES
#define CONFIG_CODING_XOR_CHECK_BYTES 3U
#endif
/**
* @brief Get the size of the needed parity buffer for a given payload size
*
* @param in Payload length
*/
#define CODING_XOR_PARITY_LEN(in) (((in) + CONFIG_CODING_XOR_CHECK_BYTES - 1) \
/ CONFIG_CODING_XOR_CHECK_BYTES)
/**
* @brief Generate parity and mix data buffer
*
* This generates parity data to recover one in @ref CONFIG_CODING_XOR_CHECK_BYTES
* bytes.
* The data buffer is then mixed to distribute bytes amongst transfer blocks, so
* that the chance for consecutive bytes to be in the same block is lowered.
*
* @param[in,out] data The data buffer to be processed
* @param[in] len Size of the data buffer
* @param[out] parity Buffer to hold parity data.
* Must be at least `CODING_XOR_PARITY_LEN(len)` bytes
*/
void coding_xor_generate(void *data, size_t len, uint8_t *parity);
/**
* @brief Restore and unmix buffer
*
*
* @param[in, out] data The data buffer to be restored
* @param[in] len Size of the data buffer
* @param[in,out] parity Buffer with parity data.
* Must be at least `CODING_XOR_PARITY_LEN(len)` bytes
* @param[in,out] blocks Bitfieled to indicate which blocks were received.
* This indicates the presence of both data and parity
* blocks. Parity blocks are appended after the last
* data block.
* If a block was restored it's bit will be set.
* @param[in] block_size Size of a data/payload block
* @param[in] recover_parity If true, missing parity blocks will be re-generated
* from data blocks.
*
* @return True if all data blocks were recovered
*/
bool coding_xor_recover(void *data, size_t len, uint8_t *parity,
uint8_t *blocks, size_t block_size, bool recover_parity);
#ifdef __cplusplus
}
#endif
#endif /* CODING_XOR_H */
/** @} */