use crate::Error;
const ISSY_LONG_FORM_BIT: u8 = 0x80;
const ISCR_SHORT_PAYLOAD_MASK: u8 = 0x7F;
const ISCR_LONG_PAYLOAD_MASK: u8 = 0x3F;
const ISSY_SIGNALLING_BIT: u8 = 0x40;
const SIGNALLING_KIND_SHIFT: u32 = 20;
const SIGNALLING_KIND_MASK: u32 = 0x03;
const BUFS_UNIT_SHIFT: u32 = 18;
const BUFS_UNIT_MASK: u32 = 0x03;
const BUFS_VALUE_SHIFT: u32 = 8;
const BUFS_VALUE_MASK: u32 = 0x03FF;
const TTO_E_MSB_SHIFT: u32 = 16;
const TTO_E_MSB_MASK: u32 = 0x0F;
const TTO_E_LSB_SHIFT: u32 = 15;
const TTO_E_LSB_MASK: u32 = 0x01;
const TTO_M_SHIFT: u32 = 8;
const TTO_M_MASK: u32 = 0x7F;
const RESERVED_PAYLOAD_MASK: u32 = 0x0F_FFFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum BufsUnit {
Bits,
Kbits,
Mbits,
Kbits8,
}
impl BufsUnit {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v & BUFS_UNIT_MASK as u8 {
0 => Self::Bits,
1 => Self::Kbits,
2 => Self::Mbits,
3 => Self::Kbits8,
_ => unreachable!(),
}
}
#[must_use]
pub fn to_u8(self) -> u8 {
match self {
Self::Bits => 0,
Self::Kbits => 1,
Self::Mbits => 2,
Self::Kbits8 => 3,
}
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::Bits => "bits",
Self::Kbits => "Kbits",
Self::Mbits => "Mbits",
Self::Kbits8 => "8 Kbits",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum SignallingKind {
Bufs {
bufs: u16,
units: BufsUnit,
},
Tto {
tto_e: u8,
tto_m: u8,
tto_l: u8,
},
Reserved(u32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum Issy {
IscrShort(u16),
IscrLong(u32),
Signalling(SignallingKind),
}
pub fn decode_issy_short(bytes: [u8; 2]) -> crate::Result<Issy> {
if bytes[0] & ISSY_LONG_FORM_BIT != 0 {
return Err(Error::InvalidIssyForm {
reason: "bit [7] is 1 (long form); use decode_issy_long for 3-byte ISSY",
});
}
let iscr = ((bytes[0] as u16 & ISCR_SHORT_PAYLOAD_MASK as u16) << 8) | bytes[1] as u16;
Ok(Issy::IscrShort(iscr))
}
pub fn decode_issy_long(bytes: [u8; 3]) -> crate::Result<Issy> {
if bytes[0] & ISSY_LONG_FORM_BIT == 0 {
return Err(Error::InvalidIssyForm {
reason: "bit [7] is 0 (short form); use decode_issy_short for 2-byte ISSY",
});
}
let payload = ((bytes[0] as u32 & ISCR_LONG_PAYLOAD_MASK as u32) << 16)
| (bytes[1] as u32) << 8
| bytes[2] as u32;
if bytes[0] & ISSY_SIGNALLING_BIT == 0 {
Ok(Issy::IscrLong(payload))
} else {
Ok(Issy::Signalling(decode_signalling(payload)))
}
}
fn decode_signalling(payload: u32) -> SignallingKind {
let kind = (payload >> SIGNALLING_KIND_SHIFT) & SIGNALLING_KIND_MASK;
match kind {
0 => {
let units = BufsUnit::from_u8(((payload >> BUFS_UNIT_SHIFT) & BUFS_UNIT_MASK) as u8);
let bufs = ((payload >> BUFS_VALUE_SHIFT) & BUFS_VALUE_MASK) as u16;
SignallingKind::Bufs { bufs, units }
}
1 => {
let tto_e = (((payload >> TTO_E_MSB_SHIFT) & TTO_E_MSB_MASK) << 1
| ((payload >> TTO_E_LSB_SHIFT) & TTO_E_LSB_MASK)) as u8;
let tto_m = ((payload >> TTO_M_SHIFT) & TTO_M_MASK) as u8;
let tto_l = (payload & 0xFF) as u8;
SignallingKind::Tto {
tto_e,
tto_m,
tto_l,
}
}
_ => {
let remainder = payload & RESERVED_PAYLOAD_MASK;
SignallingKind::Reserved(remainder)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn iscr_short_decodes_15_bits() {
assert_eq!(decode_issy_short([0x7A, 0xBC]), Ok(Issy::IscrShort(0x7ABC)));
assert_eq!(decode_issy_short([0x00, 0x01]), Ok(Issy::IscrShort(1)));
}
#[test]
fn short_rejects_long_prefix() {
assert!(decode_issy_short([0x80, 0x00]).is_err());
}
#[test]
fn iscr_long_decodes_22_bits() {
assert_eq!(
decode_issy_long([0xBF, 0xFF, 0xFF]),
Ok(Issy::IscrLong(0x3FFFFF))
);
assert_eq!(
decode_issy_long([0x80, 0x12, 0x34]),
Ok(Issy::IscrLong(0x1234))
);
}
#[test]
fn signalling_decodes_with_11_prefix() {
assert_eq!(
decode_issy_long([0xC0, 0x12, 0x34]),
Ok(Issy::Signalling(decode_signalling(0x1234)))
);
}
#[test]
fn long_rejects_short_prefix() {
assert!(decode_issy_long([0x00, 0x00, 0x00]).is_err());
}
#[test]
fn signalling_bufs_decode() {
let result = decode_issy_long([0xCB, 0xFF, 0x00]).unwrap();
match result {
Issy::Signalling(SignallingKind::Bufs { bufs, units }) => {
assert_eq!(bufs, 0x3FF);
assert_eq!(units, BufsUnit::Mbits);
}
other => panic!("expected BUFS, got {other:?}"),
}
}
#[test]
fn signalling_tto_decode() {
let result = decode_issy_long([0xD5, 0xFF, 0x80]).unwrap();
match result {
Issy::Signalling(SignallingKind::Tto {
tto_e,
tto_m,
tto_l,
}) => {
assert_eq!(tto_e, 11);
assert_eq!(tto_m, 127);
assert_eq!(tto_l, 128);
}
other => panic!("expected TTO, got {other:?}"),
}
}
#[test]
fn signalling_reserved_decode() {
let result = decode_issy_long([0xE0, 0x00, 0x00]).unwrap();
match result {
Issy::Signalling(SignallingKind::Reserved(remainder)) => {
assert_eq!(remainder, 0x00000);
}
other => panic!("expected Reserved, got {other:?}"),
}
}
#[test]
fn bufs_unit_round_trip() {
for b in 0..=3u8 {
assert_eq!(BufsUnit::from_u8(b).to_u8(), b);
}
}
#[test]
fn bufs_unit_name() {
assert_eq!(BufsUnit::Bits.name(), "bits");
assert_eq!(BufsUnit::Kbits.name(), "Kbits");
assert_eq!(BufsUnit::Mbits.name(), "Mbits");
assert_eq!(BufsUnit::Kbits8.name(), "8 Kbits");
}
}