crc16-xmodem.c

Simple implementation of crc16-xmodem
git clone git://git.finwo.net/lib/crc16-xmodem.c
Log | Files | Refs | README | LICENSE

commit e0afcc26555255a5e7dfccb3233337c0d14d2507
Author: finwo <finwo@pm.me>
Date:   Wed, 18 Mar 2026 19:36:32 +0100

Project init

Diffstat:
A.dep.export | 1+
A.editorconfig | 13+++++++++++++
A.gitignore | 4++++
ACODE_OF_CONDUCT.md | 6++++++
ALICENSE.md | 34++++++++++++++++++++++++++++++++++
AMakefile | 34++++++++++++++++++++++++++++++++++
AREADME.md | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.mk | 1+
Asrc/crc16-xmodem.c | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/crc16-xmodem.h | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest.c | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 421 insertions(+), 0 deletions(-)

diff --git a/.dep.export b/.dep.export @@ -0,0 +1 @@ +include/finwo/crc16-xmodem.h src/crc16-xmodem.h diff --git a/.editorconfig b/.editorconfig @@ -0,0 +1,13 @@ +# 2d0e4a3f-ac19-430c-bf1b-46c68651ce21 +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_size = 2 +indent_style = space +trim_trailing_whitespace = true + +[Makefile*] +indent_style = tab diff --git a/.gitignore b/.gitignore @@ -0,0 +1,4 @@ +/lib/ +*.o +/test +/benchmark diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md @@ -0,0 +1,6 @@ +<!-- 46b43825-f791-485e-9445-415ee7bbbf2d --> +# Contributor Code of Conduct + +This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters. + +For more information please visit the [No Code of Conduct](https://github.com/domgetter/NCoC) homepage. diff --git a/LICENSE.md b/LICENSE.md @@ -0,0 +1,34 @@ +Copyright (c) 2026 finwo + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to use, copy, +modify, and distribute the Software, subject to the following conditions: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions, and the following disclaimer. + + 2. Redistributions in binary form, or any public offering of the Software + (including hosted or managed services), must reproduce the above copyright + notice, this list of conditions, and the following disclaimer in the + documentation and/or other materials provided. + + 3. Any redistribution or public offering of the Software must clearly attribute + the Software to the original copyright holder, reference this License, and + include a link to the official project repository or website. + + 4. The Software may not be renamed, rebranded, or marketed in a manner that + implies it is an independent or proprietary product. Derivative works must + clearly state that they are based on the Software. + + 5. Modifications to copies of the Software must carry prominent notices stating + that changes were made, the nature of the modifications, and the date of the + modifications. + +Any violation of these conditions terminates the permissions granted herein. + +THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile @@ -0,0 +1,34 @@ +SRC=$(wildcard src/*.c) + +INCLUDES?= +INCLUDES+=-Isrc + +CFLAGS?=-Wall -std=c99 -O2 + +ifeq ($(origin SRC),undefined) +-include config.mk +endif + +CFLAGS+=$(INCLUDES) +CFLAGS+=-D_DEFAULT_SOURCE + +OBJ=$(SRC:.c=.o) + +BIN=test + +default: $(BIN) + +$(BIN): $(OBJ) test.o + $(CC) $(CFLAGS) $(OBJ) test.o -o $@ + +.PHONY: clean check +clean: + rm -f $(OBJ) + rm -f test.o + rm -f $(BIN) + +check: $(BIN) + ./$(BIN) + +README.md: src/crc16-xmodem.h + stddoc < src/crc16-xmodem.h > README.md diff --git a/README.md b/README.md @@ -0,0 +1,80 @@ +crc16-xmodem +============= + +Generate CRC-16/XMODEM codes consistently + +Why +--- + +While a fairly simple task, I was implementing this throughout multiple projects +of mine. Now if there's a bug, speed improvement to be had, or simple because +the C standard changes, I want to be able to update it in 1 location +instead of having to fix it in every individual project. + +TL;DR; I'm lazy, and want a single version of the code + +Installation +------------ + +```sh +dep add finwo/crc16-xmodem +dep install +``` + +After that, simply add `include lib/.dep/config.mk` in your makefile and include +the header file by adding `#include "finwo/crc16-xmodem.h"`. + +Usage +----- + +```c +#include "finwo/crc16-xmodem.h" + +uint8_t message[] = "Hi!\0\0"; + +// Output 12797 (0x31FD) +uint16_t crc = crc16_xmodem(message, 4); +crc16_xmodem_b(message, 4, &message[4]); + +// Now if you want to check if your message has travelled the network without errors: +if (crc16_xmodem(message, 6) != 0) { + printf("Message got corrupted in transit\n"); +} +``` + +Features +-------- + +- Consistent CRC-16/XMODEM implementation +- ANSI C (C99) +- Fast lookup-table based calculation + +API +--- + +### crc16_xmodem(data, length) + +Calculate the CRC-16/XMODEM checksum of the given data + +```c +uint16_t crc16_xmodem(const uint8_t *data, size_t length); +``` + +### crc16_xmodem_b(data, length, out) + +Calculate the CRC-16/XMODEM checksum and write it as big-endian bytes + +```c +void crc16_xmodem_b(const uint8_t *data, size_t length, uint8_t *out); +``` + +Testing +------- + +If you want to run the library's tests, simply run `make test` to compile +the testing binary, and then `./test` to run the actual tests. + +License +------- + +crc16-xmodem source code is available under the [FGPL license](LICENSE.md) diff --git a/config.mk b/config.mk @@ -0,0 +1 @@ +SRC+={{module.dirname}}/src/crc16-xmodem.c diff --git a/src/crc16-xmodem.c b/src/crc16-xmodem.c @@ -0,0 +1,55 @@ +#include "crc16-xmodem.h" + +#define polynomial 0x1021 +#define init 0x0000 +#define xorout 0x0000 + +static const uint16_t crc_table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +uint16_t crc16_xmodem(const uint8_t *data, size_t length) { + uint16_t result = init; + for (size_t i = 0; i < length; i++) { + uint8_t index = (result >> 8) ^ data[i]; + result = (result << 8) ^ crc_table[index]; + } + return result ^ xorout; +} + +void crc16_xmodem_b(const uint8_t *data, size_t length, uint8_t *out) { + uint16_t crc = crc16_xmodem(data, length); + out[0] = (crc >> 8) & 0xFF; + out[1] = crc & 0xFF; +} diff --git a/src/crc16-xmodem.h b/src/crc16-xmodem.h @@ -0,0 +1,85 @@ +#ifndef __FINWO_CRC16_XMODEM_H__ +#define __FINWO_CRC16_XMODEM_H__ + +/// crc16-xmodem +/// ============= +/// +/// Generate CRC-16/XMODEM codes consistently +/// +/// Why +/// --- +/// +/// While a fairly simple task, I was implementing this throughout multiple projects +/// of mine. Now if there's a bug, speed improvement to be had, or simple because +/// the C standard changes, I want to be able to update it in 1 location +/// instead of having to fix it in every individual project. +/// +/// TL;DR; I'm lazy, and want a single version of the code +/// +/// Usage +/// ----- +/// +/// ```c +/// #include "finwo/crc16-xmodem.h" +/// +/// uint8_t message[] = "Hi!\0\0"; +/// +/// // Output 12797 (0x31FD) +/// uint16_t crc = crc16_xmodem(message, 4); +/// crc16_xmodem_b(message, 4, &message[4]); +/// +/// // Now if you want to check if your message has travelled the network without errors: +/// if (crc16_xmodem(message, 6) != 0) { +/// printf("Message got corrupted in transit\n"); +/// } +/// ``` +/// +/// Features +/// -------- +/// +/// - Consistent CRC-16/XMODEM implementation +/// - ANSI C (C99) +/// - Fast lookup-table based calculation +/// +/// API +/// --- + +#include <stdint.h> +#include <stddef.h> + +/// <details> +/// <summary>crc16_xmodem(data, length)</summary> +/// +/// Calculate the CRC-16/XMODEM checksum of the given data +/// +/// ```c +/// uint16_t crc16_xmodem(const uint8_t *data, size_t length); +/// ``` +/// +/// </details> +uint16_t crc16_xmodem(const uint8_t *data, size_t length); + +/// <details> +/// <summary>crc16_xmodem_b(data, length, out)</summary> +/// +/// Calculate the CRC-16/XMODEM checksum and write it as big-endian bytes +/// +/// ```c +/// void crc16_xmodem_b(const uint8_t *data, size_t length, uint8_t *out); +/// ``` +/// +/// </details> +void crc16_xmodem_b(const uint8_t *data, size_t length, uint8_t *out); + +/// Testing +/// ------- +/// +/// If you want to run the library's tests, simply run `make test` to compile +/// the testing binary, and then `./test` to run the actual tests. +/// +/// License +/// ------- +/// +/// crc16-xmodem source code is available under the MIT-0 license. + +#endif diff --git a/test.c b/test.c @@ -0,0 +1,108 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "crc16-xmodem.h" + +static int tests_run = 0; +static int tests_passed = 0; + +#define ASSERT(cond, msg) do { \ + tests_run++; \ + if (cond) { \ + tests_passed++; \ + printf(" PASS: %s\n", msg); \ + } else { \ + printf(" FAIL: %s\n", msg); \ + } \ +} while(0) + +#define RUN(name) do { \ + printf("Running %s...\n", #name); \ + name(); \ +} while(0) + +void test_crc16_xmodem_basic() { + uint8_t message[] = "Hi!\0\0"; + + uint16_t crc = crc16_xmodem(message, 3); + ASSERT(crc == 12797, "'Hi!' produces good known-value numerically"); + ASSERT(crc == 0x31FD, "'Hi!' produces 0x31FD"); + + uint8_t out[2]; + crc16_xmodem_b(message, 3, out); + ASSERT(out[0] == 0x31 && out[1] == 0xFD, "'Hi!' produces 0x31FD as bytes"); +} + +void test_crc16_xmodem_known_values() { + struct { + const char *input; + uint16_t expected; + } known[] = { + {"Hello World\0\0" , 0x992A}, + {"Hello World!\0\0" , 0x0CD3}, + {"Pizza Calzone\0\0", 0x795B}, + }; + + for (size_t i = 0; i < sizeof(known) / sizeof(known[0]); i++) { + size_t len = strlen(known[i].input); + uint16_t crc = crc16_xmodem((const uint8_t *)known[i].input, len); + char buf[256]; + snprintf(buf, sizeof(buf), "known value '%s' produces correct output", known[i].input); + ASSERT(crc == known[i].expected, buf); + } +} + +void test_crc16_xmodem_inverse() { + struct { + const char *input; + uint16_t expected; + } known[] = { + {"Hello World\0\0" , 0x992A}, + {"Hello World!\0\0" , 0x0CD3}, + {"Pizza Calzone\0\0", 0x795B}, + }; + + for (size_t i = 0; i < sizeof(known) / sizeof(known[0]); i++) { + size_t len = strlen(known[i].input); + uint8_t *nb = malloc(len + 2); + memcpy(nb, known[i].input, len); + + crc16_xmodem_b(nb, len, &nb[len]); + + char buf[256]; + + snprintf(buf, sizeof(buf), "inverse '%s' resolves to 0 when no errors introduced", known[i].input); + ASSERT(crc16_xmodem(nb, len + 2) == 0, buf); + + nb[0]++; + snprintf(buf, sizeof(buf), "inverse '%s' resolves to non-0 with increment error", known[i].input); + ASSERT(crc16_xmodem(nb, len + 2) != 0, buf); + nb[0]--; + + nb[1] ^= 0x02; + snprintf(buf, sizeof(buf), "inverse '%s' resolves to non-0 with 1 bit-flip", known[i].input); + ASSERT(crc16_xmodem(nb, len + 2) != 0, buf); + nb[1] ^= 0x02; + + nb[1] ^= 0x04; + snprintf(buf, sizeof(buf), "inverse '%s' resolves to non-0 with 2 consecutive bit-flips", known[i].input); + ASSERT(crc16_xmodem(nb, len + 2) != 0, buf); + + free(nb); + } +} + +int main() { + printf("CRC16-XMODEM Tests\n"); + printf("==================\n\n"); + + RUN(test_crc16_xmodem_basic); + RUN(test_crc16_xmodem_known_values); + RUN(test_crc16_xmodem_inverse); + + printf("\n------------------\n"); + printf("Results: %d/%d tests passed\n", tests_passed, tests_run); + + return tests_passed == tests_run ? 0 : 1; +}