#![no_std]
#![doc = "Wire versioning and bounded-message primitives for BCX."]
use bcx_core::ValidationError;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProtocolVersion {
pub major: u16,
pub 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 }
}
}
#[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 {
pub version: ProtocolVersion,
pub payload_len: usize,
}
impl WireHeader {
pub const 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);
}
if self.payload_len > limits.maximum_message_len() {
return Err(ValidationError::TooLarge);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_rejects_empty_payload() {
let header = WireHeader {
version: ProtocolVersion::CURRENT,
payload_len: 0,
};
assert_eq!(
header.validate(WireLimits::DEVELOPMENT),
Err(ValidationError::Empty)
);
}
#[test]
fn header_rejects_future_minor_version() {
let header = WireHeader {
version: ProtocolVersion::new(1, 1),
payload_len: 1,
};
assert_eq!(
header.validate(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));
}
}