use core::fmt::{self, Write};
use core::str;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use musli::reader::SliceReader;
use crate::api::{
ChannelId, DecodeBody, EncodeBody, Format, Mode, RequestHeader, ResponseHeader, VERSION,
};
const END_OF_HEADERS: u8 = 0;
const ID_MASK: u8 = 0b0011_1111;
const WIDTH_SHIFT: u32 = 6;
#[derive(Clone, Copy)]
pub(crate) struct Header {
pub(crate) id: u8,
pub(crate) name: &'static str,
}
pub(crate) trait HeaderValue
where
Self: Copy,
{
fn to_u32(self) -> u32;
fn from_u32(value: u32) -> Option<Self>;
}
macro_rules! header_value {
($($ty:ty),* $(,)?) => {
$(
impl HeaderValue for $ty {
#[inline]
fn to_u32(self) -> u32 {
u32::from(self)
}
#[inline]
fn from_u32(value: u32) -> Option<Self> {
Self::try_from(value).ok()
}
}
)*
};
}
header_value!(u8, u16);
impl HeaderValue for u32 {
#[inline]
fn to_u32(self) -> u32 {
self
}
#[inline]
fn from_u32(value: u32) -> Option<Self> {
Some(value)
}
}
impl HeaderValue for ChannelId {
#[inline]
fn to_u32(self) -> u32 {
u32::from(self.raw())
}
#[inline]
fn from_u32(value: u32) -> Option<Self> {
Some(ChannelId::from_u16(u16::try_from(value).ok()?))
}
}
#[cfg_attr(
not(any(feature = "ws", feature = "client", feature = "web03")),
allow(dead_code)
)]
pub(crate) trait Envelope
where
Self: Sized,
{
const HEADERS: &'static [Header];
fn empty() -> Self;
fn get(&self, id: u8) -> u32;
fn set(&mut self, id: u8, value: u32) -> Result<(), Error>;
}
macro_rules! envelope {
($ty:ident { $($id:literal => $field:ident,)* }) => {
impl Envelope for $ty {
const HEADERS: &'static [Header] = &[
$(Header { id: $id, name: stringify!($field) },)*
];
#[inline]
fn empty() -> Self {
Self {
$($field: HeaderValue::from_u32(0).expect("Zero fits every header"),)*
}
}
#[inline]
fn get(&self, id: u8) -> u32 {
match id {
$($id => self.$field.to_u32(),)*
_ => 0,
}
}
#[inline]
fn set(&mut self, id: u8, value: u32) -> Result<(), Error> {
match id {
$(
$id => {
let Some(value) = HeaderValue::from_u32(value) else {
return Err(Error::new(ErrorKind::HeaderRange {
name: stringify!($field),
value,
}));
};
self.$field = value;
}
)*
_ => return Err(Error::new(ErrorKind::UnknownHeader { id, name: None })),
}
Ok(())
}
}
};
}
envelope! {
RequestHeader {
1 => version,
2 => serial,
3 => id,
4 => format,
5 => channel,
}
}
envelope! {
ResponseHeader {
1 => version,
2 => serial,
3 => broadcast,
4 => error,
5 => format,
6 => channel,
}
}
const VERSION_ID: u8 = 1;
#[inline]
fn header_by_id<T>(id: u8) -> Option<&'static Header>
where
T: Envelope,
{
T::HEADERS.iter().find(|h| h.id == id)
}
#[inline]
fn header_by_name<T>(name: &str) -> Option<&'static Header>
where
T: Envelope,
{
T::HEADERS.iter().find(|h| h.name == name)
}
struct Utf8Writer<'a> {
out: &'a mut Vec<u8>,
}
impl Write for Utf8Writer<'_> {
#[inline]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.out.extend_from_slice(s.as_bytes());
Ok(())
}
}
#[inline]
fn write_binary_header(out: &mut Vec<u8>, id: u8, value: u32) {
debug_assert!(id != END_OF_HEADERS && id & ID_MASK == id);
if let Ok(value) = u8::try_from(value) {
out.push(id);
out.push(value);
} else if let Ok(value) = u16::try_from(value) {
out.push(id | (1 << WIDTH_SHIFT));
out.extend_from_slice(&value.to_le_bytes());
} else {
out.push(id | (2 << WIDTH_SHIFT));
out.extend_from_slice(&value.to_le_bytes());
}
}
#[cfg_attr(
not(any(feature = "ws", feature = "client", feature = "web03")),
allow(dead_code)
)]
#[inline]
pub(crate) fn encode_envelope<T>(mode: Mode, out: &mut Vec<u8>, value: &T) -> Result<(), Error>
where
T: Envelope,
{
match mode {
Mode::Binary => {
for header in T::HEADERS {
let field = value.get(header.id);
if field == 0 {
continue;
}
write_binary_header(out, header.id, field);
}
out.push(END_OF_HEADERS);
}
Mode::Text => {
let mut writer = Utf8Writer { out };
for header in T::HEADERS {
let field = value.get(header.id);
if writeln!(writer, "{}: {field}", header.name).is_err() {
return Err(Error::new(ErrorKind::TextWrite));
}
}
out.push(b'\n');
}
}
Ok(())
}
#[cfg_attr(
not(any(feature = "ws", feature = "client", feature = "web03")),
allow(dead_code)
)]
pub(crate) fn decode_envelope<T>(mode: Mode, buf: &[u8], at: &mut usize) -> Result<T, Error>
where
T: Envelope,
{
let Some(tail) = buf.get(*at..) else {
return Err(Error::new(ErrorKind::Overflow {
at: *at,
len: buf.len(),
}));
};
let mut header = T::empty();
let mut unknown = None;
let consumed = match mode {
Mode::Binary => {
let mut rest = tail;
loop {
let Some((&tag, next)) = rest.split_first() else {
return Err(Error::new(ErrorKind::HeadersUnterminated));
};
rest = next;
if tag == END_OF_HEADERS {
break;
}
let id = tag & ID_MASK;
let width = match tag >> WIDTH_SHIFT {
0 => 1,
1 => 2,
2 => 4,
_ => return Err(Error::new(ErrorKind::HeaderWidth { id })),
};
let Some((value, next)) = rest.split_at_checked(width) else {
return Err(Error::new(ErrorKind::HeaderTruncated { id, width }));
};
rest = next;
let mut bytes = [0; 4];
bytes[..width].copy_from_slice(value);
let value = u32::from_le_bytes(bytes);
if header_by_id::<T>(id).is_none() {
if unknown.is_none() {
unknown = Some(ErrorKind::UnknownHeader { id, name: None });
}
continue;
}
header.set(id, value)?;
}
tail.len() - rest.len()
}
Mode::Text => {
let Ok(text) = str::from_utf8(tail) else {
return Err(Error::new(ErrorKind::TextNotUtf8));
};
let mut rest = text;
loop {
let Some((line, next)) = rest.split_once('\n') else {
return Err(Error::new(ErrorKind::HeadersUnterminated));
};
rest = next;
let line = line.strip_suffix('\r').unwrap_or(line);
if line.is_empty() {
break;
}
let Some((name, value)) = line.split_once(':') else {
return Err(Error::new(ErrorKind::TextSeparator));
};
let name = name.trim();
let value = value.trim();
let Some(found) = header_by_name::<T>(name) else {
if unknown.is_none() {
unknown = Some(ErrorKind::UnknownHeader {
id: 0,
name: Some(name.to_string()),
});
}
continue;
};
let Ok(value) = value.parse::<u32>() else {
return Err(Error::new(ErrorKind::TextField { key: found.name }));
};
header.set(found.id, value)?;
}
tail.len() - rest.len()
}
};
let version = header.get(VERSION_ID);
if version != VERSION {
return Err(Error::new(ErrorKind::UnsupportedVersion { version }));
}
if let Some(kind) = unknown {
return Err(Error::new(kind));
}
*at += consumed;
Ok(header)
}
macro_rules! encode_with {
($module:ident, $out:expr, $value:expr, $variant:ident) => {{
musli::$module::encode($out, $value)
.map(|_| ())
.map_err(Error::$variant)
}};
}
macro_rules! decode_with {
($module:ident, $tail:expr, $at:expr, $len:expr, $variant:ident) => {{
let mut reader = SliceReader::new($tail);
let value = musli::$module::decode(&mut reader).map_err(Error::$variant)?;
*$at += $tail.len() - reader.remaining();
let _ = $len;
Ok(value)
}};
}
impl Format {
#[inline]
pub const fn is_supported(self) -> bool {
match self {
Format::Packed => cfg!(feature = "format-packed"),
Format::Storage => cfg!(feature = "format-storage"),
Format::Wire => cfg!(feature = "format-wire"),
Format::Descriptive => cfg!(feature = "format-descriptive"),
Format::Json => cfg!(feature = "format-json"),
}
}
#[inline]
pub fn supported() -> impl Iterator<Item = Format> {
Format::ALL.iter().copied().filter(|f| f.is_supported())
}
#[cfg_attr(
not(any(feature = "ws", feature = "client", feature = "web03")),
allow(dead_code)
)]
pub(crate) fn encode<T>(self, out: &mut Vec<u8>, value: &T) -> Result<(), Error>
where
T: ?Sized + EncodeBody,
{
match self {
#[cfg(feature = "format-packed")]
Format::Packed => encode_with!(packed, out, value, packed),
#[cfg(feature = "format-storage")]
Format::Storage => encode_with!(storage, out, value, storage),
#[cfg(feature = "format-wire")]
Format::Wire => encode_with!(wire, out, value, wire),
#[cfg(feature = "format-descriptive")]
Format::Descriptive => encode_with!(descriptive, out, value, descriptive),
#[cfg(feature = "format-json")]
Format::Json => musli::json::encode(out, value)
.map(|_| ())
.map_err(Error::json),
#[allow(unreachable_patterns)]
_ => Err(Error::unsupported(self)),
}
}
#[cfg_attr(
not(any(feature = "ws", feature = "client", feature = "web03")),
allow(dead_code)
)]
pub(crate) fn decode<'de, T>(self, buf: &'de [u8], at: &mut usize) -> Result<T, Error>
where
T: DecodeBody<'de>,
{
let Some(tail) = buf.get(*at..) else {
return Err(Error::new(ErrorKind::Overflow {
at: *at,
len: buf.len(),
}));
};
match self {
#[cfg(feature = "format-packed")]
Format::Packed => decode_with!(packed, tail, at, buf.len(), packed),
#[cfg(feature = "format-storage")]
Format::Storage => decode_with!(storage, tail, at, buf.len(), storage),
#[cfg(feature = "format-wire")]
Format::Wire => decode_with!(wire, tail, at, buf.len(), wire),
#[cfg(feature = "format-descriptive")]
Format::Descriptive => decode_with!(descriptive, tail, at, buf.len(), descriptive),
#[cfg(feature = "format-json")]
Format::Json => {
let mut rest = tail;
let cursor = &mut rest;
let value = musli::json::decode(cursor).map_err(Error::json)?;
*at += tail.len() - rest.len();
Ok(value)
}
#[allow(unreachable_patterns)]
_ => Err(Error::unsupported(self)),
}
}
}
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
}
impl Error {
#[inline]
const fn new(kind: ErrorKind) -> Self {
Self { kind }
}
#[inline]
pub(crate) const fn unsupported(format: Format) -> Self {
Self::new(ErrorKind::Unsupported(format))
}
#[inline]
pub fn unsupported_format(&self) -> Option<Format> {
match self.kind {
ErrorKind::Unsupported(format) => Some(format),
_ => None,
}
}
#[inline]
pub fn unsupported_version(&self) -> Option<u32> {
match self.kind {
ErrorKind::UnsupportedVersion { version } => Some(version),
_ => None,
}
}
#[inline]
pub fn is_unknown_header(&self) -> bool {
matches!(self.kind, ErrorKind::UnknownHeader { .. })
}
}
macro_rules! error_kinds {
($($(#[$meta:meta])* $variant:ident, $ctor:ident, $ty:path;)*) => {
#[derive(Debug)]
enum ErrorKind {
Unsupported(Format),
Overflow { at: usize, len: usize },
TextWrite,
TextNotUtf8,
TextSeparator,
TextField { key: &'static str },
HeadersUnterminated,
HeaderWidth { id: u8 },
HeaderTruncated { id: u8, width: usize },
HeaderRange { name: &'static str, value: u32 },
UnknownHeader { id: u8, name: Option<String> },
UnsupportedVersion { version: u32 },
$($(#[$meta])* $variant($ty),)*
}
impl Error {
$(
$(#[$meta])*
#[inline]
fn $ctor(error: $ty) -> Self {
Self::new(ErrorKind::$variant(error))
}
)*
}
impl fmt::Display for Error {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
ErrorKind::Unsupported(format) => {
write!(f, "Format `{format}` is not supported")
}
ErrorKind::Overflow { at, len } => {
write!(f, "Offset {at} is out of bounds for a message of {len} bytes")
}
ErrorKind::TextWrite => {
write!(f, "Failed to write text envelope")
}
ErrorKind::TextNotUtf8 => {
write!(f, "Text envelope is not valid UTF-8")
}
ErrorKind::TextSeparator => {
write!(f, "Text envelope has a line without a `:` separator")
}
ErrorKind::TextField { key } => {
write!(f, "Text envelope header `{key}` is not a valid number")
}
ErrorKind::HeadersUnterminated => {
write!(f, "Envelope is missing its end of headers marker")
}
ErrorKind::HeaderWidth { id } => {
write!(f, "Header {id} uses a width this build cannot skip over")
}
ErrorKind::HeaderTruncated { id, width } => {
write!(f, "Header {id} is missing its {width} byte value")
}
ErrorKind::HeaderRange { name, value } => {
write!(f, "Header `{name}` cannot hold the value {value}")
}
ErrorKind::UnknownHeader { name: Some(name), .. } => {
write!(f, "Unknown header `{name}`")
}
ErrorKind::UnknownHeader { id, name: None } => {
write!(f, "Unknown header with id {id}")
}
ErrorKind::UnsupportedVersion { version } => {
write!(
f,
"Peer speaks protocol version {version}, this build speaks {VERSION}"
)
}
$($(#[$meta])* ErrorKind::$variant(..) => {
write!(f, concat!("Error in the `", stringify!($ctor), "` format"))
})*
}
}
}
impl core::error::Error for Error {
#[inline]
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match &self.kind {
$($(#[$meta])* ErrorKind::$variant(error) => Some(error),)*
_ => None,
}
}
}
};
}
error_kinds! {
#[cfg(feature = "format-packed")]
Packed, packed, musli::packed::Error;
#[cfg(feature = "format-storage")]
Storage, storage, musli::storage::Error;
#[cfg(feature = "format-wire")]
Wire, wire, musli::wire::Error;
#[cfg(feature = "format-descriptive")]
Descriptive, descriptive, musli::descriptive::Error;
#[cfg(feature = "format-json")]
Json, json, musli::json::Error;
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
use musli::{Decode, Encode};
use crate::api::{ChannelId, Format, Mode, RequestHeader, ResponseHeader, VERSION};
#[derive(Debug, PartialEq, Encode, Decode)]
struct Message<'de> {
message: &'de str,
tick: u32,
}
#[cfg(feature = "format-json")]
#[derive(Debug, PartialEq, Encode, Decode)]
#[musli(Text, tag = "type")]
enum JsonTarget {
#[musli(Text, name = "port")]
Port { id: u32 },
}
#[cfg(feature = "format-json")]
#[derive(Debug, PartialEq, Encode, Decode)]
#[musli(Text, tag = "type")]
enum JsonRealtime {
#[musli(Text, name = "rtkit")]
Rtkit,
}
#[test]
fn round_trip() {
for format in Format::supported() {
let mut buf = Vec::new();
let expected = Message {
message: "hello",
tick: 42,
};
format.encode(&mut buf, &expected).unwrap();
let mut at = 0;
let actual: Message<'_> = format.decode(&buf, &mut at).unwrap();
assert_eq!(actual, expected, "round trip failed for `{format}`");
assert_eq!(at, buf.len(), "`{format}` did not consume the whole body");
}
}
#[test]
fn sequential_payloads() {
for format in Format::supported() {
let mut buf = Vec::new();
let first = Message {
message: "first",
tick: 1,
};
let second = Message {
message: "second",
tick: 2,
};
format.encode(&mut buf, &first).unwrap();
let boundary = buf.len();
format.encode(&mut buf, &second).unwrap();
let mut at = 0;
let a: Message<'_> = format.decode(&buf, &mut at).unwrap();
assert_eq!(a, first, "first payload failed for `{format}`");
assert_eq!(at, boundary, "`{format}` misreported the first boundary");
let b: Message<'_> = format.decode(&buf, &mut at).unwrap();
assert_eq!(b, second, "second payload failed for `{format}`");
assert_eq!(at, buf.len(), "`{format}` did not consume both payloads");
}
}
#[test]
fn envelope_then_body() {
for format in Format::supported() {
let header = RequestHeader {
version: VERSION,
serial: 7,
id: 11,
format: format.to_u8(),
channel: ChannelId::from_u16(3),
};
let mut buf = Vec::new();
encode_envelope(Mode::DEFAULT, &mut buf, &header).unwrap();
let expected = Message {
message: "body",
tick: 9,
};
format.encode(&mut buf, &expected).unwrap();
let mut at = 0;
let decoded: RequestHeader = decode_envelope(Mode::DEFAULT, &buf, &mut at).unwrap();
assert_eq!(decoded.serial, 7);
assert_eq!(decoded.id, 11);
assert_eq!(decoded.format, format.to_u8());
let body: Message<'_> = format.decode(&buf, &mut at).unwrap();
assert_eq!(body, expected, "body failed for `{format}`");
assert_eq!(at, buf.len());
}
}
#[test]
#[cfg(feature = "format-json")]
fn json_tagged_payloads_are_sequential() {
let first = JsonTarget::Port { id: 1 };
let second = JsonRealtime::Rtkit;
let mut buf = Vec::new();
Format::Json.encode(&mut buf, &first).unwrap();
let boundary = buf.len();
Format::Json.encode(&mut buf, &second).unwrap();
let mut at = 0;
let decoded: JsonTarget = Format::Json.decode(&buf, &mut at).unwrap();
assert_eq!(decoded, first);
assert_eq!(at, boundary);
let decoded: JsonRealtime = Format::Json.decode(&buf, &mut at).unwrap();
assert_eq!(decoded, second);
assert_eq!(at, buf.len());
}
#[cfg(feature = "format-json")]
fn json_envelope_then_tagged_body(mode: Mode) {
let header = RequestHeader {
version: VERSION,
serial: 7,
id: 11,
format: Format::Json.to_u8(),
channel: ChannelId::from_u16(3),
};
let expected = JsonTarget::Port { id: 1 };
let mut buf = Vec::new();
encode_envelope(mode, &mut buf, &header).unwrap();
let boundary = buf.len();
Format::Json.encode(&mut buf, &expected).unwrap();
let mut at = 0;
let decoded: RequestHeader = decode_envelope(mode, &buf, &mut at).unwrap();
assert_eq!(decoded.serial, header.serial, "`{mode}` lost the serial");
assert_eq!(decoded.id, header.id, "`{mode}` lost the id");
assert_eq!(decoded.format, header.format, "`{mode}` lost the format");
assert_eq!(at, boundary, "`{mode}` misreported the envelope boundary");
let decoded: JsonTarget = Format::Json.decode(&buf, &mut at).unwrap();
assert_eq!(decoded, expected, "`{mode}` body failed");
assert_eq!(at, buf.len(), "`{mode}` did not consume the whole frame");
}
#[test]
#[cfg(feature = "format-json")]
fn json_binary_envelope_then_tagged_body() {
json_envelope_then_tagged_body(Mode::Binary);
}
#[test]
#[cfg(feature = "format-json")]
fn json_text_envelope_then_tagged_body() {
json_envelope_then_tagged_body(Mode::Text);
}
#[test]
#[cfg(feature = "format-json")]
fn json_is_human_readable() {
let mut buf = Vec::new();
Format::Json
.encode(
&mut buf,
&Message {
message: "hello",
tick: 42,
},
)
.unwrap();
assert_eq!(
core::str::from_utf8(&buf).unwrap(),
r#"{"message":"hello","tick":42}"#
);
}
#[test]
fn text_envelope_is_http_like() {
let mut buf = Vec::new();
let header = RequestHeader {
version: VERSION,
serial: 7,
id: 11,
format: Format::Json.to_u8(),
channel: ChannelId::from_u16(3),
};
encode_envelope(Mode::Text, &mut buf, &header).unwrap();
assert_eq!(
core::str::from_utf8(&buf).unwrap(),
"version: 1\nserial: 7\nid: 11\nformat: 5\nchannel: 3\n\n"
);
let mut buf = Vec::new();
let header = ResponseHeader {
version: VERSION,
serial: 0,
broadcast: 13,
error: 0,
format: Format::Json.to_u8(),
channel: ChannelId::NONE,
};
encode_envelope(Mode::Text, &mut buf, &header).unwrap();
assert_eq!(
core::str::from_utf8(&buf).unwrap(),
"version: 1\nserial: 0\nbroadcast: 13\nerror: 0\nformat: 5\nchannel: 0\n\n"
);
}
#[test]
fn text_envelope_round_trip() {
for mode in Mode::ALL.iter().copied() {
let mut buf = Vec::new();
let request = RequestHeader {
version: VERSION,
serial: 4294967295,
id: 65535,
format: Format::Json.to_u8(),
channel: ChannelId::from_u16(65535),
};
encode_envelope(mode, &mut buf, &request).unwrap();
let mut at = 0;
let decoded: RequestHeader = decode_envelope(mode, &buf, &mut at).unwrap();
assert_eq!(decoded.serial, request.serial, "`{mode}` lost the serial");
assert_eq!(decoded.id, request.id, "`{mode}` lost the id");
assert_eq!(decoded.format, request.format, "`{mode}` lost the format");
assert_eq!(
decoded.channel, request.channel,
"`{mode}` lost the channel"
);
assert_eq!(at, buf.len(), "`{mode}` did not consume the whole envelope");
let mut buf = Vec::new();
let response = ResponseHeader {
version: VERSION,
serial: 9,
broadcast: 13,
error: 17,
format: Format::Json.to_u8(),
channel: ChannelId::from_u16(21),
};
encode_envelope(mode, &mut buf, &response).unwrap();
let mut at = 0;
let decoded: ResponseHeader = decode_envelope(mode, &buf, &mut at).unwrap();
assert_eq!(decoded.serial, response.serial, "`{mode}` lost the serial");
assert_eq!(
decoded.broadcast, response.broadcast,
"`{mode}` lost the broadcast"
);
assert_eq!(decoded.error, response.error, "`{mode}` lost the error");
assert_eq!(decoded.format, response.format, "`{mode}` lost the format");
assert_eq!(
decoded.channel, response.channel,
"`{mode}` lost the channel"
);
assert_eq!(at, buf.len(), "`{mode}` did not consume the whole envelope");
}
}
#[test]
#[cfg(feature = "format-json")]
fn text_envelope_then_body() {
let mut buf = Vec::new();
let header = RequestHeader {
version: VERSION,
serial: 1,
id: 2,
format: Format::Json.to_u8(),
channel: ChannelId::NONE,
};
encode_envelope(Mode::Text, &mut buf, &header).unwrap();
let expected = Message {
message: "hello",
tick: 42,
};
Format::Json.encode(&mut buf, &expected).unwrap();
assert_eq!(
core::str::from_utf8(&buf).unwrap(),
"version: 1\nserial: 1\nid: 2\nformat: 5\nchannel: 0\n\n{\"message\":\"hello\",\"tick\":42}"
);
let mut at = 0;
let decoded: RequestHeader = decode_envelope(Mode::Text, &buf, &mut at).unwrap();
assert_eq!(decoded.id, 2);
let body: Message<'_> = Format::Json.decode(&buf, &mut at).unwrap();
assert_eq!(body, expected);
assert_eq!(at, buf.len());
}
#[test]
fn text_envelope_is_order_independent() {
let buf = b"channel: 3\r\nid: 11\r\nversion: 1\r\n\r\n";
let mut at = 0;
let header: RequestHeader = decode_envelope(Mode::Text, buf, &mut at).unwrap();
assert_eq!(header.id, 11);
assert_eq!(header.channel, ChannelId::from_u16(3));
assert_eq!(header.serial, 0, "an absent header reads as zero");
assert_eq!(header.format, 0, "an absent header reads as zero");
assert_eq!(at, buf.len());
}
#[test]
fn binary_envelope_is_tagged() {
let mut buf = Vec::new();
let header = RequestHeader {
version: VERSION,
serial: 7,
id: 300,
format: Format::Json.to_u8(),
channel: ChannelId::NONE,
};
encode_envelope(Mode::Binary, &mut buf, &header).unwrap();
assert_eq!(
buf,
[
0x01, 1, 0x02, 7, 0x43, 0x2c, 0x01, 0x04, 5, 0x00,
]
);
let mut at = 0;
let decoded: RequestHeader = decode_envelope(Mode::Binary, &buf, &mut at).unwrap();
assert_eq!(decoded.serial, 7);
assert_eq!(decoded.id, 300);
assert_eq!(decoded.format, Format::Json.to_u8());
assert_eq!(decoded.channel, ChannelId::NONE);
assert_eq!(at, buf.len());
}
#[test]
fn unknown_headers_are_rejected() {
let buf = [0x01, 1, 0x02, 7, 0x7f, 0xff, 0xff, 0x04, 5, 0x00];
let mut at = 0;
let error = decode_envelope::<RequestHeader>(Mode::Binary, &buf, &mut at).unwrap_err();
assert!(error.is_unknown_header(), "Unexpected error: {error}");
assert!(
error.to_string().contains("63"),
"Unexpected error: {error}"
);
assert_eq!(at, 0, "a refused envelope must not advance the cursor");
let buf = b"version: 1\nserial: 7\nfuture: whatever\nid: 11\n\n";
let mut at = 0;
let error = decode_envelope::<RequestHeader>(Mode::Text, buf, &mut at).unwrap_err();
assert!(error.is_unknown_header(), "Unexpected error: {error}");
assert!(
error.to_string().contains("future"),
"The error has to name the header: {error}"
);
assert_eq!(at, 0, "a refused envelope must not advance the cursor");
}
#[test]
fn binary_envelope_rejects_malformed_input() {
let mut at = 0;
assert!(decode_envelope::<RequestHeader>(Mode::Binary, &[0x01, 1], &mut at).is_err());
let mut at = 0;
assert!(decode_envelope::<RequestHeader>(Mode::Binary, &[0x43, 1], &mut at).is_err());
let mut at = 0;
assert!(decode_envelope::<RequestHeader>(Mode::Binary, &[0xc2, 1, 0x00], &mut at).is_err());
let mut at = 0;
assert!(
decode_envelope::<RequestHeader>(
Mode::Binary,
&[0x01, 1, 0x83, 0, 0, 1, 0, 0x00],
&mut at
)
.is_err()
);
}
#[test]
fn text_envelope_rejects_malformed_input() {
let mut at = 0;
assert!(decode_envelope::<RequestHeader>(Mode::Text, b"version: 1\n", &mut at).is_err());
let mut at = 0;
assert!(
decode_envelope::<RequestHeader>(Mode::Text, b"version: 1\nid: 65536\n\n", &mut at)
.is_err()
);
let mut at = 0;
assert!(decode_envelope::<RequestHeader>(Mode::Text, b"id 11\n\n", &mut at).is_err());
let mut at = 0;
assert!(
decode_envelope::<RequestHeader>(Mode::Text, b"version: 1\nid: none\n\n", &mut at)
.is_err()
);
let mut at = 0;
assert!(decode_envelope::<RequestHeader>(Mode::Text, &[0xff, 0xfe], &mut at).is_err());
}
#[test]
fn an_unsupported_version_is_refused() {
let buf = [0x01, VERSION as u8 + 1, 0x02, 7, 0x00];
let mut at = 0;
let error = decode_envelope::<RequestHeader>(Mode::Binary, &buf, &mut at).unwrap_err();
assert_eq!(error.unsupported_version(), Some(VERSION + 1));
assert_eq!(at, 0, "a refused envelope must not advance the cursor");
let buf = b"version: 2\nserial: 7\n\n";
let mut at = 0;
let error = decode_envelope::<RequestHeader>(Mode::Text, buf, &mut at).unwrap_err();
assert_eq!(error.unsupported_version(), Some(2));
assert_eq!(at, 0, "a refused envelope must not advance the cursor");
}
#[test]
fn an_unstated_version_is_refused() {
for (mode, buf) in [
(Mode::Binary, &[0x02, 7, 0x00][..]),
(Mode::Text, &b"serial: 7\n\n"[..]),
] {
let mut at = 0;
let error = decode_envelope::<RequestHeader>(mode, buf, &mut at).unwrap_err();
assert_eq!(
error.unsupported_version(),
Some(0),
"`{mode}` accepted an envelope which states no version"
);
}
}
#[test]
fn the_version_is_reported_before_an_unknown_header() {
let buf = [0x01, VERSION as u8 + 1, 0x7f, 0xff, 0xff, 0x00];
let mut at = 0;
let error = decode_envelope::<RequestHeader>(Mode::Binary, &buf, &mut at).unwrap_err();
assert_eq!(error.unsupported_version(), Some(VERSION + 1));
assert!(
!error.is_unknown_header(),
"The version explains the header, so it is the more useful error"
);
}
#[test]
fn text_mode_only_accepts_human_readable_formats() {
for format in Format::ALL.iter().copied() {
assert!(
Mode::Binary.accepts(format),
"`{format}` must fit a binary frame"
);
assert_eq!(
Mode::Text.accepts(format),
format.is_human_readable(),
"`{format}` is accepted by `text` exactly when it is human readable"
);
}
}
#[test]
fn unsupported_is_reported() {
for format in Format::ALL.iter().copied() {
if format.is_supported() {
continue;
}
let mut buf = Vec::new();
let error = format.encode(&mut buf, &1u32).unwrap_err();
assert_eq!(error.unsupported_format(), Some(format));
}
}
}