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",
}
}
#[must_use]
pub fn multiplier_bits(self) -> u64 {
match self {
Self::Bits => 1,
Self::Kbits => 1_000,
Self::Mbits => 1_000_000,
Self::Kbits8 => 8_000,
}
}
}
#[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,
},
BufStat {
bufstat: u16,
units: BufsUnit,
},
Reserved(u32),
}
impl SignallingKind {
#[must_use]
pub fn bufs_bits(&self) -> Option<u64> {
match self {
Self::Bufs { bufs, units } => Some(*bufs as u64 * units.multiplier_bits()),
_ => None,
}
}
#[must_use]
pub fn bufs_bytes(&self) -> Option<u64> {
self.bufs_bits().map(|b| b / 8)
}
#[must_use]
pub fn bufstat_bits(&self) -> Option<u64> {
match self {
Self::BufStat { bufstat, units } => Some(*bufstat as u64 * units.multiplier_bits()),
_ => None,
}
}
#[must_use]
pub fn bufstat_bytes(&self) -> Option<u64> {
self.bufstat_bits().map(|b| b / 8)
}
#[must_use]
pub fn tto_t_over_256(&self) -> Option<u64> {
match self {
Self::Tto {
tto_e,
tto_m,
tto_l,
} => Some((u64::from(*tto_m) * 256 + u64::from(*tto_l)) << tto_e),
_ => None,
}
}
}
#[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,
}
}
2 => {
let units = BufsUnit::from_u8(((payload >> BUFS_UNIT_SHIFT) & BUFS_UNIT_MASK) as u8);
let bufstat = ((payload >> BUFS_VALUE_SHIFT) & BUFS_VALUE_MASK) as u16;
SignallingKind::BufStat { bufstat, units }
}
_ => {
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_bufstat_decode() {
let result = decode_issy_long([0xEB, 0xFF, 0x00]).unwrap();
match result {
Issy::Signalling(SignallingKind::BufStat { bufstat, units }) => {
assert_eq!(bufstat, 0x3FF);
assert_eq!(units, BufsUnit::Mbits);
}
other => panic!("expected BufStat, got {other:?}"),
}
let bs = SignallingKind::BufStat {
bufstat: 2,
units: BufsUnit::Mbits,
};
assert_eq!(bs.bufstat_bits(), Some(2_000_000));
assert_eq!(bs.bufstat_bytes(), Some(250_000));
assert_eq!(SignallingKind::Reserved(0).bufstat_bits(), None);
}
#[test]
fn signalling_reserved_decode() {
let result = decode_issy_long([0xF0, 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");
}
#[test]
fn multiplier_bits() {
assert_eq!(BufsUnit::Bits.multiplier_bits(), 1);
assert_eq!(BufsUnit::Kbits.multiplier_bits(), 1_000);
assert_eq!(BufsUnit::Mbits.multiplier_bits(), 1_000_000);
assert_eq!(BufsUnit::Kbits8.multiplier_bits(), 8_000);
}
#[test]
fn bufs_bits_and_bytes() {
let b = SignallingKind::Bufs {
bufs: 2,
units: BufsUnit::Mbits,
};
assert_eq!(b.bufs_bits(), Some(2_000_000));
assert_eq!(b.bufs_bytes(), Some(250_000));
let b = SignallingKind::Bufs {
bufs: 1,
units: BufsUnit::Bits,
};
assert_eq!(b.bufs_bits(), Some(1));
assert_eq!(b.bufs_bytes(), Some(0));
let b = SignallingKind::Bufs {
bufs: 3,
units: BufsUnit::Kbits8,
};
assert_eq!(b.bufs_bits(), Some(24_000));
assert_eq!(b.bufs_bytes(), Some(3_000));
let t = SignallingKind::Tto {
tto_e: 0,
tto_m: 0,
tto_l: 0,
};
assert_eq!(t.bufs_bits(), None);
assert_eq!(t.bufs_bytes(), None);
assert_eq!(SignallingKind::Reserved(0).bufs_bits(), None);
}
#[test]
fn tto_t_over_256() {
let t = SignallingKind::Tto {
tto_e: 0,
tto_m: 1,
tto_l: 0,
};
assert_eq!(t.tto_t_over_256(), Some(256));
let t = SignallingKind::Tto {
tto_e: 1,
tto_m: 0,
tto_l: 128,
};
assert_eq!(t.tto_t_over_256(), Some(256));
let t = SignallingKind::Tto {
tto_e: 5,
tto_m: 3,
tto_l: 64,
};
assert_eq!(t.tto_t_over_256(), Some(26_624));
let b = SignallingKind::Bufs {
bufs: 1,
units: BufsUnit::Bits,
};
assert_eq!(b.tto_t_over_256(), None);
assert_eq!(SignallingKind::Reserved(0).tto_t_over_256(), None);
}
}