1use serde::{Deserialize, Serialize};
7use std::fmt;
8
9pub const MULTIPLEX_VERSION: u8 = 1;
11
12pub const MAX_DATA_PAYLOAD: usize = 1024 * 1024;
14
15pub const FRAME_HEADER_LEN: usize = 1 + 1 + 4 + 8 + 4;
17
18
19pub fn grow_read_buf(buf: &mut Vec<u8>, filled: usize) -> Result<(), FrameError> {
23 const CAP: usize = MAX_DATA_PAYLOAD + FRAME_HEADER_LEN;
24 if filled < buf.len() {
25 return Ok(());
26 }
27 if buf.len() >= CAP {
28 return Err(FrameError::PayloadTooLarge(filled));
29 }
30 let next = (buf.len().saturating_mul(2)).clamp(filled + 64 * 1024, CAP);
31 buf.resize(next, 0);
32 Ok(())
33}
34
35
36#[repr(u8)]
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum FrameType {
39 Open = 1,
40 Data = 2,
41 Close = 3,
42 Window = 4,
43 Ping = 5,
44 Pong = 6,
45 GoAway = 7,
46 Ack = 8,
47 Resume = 9,
48 JournalReq = 10,
50 JournalReply = 11,
52}
53
54impl FrameType {
55 pub fn from_u8(v: u8) -> Option<Self> {
56 Some(match v {
57 1 => Self::Open,
58 2 => Self::Data,
59 3 => Self::Close,
60 4 => Self::Window,
61 5 => Self::Ping,
62 6 => Self::Pong,
63 7 => Self::GoAway,
64 8 => Self::Ack,
65 9 => Self::Resume,
66 10 => Self::JournalReq,
67 11 => Self::JournalReply,
68 _ => return None,
69 })
70 }
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
75#[serde(rename_all = "snake_case")]
76pub enum ChannelKind {
77 G4aControl,
78 G4aPty,
79 G4aEvents,
80 M4a,
81 Vault,
82 Observation,
83}
84
85impl ChannelKind {
86 pub fn as_str(self) -> &'static str {
87 match self {
88 Self::G4aControl => "g4a.control",
89 Self::G4aPty => "g4a.pty",
90 Self::G4aEvents => "g4a.events",
91 Self::M4a => "m4a",
92 Self::Vault => "vault",
93 Self::Observation => "observation",
94 }
95 }
96
97 pub fn parse(s: &str) -> Option<Self> {
98 Some(match s {
99 "g4a.control" => Self::G4aControl,
100 "g4a.pty" => Self::G4aPty,
101 "g4a.events" => Self::G4aEvents,
102 "m4a" => Self::M4a,
103 "vault" => Self::Vault,
104 "observation" => Self::Observation,
105 _ => return None,
106 })
107 }
108}
109
110impl fmt::Display for ChannelKind {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 f.write_str(self.as_str())
113 }
114}
115
116#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
118pub struct OpenPayload {
119 pub channel_kind: ChannelKind,
120 pub node_id: String,
122}
123
124#[derive(Clone, Debug, PartialEq, Eq)]
126pub struct Frame {
127 pub version: u8,
128 pub frame_type: FrameType,
129 pub channel_id: u32,
130 pub seq: u64,
132 pub payload: Vec<u8>,
133}
134
135#[derive(Clone, Debug, PartialEq, Eq)]
136pub enum FrameError {
137 Truncated,
138 BadVersion(u8),
139 BadType(u8),
140 PayloadTooLarge(usize),
141 Io(String),
142}
143
144impl fmt::Display for FrameError {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 match self {
147 Self::Truncated => write!(f, "truncated frame"),
148 Self::BadVersion(v) => write!(f, "unsupported multiplex version {v}"),
149 Self::BadType(t) => write!(f, "unknown frame type {t}"),
150 Self::PayloadTooLarge(n) => write!(f, "payload length {n} exceeds limit"),
151 Self::Io(msg) => write!(f, "{msg}"),
152 }
153 }
154}
155
156impl std::error::Error for FrameError {}
157
158impl Frame {
159 pub fn open(channel_id: u32, payload: &OpenPayload) -> Result<Self, FrameError> {
160 let body = serde_json::to_vec(payload).map_err(|e| FrameError::Io(e.to_string()))?;
161 Ok(Self {
162 version: MULTIPLEX_VERSION,
163 frame_type: FrameType::Open,
164 channel_id,
165 seq: 0,
166 payload: body,
167 })
168 }
169
170 pub fn data(channel_id: u32, seq: u64, payload: Vec<u8>) -> Result<Self, FrameError> {
171 if payload.len() > MAX_DATA_PAYLOAD {
172 return Err(FrameError::PayloadTooLarge(payload.len()));
173 }
174 Ok(Self {
175 version: MULTIPLEX_VERSION,
176 frame_type: FrameType::Data,
177 channel_id,
178 seq,
179 payload,
180 })
181 }
182
183 pub fn ack(seq: u64) -> Self {
184 Self {
185 version: MULTIPLEX_VERSION,
186 frame_type: FrameType::Ack,
187 channel_id: 0,
188 seq,
189 payload: Vec::new(),
190 }
191 }
192
193 pub fn resume(after: u64) -> Self {
194 Self {
195 version: MULTIPLEX_VERSION,
196 frame_type: FrameType::Resume,
197 channel_id: 0,
198 seq: after,
199 payload: Vec::new(),
200 }
201 }
202
203 pub fn window(channel_id: u32, credit: u32) -> Self {
204 Self {
205 version: MULTIPLEX_VERSION,
206 frame_type: FrameType::Window,
207 channel_id,
208 seq: u64::from(credit),
209 payload: Vec::new(),
210 }
211 }
212
213 pub fn ping(nonce: u64) -> Self {
214 Self {
215 version: MULTIPLEX_VERSION,
216 frame_type: FrameType::Ping,
217 channel_id: 0,
218 seq: nonce,
219 payload: Vec::new(),
220 }
221 }
222
223 pub fn pong(nonce: u64) -> Self {
224 Self {
225 version: MULTIPLEX_VERSION,
226 frame_type: FrameType::Pong,
227 channel_id: 0,
228 seq: nonce,
229 payload: Vec::new(),
230 }
231 }
232
233 pub fn goaway(code: u32) -> Self {
234 Self {
235 version: MULTIPLEX_VERSION,
236 frame_type: FrameType::GoAway,
237 channel_id: 0,
238 seq: u64::from(code),
239 payload: Vec::new(),
240 }
241 }
242
243 pub fn close(channel_id: u32) -> Self {
244 Self {
245 version: MULTIPLEX_VERSION,
246 frame_type: FrameType::Close,
247 channel_id,
248 seq: 0,
249 payload: Vec::new(),
250 }
251 }
252
253 pub fn encode(&self) -> Result<Vec<u8>, FrameError> {
255 if self.payload.len() > MAX_DATA_PAYLOAD {
256 return Err(FrameError::PayloadTooLarge(self.payload.len()));
257 }
258 let mut out = Vec::with_capacity(FRAME_HEADER_LEN + self.payload.len());
259 out.push(self.version);
260 out.push(self.frame_type as u8);
261 out.extend_from_slice(&self.channel_id.to_be_bytes());
262 out.extend_from_slice(&self.seq.to_be_bytes());
263 out.extend_from_slice(&(self.payload.len() as u32).to_be_bytes());
264 out.extend_from_slice(&self.payload);
265 Ok(out)
266 }
267
268 pub fn decode(buf: &[u8]) -> Result<(Self, usize), FrameError> {
270 if buf.len() < FRAME_HEADER_LEN {
271 return Err(FrameError::Truncated);
272 }
273 let version = buf[0];
274 if version != MULTIPLEX_VERSION {
275 return Err(FrameError::BadVersion(version));
276 }
277 let frame_type = FrameType::from_u8(buf[1]).ok_or(FrameError::BadType(buf[1]))?;
278 let channel_id = u32::from_be_bytes([buf[2], buf[3], buf[4], buf[5]]);
279 let seq = u64::from_be_bytes([
280 buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], buf[12], buf[13],
281 ]);
282 let length = u32::from_be_bytes([buf[14], buf[15], buf[16], buf[17]]) as usize;
283 if length > MAX_DATA_PAYLOAD {
284 return Err(FrameError::PayloadTooLarge(length));
285 }
286 let total = FRAME_HEADER_LEN + length;
287 if buf.len() < total {
288 return Err(FrameError::Truncated);
289 }
290 Ok((
291 Self {
292 version,
293 frame_type,
294 channel_id,
295 seq,
296 payload: buf[FRAME_HEADER_LEN..total].to_vec(),
297 },
298 total,
299 ))
300 }
301
302 pub fn parse_open(&self) -> Result<OpenPayload, FrameError> {
303 if self.frame_type != FrameType::Open {
304 return Err(FrameError::Io("not an OPEN frame".into()));
305 }
306 serde_json::from_slice(&self.payload).map_err(|e| FrameError::Io(e.to_string()))
307 }
308
309 pub fn journal_req(channel_id: u32, after: u64, limit: u32) -> Self {
311 Self {
312 version: MULTIPLEX_VERSION,
313 frame_type: FrameType::JournalReq,
314 channel_id,
315 seq: after,
316 payload: limit.to_be_bytes().to_vec(),
317 }
318 }
319
320 pub fn journal_req_limit(&self) -> Option<u32> {
321 if self.frame_type != FrameType::JournalReq || self.payload.len() != 4 {
322 return None;
323 }
324 Some(u32::from_be_bytes([
325 self.payload[0],
326 self.payload[1],
327 self.payload[2],
328 self.payload[3],
329 ]))
330 }
331
332 pub fn journal_reply(channel_id: u32, body: &JournalReplyBody) -> Result<Self, FrameError> {
333 let payload = body.encode()?;
334 if payload.len() > MAX_DATA_PAYLOAD {
335 return Err(FrameError::PayloadTooLarge(payload.len()));
336 }
337 Ok(Self {
338 version: MULTIPLEX_VERSION,
339 frame_type: FrameType::JournalReply,
340 channel_id,
341 seq: body.after,
342 payload,
343 })
344 }
345}
346
347#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
349pub struct JournalWireEntry {
350 pub seq: u64,
351 pub payload: Vec<u8>,
352}
353
354#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
356pub struct JournalReplyBody {
357 pub after: u64,
358 pub entries: Vec<JournalWireEntry>,
359}
360
361impl JournalReplyBody {
362 pub fn encode(&self) -> Result<Vec<u8>, FrameError> {
363 serde_json::to_vec(self).map_err(|e| FrameError::Io(e.to_string()))
364 }
365
366 pub fn decode(bytes: &[u8]) -> Result<Self, FrameError> {
367 serde_json::from_slice(bytes).map_err(|e| FrameError::Io(e.to_string()))
368 }
369}
370
371#[derive(Clone, Debug, PartialEq, Eq)]
373pub struct DrainEnvelope {
374 pub node_id: String,
376 pub frame: Frame,
377}
378
379impl DrainEnvelope {
380 pub fn encode(&self) -> Result<Vec<u8>, FrameError> {
381 let id = self.node_id.as_bytes();
382 if id.len() > 255 {
383 return Err(FrameError::Io("node_id too long".into()));
384 }
385 let frame_bytes = self.frame.encode()?;
386 let mut out = Vec::with_capacity(1 + id.len() + frame_bytes.len());
387 out.push(id.len() as u8);
388 out.extend_from_slice(id);
389 out.extend_from_slice(&frame_bytes);
390 Ok(out)
391 }
392
393 pub fn decode(buf: &[u8]) -> Result<(Self, usize), FrameError> {
394 if buf.is_empty() {
395 return Err(FrameError::Truncated);
396 }
397 let id_len = buf[0] as usize;
398 if buf.len() < 1 + id_len {
399 return Err(FrameError::Truncated);
400 }
401 let node_id = String::from_utf8(buf[1..1 + id_len].to_vec())
402 .map_err(|_| FrameError::Io("node_id is not utf-8".into()))?;
403 let (frame, n) = Frame::decode(&buf[1 + id_len..])?;
404 Ok((Self { node_id, frame }, 1 + id_len + n))
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411
412 #[test]
413 fn roundtrip_data_ack_resume() {
414 let data = Frame::data(7, 42, b"opaque-ciphertext".to_vec()).unwrap();
415 let bytes = data.encode().unwrap();
416 let (decoded, n) = Frame::decode(&bytes).unwrap();
417 assert_eq!(n, bytes.len());
418 assert_eq!(decoded, data);
419
420 let ack = Frame::ack(42);
421 let (d, _) = Frame::decode(&ack.encode().unwrap()).unwrap();
422 assert_eq!(d.frame_type, FrameType::Ack);
423 assert_eq!(d.seq, 42);
424
425 let resume = Frame::resume(41);
426 let (d, _) = Frame::decode(&resume.encode().unwrap()).unwrap();
427 assert_eq!(d.frame_type, FrameType::Resume);
428 assert_eq!(d.seq, 41);
429 }
430
431 #[test]
432 fn open_payload_roundtrip() {
433 let open = Frame::open(
434 1,
435 &OpenPayload {
436 channel_kind: ChannelKind::M4a,
437 node_id: "node-a".into(),
438 },
439 )
440 .unwrap();
441 let (decoded, _) = Frame::decode(&open.encode().unwrap()).unwrap();
442 let payload = decoded.parse_open().unwrap();
443 assert_eq!(payload.channel_kind, ChannelKind::M4a);
444 assert_eq!(payload.node_id, "node-a");
445 }
446
447
448 #[test]
449 fn grow_read_buf_accepts_frames_between_64kib_and_1mib() {
450 let payload = vec![0xABu8; 100 * 1024];
452 let frame = Frame::data(1, 7, payload.clone()).unwrap();
453 let wire = frame.encode().unwrap();
454 assert!(wire.len() > 64 * 1024);
455
456 let mut buf = vec![0u8; 64 * 1024];
457 let mut filled = 0usize;
458 let mut offset = 0usize;
459 loop {
460 if filled == buf.len() {
461 grow_read_buf(&mut buf, filled).expect("must grow for 100 KiB frame");
462 }
463 let space = buf.len() - filled;
464 let take = space.min(wire.len() - offset);
465 buf[filled..filled + take].copy_from_slice(&wire[offset..offset + take]);
466 filled += take;
467 offset += take;
468 match Frame::decode(&buf[..filled]) {
469 Ok((decoded, consumed)) => {
470 assert_eq!(decoded.payload, payload);
471 assert_eq!(consumed, wire.len());
472 break;
473 }
474 Err(FrameError::Truncated) => {
475 assert!(offset < wire.len(), "still truncated after full wire");
476 continue;
477 }
478 Err(e) => panic!("unexpected {e}"),
479 }
480 }
481 }
482
483 #[test]
484 fn rejects_oversized_payload() {
485 let big = vec![0u8; MAX_DATA_PAYLOAD + 1];
486 assert!(matches!(
487 Frame::data(1, 1, big),
488 Err(FrameError::PayloadTooLarge(_))
489 ));
490 }
491
492 #[test]
493 fn drain_envelope_prefixes_opaque_node_id() {
494 let env = DrainEnvelope {
495 node_id: "node-b".into(),
496 frame: Frame::data(3, 9, b"inner".to_vec()).unwrap(),
497 };
498 let bytes = env.encode().unwrap();
499 let (decoded, n) = DrainEnvelope::decode(&bytes).unwrap();
500 assert_eq!(n, bytes.len());
501 assert_eq!(decoded.node_id, "node-b");
502 assert_eq!(decoded.frame.seq, 9);
503 assert_eq!(decoded.frame.payload, b"inner");
504 }
505
506 #[test]
507 fn journal_req_roundtrip() {
508 let f = Frame::journal_req(3, 10, 64);
509 let (d, _) = Frame::decode(&f.encode().unwrap()).unwrap();
510 assert_eq!(d.frame_type, FrameType::JournalReq);
511 assert_eq!(d.seq, 10);
512 assert_eq!(d.journal_req_limit(), Some(64));
513 let body = JournalReplyBody {
514 after: 10,
515 entries: vec![JournalWireEntry {
516 seq: 11,
517 payload: b"x".to_vec(),
518 }],
519 };
520 let reply = Frame::journal_reply(3, &body).unwrap();
521 let (d, _) = Frame::decode(&reply.encode().unwrap()).unwrap();
522 let decoded = JournalReplyBody::decode(&d.payload).unwrap();
523 assert_eq!(decoded.entries[0].seq, 11);
524 }
525
526 #[test]
527 fn truncated_frame_errors() {
528 assert!(matches!(Frame::decode(&[1, 2, 3]), Err(FrameError::Truncated)));
529 let mut partial = Frame::data(1, 1, b"hello".to_vec()).unwrap().encode().unwrap();
530 partial.pop();
531 assert!(matches!(Frame::decode(&partial), Err(FrameError::Truncated)));
532 }
533}