use crate::log_protocol;
use crate::logging::LogLevel;
const T_CONNECT: u8 = 0x80;
const T_DISCONNECT: u8 = 0x81;
const T_DATA_CONNECTED: u8 = 0x40;
const T_ACK: u8 = 0xC2;
const T_NAK: u8 = 0xC3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TpciFrame {
Connect,
Disconnect,
DataConnected {
sequence: u8,
},
Ack {
sequence: u8,
},
Nak {
sequence: u8,
},
}
impl TpciFrame {
#[must_use]
pub fn encode(&self) -> u8 {
let result = match self {
Self::Connect => T_CONNECT,
Self::Disconnect => T_DISCONNECT,
Self::DataConnected { sequence } => T_DATA_CONNECTED | ((sequence & 0x0F) << 2),
Self::Ack { sequence } => T_ACK | ((sequence & 0x0F) << 2),
Self::Nak { sequence } => T_NAK | ((sequence & 0x0F) << 2),
};
log_protocol!(
LogLevel::Trace,
"TPCI encode: {:?} \u{2192} 0x{:02X}",
self,
result
);
result
}
#[must_use]
pub fn decode(byte: u8) -> TpciFrame {
let sequence = (byte >> 2) & 0x0F;
let frame = match byte {
T_CONNECT => Self::Connect,
T_DISCONNECT => Self::Disconnect,
b if (b & 0xC3) == T_ACK => Self::Ack { sequence },
b if (b & 0xC3) == T_NAK => Self::Nak { sequence },
b if (b & 0xC3) == T_DATA_CONNECTED => Self::DataConnected { sequence },
_ => {
log_protocol!(
LogLevel::Warn,
"TPCI: unrecognised control byte 0x{:02X}",
byte
);
Self::DataConnected { sequence }
}
};
log_protocol!(
LogLevel::Trace,
"TPCI decode: 0x{:02X} \u{2192} {:?}",
byte,
frame
);
frame
}
}
#[cfg(test)]
mod tests {
use super::TpciFrame;
#[test]
fn connect_round_trip() {
assert_eq!(TpciFrame::Connect.encode(), 0x80);
assert_eq!(TpciFrame::decode(0x80), TpciFrame::Connect);
}
#[test]
fn disconnect_round_trip() {
assert_eq!(TpciFrame::Disconnect.encode(), 0x81);
assert_eq!(TpciFrame::decode(0x81), TpciFrame::Disconnect);
}
#[test]
fn data_connected_round_trip() {
for sequence in [0u8, 1, 5, 15] {
let frame = TpciFrame::DataConnected { sequence };
let byte = frame.encode();
assert_eq!(byte, 0x40 | (sequence << 2));
assert_eq!(TpciFrame::decode(byte), frame);
}
}
#[test]
fn ack_round_trip() {
for sequence in [0u8, 3, 7, 15] {
let frame = TpciFrame::Ack { sequence };
let byte = frame.encode();
assert_eq!(byte, 0xC2 | (sequence << 2));
assert_eq!(TpciFrame::decode(byte), frame);
}
}
#[test]
fn nak_round_trip() {
for sequence in [0u8, 2, 9, 15] {
let frame = TpciFrame::Nak { sequence };
let byte = frame.encode();
assert_eq!(byte, 0xC3 | (sequence << 2));
assert_eq!(TpciFrame::decode(byte), frame);
}
}
#[test]
fn data_connected_known_bytes() {
assert_eq!(
TpciFrame::decode(0x40),
TpciFrame::DataConnected { sequence: 0 }
);
assert_eq!(
TpciFrame::decode(0x44),
TpciFrame::DataConnected { sequence: 1 }
);
}
#[test]
fn ack_known_bytes() {
assert_eq!(TpciFrame::decode(0xC2), TpciFrame::Ack { sequence: 0 });
assert_eq!(TpciFrame::decode(0xC6), TpciFrame::Ack { sequence: 1 });
}
#[test]
fn nak_known_bytes() {
assert_eq!(TpciFrame::decode(0xC3), TpciFrame::Nak { sequence: 0 });
assert_eq!(TpciFrame::decode(0xC7), TpciFrame::Nak { sequence: 1 });
}
#[test]
fn sequence_is_masked_to_four_bits() {
let frame = TpciFrame::DataConnected { sequence: 0x10 };
assert_eq!(frame.encode(), 0x40);
}
}