use alloc::vec::Vec;
use crate::error::{Error, Result};
use crate::ext::{self, HeaderExtension, WORD};
pub const NORM_VERSION: u8 = 1;
pub const COMMON_HEADER_LEN: usize = 8;
pub const SENDER_WORD_LEN: usize = 4;
pub const NORM_NODE_NONE: u32 = 0x0000_0000;
pub const NORM_NODE_ANY: u32 = 0xFFFF_FFFF;
pub const HET_EXT_AUTH: u8 = 1;
pub const HET_EXT_CC: u8 = 3;
pub const HET_EXT_FTI: u8 = 64;
pub const HET_EXT_RATE: u8 = 128;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum NormMessageType {
Info,
Data,
Cmd,
Nack,
Ack,
Report,
Other(u8),
}
impl NormMessageType {
pub fn from_u8(v: u8) -> Self {
match v {
1 => NormMessageType::Info,
2 => NormMessageType::Data,
3 => NormMessageType::Cmd,
4 => NormMessageType::Nack,
5 => NormMessageType::Ack,
6 => NormMessageType::Report,
other => NormMessageType::Other(other),
}
}
pub fn to_u8(self) -> u8 {
match self {
NormMessageType::Info => 1,
NormMessageType::Data => 2,
NormMessageType::Cmd => 3,
NormMessageType::Nack => 4,
NormMessageType::Ack => 5,
NormMessageType::Report => 6,
NormMessageType::Other(v) => v,
}
}
pub fn name(&self) -> &'static str {
match self {
NormMessageType::Info => "NORM_INFO",
NormMessageType::Data => "NORM_DATA",
NormMessageType::Cmd => "NORM_CMD",
NormMessageType::Nack => "NORM_NACK",
NormMessageType::Ack => "NORM_ACK",
NormMessageType::Report => "NORM_REPORT",
NormMessageType::Other(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(NormMessageType, Other);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct NormCommonHeader {
pub version: u8,
pub message_type: NormMessageType,
pub sequence: u16,
pub source_id: u32,
}
impl NormCommonHeader {
pub fn parse(data: &[u8]) -> Result<(Self, u8)> {
if data.len() < COMMON_HEADER_LEN {
return Err(Error::BufferTooShort {
need: COMMON_HEADER_LEN,
have: data.len(),
what: "NORM common header",
});
}
let version = data[0] >> 4;
let message_type = NormMessageType::from_u8(data[0] & 0x0F);
let hdr_len = data[1];
let sequence = u16::from_be_bytes([data[2], data[3]]);
let source_id = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
Ok((
NormCommonHeader {
version,
message_type,
sequence,
source_id,
},
hdr_len,
))
}
pub fn serialize_into(&self, out: &mut [u8], hdr_len: u8) -> Result<usize> {
if out.len() < COMMON_HEADER_LEN {
return Err(Error::OutputBufferTooSmall {
need: COMMON_HEADER_LEN,
have: out.len(),
});
}
if self.version > 0x0F {
return Err(Error::FieldTooWide {
what: "version",
value: self.version as u64,
bits: 4,
});
}
let ty = self.message_type.to_u8();
if ty > 0x0F {
return Err(Error::FieldTooWide {
what: "type",
value: ty as u64,
bits: 4,
});
}
out[0] = (self.version << 4) | (ty & 0x0F);
out[1] = hdr_len;
out[2..4].copy_from_slice(&self.sequence.to_be_bytes());
out[4..8].copy_from_slice(&self.source_id.to_be_bytes());
Ok(COMMON_HEADER_LEN)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SenderWord {
pub instance_id: u16,
pub grtt: u8,
pub backoff: u8,
pub gsize: u8,
}
impl SenderWord {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < SENDER_WORD_LEN {
return Err(Error::BufferTooShort {
need: SENDER_WORD_LEN,
have: data.len(),
what: "NORM sender word",
});
}
Ok(SenderWord {
instance_id: u16::from_be_bytes([data[0], data[1]]),
grtt: data[2],
backoff: data[3] >> 4,
gsize: data[3] & 0x0F,
})
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
if out.len() < SENDER_WORD_LEN {
return Err(Error::OutputBufferTooSmall {
need: SENDER_WORD_LEN,
have: out.len(),
});
}
if self.backoff > 0x0F {
return Err(Error::FieldTooWide {
what: "backoff",
value: self.backoff as u64,
bits: 4,
});
}
if self.gsize > 0x0F {
return Err(Error::FieldTooWide {
what: "gsize",
value: self.gsize as u64,
bits: 4,
});
}
out[0..2].copy_from_slice(&self.instance_id.to_be_bytes());
out[2] = self.grtt;
out[3] = (self.backoff << 4) | (self.gsize & 0x0F);
Ok(SENDER_WORD_LEN)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct NormInfo<'a> {
pub common: NormCommonHeader,
pub sender: SenderWord,
pub flags: u8,
pub fec_id: u8,
pub object_transport_id: u16,
pub extensions: Vec<HeaderExtension<'a>>,
pub payload: &'a [u8],
}
pub const NORM_INFO_FIXED_LEN: usize = COMMON_HEADER_LEN + SENDER_WORD_LEN + WORD;
impl<'a> NormInfo<'a> {
fn header_bytes(&self) -> usize {
NORM_INFO_FIXED_LEN + ext::chain_len(&self.extensions)
}
pub fn serialized_len(&self) -> usize {
self.header_bytes() + self.payload.len()
}
pub fn parse(data: &'a [u8]) -> Result<Self> {
let (common, hdr_len) = NormCommonHeader::parse(data)?;
let sender = SenderWord::parse(&data[COMMON_HEADER_LEN..])?;
let off = COMMON_HEADER_LEN + SENDER_WORD_LEN;
if data.len() < off + WORD {
return Err(Error::BufferTooShort {
need: off + WORD,
have: data.len(),
what: "NORM_INFO flags word",
});
}
let flags = data[off];
let fec_id = data[off + 1];
let object_transport_id = u16::from_be_bytes([data[off + 2], data[off + 3]]);
let header_end = hdr_len as usize * WORD;
if header_end < NORM_INFO_FIXED_LEN {
return Err(Error::InconsistentLength {
length: hdr_len,
reason: "hdr_len smaller than the NORM_INFO fixed header",
});
}
if data.len() < header_end {
return Err(Error::BufferTooShort {
need: header_end,
have: data.len(),
what: "NORM_INFO header (per hdr_len)",
});
}
let extensions = ext::parse_chain(&data[NORM_INFO_FIXED_LEN..header_end])?;
let payload = &data[header_end..];
Ok(NormInfo {
common,
sender,
flags,
fec_id,
object_transport_id,
extensions,
payload,
})
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
if out.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: out.len(),
});
}
let header_bytes = self.header_bytes();
if header_bytes % WORD != 0 {
return Err(Error::InvalidField {
what: "hdr_len",
reason: "NORM_INFO header length is not a multiple of 4 bytes",
});
}
let words = header_bytes / WORD;
if words > u8::MAX as usize {
return Err(Error::FieldTooWide {
what: "hdr_len",
value: words as u64,
bits: 8,
});
}
let mut off = self.common.serialize_into(out, words as u8)?;
off += self.sender.serialize_into(&mut out[off..])?;
out[off] = self.flags;
out[off + 1] = self.fec_id;
out[off + 2..off + 4].copy_from_slice(&self.object_transport_id.to_be_bytes());
off += WORD;
off += ext::serialize_chain(&self.extensions, &mut out[off..])?;
out[off..off + self.payload.len()].copy_from_slice(self.payload);
off += self.payload.len();
Ok(off)
}
}
pub const NORM_FLAG_REPAIR: u8 = 0x01;
pub const NORM_FLAG_EXPLICIT: u8 = 0x02;
pub const NORM_FLAG_INFO: u8 = 0x04;
pub const NORM_FLAG_UNRELIABLE: u8 = 0x08;
pub const NORM_FLAG_FILE: u8 = 0x10;
pub const NORM_FLAG_STREAM: u8 = 0x20;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct NormData<'a> {
pub common: NormCommonHeader,
pub sender: SenderWord,
pub flags: u8,
pub fec_id: u8,
pub object_transport_id: u16,
pub fec_payload_id: &'a [u8],
pub extensions: Vec<HeaderExtension<'a>>,
pub payload: &'a [u8],
}
impl<'a> NormData<'a> {
fn header_bytes(&self) -> usize {
COMMON_HEADER_LEN
+ SENDER_WORD_LEN
+ WORD
+ self.fec_payload_id.len()
+ ext::chain_len(&self.extensions)
}
pub fn serialized_len(&self) -> usize {
self.header_bytes() + self.payload.len()
}
pub fn parse(data: &'a [u8], fec_payload_id_len: usize) -> Result<Self> {
let (common, hdr_len) = NormCommonHeader::parse(data)?;
let sender = SenderWord::parse(&data[COMMON_HEADER_LEN..])?;
let mut off = COMMON_HEADER_LEN + SENDER_WORD_LEN;
if data.len() < off + WORD {
return Err(Error::BufferTooShort {
need: off + WORD,
have: data.len(),
what: "NORM_DATA flags word",
});
}
let flags = data[off];
let fec_id = data[off + 1];
let object_transport_id = u16::from_be_bytes([data[off + 2], data[off + 3]]);
off += WORD;
if data.len() < off + fec_payload_id_len {
return Err(Error::BufferTooShort {
need: off + fec_payload_id_len,
have: data.len(),
what: "NORM_DATA fec_payload_id",
});
}
let fec_payload_id = &data[off..off + fec_payload_id_len];
off += fec_payload_id_len;
let header_end = hdr_len as usize * WORD;
if header_end < off {
return Err(Error::InconsistentLength {
length: hdr_len,
reason: "hdr_len smaller than the fixed NORM_DATA header + fec_payload_id",
});
}
if data.len() < header_end {
return Err(Error::BufferTooShort {
need: header_end,
have: data.len(),
what: "NORM_DATA header (per hdr_len)",
});
}
let extensions = ext::parse_chain(&data[off..header_end])?;
let payload = &data[header_end..];
Ok(NormData {
common,
sender,
flags,
fec_id,
object_transport_id,
fec_payload_id,
extensions,
payload,
})
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
if out.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: out.len(),
});
}
let header_bytes = self.header_bytes();
if header_bytes % WORD != 0 {
return Err(Error::InvalidField {
what: "hdr_len",
reason: "NORM_DATA header length is not a multiple of 4 bytes",
});
}
let words = header_bytes / WORD;
if words > u8::MAX as usize {
return Err(Error::FieldTooWide {
what: "hdr_len",
value: words as u64,
bits: 8,
});
}
let mut off = self.common.serialize_into(out, words as u8)?;
off += self.sender.serialize_into(&mut out[off..])?;
out[off] = self.flags;
out[off + 1] = self.fec_id;
out[off + 2..off + 4].copy_from_slice(&self.object_transport_id.to_be_bytes());
off += WORD;
out[off..off + self.fec_payload_id.len()].copy_from_slice(self.fec_payload_id);
off += self.fec_payload_id.len();
off += ext::serialize_chain(&self.extensions, &mut out[off..])?;
out[off..off + self.payload.len()].copy_from_slice(self.payload);
off += self.payload.len();
Ok(off)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum NormCmdType {
Flush,
Eot,
Squelch,
Cc,
RepairAdv,
AckReq,
Application,
Other(u8),
}
impl NormCmdType {
pub fn from_u8(v: u8) -> Self {
match v {
1 => NormCmdType::Flush,
2 => NormCmdType::Eot,
3 => NormCmdType::Squelch,
4 => NormCmdType::Cc,
5 => NormCmdType::RepairAdv,
6 => NormCmdType::AckReq,
7 => NormCmdType::Application,
other => NormCmdType::Other(other),
}
}
pub fn to_u8(self) -> u8 {
match self {
NormCmdType::Flush => 1,
NormCmdType::Eot => 2,
NormCmdType::Squelch => 3,
NormCmdType::Cc => 4,
NormCmdType::RepairAdv => 5,
NormCmdType::AckReq => 6,
NormCmdType::Application => 7,
NormCmdType::Other(v) => v,
}
}
pub fn name(&self) -> &'static str {
match self {
NormCmdType::Flush => "NORM_CMD(FLUSH)",
NormCmdType::Eot => "NORM_CMD(EOT)",
NormCmdType::Squelch => "NORM_CMD(SQUELCH)",
NormCmdType::Cc => "NORM_CMD(CC)",
NormCmdType::RepairAdv => "NORM_CMD(REPAIR_ADV)",
NormCmdType::AckReq => "NORM_CMD(ACK_REQ)",
NormCmdType::Application => "NORM_CMD(APPLICATION)",
NormCmdType::Other(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(NormCmdType, Other);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum NormAckType {
Cc,
Flush,
Reserved(u8),
Application(u8),
}
impl NormAckType {
pub fn from_u8(v: u8) -> Self {
match v {
1 => NormAckType::Cc,
2 => NormAckType::Flush,
3..=15 => NormAckType::Reserved(v),
_ => NormAckType::Application(v),
}
}
pub fn to_u8(self) -> u8 {
match self {
NormAckType::Cc => 1,
NormAckType::Flush => 2,
NormAckType::Reserved(v) | NormAckType::Application(v) => v,
}
}
pub fn name(&self) -> &'static str {
match self {
NormAckType::Cc => "NORM_ACK(CC)",
NormAckType::Flush => "NORM_ACK(FLUSH)",
NormAckType::Reserved(_) => "reserved",
NormAckType::Application(_) => "application",
}
}
}
broadcast_common::impl_spec_display!(NormAckType, Reserved, Application);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct NormCmd<'a> {
pub common: NormCommonHeader,
pub sender: SenderWord,
pub sub_type: NormCmdType,
pub head: [u8; 3],
pub extensions: Vec<HeaderExtension<'a>>,
pub content: &'a [u8],
}
impl<'a> NormCmd<'a> {
fn header_bytes(&self) -> usize {
COMMON_HEADER_LEN + SENDER_WORD_LEN + WORD + ext::chain_len(&self.extensions)
}
pub fn serialized_len(&self) -> usize {
self.header_bytes() + self.content.len()
}
pub fn parse(data: &'a [u8]) -> Result<Self> {
let (common, hdr_len) = NormCommonHeader::parse(data)?;
let sender = SenderWord::parse(&data[COMMON_HEADER_LEN..])?;
let mut off = COMMON_HEADER_LEN + SENDER_WORD_LEN;
if data.len() < off + WORD {
return Err(Error::BufferTooShort {
need: off + WORD,
have: data.len(),
what: "NORM_CMD sub-type word",
});
}
let sub_type = NormCmdType::from_u8(data[off]);
let head = [data[off + 1], data[off + 2], data[off + 3]];
off += WORD;
let header_end = hdr_len as usize * WORD;
if header_end < off {
return Err(Error::InconsistentLength {
length: hdr_len,
reason: "hdr_len smaller than the NORM_CMD fixed header",
});
}
if data.len() < header_end {
return Err(Error::BufferTooShort {
need: header_end,
have: data.len(),
what: "NORM_CMD header (per hdr_len)",
});
}
let extensions = ext::parse_chain(&data[off..header_end])?;
let content = &data[header_end..];
Ok(NormCmd {
common,
sender,
sub_type,
head,
extensions,
content,
})
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
if out.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: out.len(),
});
}
let words = self.header_bytes() / WORD;
if words > u8::MAX as usize {
return Err(Error::FieldTooWide {
what: "hdr_len",
value: words as u64,
bits: 8,
});
}
let mut off = self.common.serialize_into(out, words as u8)?;
off += self.sender.serialize_into(&mut out[off..])?;
out[off] = self.sub_type.to_u8();
out[off + 1..off + 4].copy_from_slice(&self.head);
off += WORD;
off += ext::serialize_chain(&self.extensions, &mut out[off..])?;
out[off..off + self.content.len()].copy_from_slice(self.content);
off += self.content.len();
Ok(off)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct NormFeedback<'a> {
pub common: NormCommonHeader,
pub server_id: u32,
pub instance_id: u16,
pub ack_or_reserved: u16,
pub grtt_response_sec: u32,
pub grtt_response_usec: u32,
pub extensions: Vec<HeaderExtension<'a>>,
pub payload: &'a [u8],
}
pub const FEEDBACK_FIXED_LEN: usize = COMMON_HEADER_LEN + 4 + 4 + 8;
impl<'a> NormFeedback<'a> {
pub fn ack_type(&self) -> NormAckType {
NormAckType::from_u8((self.ack_or_reserved >> 8) as u8)
}
pub fn ack_id(&self) -> u8 {
self.ack_or_reserved as u8
}
fn header_bytes(&self) -> usize {
FEEDBACK_FIXED_LEN + ext::chain_len(&self.extensions)
}
pub fn serialized_len(&self) -> usize {
self.header_bytes() + self.payload.len()
}
pub fn parse(data: &'a [u8]) -> Result<Self> {
let (common, hdr_len) = NormCommonHeader::parse(data)?;
if data.len() < FEEDBACK_FIXED_LEN {
return Err(Error::BufferTooShort {
need: FEEDBACK_FIXED_LEN,
have: data.len(),
what: "NORM feedback fixed header",
});
}
let server_id = u32::from_be_bytes([data[8], data[9], data[10], data[11]]);
let instance_id = u16::from_be_bytes([data[12], data[13]]);
let ack_or_reserved = u16::from_be_bytes([data[14], data[15]]);
let grtt_response_sec = u32::from_be_bytes([data[16], data[17], data[18], data[19]]);
let grtt_response_usec = u32::from_be_bytes([data[20], data[21], data[22], data[23]]);
let header_end = hdr_len as usize * WORD;
if header_end < FEEDBACK_FIXED_LEN {
return Err(Error::InconsistentLength {
length: hdr_len,
reason: "hdr_len smaller than the NORM feedback fixed header",
});
}
if data.len() < header_end {
return Err(Error::BufferTooShort {
need: header_end,
have: data.len(),
what: "NORM feedback header (per hdr_len)",
});
}
let extensions = ext::parse_chain(&data[FEEDBACK_FIXED_LEN..header_end])?;
let payload = &data[header_end..];
Ok(NormFeedback {
common,
server_id,
instance_id,
ack_or_reserved,
grtt_response_sec,
grtt_response_usec,
extensions,
payload,
})
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
if out.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: out.len(),
});
}
let words = self.header_bytes() / WORD;
if words > u8::MAX as usize {
return Err(Error::FieldTooWide {
what: "hdr_len",
value: words as u64,
bits: 8,
});
}
let mut off = self.common.serialize_into(out, words as u8)?;
out[off..off + 4].copy_from_slice(&self.server_id.to_be_bytes());
out[off + 4..off + 6].copy_from_slice(&self.instance_id.to_be_bytes());
out[off + 6..off + 8].copy_from_slice(&self.ack_or_reserved.to_be_bytes());
out[off + 8..off + 12].copy_from_slice(&self.grtt_response_sec.to_be_bytes());
out[off + 12..off + 16].copy_from_slice(&self.grtt_response_usec.to_be_bytes());
off += 16;
off += ext::serialize_chain(&self.extensions, &mut out[off..])?;
out[off..off + self.payload.len()].copy_from_slice(self.payload);
off += self.payload.len();
Ok(off)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
use alloc::vec;
fn common(ty: NormMessageType) -> NormCommonHeader {
NormCommonHeader {
version: NORM_VERSION,
message_type: ty,
sequence: 0x1234,
source_id: 0xCAFEBABE,
}
}
fn sender() -> SenderWord {
SenderWord {
instance_id: 0x00FF,
grtt: 0x40,
backoff: 0x0A,
gsize: 0x05,
}
}
#[test]
fn message_type_round_trip() {
for v in 0u8..=6 {
assert_eq!(NormMessageType::from_u8(v).to_u8(), v);
}
assert_eq!(NormMessageType::Data.to_string(), "NORM_DATA");
assert_eq!(NormMessageType::Other(9).to_string(), "reserved(0x09)");
}
#[test]
fn common_header_exact_bytes() {
let c = common(NormMessageType::Data);
let mut out = [0u8; COMMON_HEADER_LEN];
c.serialize_into(&mut out, 7).unwrap();
assert_eq!(out, [0x12, 0x07, 0x12, 0x34, 0xCA, 0xFE, 0xBA, 0xBE]);
let (re, hl) = NormCommonHeader::parse(&out).unwrap();
assert_eq!(re, c);
assert_eq!(hl, 7);
}
#[test]
fn sender_word_exact_bytes() {
let s = sender();
let mut out = [0u8; SENDER_WORD_LEN];
s.serialize_into(&mut out).unwrap();
assert_eq!(out, [0x00, 0xFF, 0x40, 0xA5]);
assert_eq!(SenderWord::parse(&out).unwrap(), s);
}
#[test]
fn norm_data_round_trip_with_fec_payload_id() {
let fpid = [0x00u8, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x02];
let payload = [0xDEu8, 0xAD, 0xBE, 0xEF];
let d = NormData {
common: common(NormMessageType::Data),
sender: sender(),
flags: NORM_FLAG_FILE,
fec_id: 129,
object_transport_id: 0x0007,
fec_payload_id: &fpid,
extensions: vec![],
payload: &payload,
};
let mut out = vec![0u8; d.serialized_len()];
let n = d.serialize_into(&mut out).unwrap();
assert_eq!(n, d.serialized_len());
assert_eq!(out[1], 6, "hdr_len");
let re = NormData::parse(&out, 8).unwrap();
assert_eq!(re, d);
}
#[test]
fn norm_data_with_ext_fti() {
let fti = [
0x40u8, 0x04, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x01, 0x02, 0x00, 0x04, 0x00, 0x00, 0x10, ];
let (ext_fti, used) = HeaderExtension::parse(&fti).unwrap();
assert_eq!(used, 16);
let fpid = [0u8; 8];
let d = NormData {
common: common(NormMessageType::Data),
sender: sender(),
flags: 0,
fec_id: 129,
object_transport_id: 1,
fec_payload_id: &fpid,
extensions: vec![ext_fti],
payload: &[],
};
let mut out = vec![0u8; d.serialized_len()];
d.serialize_into(&mut out).unwrap();
assert_eq!(out[1], 10);
let re = NormData::parse(&out, 8).unwrap();
assert_eq!(re, d);
assert_eq!(re.extensions.len(), 1);
assert_eq!(re.extensions[0].het, HET_EXT_FTI);
}
#[test]
fn norm_cmd_eot_round_trip() {
let c = NormCmd {
common: common(NormMessageType::Cmd),
sender: sender(),
sub_type: NormCmdType::Eot,
head: [0, 0, 0], extensions: vec![],
content: &[],
};
let mut out = vec![0u8; c.serialized_len()];
c.serialize_into(&mut out).unwrap();
assert_eq!(out[1], 4);
assert_eq!(out[12], 2);
let re = NormCmd::parse(&out).unwrap();
assert_eq!(re, c);
assert_eq!(re.sub_type, NormCmdType::Eot);
}
#[test]
fn norm_cmd_flush_with_content() {
let c = NormCmd {
common: common(NormMessageType::Cmd),
sender: sender(),
sub_type: NormCmdType::Flush,
head: [129, 0x00, 0x07], extensions: vec![],
content: &[0, 0, 0, 1, 0, 5, 0, 2],
};
let mut out = vec![0u8; c.serialized_len()];
c.serialize_into(&mut out).unwrap();
let re = NormCmd::parse(&out).unwrap();
assert_eq!(re, c);
}
#[test]
fn norm_nack_round_trip() {
let f = NormFeedback {
common: common(NormMessageType::Nack),
server_id: 0x11223344,
instance_id: 0x00FF,
ack_or_reserved: 0, grtt_response_sec: 0x55667788,
grtt_response_usec: 0x99AABBCC,
extensions: vec![],
payload: &[0x01, 0x02, 0x00, 0x04],
};
let mut out = vec![0u8; f.serialized_len()];
f.serialize_into(&mut out).unwrap();
assert_eq!(out[1], 6);
let re = NormFeedback::parse(&out).unwrap();
assert_eq!(re, f);
}
#[test]
fn norm_ack_ack_type_id() {
let f = NormFeedback {
common: common(NormMessageType::Ack),
server_id: 1,
instance_id: 2,
ack_or_reserved: (2 << 8) | 0x07, grtt_response_sec: 0,
grtt_response_usec: 0,
extensions: vec![],
payload: &[],
};
assert_eq!(f.ack_type(), NormAckType::Flush);
assert_eq!(f.ack_id(), 7);
let mut out = vec![0u8; f.serialized_len()];
f.serialize_into(&mut out).unwrap();
let re = NormFeedback::parse(&out).unwrap();
assert_eq!(re, f);
}
#[test]
fn ack_type_ranges() {
assert_eq!(NormAckType::from_u8(1), NormAckType::Cc);
assert_eq!(NormAckType::from_u8(2), NormAckType::Flush);
assert_eq!(NormAckType::from_u8(10), NormAckType::Reserved(10));
assert_eq!(NormAckType::from_u8(200), NormAckType::Application(200));
assert_eq!(NormAckType::Reserved(10).to_string(), "reserved(0x0A)");
}
#[test]
fn norm_info_round_trip() {
let payload = [0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01, 0x02, 0x03u8];
let info = NormInfo {
common: common(NormMessageType::Info),
sender: sender(),
flags: NORM_FLAG_FILE,
fec_id: 129,
object_transport_id: 0x0042,
extensions: vec![],
payload: &payload,
};
assert_eq!(info.header_bytes(), NORM_INFO_FIXED_LEN);
let total = info.serialized_len();
assert_eq!(total, NORM_INFO_FIXED_LEN + payload.len());
let mut out = vec![0u8; total];
let n = info.serialize_into(&mut out).unwrap();
assert_eq!(n, total);
assert_eq!(out[0], 0x11, "version=1 type=1");
assert_eq!(out[1], 4, "hdr_len = 4 words");
assert_eq!(out[12], NORM_FLAG_FILE);
assert_eq!(out[13], 129);
assert_eq!(u16::from_be_bytes([out[14], out[15]]), 0x0042);
assert_eq!(&out[16..], &payload);
let re = NormInfo::parse(&out).unwrap();
assert_eq!(re, info);
}
#[test]
fn norm_info_mutated_field_changes_wire() {
let payload = [0u8; 4];
let make = |oti: u16| {
let i = NormInfo {
common: common(NormMessageType::Info),
sender: sender(),
flags: 0,
fec_id: 0,
object_transport_id: oti,
extensions: vec![],
payload: &payload,
};
let mut out = vec![0u8; i.serialized_len()];
i.serialize_into(&mut out).unwrap();
out
};
let a = make(0x0001);
let b = make(0x0002);
assert_ne!(a, b);
assert_eq!(u16::from_be_bytes([a[14], a[15]]), 0x0001);
assert_eq!(u16::from_be_bytes([b[14], b[15]]), 0x0002);
}
#[test]
fn norm_info_with_ext_fti() {
let fti = [
0x40u8, 0x04, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x01, 0x02, 0x00, 0x04, 0x00,
0x00, 0x10,
];
let (ext_fti, _) = HeaderExtension::parse(&fti).unwrap();
let payload = [0xAB, 0xCDu8];
let info = NormInfo {
common: common(NormMessageType::Info),
sender: sender(),
flags: NORM_FLAG_INFO,
fec_id: 129,
object_transport_id: 3,
extensions: vec![ext_fti],
payload: &payload,
};
assert_eq!(info.header_bytes(), 32);
let mut out = vec![0u8; info.serialized_len()];
info.serialize_into(&mut out).unwrap();
assert_eq!(out[1], 8, "hdr_len with EXT_FTI");
let re = NormInfo::parse(&out).unwrap();
assert_eq!(re, info);
assert_eq!(re.extensions.len(), 1);
assert_eq!(re.extensions[0].het, HET_EXT_FTI);
}
}