Files
trx-rs/src/decoders/trx-vdes/src/crc.rs
T
sjg ba48de2d30
Sync docs to Wiki / wiki (push) Has been cancelled
Initial commit
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-05-17 23:25:14 +02:00

154 lines
4.3 KiB
Rust

// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
//
// SPDX-License-Identifier: GPL-2.0-or-later
//! CRC-16 for VDES link-layer frames.
//!
//! ITU-R M.2092-1 uses the same CRC-16-CCITT polynomial (0x1021) as AIS,
//! applied over the decoded information bits (excluding FEC tail). The CRC
//! is transmitted MSB-first in the encoded frame.
/// Pre-computed CRC-16-CCITT lookup table (normal / MSB-first form,
/// polynomial 0x1021).
const CRC16_CCITT_TABLE: [u16; 256] = {
let mut table = [0u16; 256];
let mut i = 0usize;
while i < 256 {
let mut crc = (i as u16) << 8;
let mut j = 0;
while j < 8 {
if crc & 0x8000 != 0 {
crc = (crc << 1) ^ 0x1021;
} else {
crc <<= 1;
}
j += 1;
}
table[i] = crc;
i += 1;
}
table
};
/// Compute CRC-16-CCITT over a byte slice (MSB-first, init 0xFFFF).
pub fn crc16_ccitt(data: &[u8]) -> u16 {
let mut crc: u16 = 0xFFFF;
for &b in data {
crc = (crc << 8) ^ CRC16_CCITT_TABLE[((crc >> 8) ^ b as u16) as usize];
}
crc ^ 0xFFFF
}
/// Compute CRC-16-CCITT over a bit slice (MSB-first packing).
///
/// Packs the bit slice into bytes (zero-padding the last byte if needed),
/// then runs the CRC over the packed data.
pub fn crc16_ccitt_bits(bits: &[u8]) -> u16 {
let bytes = pack_bits_to_bytes(bits);
crc16_ccitt(&bytes)
}
/// Check CRC-16-CCITT on a decoded bit-stream.
///
/// The last 16 bits of `bits` are the transmitted CRC. Returns `true` if
/// the CRC computed over the preceding bits matches the received CRC.
pub fn check_crc16(bits: &[u8]) -> bool {
if bits.len() < 16 {
return false;
}
let payload_bits = &bits[..bits.len() - 16];
let crc_bits = &bits[bits.len() - 16..];
let computed = crc16_ccitt_bits(payload_bits);
let received = bits_to_u16(crc_bits);
computed == received
}
/// Extract the 16-bit CRC value from a bit slice.
fn bits_to_u16(bits: &[u8]) -> u16 {
let mut value = 0u16;
for &bit in bits.iter().take(16) {
value = (value << 1) | u16::from(bit & 1);
}
value
}
/// Pack a bit slice into bytes (MSB-first, zero-pad last byte).
fn pack_bits_to_bytes(bits: &[u8]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(bits.len().div_ceil(8));
for chunk in bits.chunks(8) {
let mut byte = 0u8;
for (i, &bit) in chunk.iter().enumerate() {
byte |= (bit & 1) << (7 - i);
}
bytes.push(byte);
}
bytes
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crc16_known_vector() {
// CRC-16-CCITT (init=0xFFFF, poly=0x1021, xorout=0xFFFF) of "123456789"
let data = b"123456789";
let crc = crc16_ccitt(data);
assert_eq!(crc, 0xD64E, "CRC-16-CCITT of '123456789'");
}
#[test]
fn crc16_bits_matches_bytes() {
let data = [0xDE, 0xAD, 0xBE, 0xEF];
let crc_bytes = crc16_ccitt(&data);
let bits: Vec<u8> = data
.iter()
.flat_map(|&b| (0..8).rev().map(move |i| (b >> i) & 1))
.collect();
let crc_bits = crc16_ccitt_bits(&bits);
assert_eq!(crc_bytes, crc_bits);
}
#[test]
fn check_crc16_valid() {
let payload = [0x01, 0x02, 0x03, 0x04];
let crc = crc16_ccitt(&payload);
let mut bits: Vec<u8> = payload
.iter()
.flat_map(|&b| (0..8).rev().map(move |i| (b >> i) & 1))
.collect();
for i in (0..16).rev() {
bits.push(((crc >> i) & 1) as u8);
}
assert!(check_crc16(&bits));
}
#[test]
fn check_crc16_invalid() {
let payload = [0x01, 0x02, 0x03, 0x04];
let mut bits: Vec<u8> = payload
.iter()
.flat_map(|&b| (0..8).rev().map(move |i| (b >> i) & 1))
.collect();
// Append wrong CRC
for _ in 0..16 {
bits.push(0);
}
assert!(!check_crc16(&bits));
}
#[test]
fn pack_bits_round_trips() {
let original = [0xAB, 0xCD];
let bits: Vec<u8> = original
.iter()
.flat_map(|&b| (0..8).rev().map(move |i| (b >> i) & 1))
.collect();
let packed = pack_bits_to_bytes(&bits);
assert_eq!(packed, original);
}
}