const FORMAT_INFO_GEN: u32 = 0x537;
pub const FORMAT_INFO_MASK: u32 = 0x5412;
const VERSION_INFO_GEN: u32 = 0x1F25;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EcLevel {
L,
M,
Q,
H,
}
impl EcLevel {
pub fn bits(self) -> u8 {
match self {
EcLevel::L => 0b01,
EcLevel::M => 0b00,
EcLevel::Q => 0b11,
EcLevel::H => 0b10,
}
}
pub fn from_bits(b: u8) -> Option<EcLevel> {
Some(match b & 0b11 {
0b01 => EcLevel::L,
0b00 => EcLevel::M,
0b11 => EcLevel::Q,
0b10 => EcLevel::H,
_ => return None,
})
}
}
fn polynomial_remainder(mut dividend: u32, generator: u32, gen_deg: u32) -> u32 {
let mut top = 31u32;
while top >= gen_deg {
if (dividend >> top) & 1 == 1 {
dividend ^= generator << (top - gen_deg);
}
if top == 0 {
break;
}
top -= 1;
}
dividend
}
pub fn encode_format(ec_level: EcLevel, mask: u8) -> u32 {
debug_assert!(mask < 8);
let data = ((ec_level.bits() as u32) << 3) | (mask as u32 & 0b111); let remainder = polynomial_remainder(data << 10, FORMAT_INFO_GEN, 10);
((data << 10) | remainder) ^ FORMAT_INFO_MASK
}
pub fn decode_format(received: u32) -> Option<(EcLevel, u8, u8)> {
let received = received & 0x7FFF;
let mut best: Option<(EcLevel, u8, u8)> = None;
for level_bits in [0b00u8, 0b01, 0b10, 0b11] {
let level = EcLevel::from_bits(level_bits).unwrap();
for mask in 0u8..8 {
let codeword = encode_format(level, mask);
let dist = (codeword ^ received).count_ones() as u8;
if dist > 3 {
continue;
}
best = match best {
None => Some((level, mask, dist)),
Some((_, _, bd)) if dist < bd => Some((level, mask, dist)),
_ => best,
};
}
}
best
}
pub fn encode_version(version: u8) -> u32 {
debug_assert!((7..=40).contains(&version));
let data = version as u32;
let remainder = polynomial_remainder(data << 12, VERSION_INFO_GEN, 12);
(data << 12) | remainder
}
#[allow(dead_code)]
pub fn decode_version(received: u32) -> Option<(u8, u8)> {
let received = received & 0x3FFFF;
let mut best: Option<(u8, u8)> = None;
for v in 7u8..=40 {
let codeword = encode_version(v);
let dist = (codeword ^ received).count_ones() as u8;
if dist > 3 {
continue;
}
best = match best {
None => Some((v, dist)),
Some((_, bd)) if dist < bd => Some((v, dist)),
_ => best,
};
}
best
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_iso_examples() {
assert_eq!(encode_format(EcLevel::L, 0), 0x77C4);
assert_eq!(encode_format(EcLevel::L, 1), 0x72F3);
assert_eq!(encode_format(EcLevel::M, 0), 0x5412);
assert_eq!(encode_format(EcLevel::M, 5), 0x40CE);
assert_eq!(encode_format(EcLevel::Q, 0), 0x355F);
assert_eq!(encode_format(EcLevel::H, 0), 0x1689);
}
#[test]
fn format_round_trip() {
for &level in &[EcLevel::L, EcLevel::M, EcLevel::Q, EcLevel::H] {
for mask in 0u8..8 {
let cw = encode_format(level, mask);
let (l, m, d) = decode_format(cw).unwrap();
assert_eq!(l, level);
assert_eq!(m, mask);
assert_eq!(d, 0);
}
}
}
#[test]
fn format_corrects_three_errors() {
let cw = encode_format(EcLevel::Q, 5);
let corrupted = cw ^ 0b101_0000_0010_0000;
let (l, m, d) = decode_format(corrupted).unwrap();
assert_eq!(l, EcLevel::Q);
assert_eq!(m, 5);
assert_eq!(d, 3);
}
#[test]
fn version_iso_examples() {
assert_eq!(encode_version(7), 0x07C94);
assert_eq!(encode_version(8), 0x085BC);
assert_eq!(encode_version(40), 0x28C69);
}
#[test]
fn version_round_trip() {
for v in 7u8..=40 {
let cw = encode_version(v);
let (vr, d) = decode_version(cw).unwrap();
assert_eq!(vr, v);
assert_eq!(d, 0);
}
}
#[test]
fn version_corrects_three_errors() {
let cw = encode_version(25);
let corrupted = cw ^ 0b1_0000_0001_0000_0001;
let (v, d) = decode_version(corrupted).unwrap();
assert_eq!(v, 25);
assert_eq!(d, 3);
}
#[test]
fn ec_level_bits_round_trip() {
for &l in &[EcLevel::L, EcLevel::M, EcLevel::Q, EcLevel::H] {
assert_eq!(EcLevel::from_bits(l.bits()), Some(l));
}
}
}