net/adapter/net/subprotocol/
descriptor.rs1use bytes::{Buf, BufMut};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct SubprotocolVersion {
15 pub major: u8,
17 pub minor: u8,
19}
20
21impl SubprotocolVersion {
22 pub const fn new(major: u8, minor: u8) -> Self {
24 Self { major, minor }
25 }
26
27 #[inline]
29 pub fn satisfies(self, min_required: Self) -> bool {
30 self >= min_required
31 }
32
33 #[inline]
35 pub fn to_bytes(self) -> [u8; 2] {
36 [self.major, self.minor]
37 }
38
39 #[inline]
41 pub fn from_bytes(data: &[u8; 2]) -> Self {
42 Self {
43 major: data[0],
44 minor: data[1],
45 }
46 }
47}
48
49impl std::fmt::Display for SubprotocolVersion {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 write!(f, "{}.{}", self.major, self.minor)
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct SubprotocolDescriptor {
58 pub id: u16,
60 pub name: String,
62 pub version: SubprotocolVersion,
64 pub min_compatible: SubprotocolVersion,
66 pub handler_present: bool,
69}
70
71impl SubprotocolDescriptor {
72 pub fn new(id: u16, name: impl Into<String>, version: SubprotocolVersion) -> Self {
74 Self {
75 id,
76 name: name.into(),
77 version,
78 min_compatible: version,
79 handler_present: true,
80 }
81 }
82
83 pub fn with_min_compatible(mut self, min: SubprotocolVersion) -> Self {
100 self.min_compatible = if min > self.version {
101 self.version
102 } else {
103 min
104 };
105 self
106 }
107
108 pub fn forwarding_only(mut self) -> Self {
110 self.handler_present = false;
111 self
112 }
113
114 pub fn is_compatible_with(&self, other: &Self) -> bool {
118 self.id == other.id
119 && self.version.satisfies(other.min_compatible)
120 && other.version.satisfies(self.min_compatible)
121 }
122
123 pub fn capability_tag(&self) -> String {
125 format!("subprotocol:{:#06x}", self.id)
126 }
127}
128
129impl std::fmt::Display for SubprotocolDescriptor {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 write!(f, "{}({:#06x}) v{}", self.name, self.id, self.version)
132 }
133}
134
135pub const MANIFEST_ENTRY_SIZE: usize = 6;
143
144pub fn write_manifest_entry(desc: &SubprotocolDescriptor, buf: &mut impl BufMut) {
146 buf.put_u16_le(desc.id);
147 buf.put_slice(&desc.version.to_bytes());
148 buf.put_slice(&desc.min_compatible.to_bytes());
149}
150
151pub fn read_manifest_entry(
165 buf: &mut impl Buf,
166) -> Option<(u16, SubprotocolVersion, SubprotocolVersion)> {
167 if buf.remaining() < MANIFEST_ENTRY_SIZE {
168 return None;
169 }
170 let id = buf.get_u16_le();
171 let version = SubprotocolVersion::new(buf.get_u8(), buf.get_u8());
172 let min_compat = SubprotocolVersion::new(buf.get_u8(), buf.get_u8());
173 if min_compat > version {
174 return None;
175 }
176 Some((id, version, min_compat))
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_version_ordering() {
185 let v1_0 = SubprotocolVersion::new(1, 0);
186 let v1_1 = SubprotocolVersion::new(1, 1);
187 let v2_0 = SubprotocolVersion::new(2, 0);
188
189 assert!(v1_0 < v1_1);
190 assert!(v1_1 < v2_0);
191 assert!(v1_0 < v2_0);
192 }
193
194 #[test]
195 fn test_version_satisfies() {
196 let v1_0 = SubprotocolVersion::new(1, 0);
197 let v1_1 = SubprotocolVersion::new(1, 1);
198 let v2_0 = SubprotocolVersion::new(2, 0);
199
200 assert!(v1_1.satisfies(v1_0)); assert!(v2_0.satisfies(v1_0)); assert!(!v1_0.satisfies(v1_1)); }
204
205 #[test]
206 fn test_descriptor_compatibility() {
207 let a = SubprotocolDescriptor::new(0x0400, "causal", SubprotocolVersion::new(1, 1))
208 .with_min_compatible(SubprotocolVersion::new(1, 0));
209 let b = SubprotocolDescriptor::new(0x0400, "causal", SubprotocolVersion::new(1, 0));
210
211 assert!(a.is_compatible_with(&b)); }
213
214 #[test]
215 fn test_descriptor_incompatible() {
216 let a = SubprotocolDescriptor::new(0x0400, "causal", SubprotocolVersion::new(2, 0))
217 .with_min_compatible(SubprotocolVersion::new(2, 0));
218 let b = SubprotocolDescriptor::new(0x0400, "causal", SubprotocolVersion::new(1, 0));
219
220 assert!(!a.is_compatible_with(&b)); }
222
223 #[test]
224 fn test_descriptor_different_id() {
225 let a = SubprotocolDescriptor::new(0x0400, "causal", SubprotocolVersion::new(1, 0));
226 let b = SubprotocolDescriptor::new(0x0500, "migration", SubprotocolVersion::new(1, 0));
227
228 assert!(!a.is_compatible_with(&b));
229 }
230
231 #[test]
232 fn test_capability_tag() {
233 let d = SubprotocolDescriptor::new(0x0400, "causal", SubprotocolVersion::new(1, 0));
234 assert_eq!(d.capability_tag(), "subprotocol:0x0400");
235 }
236
237 #[test]
238 fn test_manifest_entry_roundtrip() {
239 let desc = SubprotocolDescriptor::new(0x1234, "test", SubprotocolVersion::new(3, 7))
240 .with_min_compatible(SubprotocolVersion::new(2, 0));
241
242 let mut buf = Vec::new();
243 write_manifest_entry(&desc, &mut buf);
244 assert_eq!(buf.len(), MANIFEST_ENTRY_SIZE);
245
246 let mut cursor = &buf[..];
247 let (id, version, min_compat) = read_manifest_entry(&mut cursor).unwrap();
248 assert_eq!(id, 0x1234);
249 assert_eq!(version, SubprotocolVersion::new(3, 7));
250 assert_eq!(min_compat, SubprotocolVersion::new(2, 0));
251 }
252
253 #[test]
254 fn test_version_display() {
255 assert_eq!(format!("{}", SubprotocolVersion::new(1, 2)), "1.2");
256 }
257
258 #[test]
259 fn test_descriptor_display() {
260 let d = SubprotocolDescriptor::new(0x0400, "causal", SubprotocolVersion::new(1, 0));
261 assert_eq!(format!("{}", d), "causal(0x0400) v1.0");
262 }
263
264 #[test]
275 fn read_manifest_entry_rejects_min_compatible_above_version() {
276 let mut buf = Vec::new();
279 buf.extend_from_slice(&0x1234u16.to_le_bytes()); buf.extend_from_slice(&[1, 0]); buf.extend_from_slice(&[255, 255]); let mut cursor = &buf[..];
284 let parsed = read_manifest_entry(&mut cursor);
285 assert!(
286 parsed.is_none(),
287 "read_manifest_entry must reject min_compat > version",
288 );
289 }
290
291 #[test]
297 fn read_manifest_entry_accepts_min_compatible_equal_to_version() {
298 let mut buf = Vec::new();
299 buf.extend_from_slice(&0x4242u16.to_le_bytes());
300 buf.extend_from_slice(&[3, 7]);
301 buf.extend_from_slice(&[3, 7]);
302
303 let mut cursor = &buf[..];
304 let (id, version, min_compat) =
305 read_manifest_entry(&mut cursor).expect("equal min_compat must be accepted");
306 assert_eq!(id, 0x4242);
307 assert_eq!(version, SubprotocolVersion::new(3, 7));
308 assert_eq!(min_compat, SubprotocolVersion::new(3, 7));
309 }
310
311 #[test]
318 fn with_min_compatible_clamps_to_version() {
319 let desc = SubprotocolDescriptor::new(0x1000, "x", SubprotocolVersion::new(1, 0))
320 .with_min_compatible(SubprotocolVersion::new(2, 5));
321 assert_eq!(
322 desc.min_compatible,
323 SubprotocolVersion::new(1, 0),
324 "with_min_compatible must clamp to self.version",
325 );
326 }
327
328 #[test]
331 fn with_min_compatible_preserves_lower_floor() {
332 let desc = SubprotocolDescriptor::new(0x1000, "x", SubprotocolVersion::new(2, 5))
333 .with_min_compatible(SubprotocolVersion::new(1, 0));
334 assert_eq!(desc.min_compatible, SubprotocolVersion::new(1, 0));
335 }
336}