From a71ac0c43e21a92566b8d1d55d7ca981b428ae58 Mon Sep 17 00:00:00 2001 From: ju6ge Date: Tue, 17 Mar 2026 20:03:39 +0100 Subject: [PATCH] inital implementation of crc6 --- lib-bms-protocol/src/types.rs | 119 ++++++++++++++++++++++++++++++---- 1 file changed, 106 insertions(+), 13 deletions(-) diff --git a/lib-bms-protocol/src/types.rs b/lib-bms-protocol/src/types.rs index 48df79a..7b5996c 100644 --- a/lib-bms-protocol/src/types.rs +++ b/lib-bms-protocol/src/types.rs @@ -77,7 +77,7 @@ impl Stored_U26 { self.0.to_be_bytes() } - pub fn from_be_bytes(bytes: [u8;4]) -> Result { + pub fn from_be_bytes(bytes: [u8; 4]) -> Result { let raw_u32 = u32::from_be_bytes(bytes); // the last bit is expetced to be 0 if raw_u32 & 0x1 != 0x0 { @@ -86,9 +86,7 @@ impl Stored_U26 { let value = (raw_u32 & U26_VALUE_MASK) >> 6; let hemming = ((raw_u32 & U26_HEMMING_MASK) >> 1) as u8; match check_hemming(value, hemming) { - Ok(()) => Ok( - Stored_U26(raw_u32) - ), + Ok(()) => Ok(Stored_U26(raw_u32)), Err(correction_data) => { let (dc, hc) = correction_data.calc_correction_data(); if check_hemming(value ^ dc, hemming ^ hc).is_err() { @@ -101,7 +99,7 @@ impl Stored_U26 { } else { Ok(Self::new(value ^ dc)) } - }, + } } } } @@ -282,9 +280,9 @@ impl HemmingCorrectionValue { /// calculate xor bit patterns to apply to the data or the stored hemming code /// returns tuple -> (xor with data, xor with hemming code) fn calc_correction_data(&self) -> (u32, u8) { - if [1, 2, 4, 8, 16].contains(&self.0) { + if [1, 2, 4, 8, 16].contains(&self.0) { (0x00000000, 0x1 << self.0.ilog2()) - } else { + } else { if self.0 == 3 { (0x1, 0x00) } else if self.0 < 8 { @@ -304,18 +302,70 @@ pub fn check_hemming(data: u32, hemming: u8) -> Result<(), HemmingCorrectionValu if h == hemming { Ok(()) } else { - Err(HemmingCorrectionValue::new(h^hemming)) + Err(HemmingCorrectionValue::new(h ^ hemming)) } } +/// CRC-6 calculation using polynomial 0x21 (MSB first) +/// +/// This function calculates a 6-bit CRC for 26 bits of data. +/// The polynomial used is 0x21 in the crc world polynoms +/// are named after the bit pattern with implicit assumption +/// of the trailing 1, thus 0x21 become 0x43 referring to +/// the 0b1000011 pattern. (Don't ask why just accept it …) +/// +/// # Algorithm +/// +/// The CRC is calculated using the standard CRC algorithm with MSB first bit ordering, +/// the inital value is equal to 0x00 and no additional transformat at the end is performed. +/// +/// # Error Detection +/// This CRC provides a Hamming distance of 3, meaning it can detect: +/// - All single-bit errors +/// - All double-bit errors +/// - Random errors with a probability of 2^-6 = 0.015625 of going undetected +#[cfg(test)] +pub fn calc_crc6(data: u32) -> u8 { + let mut working_data: u32 = data << 6; + let mut polynom: u32 = 0x43 << 25; + let mut mask: u32 = 0x40 << 25; + while working_data >= 0x40 { + if working_data & mask != 0 { + working_data ^= polynom; + } else { + polynom >>= 1; + mask >>= 1; + } + } + (working_data & 0x3f) as u8 +} + +/// CRC-6 checking +/// +/// This function is the mirror piece to the crc6 calculation, it checks a crc value against +/// the data to determine if the crc is valid. +pub fn check_crc6(data: u32, crc: u8) -> bool { + let mut working_data: u32 = data << 6 | crc as u32; + let mut polynom: u32 = 0x43 << 25; + let mut mask: u32 = 0x40 << 25; + while working_data >= 0x40 { + if working_data & mask != 0 { + working_data ^= polynom; + } else { + polynom >>= 1; + mask >>= 1; + } + } + working_data == 0x00000000 +} + #[cfg(test)] mod base_type_tests { - use std::println; - + use super::{ + Stored_U26, Transit_U26, U26_MAX_VALUE, calc_crc6, calc_hemming, check_crc6, check_hemming, + }; use crate::types::U26Error; - use super::{calc_hemming, check_hemming, Stored_U26, U26_MAX_VALUE}; - #[test] fn hemming_code_generation() { assert_eq!(calc_hemming(0x000), 0b00000); @@ -378,7 +428,7 @@ mod base_type_tests { let invalid_hemming = hemming ^ bit_error; let correction = check_hemming(data, invalid_hemming).unwrap_err(); let (_, hc) = correction.calc_correction_data(); - assert_eq!(invalid_hemming^hc, hemming); + assert_eq!(invalid_hemming ^ hc, hemming); } } @@ -391,4 +441,47 @@ mod base_type_tests { bytes[2] = bytes[2] ^ 0x01; assert_eq!(Stored_U26::from_be_bytes(bytes).unwrap(), data); } + + #[test] + fn crc6_simple_data() { + assert_eq!(calc_crc6(0x0000000), 0x00); + assert_eq!(calc_crc6(0x0000001), 0x03); + assert_eq!(calc_crc6(0x0000043), 0x00); + assert_eq!(calc_crc6(0x3ffffff), 0x06); + } + + #[test] + fn crc6_consistency() { + // Test that same input always produces same output + let data = 0x123456; + let crc = calc_crc6(data); + assert!(check_crc6(data, crc)) + } + + #[test] + fn crc6_different_inputs_different_outputs() { + // Test that different inputs produce different CRCs (with high probability) + let data1 = 0x123456; + let data2 = 0x123457; + let crc1 = calc_crc6(data1); + let crc2 = calc_crc6(data2); + assert_ne!(crc1, crc2); + } + + #[test] + fn crc6_bit_error_detection() { + // Test that single bit errors are detected + let original_data = 0x123456; + let original_crc = calc_crc6(original_data); + + // Flip each bit and verify CRC changes + for bit_pos in 0..26 { + let mut corrupted_data = original_data; + let mask = 1 << (25 - bit_pos); // Flip specific bit + corrupted_data ^= mask; + + // With high probability, CRC should change + assert!(!check_crc6(corrupted_data, original_crc)); + } + } }