From ddaa9863f2a2166ae7a2b56a62ba3b64d449e17d Mon Sep 17 00:00:00 2001 From: Lucas Jenss Date: Wed, 4 Nov 2015 16:54:33 +0100 Subject: [PATCH] Add 256 byte block hamming code implementation --- dist/tools/licenses/patterns/3c-BSD-atmel4 | 21 ++ sys/Makefile | 4 + sys/ecc/doc.txt | 14 + sys/ecc/hamming256/Makefile | 1 + sys/ecc/hamming256/hamming256.c | 321 +++++++++++++++++++++ sys/include/ecc/hamming256.h | 72 +++++ tests/unittests/tests-ecc/Makefile | 1 + tests/unittests/tests-ecc/Makefile.include | 1 + tests/unittests/tests-ecc/tests-ecc.c | 90 ++++++ 9 files changed, 525 insertions(+) create mode 100644 dist/tools/licenses/patterns/3c-BSD-atmel4 create mode 100644 sys/ecc/doc.txt create mode 100644 sys/ecc/hamming256/Makefile create mode 100644 sys/ecc/hamming256/hamming256.c create mode 100644 sys/include/ecc/hamming256.h create mode 100644 tests/unittests/tests-ecc/Makefile create mode 100644 tests/unittests/tests-ecc/Makefile.include create mode 100644 tests/unittests/tests-ecc/tests-ecc.c diff --git a/dist/tools/licenses/patterns/3c-BSD-atmel4 b/dist/tools/licenses/patterns/3c-BSD-atmel4 new file mode 100644 index 0000000000..35db9d25fc --- /dev/null +++ b/dist/tools/licenses/patterns/3c-BSD-atmel4 @@ -0,0 +1,21 @@ +All rights reserved\. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the disclaimer below\. + +Atmel's name may not be used to endorse or promote products derived from +this software without specific prior written permission\. + +DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE +DISCLAIMED\. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES \(INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION\) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT \(INCLUDING +NEGLIGENCE OR OTHERWISE\) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE\. diff --git a/sys/Makefile b/sys/Makefile index 226e3c02c6..0c94dd071b 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -68,6 +68,10 @@ ifneq (,$(filter udp,$(USEMODULE))) DIRS += net/transport_layer/udp endif +ifneq (,$(filter hamming256,$(USEMODULE))) + DIRS += ecc/hamming256 +endif + ifneq (,$(filter netopt,$(USEMODULE))) DIRS += net/crosslayer/netopt endif diff --git a/sys/ecc/doc.txt b/sys/ecc/doc.txt new file mode 100644 index 0000000000..da520185cc --- /dev/null +++ b/sys/ecc/doc.txt @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2015 Lucas Jenß + * + * 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_ecc ECC + * @ingroup sys + * + * @brief A collection of error correction code (ECC) algorithms. + */ diff --git a/sys/ecc/hamming256/Makefile b/sys/ecc/hamming256/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/ecc/hamming256/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/ecc/hamming256/hamming256.c b/sys/ecc/hamming256/hamming256.c new file mode 100644 index 0000000000..544a287fb2 --- /dev/null +++ b/sys/ecc/hamming256/hamming256.c @@ -0,0 +1,321 @@ +/* ---------------------------------------------------------------------------- + * ATMEL Microcontroller Software Support + * ---------------------------------------------------------------------------- + * Copyright (c) 2008, Atmel Corporation + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the disclaimer below. + * + * Atmel's name may not be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE + * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ---------------------------------------------------------------------------- + */ +/** + * @file + * + * Implementation of the hamming code functions. + * + */ + + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include + +#define ENABLE_DEBUG (0) +#include "debug.h" +#include "ecc/hamming256.h" +#include "bitarithm.h" + +/*---------------------------------------------------------------------------- + * Internal function + *----------------------------------------------------------------------------*/ + +/** + * @brief Counts and return the number of bits set to '1' in the given hamming code. + * @param code Hamming code. + */ +static uint8_t count_bits_in_code256(uint8_t *code) +{ + return bitarithm_bits_set(code[0]) + bitarithm_bits_set(code[1]) + bitarithm_bits_set(code[2]); +} + +/** + * @brief Calculates the 22-bit hamming code for a 256-bytes block of data. + * @param data Data buffer to calculate code for. + * @param code Pointer to a buffer where the code should be stored. + * @param padding Amount of zeroes to be appended to the data such that it sizes + * equals 256 bytes + */ +static void compute256(const uint8_t *data, uint8_t *code, uint8_t padding) +{ + uint32_t i; + uint8_t columnSum = 0; + uint8_t evenLineCode = 0; + uint8_t oddLineCode = 0; + uint8_t evenColumnCode = 0; + uint8_t oddColumnCode = 0; + + /* + * Xor all bytes together to get the column sum; + * At the same time, calculate the even and odd line codes + */ + for (i = 0; i < 256; i++) { + /* Allow non-multiples of 256 to be calculated by padding the data with zeroes */ + uint8_t current = 0; + if (i < ((uint16_t)(256 - padding))) { + current = data[i]; + } + + columnSum ^= current; + + /* + * If the xor sum of the byte is 0, then this byte has no incidence on + * the computed code; so check if the sum is 1. + */ + if ((bitarithm_bits_set(current) & 1) == 1) { + /* + * Parity groups are formed by forcing a particular index bit to 0 + * (even) or 1 (odd). + * Example on one byte: + * + * bits (dec) 7 6 5 4 3 2 1 0 + * (bin) 111 110 101 100 011 010 001 000 + * '---'---'---'----------. + * | + * groups P4' ooooooooooooooo eeeeeeeeeeeeeee P4 | + * P2' ooooooo eeeeeee ooooooo eeeeeee P2 | + * P1' ooo eee ooo eee ooo eee ooo eee P1 | + * | + * We can see that: | + * - P4 -> bit 2 of index is 0 --------------------' + * - P4' -> bit 2 of index is 1. + * - P2 -> bit 1 of index if 0. + * - etc... + * We deduce that a bit position has an impact on all even Px if + * the log2(x)nth bit of its index is 0 + * ex: log2(4) = 2, bit2 of the index must be 0 (-> 0 1 2 3) + * and on all odd Px' if the log2(x)nth bit of its index is 1 + * ex: log2(2) = 1, bit1 of the index must be 1 (-> 0 1 4 5) + * + * As such, we calculate all the possible Px and Px' values at the + * same time in two variables, evenLineCode and oddLineCode, such as + * evenLineCode bits: P128 P64 P32 P16 P8 P4 P2 P1 + * oddLineCode bits: P128' P64' P32' P16' P8' P4' P2' P1' + */ + evenLineCode ^= (255 - i); + oddLineCode ^= i; + } + } + + /* + * At this point, we have the line parities, and the column sum. First, We + * must caculate the parity group values on the column sum. + */ + for (i = 0; i < 8; i++) { + if (columnSum & 1) { + evenColumnCode ^= (7 - i); + oddColumnCode ^= i; + } + columnSum >>= 1; + } + + /* + * Now, we must interleave the parity values, to obtain the following layout: + * Code[0] = Line1 + * Code[1] = Line2 + * Code[2] = Column + * Line = Px' Px P(x-1)- P(x-1) ... + * Column = P4' P4 P2' P2 P1' P1 PadBit PadBit + */ + code[0] = 0; + code[1] = 0; + code[2] = 0; + + for (i = 0; i < 4; i++) { + code[0] <<= 2; + code[1] <<= 2; + code[2] <<= 2; + + /* Line 1 */ + if ((oddLineCode & 0x80) != 0) { + code[0] |= 2; + } + + if ((evenLineCode & 0x80) != 0) { + code[0] |= 1; + } + + /* Line 2 */ + if ((oddLineCode & 0x08) != 0) { + code[1] |= 2; + } + + if ((evenLineCode & 0x08) != 0) { + code[1] |= 1; + } + + /* Column */ + if ((oddColumnCode & 0x04) != 0) { + code[2] |= 2; + } + + if ((evenColumnCode & 0x04) != 0) { + code[2] |= 1; + } + + oddLineCode <<= 1; + evenLineCode <<= 1; + oddColumnCode <<= 1; + evenColumnCode <<= 1; + } + + /* Invert codes (linux compatibility) */ + code[0] = (~(uint32_t)code[0]); + code[1] = (~(uint32_t)code[1]); + code[2] = (~(uint32_t)code[2]); + + DEBUG("Computed code = %02X %02X %02X\n\r", + code[0], code[1], code[2]); +} + +/** + * @brief Verifies and corrects a 256-bytes block of data using the given 22-bits + * hamming code. + * + * @param data Data buffer to check. + * @param originalCode Hamming code to use for verifying the data. + * @param padding Amount of zeroes to be appended to the data such that it sizes + * equals 256 bytes + * + * @return 0 if there is no error, otherwise returns a HAMMING_ERROR code. + */ +uint8_t verify256( uint8_t *pucData, const uint8_t *pucOriginalCode, uint8_t padding ) +{ + /* Calculate new code */ + uint8_t computedCode[3]; + uint8_t correctionCode[3]; + + compute256( pucData, computedCode, padding); + + /* Xor both codes together */ + correctionCode[0] = computedCode[0] ^ pucOriginalCode[0]; + correctionCode[1] = computedCode[1] ^ pucOriginalCode[1]; + correctionCode[2] = computedCode[2] ^ pucOriginalCode[2]; + + DEBUG( "Correction code = %02X %02X %02X\n\r", correctionCode[0], correctionCode[1], correctionCode[2] ); + + /* If all bytes are 0, there is no error */ + if ((correctionCode[0] == 0) && (correctionCode[1] == 0) && (correctionCode[2] == 0)) { + return 0; + } + + /* If there is a single bit error, there are 11 bits set to 1 */ + if (count_bits_in_code256( correctionCode ) == 11) { + /* Get byte and bit indexes */ + uint8_t byte; + uint8_t bit; + + byte = correctionCode[0] & 0x80; + byte |= (correctionCode[0] << 1) & 0x40; + byte |= (correctionCode[0] << 2) & 0x20; + byte |= (correctionCode[0] << 3) & 0x10; + + byte |= (correctionCode[1] >> 4) & 0x08; + byte |= (correctionCode[1] >> 3) & 0x04; + byte |= (correctionCode[1] >> 2) & 0x02; + byte |= (correctionCode[1] >> 1) & 0x01; + + bit = (correctionCode[2] >> 5) & 0x04; + bit |= (correctionCode[2] >> 4) & 0x02; + bit |= (correctionCode[2] >> 3) & 0x01; + + /* Correct bit */ + DEBUG("Correcting byte #%d at bit %d\n\r", byte, bit ); + pucData[byte] ^= (1 << bit); + + return Hamming_ERROR_SINGLEBIT; + } + + /* Check if ECC has been corrupted */ + if (count_bits_in_code256( correctionCode ) == 1) { + return Hamming_ERROR_ECC; + } + /* Otherwise, this is a multi-bit error */ + else { + return Hamming_ERROR_MULTIPLEBITS; + } +} + +/*---------------------------------------------------------------------------- + * Exported functions + *----------------------------------------------------------------------------*/ + +void hamming_compute256x( const uint8_t *pucData, uint32_t dwSize, uint8_t *puCode ) +{ + DEBUG("hamming_compute256x()\n\r"); + + uint8_t padding; + while (dwSize > 0) { + padding = 0; + if (dwSize < 256) { + padding = 256 - dwSize; + } + + compute256( pucData, puCode, padding ); + + pucData += 256; + puCode += 3; + dwSize -= (256 - padding); + } +} + +uint8_t hamming_verify256x( uint8_t *pucData, uint32_t dwSize, const uint8_t *pucCode ) +{ + uint8_t error; + uint8_t result = 0; + + DEBUG( "hamming_verify256x()\n\r" ); + + uint8_t padding; + while (dwSize > 0) { + padding = 0; + if (dwSize < 256) { + padding = 256 - dwSize; + } + error = verify256( pucData, pucCode, padding ); + + if (error == Hamming_ERROR_SINGLEBIT) { + result = Hamming_ERROR_SINGLEBIT; + } + else { + if (error) { + return error; + } + } + + pucData += 256; + pucCode += 3; + dwSize -= (256 - padding); + } + + return result; +} diff --git a/sys/include/ecc/hamming256.h b/sys/include/ecc/hamming256.h new file mode 100644 index 0000000000..5c2cc98918 --- /dev/null +++ b/sys/include/ecc/hamming256.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2015 Lucas Jenß + * + * 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_ecc + * @brief + * @{ + * + * @brief Hamming Code implementation for 256byte data segments + * @author Lucas Jenß + */ + +#ifndef _HAMMING256_H +#define _HAMMING256_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** No bit errors were detected in the message */ +#define Hamming_ERROR_NONE 0 + +/** A single bit was incorrect but has been recovered. */ +#define Hamming_ERROR_SINGLEBIT 1 + +/** The original code has been corrupted. */ +#define Hamming_ERROR_ECC 2 + +/** Multiple bits are incorrect in the data and they cannot be corrected. */ +#define Hamming_ERROR_MULTIPLEBITS 3 + + +/** + * @brief Computes 3-bytes hamming codes for a data block whose size is multiple of + * 256 bytes. Each 256 bytes block gets its own code. + * + * @param[in] data Data to compute code for. + * @param[in] size Data size in bytes. + * @param[out] code Codes buffer. + */ +void hamming_compute256x( const uint8_t *data, uint32_t size, uint8_t *code ); + + +/** + * @brief Verifies 3-bytes hamming codes for a data block whose size is multiple of + * 256 bytes. Each 256-bytes block is verified with its own code. + * + * @return Hamming_ERROR_NONE if the data is correct, Hamming_ERROR_SINGLEBIT if one or more + * block(s) have had a single bit corrected, or either Hamming_ERROR_ECC + * or Hamming_ERROR_MULTIPLEBITS. + * + * @param[in] data Data buffer to verify. + * @param[in] size Size of the data in bytes. + * @param[in] code Original codes. + */ +uint8_t hamming_verify256x( uint8_t *data, uint32_t size, const uint8_t *code ); + + +#ifdef __cplusplus +} +#endif + +#endif +/** @} */ diff --git a/tests/unittests/tests-ecc/Makefile b/tests/unittests/tests-ecc/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/tests/unittests/tests-ecc/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/tests/unittests/tests-ecc/Makefile.include b/tests/unittests/tests-ecc/Makefile.include new file mode 100644 index 0000000000..f676c7643d --- /dev/null +++ b/tests/unittests/tests-ecc/Makefile.include @@ -0,0 +1 @@ +USEMODULE += hamming256 diff --git a/tests/unittests/tests-ecc/tests-ecc.c b/tests/unittests/tests-ecc/tests-ecc.c new file mode 100644 index 0000000000..b520c356cd --- /dev/null +++ b/tests/unittests/tests-ecc/tests-ecc.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015 Lucas Jenß + * + * 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 fs + * @brief + * @{ + * + * @brief Tests for Hamming Code implementation + * @author Lucas Jenß + */ +#include +#include "embUnit.h" + +#include "ecc/hamming256.h" + +static void test_single(void) +{ + uint8_t data[256]; + uint8_t ecc[3]; + uint8_t result; + + memset(data, 0xAB, 256); + + hamming_compute256x(data, 256, ecc); + result = hamming_verify256x(data, 256, ecc); + TEST_ASSERT_EQUAL_INT(Hamming_ERROR_NONE, result); + + data[10] |= (2 << 3); + result = hamming_verify256x(data, 256, ecc); + TEST_ASSERT_EQUAL_INT(Hamming_ERROR_SINGLEBIT, result); + + data[10] |= (2 << 3); + data[20] |= (2 << 5); + result = hamming_verify256x(data, 256, ecc); + TEST_ASSERT_EQUAL_INT(Hamming_ERROR_MULTIPLEBITS, result); + + memset(data, 0xAB, 256); + ecc[1] ^= 1; // Flip first bit, corrupting the ECC + result = hamming_verify256x(data, 256, ecc); + TEST_ASSERT_EQUAL_INT(Hamming_ERROR_ECC, result); +} + +static void test_padding(void) +{ + uint8_t data[203]; + uint8_t ecc[3]; + uint8_t result; + + memset(data, 0xAB, 203); + + hamming_compute256x(data, 203, ecc); + result = hamming_verify256x(data, 203, ecc); + TEST_ASSERT_EQUAL_INT(Hamming_ERROR_NONE, result); + + data[10] |= (2 << 3); + result = hamming_verify256x(data, 203, ecc); + TEST_ASSERT_EQUAL_INT(Hamming_ERROR_SINGLEBIT, result); + + data[10] |= (2 << 3); + data[20] |= (2 << 5); + result = hamming_verify256x(data, 203, ecc); + TEST_ASSERT_EQUAL_INT(Hamming_ERROR_MULTIPLEBITS, result); + + memset(data, 0xAB, 203); + ecc[1] ^= 1; // Flip first bit, corrupting the ECC + result = hamming_verify256x(data, 203, ecc); + TEST_ASSERT_EQUAL_INT(Hamming_ERROR_ECC, result); +} + +TestRef test_all(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_single), + new_TestFixture(test_padding), + }; + + EMB_UNIT_TESTCALLER(EccTest, 0, 0, fixtures); + return (TestRef) & EccTest; +} + +void tests_ecc(void) +{ + TESTS_RUN(test_all()); +}