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 UNSAFE_DEVELOPMENT_DO_NOT_USE_IN_PRODUCTION: Self = Self {
65 maximum_message_len: 1_048_576,
66 maximum_parent_events: 16,
67 maximum_why_depth: 5,
68 maximum_explanation_events: 100,
69 };
70
71 pub const fn new(
73 maximum_message_len: usize,
74 maximum_parent_events: usize,
75 maximum_why_depth: usize,
76 maximum_explanation_events: usize,
77 ) -> Result<Self, ValidationError> {
78 if maximum_message_len == 0
79 || maximum_parent_events == 0
80 || maximum_why_depth == 0
81 || maximum_explanation_events == 0
82 {
83 return Err(ValidationError::Empty);
84 }
85 if maximum_message_len > Self::MAXIMUM_MESSAGE_LEN
86 || maximum_parent_events > Self::MAXIMUM_PARENT_EVENTS
87 || maximum_why_depth > Self::MAXIMUM_WHY_DEPTH
88 || maximum_explanation_events > Self::MAXIMUM_EXPLANATION_EVENTS
89 {
90 return Err(ValidationError::TooLarge);
91 }
92 Ok(Self {
93 maximum_message_len,
94 maximum_parent_events,
95 maximum_why_depth,
96 maximum_explanation_events,
97 })
98 }
99
100 #[must_use]
102 pub const fn maximum_message_len(self) -> usize {
103 self.maximum_message_len
104 }
105
106 #[must_use]
108 pub const fn maximum_parent_events(self) -> usize {
109 self.maximum_parent_events
110 }
111
112 #[must_use]
114 pub const fn maximum_why_depth(self) -> usize {
115 self.maximum_why_depth
116 }
117
118 #[must_use]
120 pub const fn maximum_explanation_events(self) -> usize {
121 self.maximum_explanation_events
122 }
123}
124
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
127pub struct WireHeader {
128 version: ProtocolVersion,
129 payload_len: u32,
130}
131
132impl WireHeader {
133 pub fn new(
135 version: ProtocolVersion,
136 payload_len: u32,
137 limits: WireLimits,
138 ) -> Result<Self, ValidationError> {
139 let header = Self {
140 version,
141 payload_len,
142 };
143 match header.validate(limits) {
144 Ok(()) => Ok(header),
145 Err(error) => Err(error),
146 }
147 }
148
149 pub(crate) fn validate(&self, limits: WireLimits) -> Result<(), ValidationError> {
151 if self.version.major() != ProtocolVersion::CURRENT.major() {
152 return Err(ValidationError::NotPermitted);
153 }
154 if self.version.minor() != ProtocolVersion::CURRENT.minor() {
155 return Err(ValidationError::NotPermitted);
156 }
157 if self.payload_len == 0 {
158 return Err(ValidationError::Empty);
159 }
160 let payload_len =
161 usize::try_from(self.payload_len).map_err(|_| ValidationError::TooLarge)?;
162 if payload_len > limits.maximum_message_len() {
163 return Err(ValidationError::TooLarge);
164 }
165 Ok(())
166 }
167
168 #[must_use]
170 pub const fn version(self) -> ProtocolVersion {
171 self.version
172 }
173
174 #[must_use]
176 pub const fn payload_len(self) -> u32 {
177 self.payload_len
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn header_rejects_empty_payload() {
187 assert_eq!(
188 WireHeader::new(
189 ProtocolVersion::CURRENT,
190 0,
191 WireLimits::UNSAFE_DEVELOPMENT_DO_NOT_USE_IN_PRODUCTION,
192 ),
193 Err(ValidationError::Empty)
194 );
195 }
196
197 #[test]
198 fn header_rejects_future_minor_version() {
199 assert_eq!(
200 WireHeader::new(
201 ProtocolVersion::new(1, 1),
202 1,
203 WireLimits::UNSAFE_DEVELOPMENT_DO_NOT_USE_IN_PRODUCTION,
204 ),
205 Err(ValidationError::NotPermitted)
206 );
207 }
208
209 #[test]
210 fn limits_reject_unbounded_values() {
211 assert_eq!(
212 WireLimits::new(usize::MAX, 1, 1, 1),
213 Err(ValidationError::TooLarge)
214 );
215 assert_eq!(WireLimits::new(1, 0, 1, 1), Err(ValidationError::Empty));
216 }
217}