1#![no_std]
2#![doc = "Wire versioning and bounded-message primitives for BCX."]
3
4use bcx_core::ValidationError;
5use core::convert::TryFrom;
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub struct ProtocolVersion {
10 major: u16,
11 minor: u16,
12}
13
14impl ProtocolVersion {
15 pub const CURRENT: Self = Self::new(1, 0);
17
18 #[must_use]
20 pub const fn new(major: u16, minor: u16) -> Self {
21 Self { major, minor }
22 }
23
24 #[must_use]
26 pub const fn major(self) -> u16 {
27 self.major
28 }
29
30 #[must_use]
32 pub const fn minor(self) -> u16 {
33 self.minor
34 }
35}
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub struct WireLimits {
40 maximum_message_len: usize,
42 maximum_parent_events: usize,
44 maximum_why_depth: usize,
46 maximum_explanation_events: usize,
48}
49
50impl WireLimits {
51 pub const MAXIMUM_MESSAGE_LEN: usize = 16 * 1024 * 1024;
53 pub const MAXIMUM_PARENT_EVENTS: usize = 1_024;
55 pub const MAXIMUM_WHY_DEPTH: usize = 32;
57 pub const MAXIMUM_EXPLANATION_EVENTS: usize = 10_000;
59
60 pub const DEVELOPMENT: Self = Self {
62 maximum_message_len: 1_048_576,
63 maximum_parent_events: 16,
64 maximum_why_depth: 5,
65 maximum_explanation_events: 100,
66 };
67
68 pub const fn new(
70 maximum_message_len: usize,
71 maximum_parent_events: usize,
72 maximum_why_depth: usize,
73 maximum_explanation_events: usize,
74 ) -> Result<Self, ValidationError> {
75 if maximum_message_len == 0
76 || maximum_parent_events == 0
77 || maximum_why_depth == 0
78 || maximum_explanation_events == 0
79 {
80 return Err(ValidationError::Empty);
81 }
82 if maximum_message_len > Self::MAXIMUM_MESSAGE_LEN
83 || maximum_parent_events > Self::MAXIMUM_PARENT_EVENTS
84 || maximum_why_depth > Self::MAXIMUM_WHY_DEPTH
85 || maximum_explanation_events > Self::MAXIMUM_EXPLANATION_EVENTS
86 {
87 return Err(ValidationError::TooLarge);
88 }
89 Ok(Self {
90 maximum_message_len,
91 maximum_parent_events,
92 maximum_why_depth,
93 maximum_explanation_events,
94 })
95 }
96
97 #[must_use]
99 pub const fn maximum_message_len(self) -> usize {
100 self.maximum_message_len
101 }
102
103 #[must_use]
105 pub const fn maximum_parent_events(self) -> usize {
106 self.maximum_parent_events
107 }
108
109 #[must_use]
111 pub const fn maximum_why_depth(self) -> usize {
112 self.maximum_why_depth
113 }
114
115 #[must_use]
117 pub const fn maximum_explanation_events(self) -> usize {
118 self.maximum_explanation_events
119 }
120}
121
122#[derive(Clone, Copy, Debug, Eq, PartialEq)]
124pub struct WireHeader {
125 version: ProtocolVersion,
126 payload_len: u32,
127}
128
129impl WireHeader {
130 pub fn new(
132 version: ProtocolVersion,
133 payload_len: u32,
134 limits: WireLimits,
135 ) -> Result<Self, ValidationError> {
136 let header = Self {
137 version,
138 payload_len,
139 };
140 match header.validate(limits) {
141 Ok(()) => Ok(header),
142 Err(error) => Err(error),
143 }
144 }
145
146 pub fn validate(&self, limits: WireLimits) -> Result<(), ValidationError> {
148 if self.version.major() != ProtocolVersion::CURRENT.major() {
149 return Err(ValidationError::NotPermitted);
150 }
151 if self.version.minor() != ProtocolVersion::CURRENT.minor() {
152 return Err(ValidationError::NotPermitted);
153 }
154 if self.payload_len == 0 {
155 return Err(ValidationError::Empty);
156 }
157 let payload_len =
158 usize::try_from(self.payload_len).map_err(|_| ValidationError::TooLarge)?;
159 if payload_len > limits.maximum_message_len() {
160 return Err(ValidationError::TooLarge);
161 }
162 Ok(())
163 }
164
165 #[must_use]
167 pub const fn version(self) -> ProtocolVersion {
168 self.version
169 }
170
171 #[must_use]
173 pub const fn payload_len(self) -> u32 {
174 self.payload_len
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn header_rejects_empty_payload() {
184 assert_eq!(
185 WireHeader::new(ProtocolVersion::CURRENT, 0, WireLimits::DEVELOPMENT),
186 Err(ValidationError::Empty)
187 );
188 }
189
190 #[test]
191 fn header_rejects_future_minor_version() {
192 assert_eq!(
193 WireHeader::new(ProtocolVersion::new(1, 1), 1, WireLimits::DEVELOPMENT),
194 Err(ValidationError::NotPermitted)
195 );
196 }
197
198 #[test]
199 fn limits_reject_unbounded_values() {
200 assert_eq!(
201 WireLimits::new(usize::MAX, 1, 1, 1),
202 Err(ValidationError::TooLarge)
203 );
204 assert_eq!(WireLimits::new(1, 0, 1, 1), Err(ValidationError::Empty));
205 }
206}