#![no_std]
#![doc = "Wire versioning and bounded-message primitives for BCX."]
use bcx_core::ValidationError;
use core::convert::TryFrom;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProtocolVersion {
major: u16,
minor: u16,
}
impl ProtocolVersion {
pub const CURRENT: Self = Self::new(1, 0);
#[must_use]
pub const fn new(major: u16, minor: u16) -> Self {
Self { major, minor }
}
#[must_use]
pub const fn major(self) -> u16 {
self.major
}
#[must_use]
pub const fn minor(self) -> u16 {
self.minor
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct WireLimits {
maximum_message_len: usize,
maximum_parent_events: usize,
maximum_why_depth: usize,
maximum_explanation_events: usize,
}
impl WireLimits {
pub const MAXIMUM_MESSAGE_LEN: usize = 16 * 1024 * 1024;
pub const MAXIMUM_PARENT_EVENTS: usize = 1_024;
pub const MAXIMUM_WHY_DEPTH: usize = 32;
pub const MAXIMUM_EXPLANATION_EVENTS: usize = 10_000;
pub const DEVELOPMENT: Self = Self {
maximum_message_len: 1_048_576,
maximum_parent_events: 16,
maximum_why_depth: 5,
maximum_explanation_events: 100,
};
pub const fn new(
maximum_message_len: usize,
maximum_parent_events: usize,
maximum_why_depth: usize,
maximum_explanation_events: usize,
) -> Result<Self, ValidationError> {
if maximum_message_len == 0
|| maximum_parent_events == 0
|| maximum_why_depth == 0
|| maximum_explanation_events == 0
{
return Err(ValidationError::Empty);
}
if maximum_message_len > Self::MAXIMUM_MESSAGE_LEN
|| maximum_parent_events > Self::MAXIMUM_PARENT_EVENTS
|| maximum_why_depth > Self::MAXIMUM_WHY_DEPTH
|| maximum_explanation_events > Self::MAXIMUM_EXPLANATION_EVENTS
{
return Err(ValidationError::TooLarge);
}
Ok(Self {
maximum_message_len,
maximum_parent_events,
maximum_why_depth,
maximum_explanation_events,
})
}
#[must_use]
pub const fn maximum_message_len(self) -> usize {
self.maximum_message_len
}
#[must_use]
pub const fn maximum_parent_events(self) -> usize {
self.maximum_parent_events
}
#[must_use]
pub const fn maximum_why_depth(self) -> usize {
self.maximum_why_depth
}
#[must_use]
pub const fn maximum_explanation_events(self) -> usize {
self.maximum_explanation_events
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct WireHeader {
version: ProtocolVersion,
payload_len: u32,
}
impl WireHeader {
pub fn new(
version: ProtocolVersion,
payload_len: u32,
limits: WireLimits,
) -> Result<Self, ValidationError> {
let header = Self {
version,
payload_len,
};
match header.validate(limits) {
Ok(()) => Ok(header),
Err(error) => Err(error),
}
}
pub fn validate(&self, limits: WireLimits) -> Result<(), ValidationError> {
if self.version.major() != ProtocolVersion::CURRENT.major() {
return Err(ValidationError::NotPermitted);
}
if self.version.minor() != ProtocolVersion::CURRENT.minor() {
return Err(ValidationError::NotPermitted);
}
if self.payload_len == 0 {
return Err(ValidationError::Empty);
}
let payload_len =
usize::try_from(self.payload_len).map_err(|_| ValidationError::TooLarge)?;
if payload_len > limits.maximum_message_len() {
return Err(ValidationError::TooLarge);
}
Ok(())
}
#[must_use]
pub const fn version(self) -> ProtocolVersion {
self.version
}
#[must_use]
pub const fn payload_len(self) -> u32 {
self.payload_len
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_rejects_empty_payload() {
assert_eq!(
WireHeader::new(ProtocolVersion::CURRENT, 0, WireLimits::DEVELOPMENT),
Err(ValidationError::Empty)
);
}
#[test]
fn header_rejects_future_minor_version() {
assert_eq!(
WireHeader::new(ProtocolVersion::new(1, 1), 1, WireLimits::DEVELOPMENT),
Err(ValidationError::NotPermitted)
);
}
#[test]
fn limits_reject_unbounded_values() {
assert_eq!(
WireLimits::new(usize::MAX, 1, 1, 1),
Err(ValidationError::TooLarge)
);
assert_eq!(WireLimits::new(1, 0, 1, 1), Err(ValidationError::Empty));
}
}