1mod connection;
13mod consumption;
14mod credit;
15mod payload {
16 use serde::{Deserialize, Deserializer, Serializer};
17
18 pub fn serialize<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
19 serializer.serialize_bytes(bytes)
20 }
21
22 pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<&'de [u8], D::Error> {
23 <&[u8]>::deserialize(deserializer)
24 }
25}
26
27pub use connection::{Connection, Driver, Io, Received, Receiver, Sender};
30pub use consumption::{Consumption, ReceiveCredit};
31pub use credit::{Credit, Reservation};
32
33use std::io::{self, IoSlice};
37use std::ops::Range;
38use std::sync::Arc;
39use serde::{Deserialize, Serialize};
41use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
42use crate::{Frame, FramePayload};
44
45const VERSION: u8 = 1;
48const CONTROL_LIMIT: usize = 64;
49const COALESCE_BYTES: usize = 64 * 1024;
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60pub enum Role {
61 Data,
62 Control,
63}
64
65#[derive(Debug, PartialEq, Eq)]
66pub enum Packet {
67 Frame(Frame),
68 Credit(u64),
69 Cancellation { id: u64, preceding: u64 },
70}
71
72#[derive(Serialize, Deserialize)]
74enum Body<'a> {
75 Call {
76 id: u64,
77 method: &'a str,
78
79 #[serde(borrow, with = "payload")]
80 params: &'a [u8],
81 },
82 Cancel {
83 id: u64,
84 preceding: u64,
85 },
86 Push {
87 method: &'a str,
88
89 #[serde(borrow, with = "payload")]
90 params: &'a [u8],
91 },
92 Answer {
93 id: u64,
94
95 #[serde(borrow, with = "payload")]
96 result: &'a [u8],
97 },
98 Refusal {
99 id: u64,
100 refusal: std::borrow::Cow<'a, harmos_domain::Refusal>,
101 },
102 Credit {
103 consumed: u64,
104 },
105}
106
107impl Role {
112 fn limit(self) -> usize {
113 match self {
114 Self::Data => Frame::LIMIT,
115 Self::Control => CONTROL_LIMIT,
116 }
117 }
118}
119
120impl Packet {
121 pub fn channel(&self) -> Role {
122 match self {
123 Self::Frame(Frame::Cancel { .. }) | Self::Credit(_) | Self::Cancellation { .. } => Role::Control,
124 _ => Role::Data,
125 }
126 }
127
128 pub fn encoded_len(&self) -> io::Result<usize> {
129 let size = postcard::experimental::serialized_size(&(VERSION, self.body()?)).map_err(invalid)?;
130 if size > self.channel().limit() {
131 return Err(invalid("packet exceeds channel limit"));
132 }
133 Ok(size + 4)
134 }
135
136 pub fn encode(&self) -> io::Result<Vec<u8>> {
137 let mut output = Vec::new();
138 self.encode_into(&mut output)?;
139 Ok(output)
140 }
141
142 fn encode_into(&self, output: &mut Vec<u8>) -> io::Result<()> {
143 let size = self.encoded_len()?;
144 if size > output.capacity() {
145 output.try_reserve_exact(size - output.len()).map_err(io::Error::other)?;
146 }
147 output.resize(size, 0);
148 output[..4].copy_from_slice(&((size - 4) as u32).to_be_bytes());
149 postcard::to_slice(&(VERSION, self.body()?), &mut output[4..]).map_err(invalid)?;
150 Ok(())
151 }
152
153 #[tracing::instrument(target = "harmos::carrier", name = "sidecar.frame.decode", level = "trace", skip_all, fields(wire_bytes = bytes.len(), channel = ?channel))]
154 pub fn decode(channel: Role, bytes: Vec<u8>) -> io::Result<Self> {
155 if bytes.is_empty() || bytes.len() > channel.limit() {
156 return Err(invalid("packet exceeds channel limit or is empty"));
157 }
158 let (_version, rest) = postcard::take_from_bytes::<u8>(&bytes).map_err(invalid)?;
159 let (body, rest) = postcard::take_from_bytes::<Body<'_>>(rest).map_err(invalid)?;
160 if !rest.is_empty() {
161 return Err(invalid("trailing sidecar transport bytes"));
162 }
163 let control = matches!(body, Body::Cancel { .. } | Body::Credit { .. });
164 if control != (channel == Role::Control) {
165 return Err(invalid("packet arrived on the wrong physical channel"));
166 }
167 Ok(match body {
168 Body::Call { id, method, params } => {
169 let method = method.to_owned();
170 let range = payload_range(&bytes, params)?;
171 Self::Frame(Frame::Call {
172 id,
173 method,
174 params: FramePayload::from_range(bytes, range),
175 })
176 }
177 Body::Cancel { id, preceding } => Self::Cancellation { id, preceding },
178 Body::Push { method, params } => {
179 let method = method.to_owned();
180 let range = payload_range(&bytes, params)?;
181 Self::Frame(Frame::Push {
182 method,
183 params: FramePayload::from_range(bytes, range),
184 })
185 }
186 Body::Answer { id, result } => {
187 let range = payload_range(&bytes, result)?;
188 Self::Frame(Frame::Answer {
189 id,
190 result: FramePayload::from_range(bytes, range),
191 })
192 }
193 Body::Refusal { id, refusal } => Self::Frame(Frame::Refusal { id, refusal: refusal.into_owned() }),
194 Body::Credit { consumed } => Self::Credit(consumed),
195 })
196 }
197
198 fn body(&self) -> io::Result<Body<'_>> {
199 Ok(match self {
200 Self::Frame(Frame::Call { id, method, params }) => Body::Call { id: *id, method, params },
201 Self::Frame(Frame::Cancel { .. }) => {
202 return Err(invalid("cancellation requires a data watermark"));
203 }
204 Self::Frame(Frame::Push { method, params }) => Body::Push { method, params },
205 Self::Frame(Frame::Answer { id, result }) => Body::Answer { id: *id, result },
206 Self::Frame(Frame::Refusal { id, refusal }) => Body::Refusal {
207 id: *id,
208 refusal: std::borrow::Cow::Borrowed(refusal),
209 },
210 Self::Credit(consumed) => Body::Credit { consumed: *consumed },
211 Self::Cancellation { id, preceding } => Body::Cancel { id: *id, preceding: *preceding },
212 })
213 }
214}
215
216#[tracing::instrument(target = "harmos::carrier", name = "sidecar.frame.read", level = "trace", skip_all, fields(channel = ?channel))]
219pub async fn read<R: AsyncRead + Unpin>(reader: &mut R, channel: Role) -> io::Result<Option<Packet>> {
220 let Some(length) = read_length(reader, channel).await? else {
221 return Ok(None);
222 };
223 let mut body = vec![0; length];
224 reader.read_exact(&mut body).await?;
225 Packet::decode(channel, body).map(Some)
226}
227
228#[tracing::instrument(target = "harmos::carrier", name = "sidecar.frame.read", level = "trace", skip_all, fields(channel = "Data"))]
229pub async fn read_data<R: AsyncRead + Unpin>(reader: &mut R, credit: &Arc<ReceiveCredit>) -> io::Result<Option<(Packet, Consumption)>> {
230 let Some(length) = read_length(reader, Role::Data).await? else {
231 return Ok(None);
232 };
233 let consumption = credit.admit(length + 4)?;
234 let mut body = vec![0; length];
235 reader.read_exact(&mut body).await?;
236 let packet = Packet::decode(Role::Data, body)?;
237 Ok(Some((packet, consumption)))
238}
239
240pub async fn write<W: AsyncWrite + Unpin>(writer: &mut W, packet: &Packet) -> io::Result<()> {
241 write_with(writer, packet, &mut Vec::new()).await
242}
243
244fn payload_range(bytes: &[u8], payload: &[u8]) -> io::Result<Range<usize>> {
247 if payload.is_empty() {
248 return Ok(0..0);
249 }
250 let start =
251 (payload.as_ptr() as usize).checked_sub(bytes.as_ptr() as usize).ok_or_else(|| invalid("payload lies outside its packet"))?;
252 let end = start.checked_add(payload.len()).filter(|end| *end <= bytes.len()).ok_or_else(|| invalid("payload exceeds its packet"))?;
253 Ok(start..end)
254}
255
256#[tracing::instrument(target = "harmos::carrier", name = "sidecar.frame.write", level = "trace", skip_all, fields(wire_bytes = tracing::field::Empty, coalesced = tracing::field::Empty))]
257async fn write_with<W: AsyncWrite + Unpin>(writer: &mut W, packet: &Packet, header: &mut Vec<u8>) -> io::Result<()> {
258 let payload = match packet {
259 Packet::Frame(Frame::Call { params, .. } | Frame::Push { params, .. }) => params.as_slice(),
260 Packet::Frame(Frame::Answer { result, .. }) => result.as_slice(),
261 _ => {
262 packet.encode_into(header)?;
263 tracing::Span::current().record("wire_bytes", header.len());
264 return writer.write_all(header).await;
265 }
266 };
267 let size = packet.encoded_len()?;
268 let prefix_size = size - payload.len();
269 let coalesce = !writer.is_write_vectored() && size <= COALESCE_BYTES;
270 tracing::Span::current().record("wire_bytes", size);
271 tracing::Span::current().record("coalesced", coalesce);
272 let capacity = if coalesce { size } else { prefix_size };
273 if capacity > header.capacity() {
274 header.try_reserve_exact(capacity - header.len()).map_err(io::Error::other)?;
275 }
276 header.resize(prefix_size, 0);
277 header[..4].copy_from_slice(&((size - 4) as u32).to_be_bytes());
278 match packet {
280 Packet::Frame(Frame::Call { id, method, .. }) => {
281 postcard::to_slice(&(VERSION, 0_u32, id, method.as_str(), payload.len()), &mut header[4..]).map_err(invalid)?;
282 }
283 Packet::Frame(Frame::Push { method, .. }) => {
284 postcard::to_slice(&(VERSION, 2_u32, method.as_str(), payload.len()), &mut header[4..]).map_err(invalid)?;
285 }
286 Packet::Frame(Frame::Answer { id, .. }) => {
287 postcard::to_slice(&(VERSION, 3_u32, id, payload.len()), &mut header[4..]).map_err(invalid)?;
288 }
289 _ => unreachable!("only payload frames use a split write"),
290 }
291 if coalesce {
294 header.extend_from_slice(payload);
295 return writer.write_all(header).await;
296 }
297 let mut prefix = header.as_slice();
298 let mut remaining = payload;
299 while !prefix.is_empty() || !remaining.is_empty() {
300 let slices = [IoSlice::new(prefix), IoSlice::new(remaining)];
301 let written = writer.write_vectored(&slices).await?;
302 if written == 0 {
303 return Err(io::Error::new(io::ErrorKind::WriteZero, "sidecar packet write made no progress"));
304 }
305 let prefix_bytes = written.min(prefix.len());
306 prefix = &prefix[prefix_bytes..];
307 remaining = &remaining[written - prefix_bytes..];
308 }
309 Ok(())
310}
311
312async fn read_length<R: AsyncRead + Unpin>(reader: &mut R, channel: Role) -> io::Result<Option<usize>> {
313 let mut header = [0; 4];
314 let read = reader.read(&mut header).await?;
315 if read == 0 {
316 return Ok(None);
317 }
318 reader.read_exact(&mut header[read..]).await?;
319 let length = u32::from_be_bytes(header) as usize;
320 if length == 0 || length > channel.limit() {
321 return Err(invalid("invalid sidecar packet length"));
322 }
323 Ok(Some(length))
324}
325
326fn invalid(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Error {
327 io::Error::new(io::ErrorKind::InvalidData, error)
328}
329
330#[cfg(test)]
333mod tests {
334 use super::*;
335 use std::pin::Pin;
336 use std::task::{Context, Poll};
337
338 #[derive(Default)]
339 struct ScalarWriter {
340 bytes: Vec<u8>,
341 writes: usize,
342 maximum: Option<usize>,
343 }
344
345 struct PrefixReader {
346 bytes: Vec<u8>,
347 position: usize,
348 maximum: usize,
349 reads: usize,
350 interrupted: bool,
351 }
352
353 impl PrefixReader {
354 fn new(bytes: Vec<u8>, maximum: usize) -> Self {
355 Self {
356 bytes,
357 position: 0,
358 maximum,
359 reads: 0,
360 interrupted: false,
361 }
362 }
363 }
364
365 impl AsyncRead for PrefixReader {
366 fn poll_read(mut self: Pin<&mut Self>, _: &mut Context<'_>, output: &mut tokio::io::ReadBuf<'_>) -> Poll<io::Result<()>> {
367 self.reads += 1;
368 if std::mem::take(&mut self.interrupted) {
369 let error = io::ErrorKind::Interrupted.into();
370 return Poll::Ready(Err(error));
371 }
372 let remaining = self.bytes.len() - self.position;
373 let count = remaining.min(self.maximum).min(output.remaining());
374 let end = self.position + count;
375 output.put_slice(&self.bytes[self.position..end]);
376 self.position = end;
377 Poll::Ready(Ok(()))
378 }
379 }
380
381 impl AsyncWrite for ScalarWriter {
382 fn poll_write(mut self: Pin<&mut Self>, _: &mut Context<'_>, bytes: &[u8]) -> Poll<io::Result<usize>> {
383 let count = bytes.len().min(self.maximum.unwrap_or(usize::MAX));
384 self.bytes.extend_from_slice(&bytes[..count]);
385 self.writes += 1;
386 Poll::Ready(Ok(count))
387 }
388
389 fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
390 Poll::Ready(Ok(()))
391 }
392
393 fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
394 Poll::Ready(Ok(()))
395 }
396 }
397
398 #[tokio::test]
399 async fn prefix_reads_once() {
400 let mut reader = PrefixReader::new(128_u32.to_be_bytes().to_vec(), 4);
401 assert_eq!(read_length(&mut reader, Role::Data).await.unwrap(), Some(128));
402 assert_eq!(reader.reads, 1);
403
404 for maximum in 1..4 {
405 let mut reader = PrefixReader::new(128_u32.to_be_bytes().to_vec(), maximum);
406 assert_eq!(read_length(&mut reader, Role::Data).await.unwrap(), Some(128));
407 assert_eq!(reader.reads, 4_usize.div_ceil(maximum));
408 }
409 }
410
411 #[tokio::test]
412 async fn prefix_eof_errors() {
413 let mut reader = PrefixReader::new(Vec::new(), 4);
414 assert_eq!(read_length(&mut reader, Role::Data).await.unwrap(), None);
415 for length in 1..4 {
416 let bytes = 128_u32.to_be_bytes()[..length].to_vec();
417 let mut reader = PrefixReader::new(bytes, 4);
418 let error = read_length(&mut reader, Role::Data).await.unwrap_err();
419 assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
420 }
421 }
422
423 #[tokio::test]
424 async fn prefix_invalid_interrupted() {
425 for (channel, length) in [(Role::Data, 0), (Role::Data, Frame::LIMIT + 1), (Role::Control, 0), (Role::Control, CONTROL_LIMIT + 1)] {
426 let bytes = u32::try_from(length).unwrap().to_be_bytes().to_vec();
427 let mut reader = PrefixReader::new(bytes, 4);
428 let error = read_length(&mut reader, channel).await.unwrap_err();
429 assert_eq!(error.kind(), io::ErrorKind::InvalidData);
430 }
431
432 let mut reader = PrefixReader::new(128_u32.to_be_bytes().to_vec(), 4);
433 reader.interrupted = true;
434 let error = read_length(&mut reader, Role::Data).await.unwrap_err();
435 assert_eq!(error.kind(), io::ErrorKind::Interrupted);
436 assert_eq!(reader.position, 0);
437 assert_eq!(read_length(&mut reader, Role::Data).await.unwrap(), Some(128));
438 }
439
440 #[tokio::test]
441 async fn scalar_frames_coalesce() {
442 let mut scratch = Vec::new();
443 for frame in [
444 Frame::Call {
445 id: 1,
446 method: "call".into(),
447 params: vec![23; 4096].into(),
448 },
449 Frame::Push {
450 method: "push".into(),
451 params: vec![23; 8192].into(),
452 },
453 Frame::Answer { id: 2, result: vec![23; 32_768].into() },
454 ] {
455 let packet = Packet::Frame(frame);
456 let expected = packet.encode().unwrap();
457 let mut writer = ScalarWriter::default();
458 write_with(&mut writer, &packet, &mut scratch).await.unwrap();
459 assert_eq!(writer.bytes, expected);
460 assert_eq!(writer.writes, 1);
461 assert_eq!(scratch.capacity(), expected.len());
462 }
463 }
464
465 #[tokio::test]
466 async fn scalar_partial_bulk() {
467 let packet = Packet::Frame(Frame::Answer { id: 1, result: vec![7; 4096].into() });
468 let mut writer = ScalarWriter {
469 maximum: Some(3),
470 ..ScalarWriter::default()
471 };
472 write_with(&mut writer, &packet, &mut Vec::new()).await.unwrap();
473 assert_eq!(writer.bytes, packet.encode().unwrap());
474
475 let packet = Packet::Frame(Frame::Answer {
476 id: 2,
477 result: vec![7; COALESCE_BYTES].into(),
478 });
479 let mut writer = ScalarWriter::default();
480 let mut scratch = Vec::new();
481 write_with(&mut writer, &packet, &mut scratch).await.unwrap();
482 assert_eq!(writer.bytes, packet.encode().unwrap());
483 assert_eq!(writer.writes, 2);
484 assert!(scratch.capacity() < 32);
485 }
486
487 #[test]
488 fn malformed_payloads() {
489 let bytes = vec![1, 2, 3];
490 let foreign = vec![4, 5, 6];
491 assert!(payload_range(&bytes, &foreign).is_err());
492 assert!(std::panic::catch_unwind(|| FramePayload::from_range(vec![1], 0..2)).is_err());
493
494 let packet = Packet::Frame(Frame::Answer { id: 1, result: vec![7; 128].into() });
495 let encoded = packet.encode().unwrap();
496 let mut body = encoded[4..].to_vec();
497 body.pop();
498 assert!(Packet::decode(Role::Data, body).is_err());
499 let mut body = encoded[4..].to_vec();
500 body.push(0);
501 assert!(Packet::decode(Role::Data, body).is_err());
502 }
503
504 #[test]
505 fn payload_keeps_backing() {
506 for frame in [
507 Frame::Call {
508 id: 1,
509 method: "call".into(),
510 params: vec![23; 4096].into(),
511 },
512 Frame::Push {
513 method: "push".into(),
514 params: vec![23; 4096].into(),
515 },
516 Frame::Answer { id: 2, result: vec![23; 4096].into() },
517 ] {
518 let encoded = Packet::Frame(frame).encode().unwrap();
519 let body = encoded[4..].to_vec();
520 let expected = body.as_ptr().wrapping_add(body.len() - 4096);
521 let Packet::Frame(frame) = Packet::decode(Role::Data, body).unwrap() else { panic!("payload frame") };
522 let payload = match frame {
523 Frame::Call { params, .. } | Frame::Push { params, .. } => params,
524 Frame::Answer { result, .. } => result,
525 _ => unreachable!(),
526 };
527 assert_eq!(payload.as_ptr(), expected);
528 let retained = payload.clone();
529 drop(payload);
530 assert_eq!(retained.as_ptr(), expected);
531 assert_eq!(retained.as_slice(), [23; 4096]);
532 }
533 }
534}