use core::fmt;
use kinavis_kernel::InlineStr;
use crate::error::AisError;
pub const MAX_MESSAGE_BITS: usize = MAX_MESSAGE_BYTES * 8;
const MAX_MESSAGE_BYTES: usize = 136;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Bits {
bytes: [u8; MAX_MESSAGE_BYTES],
len: usize,
}
impl Default for Bits {
fn default() -> Self {
Self::new()
}
}
impl Bits {
#[must_use]
pub const fn new() -> Self {
Self {
bytes: [0; MAX_MESSAGE_BYTES],
len: 0,
}
}
pub fn unarmour(payload: &[u8], fill_bits: u8) -> Result<Self, AisError> {
let mut bits = Self::new();
bits.append(payload, fill_bits)?;
Ok(bits)
}
pub fn append(&mut self, payload: &[u8], fill_bits: u8) -> Result<(), AisError> {
let payload_bits = payload.len().saturating_mul(6);
if usize::from(fill_bits) > payload_bits {
return Err(AisError::BadFill {
fill_bits,
payload_bits,
});
}
let added = payload_bits - usize::from(fill_bits);
let end = self.len.saturating_add(added);
if end > MAX_MESSAGE_BITS {
return Err(AisError::TooLong {
bits: end,
limit: MAX_MESSAGE_BITS,
});
}
if let Some(offset) = payload.iter().position(|&byte| unarmour(byte).is_none()) {
return Err(AisError::BadArmouring { offset });
}
let mut at = self.len;
for value in payload.iter().map(|&byte| unarmour(byte).unwrap_or(0)) {
for shift in (0..6).rev() {
if at == end {
break;
}
if (value >> shift) & 1 == 1 {
if let Some(byte) = self.bytes.get_mut(at / 8) {
*byte |= 0x80 >> (at % 8);
}
}
at = at.saturating_add(1);
}
}
self.len = end;
Ok(())
}
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn bit(&self, offset: usize) -> Option<bool> {
if offset >= self.len {
return None;
}
self.bytes
.get(offset / 8)
.map(|byte| (byte >> (7 - offset % 8)) & 1 == 1)
}
#[must_use]
pub fn unsigned(&self, offset: usize, width: usize) -> Option<u32> {
if width > 32 || offset.checked_add(width)? > self.len {
return None;
}
let mut value = 0;
for at in offset..offset + width {
value = (value << 1) | u32::from(self.bit(at)?);
}
Some(value)
}
#[must_use]
pub fn signed(&self, offset: usize, width: usize) -> Option<i32> {
if width == 0 {
return None;
}
let raw = self.unsigned(offset, width)?;
let sign = 1u32 << (width - 1);
#[allow(clippy::cast_possible_wrap)]
Some(if raw & sign == 0 {
raw as i32
} else {
(raw as i32).wrapping_sub((sign as i32).wrapping_mul(2))
})
}
#[must_use]
pub fn text<const N: usize>(&self, offset: usize, chars: usize) -> Option<InlineStr<N>> {
let end = offset.checked_add(chars.checked_mul(6)?)?;
if end > self.len {
return None;
}
self.text_at((0..chars).map(|index| offset + index * 6))
}
pub(crate) fn text_at<const N: usize>(
&self,
offsets: impl Iterator<Item = usize>,
) -> Option<InlineStr<N>> {
let mut buffer = [0_u8; N];
let mut len = 0;
for (slot, offset) in buffer.iter_mut().zip(offsets) {
#[allow(clippy::cast_possible_truncation)]
let code = self.unsigned(offset, 6)? as u8;
if code == 0 {
break;
}
*slot = if code < 32 { code + 64 } else { code };
len += 1;
}
while len > 0 && buffer.get(len - 1) == Some(&b' ') {
len -= 1;
}
let text = core::str::from_utf8(buffer.get(..len)?).ok()?;
Some(InlineStr::new(text))
}
}
const fn unarmour(byte: u8) -> Option<u8> {
match byte {
b'0'..=b'W' => Some(byte - 48),
b'`'..=b'w' => Some(byte - 56),
_ => None,
}
}
impl fmt::Debug for Bits {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Bits({} bits)", self.len)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn the_alphabet_is_the_standards() {
assert_eq!(unarmour(b'0'), Some(0));
assert_eq!(unarmour(b'W'), Some(39));
assert_eq!(unarmour(b'`'), Some(40));
assert_eq!(unarmour(b'w'), Some(63));
assert_eq!(unarmour(b'X'), None);
assert_eq!(unarmour(b'x'), None);
assert_eq!(unarmour(b' '), None);
}
#[test]
fn fields_read_most_significant_bit_first() {
let bits = Bits::unarmour(b"1w", 0).unwrap();
assert_eq!(bits.len(), 12);
assert_eq!(bits.unsigned(0, 6), Some(1));
assert_eq!(bits.unsigned(6, 6), Some(63));
assert_eq!(bits.unsigned(5, 2), Some(3));
assert_eq!(bits.unsigned(0, 12), Some(0b0000_0111_1111));
assert_eq!(bits.unsigned(7, 6), None);
assert_eq!(bits.unsigned(0, 33), None);
}
#[test]
fn signed_fields_are_twos_complement() {
let bits = Bits::unarmour(b"1w", 0).unwrap();
assert_eq!(bits.signed(6, 6), Some(-1));
assert_eq!(bits.signed(0, 6), Some(1));
assert_eq!(bits.signed(5, 2), Some(-1));
assert_eq!(bits.signed(0, 0), None);
let bits = Bits::unarmour(b"w00000", 0).unwrap();
assert_eq!(bits.signed(0, 32), Some(-67_108_864));
}
#[test]
fn fill_bits_come_off_the_end_and_stay_zero() {
let bits = Bits::unarmour(b"ww", 2).unwrap();
assert_eq!(bits.len(), 10);
assert_eq!(bits.unsigned(0, 10), Some(0x3FF));
assert_eq!(bits.bit(10), None);
assert_eq!(bits, Bits::unarmour(b"wt", 2).unwrap());
assert_eq!(
Bits::unarmour(b"", 1),
Err(AisError::BadFill {
fill_bits: 1,
payload_bits: 0
})
);
}
#[test]
fn appending_continues_where_the_last_fragment_ended() {
let mut bits = Bits::unarmour(b"1", 0).unwrap();
bits.append(b"w", 0).unwrap();
assert_eq!(bits, Bits::unarmour(b"1w", 0).unwrap());
assert_eq!(
bits.append(b"1X", 0),
Err(AisError::BadArmouring { offset: 1 })
);
assert_eq!(bits.len(), 12, "nothing appended on an error");
}
#[test]
fn text_is_the_standards_alphabet_cut_at_the_padding() {
let mut bits = Bits::new();
for code in 0..64_u8 {
let armoured = if code < 40 { code + 48 } else { code + 56 };
bits.append(&[armoured], 0).unwrap();
}
let alphabet = bits.text::<64>(0, 64).unwrap();
assert_eq!(alphabet, "");
let alphabet = bits.text::<64>(6, 63).unwrap();
assert_eq!(
alphabet,
"ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?"
);
let bits = Bits::unarmour(b"5P5TL01VIaAL@7WKO@mBplU@<PDhh000000001S", 0).unwrap();
assert_eq!(bits.text::<20>(106, 20).unwrap(), "MT.MITCHELL");
assert_eq!(bits.text::<4>(106, 20).unwrap(), "MT.M");
assert_eq!(bits.text::<20>(106, 22), None);
assert_eq!(bits.text::<20>(usize::MAX, 1), None);
}
#[test]
fn trailing_spaces_come_off_and_inner_ones_stay() {
let mut bits = Bits::new();
for code in [1_u8, 32, 2, 32, 32, 0, 3] {
let armoured = if code < 40 { code + 48 } else { code + 56 };
bits.append(&[armoured], 0).unwrap();
}
assert_eq!(bits.text::<8>(0, 7).unwrap(), "A B");
assert_eq!(bits.text::<8>(18, 2).unwrap(), "");
}
#[test]
fn a_message_cannot_outgrow_the_buffer() {
let mut bits = Bits::new();
let fragment = [b'w'; 62];
for _ in 0..2 {
bits.append(&fragment, 0).unwrap();
}
assert_eq!(bits.len(), 744);
assert_eq!(
bits.append(&fragment, 0),
Err(AisError::TooLong {
bits: 1116,
limit: MAX_MESSAGE_BITS
})
);
let exact = [b'w'; 58];
bits.append(&exact, 4).unwrap();
assert_eq!(bits.len(), MAX_MESSAGE_BITS);
assert_eq!(bits.unsigned(MAX_MESSAGE_BITS - 8, 8), Some(0xFF));
}
}