sozu_command_lib/channel.rs
1//! Bidirectional length-delimited unix-socket channel.
2//!
3//! Implements the master ↔ worker / master ↔ CLI message channel: each
4//! payload is preceded by a native `usize` length prefix (NOT a NUL
5//! separator — that scheme belongs to the state-file save format written
6//! by `ConfigState::write_requests_to_file` in `command/src/state.rs`).
7//! Bounded by the per-channel `Channel::max_buffer_size`, checked against
8//! the declared length in `try_read_delimited_message` before any payload
9//! byte is allocated.
10
11use std::{
12 cmp::min,
13 fmt::Debug,
14 io::{self, ErrorKind, Read, Write},
15 marker::PhantomData,
16 mem,
17 ops::Deref,
18 os::unix::{
19 io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
20 net::UnixStream as StdUnixStream,
21 },
22 time::Duration,
23};
24
25use mio::{event::Source, net::UnixStream as MioUnixStream};
26use prost::{DecodeError, Message as ProstMessage};
27use zeroize::{Zeroize, Zeroizing};
28
29use crate::{buffer::growable::Buffer, ready::Ready};
30
31/// High watermark threshold: log a warning when buffer usage exceeds 80% of max
32const HIGH_WATERMARK_RATIO: f64 = 0.8;
33const CHANNEL_SNAPSHOT_VERSION: u16 = 1;
34const CHANNEL_READY_MASK: u16 =
35 Ready::READABLE.0 | Ready::WRITABLE.0 | Ready::ERROR.0 | Ready::HUP.0 | Ready::WRITE_CLOSED.0;
36
37#[derive(thiserror::Error, Debug)]
38pub enum ChannelError {
39 #[error("io read error")]
40 Read(std::io::Error),
41 #[error("no byte written on the channel")]
42 NoByteWritten,
43 #[error("no byte left to read on the channel")]
44 NoByteToRead,
45 #[error(
46 "message ({message_len} bytes) too large for back buffer capacity ({capacity} bytes, max {max} bytes)"
47 )]
48 MessageTooLarge {
49 message_len: usize,
50 capacity: usize,
51 max: usize,
52 },
53 #[error(
54 "declared message length ({message_len} bytes) is shorter than the {delimiter_size}-byte length prefix"
55 )]
56 MessageLengthUnderDelimiter {
57 message_len: usize,
58 delimiter_size: usize,
59 },
60 #[error("io write error")]
61 Write(std::io::Error),
62 #[error("channel buffer is full ({capacity} bytes, max {max} bytes), cannot grow more")]
63 BufferFull { capacity: usize, max: usize },
64 #[error("Timeout is reached: {0:?}")]
65 TimeoutReached(Duration),
66 #[error("Could not read anything on the channel")]
67 NothingRead,
68 #[error("invalid char set in command message, ignoring: {0}")]
69 InvalidCharSet(String),
70 #[error("could not set the timeout of the unix stream with file descriptor {fd}: {error}")]
71 SetTimeout { fd: i32, error: String },
72 #[error(
73 "Could not change the blocking status ef the unix stream with file descriptor {fd}: {error}"
74 )]
75 BlockingStatus { fd: i32, error: String },
76 #[error("Connection error: {0:?}")]
77 Connection(Option<std::io::Error>),
78 #[error("Invalid protobuf message: {0}")]
79 InvalidProtobufMessage(DecodeError),
80 #[error("This should never happen (index out of bound on a tested buffer)")]
81 MismatchBufferSize,
82}
83
84/// Serializable userspace state of a [`Channel`].
85///
86/// The Hub supplies the effective descriptor after fork/exec inheritance or
87/// explicit duplication. Only bytes that have not been consumed are captured;
88/// buffer offsets are normalized on restore while their capacities and
89/// backpressure state are preserved.
90#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
91pub struct ChannelSnapshot {
92 version: u16,
93 front: SnapshotBytes,
94 back: SnapshotBytes,
95 initial_buffer_size: usize,
96 max_buffer_size: usize,
97 front_buffer_capacity: usize,
98 back_buffer_capacity: usize,
99 readiness: u16,
100 interest: u16,
101 blocking: bool,
102 front_high_watermark_logged: bool,
103 back_high_watermark_logged: bool,
104}
105
106/// Owner of the unconsumed bytes a [`ChannelSnapshot`] carries.
107///
108/// Those bytes can be frames holding protected values, and a plain `Vec<u8>`
109/// leaves copies of them behind: serde grows it by `push`, so every
110/// reallocation frees the previous block unwiped, and a deserialization that
111/// fails after receiving some elements drops a half-built vector. This type
112/// wipes on drop (hence also the clone's own copy), wipes the previous block
113/// before a growth step releases it, and prints no bytes.
114///
115/// The wire form is the one `Vec<u8>` has, a sequence of unsigned integers, so
116/// serialized snapshots, and the partial frames they may carry, are unchanged.
117/// Only what lives in this process's memory is wiped: whatever buffer a
118/// serde format reads from or writes to is the caller's.
119#[derive(Clone, Eq, PartialEq)]
120struct SnapshotBytes(Vec<u8>);
121
122/// Capacity a deserialization starts from before doubling.
123const SNAPSHOT_BYTES_FLOOR: usize = 64;
124
125impl SnapshotBytes {
126 fn copy_of(bytes: &[u8]) -> Self {
127 SnapshotBytes(bytes.to_owned())
128 }
129
130 /// Append `byte`, widening into a fresh allocation and wiping the previous
131 /// one first when the current one is full.
132 fn push(&mut self, byte: u8) {
133 if self.0.len() == self.0.capacity() {
134 let wider = Vec::with_capacity(
135 self.0
136 .capacity()
137 .saturating_mul(2)
138 .max(SNAPSHOT_BYTES_FLOOR),
139 );
140 let mut previous = mem::replace(&mut self.0, wider);
141 self.0.extend_from_slice(&previous);
142 previous.zeroize();
143 }
144 self.0.push(byte);
145 }
146}
147
148impl Deref for SnapshotBytes {
149 type Target = [u8];
150
151 fn deref(&self) -> &[u8] {
152 &self.0
153 }
154}
155
156impl Drop for SnapshotBytes {
157 fn drop(&mut self) {
158 self.0.zeroize();
159 }
160}
161
162impl Debug for SnapshotBytes {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 write!(f, "<{} bytes redacted>", self.0.len())
165 }
166}
167
168impl serde::Serialize for SnapshotBytes {
169 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
170 serializer.collect_seq(&self.0)
171 }
172}
173
174impl<'de> serde::Deserialize<'de> for SnapshotBytes {
175 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
176 struct BytesVisitor;
177
178 impl<'de> serde::de::Visitor<'de> for BytesVisitor {
179 type Value = SnapshotBytes;
180
181 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 formatter.write_str("a sequence")
183 }
184
185 fn visit_seq<A: serde::de::SeqAccess<'de>>(
186 self,
187 mut seq: A,
188 ) -> Result<SnapshotBytes, A::Error> {
189 // An early return drops `bytes`, which wipes what it received.
190 let mut bytes = SnapshotBytes(Vec::new());
191 while let Some(byte) = seq.next_element::<u8>()? {
192 bytes.push(byte);
193 }
194 Ok(bytes)
195 }
196 }
197
198 deserializer.deserialize_seq(BytesVisitor)
199 }
200}
201
202#[derive(thiserror::Error, Debug, Eq, PartialEq)]
203pub enum ChannelSnapshotError {
204 #[error("unsupported channel snapshot version {version}; supported version is {supported}")]
205 UnsupportedVersion { version: u16, supported: u16 },
206 #[error(
207 "invalid channel snapshot buffer bounds: initial capacity {initial} exceeds maximum {max}"
208 )]
209 InvalidBufferBounds { initial: usize, max: usize },
210 #[error(
211 "invalid {buffer} buffer capacity {capacity}; expected a value between initial {initial} and maximum {max}"
212 )]
213 InvalidBufferCapacity {
214 buffer: &'static str,
215 capacity: usize,
216 initial: usize,
217 max: usize,
218 },
219 #[error(
220 "{buffer} buffer carries {data_len} bytes but its restored capacity is only {capacity}"
221 )]
222 BufferedDataExceedsCapacity {
223 buffer: &'static str,
224 data_len: usize,
225 capacity: usize,
226 },
227 #[error("invalid channel snapshot {field} bits: {bits:#06x}")]
228 InvalidReadyBits { field: &'static str, bits: u16 },
229 #[error(
230 "channel snapshot buffer limits ({snapshot_initial}, {snapshot_max}) do not match the receiver's configured limits ({expected_initial}, {expected_max})"
231 )]
232 BufferLimitsMismatch {
233 snapshot_initial: usize,
234 snapshot_max: usize,
235 expected_initial: usize,
236 expected_max: usize,
237 },
238 #[error("could not read blocking mode of channel descriptor {fd}: {error}")]
239 BlockingModeQuery { fd: RawFd, error: String },
240 #[error(
241 "channel snapshot blocking mode ({snapshot_blocking}) disagrees with descriptor blocking mode ({fd_blocking})"
242 )]
243 BlockingModeMismatch {
244 snapshot_blocking: bool,
245 fd_blocking: bool,
246 },
247}
248
249impl ChannelSnapshot {
250 /// Whether restoring this channel leaves input already available in
251 /// userspace, independently of kernel readiness notifications.
252 pub fn has_buffered_input(&self) -> bool {
253 !self.front.is_empty()
254 }
255
256 /// Whether restoring this channel leaves output queued in userspace,
257 /// independently of kernel readiness notifications.
258 pub fn has_buffered_output(&self) -> bool {
259 !self.back.is_empty()
260 }
261
262 /// Total userspace bytes that the kernel cannot report through readiness.
263 pub fn buffered_bytes(&self) -> usize {
264 self.front.len().saturating_add(self.back.len())
265 }
266
267 fn validate(&self) -> Result<(), ChannelSnapshotError> {
268 if self.version != CHANNEL_SNAPSHOT_VERSION {
269 return Err(ChannelSnapshotError::UnsupportedVersion {
270 version: self.version,
271 supported: CHANNEL_SNAPSHOT_VERSION,
272 });
273 }
274 if self.initial_buffer_size > self.max_buffer_size {
275 return Err(ChannelSnapshotError::InvalidBufferBounds {
276 initial: self.initial_buffer_size,
277 max: self.max_buffer_size,
278 });
279 }
280
281 for (buffer, capacity) in [
282 ("front", self.front_buffer_capacity),
283 ("back", self.back_buffer_capacity),
284 ] {
285 if capacity < self.initial_buffer_size || capacity > self.max_buffer_size {
286 return Err(ChannelSnapshotError::InvalidBufferCapacity {
287 buffer,
288 capacity,
289 initial: self.initial_buffer_size,
290 max: self.max_buffer_size,
291 });
292 }
293 }
294
295 for (buffer, data_len, capacity) in [
296 ("front", self.front.len(), self.front_buffer_capacity),
297 ("back", self.back.len(), self.back_buffer_capacity),
298 ] {
299 if data_len > capacity {
300 return Err(ChannelSnapshotError::BufferedDataExceedsCapacity {
301 buffer,
302 data_len,
303 capacity,
304 });
305 }
306 }
307
308 for (field, bits) in [("readiness", self.readiness), ("interest", self.interest)] {
309 if bits & !CHANNEL_READY_MASK != 0 {
310 return Err(ChannelSnapshotError::InvalidReadyBits { field, bits });
311 }
312 }
313
314 Ok(())
315 }
316}
317
318/// Does this read failure leave a channel that could parse the peer's next
319/// frame, if only it were allowed to refill from the socket?
320///
321/// `readable()` drops `Ready::READABLE` from `interest` whenever it fills
322/// `front_buf` to a capacity it may not grow past, and then refuses to run at
323/// all while the bit is missing (`if !(self.interest & self.readiness)
324/// .is_readable()`). That is correct backpressure, and the parse that frees
325/// room is what undoes it -- but until sozu-proxy/sozu#1445 only ONE parse
326/// outcome did: `read_message_nonblocking` re-armed on its `NothingRead` path
327/// and propagated every error through `?` before reaching it. A frame the
328/// parser rejected and then re-synchronised past therefore re-aligned a buffer
329/// it could no longer refill, and the bytes still in the socket were stranded
330/// on a session `wants_to_tick` (`bin/src/command/sessions.rs`) does not
331/// re-schedule for being merely readable.
332///
333/// The answer is per-variant, not a blanket re-arm before the `?`: a socket
334/// that just failed a `read(2)`, or a peer this end has decided to drop, must
335/// not have its readability re-asserted. The match below is exhaustive with no
336/// wildcard arm ON PURPOSE -- a variant added to `ChannelError` must fail to
337/// compile here rather than inherit someone else's disposition.
338///
339/// Re-arm (the channel retired bytes and is framed on a boundary again; the
340/// only thing missing is more bytes):
341///
342/// - `NothingRead` -- the frame is simply incomplete. The pre-existing case,
343/// and the reason the bit is ever re-armed at all.
344/// - `MessageLengthUnderDelimiter` -- the parser consumed exactly
345/// `delimiter_size()` bytes carrying a declared length no writer can emit for
346/// any payload, so those bytes are provably not a header and skipping them
347/// re-aligns the stream. Re-syncing a buffer that may never refill only
348/// re-syncs the bytes already in hand, which is the case #1445 was filed on.
349/// - `InvalidProtobufMessage` -- since sozu-proxy/sozu#1428 the whole frame is
350/// consumed before the decode failure is returned, so the buffer is left
351/// exactly as the decode-success path leaves it. The frame is lost; the
352/// channel is not.
353///
354/// Do not re-arm:
355///
356/// - `MessageTooLarge` -- twice not, and the first reason does not depend on
357/// the second. It consumes NOTHING: unlike its two siblings above it cannot,
358/// because the bytes behind the header may be payload rather than a fresh
359/// delimiter and re-framing on them would decode peer-chosen bytes as a
360/// control-plane request. So the buffer is not on a boundary and refilling
361/// only re-parses the same header -- a busy loop, not a recovery, and the
362/// frame could not complete anyway, a length above `max_buffer_size` not
363/// fitting a buffer bounded by `max_buffer_size` however much more is read.
364/// On top of that the branch has marked `Ready::ERROR` since #1428, so
365/// re-arming would assert readability on a channel this end has already
366/// decided to close.
367/// - `BufferFull` -- past #1436's compaction this arm means a compacted buffer
368/// at the ceiling whose whole capacity cannot hold a length prefix, so
369/// `available_space()` is zero and cannot grow. `readable()` would read zero
370/// bytes and immediately remove the bit again; refilling cannot change the
371/// outcome.
372/// - `MismatchBufferSize` -- "this should never happen": a `try_into` on a
373/// slice whose length was just checked against `delimiter_size()`. It is an
374/// internal invariant violation, and the same slice fails identically on
375/// every retry, so re-arming would spin instead of recovering.
376/// - `NoByteToRead` -- `read(2)` returned zero, the peer is gone.
377/// `readable()` has already set `interest = Ready::EMPTY` and raised HUP;
378/// re-arming would contradict the hangup it just recorded.
379/// - `Read` -- a hard socket failure, after which `readable()` has already
380/// cleared `interest` and raised HUP and ERROR (sozu-proxy/sozu#1560).
381/// - `Connection` -- either a failed `connect(2)` or `readable()`/`writable()`
382/// rejecting the call because the interest gate is shut. Restoring from here
383/// the very bit that gate just refused is the loop this function exists to
384/// avoid.
385/// - `NoByteWritten`, `Write` -- back-buffer/socket write failures, raised on
386/// the write path and terminal there (`NoByteWritten` raises HUP, a
387/// `writable()` socket error raises HUP and ERROR); nothing about them says
388/// the read side may refill.
389/// - `TimeoutReached` -- the blocking read path's deadline
390/// (`read_message_blocking_timeout`), an operator-visible bound rather than a
391/// framing state.
392/// - `SetTimeout`, `BlockingStatus` -- `setsockopt`/`fcntl` failures on the
393/// underlying stream, i.e. blocking-mode plumbing.
394/// - `InvalidCharSet` -- declared here but constructed nowhere; the live
395/// variant of that name is `ScmSocketError::InvalidCharSet`
396/// (`command/src/scm_socket.rs`). It is listed so the match stays exhaustive,
397/// and it is not a framing state either way.
398///
399/// Exactly six of the fifteen reach this function today.
400/// `try_read_delimited_message` raises five of them -- `MessageTooLarge`,
401/// `MessageLengthUnderDelimiter`, `InvalidProtobufMessage`, `BufferFull` and
402/// `MismatchBufferSize` -- and `read_message_nonblocking` mints the sixth,
403/// `NothingRead`, from its `Ok(None)`. The other nine (`NoByteToRead`, `Read`,
404/// `Connection`, `NoByteWritten`, `Write`, `TimeoutReached`, `SetTimeout`,
405/// `BlockingStatus`, `InvalidCharSet`) are dispositioned anyway rather than
406/// left to a wildcard, because a future call site is exactly how a wildcard
407/// becomes a wrong answer nobody wrote down.
408fn rearms_readable(error: &ChannelError) -> bool {
409 match error {
410 ChannelError::NothingRead
411 | ChannelError::MessageLengthUnderDelimiter { .. }
412 | ChannelError::InvalidProtobufMessage(_) => true,
413
414 ChannelError::MessageTooLarge { .. }
415 | ChannelError::BufferFull { .. }
416 | ChannelError::MismatchBufferSize
417 | ChannelError::NoByteToRead
418 | ChannelError::Read(_)
419 | ChannelError::Connection(_)
420 | ChannelError::NoByteWritten
421 | ChannelError::Write(_)
422 | ChannelError::TimeoutReached(_)
423 | ChannelError::SetTimeout { .. }
424 | ChannelError::BlockingStatus { .. }
425 | ChannelError::InvalidCharSet(_) => false,
426 }
427}
428
429/// Channel meant for communication between Sōzu processes over a UNIX socket.
430/// It wraps a unix socket using the mio crate, and transmit prost messages
431/// by serializing them in a binary format, with a fix-sized delimiter.
432/// To function, channels must come in pairs, one for each agent.
433/// They can function in a blocking or non-blocking way.
434pub struct Channel<Tx, Rx> {
435 pub sock: MioUnixStream,
436 pub front_buf: Buffer,
437 pub back_buf: Buffer,
438 initial_buffer_size: usize,
439 max_buffer_size: usize,
440 pub readiness: Ready,
441 pub interest: Ready,
442 blocking: bool,
443 /// true if a high watermark warning has been logged for the front buffer
444 front_high_watermark_logged: bool,
445 /// true if a high watermark warning has been logged for the back buffer
446 back_high_watermark_logged: bool,
447 phantom_tx: PhantomData<Tx>,
448 phantom_rx: PhantomData<Rx>,
449}
450
451/// A restored channel that may be registered with mio but cannot perform I/O.
452///
453/// Keeping the active [`Channel`] private prevents the receiving process from
454/// reading or writing a shared socket before the upgrade handoff commits.
455pub struct PausedChannel<Tx, Rx> {
456 channel: Channel<Tx, Rx>,
457}
458
459impl<Tx, Rx> PausedChannel<Tx, Rx> {
460 /// Returns the received descriptor for handoff bookkeeping.
461 pub fn fd(&self) -> RawFd {
462 self.channel.sock.as_raw_fd()
463 }
464
465 /// Validate mio registration during the prepared handoff phase.
466 ///
467 /// Registration changes neither the socket's blocking mode nor its byte
468 /// streams. No readiness is handled until [`PausedChannel::resume`]
469 /// returns the active channel after commit.
470 pub fn register(
471 &mut self,
472 registry: &Registry,
473 token: Token,
474 interests: Interest,
475 ) -> io::Result<()> {
476 self.channel.sock.register(registry, token, interests)
477 }
478
479 /// Activate the restored channel after the upgrade handoff commits.
480 pub fn resume(self) -> Channel<Tx, Rx> {
481 self.channel
482 }
483}
484
485impl<Tx, Rx> std::fmt::Debug for Channel<Tx, Rx> {
486 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
487 f.debug_struct(&format!(
488 "Channel<{}, {}>",
489 std::any::type_name::<Tx>(),
490 std::any::type_name::<Rx>()
491 ))
492 .field("sock", &self.sock.as_raw_fd())
493 // .field("front_buf", &self.front_buf)
494 // .field("back_buf", &self.back_buf)
495 // .field("max_buffer_size", &self.max_buffer_size)
496 .field("readiness", &self.readiness)
497 .field("interest", &self.interest)
498 .field("blocking", &self.blocking)
499 .finish()
500 }
501}
502
503impl<Tx: Debug + ProstMessage + Default, Rx: Debug + ProstMessage + Default> Channel<Tx, Rx> {
504 /// Creates a nonblocking channel on a given socket path
505 pub fn from_path(
506 path: &str,
507 buffer_size: u64,
508 max_buffer_size: u64,
509 ) -> Result<Channel<Tx, Rx>, ChannelError> {
510 let unix_stream = MioUnixStream::connect(path)
511 .map_err(|io_error| ChannelError::Connection(Some(io_error)))?;
512 Ok(Channel::new(unix_stream, buffer_size, max_buffer_size))
513 }
514
515 /// Creates a nonblocking channel, using a unix stream
516 ///
517 /// `buffer_size` is clamped to `max_buffer_size` (sozu-proxy/sozu#1416):
518 /// every growth/shrink path in this file — `grow_size`, `try_read_delimited_message`,
519 /// `try_shrink_front_buf`/`try_shrink_back_buf` — reasons about `front_buf`/`back_buf`
520 /// capacity never exceeding `max_buffer_size`; a caller-supplied `buffer_size` above
521 /// that ceiling would start the channel already violating the invariant those paths
522 /// assert, before a single byte is read.
523 ///
524 /// A clamp, not a fallible constructor, because `max_buffer_size` and
525 /// `initial_buffer_size` are private fields with no external struct-literal path
526 /// around this function: `Channel::new` is the *only* place a `Channel` is built, so
527 /// unlike `H2FloodConfig::new` (sozu-proxy/sozu#1418, whose clamp is bypassed in
528 /// production by a raw struct literal in `get_h2_flood_config`) this clamp cannot be
529 /// routed around — it runs on every construction, including the dozen-plus call sites
530 /// across `bin/`, `e2e/`, `lib/examples/` and this module's own tests. Config load
531 /// (`ConfigBuilder::into_config`) is the operator-facing gate and rejects the
532 /// misconfiguration with a named error before a `Channel` is ever built; this clamp is
533 /// the structural backstop for any other direct caller that bypasses config
534 /// validation (tests, examples, a future call site), so it logs instead of silently
535 /// doing nothing observable.
536 pub fn new(sock: MioUnixStream, buffer_size: u64, max_buffer_size: u64) -> Channel<Tx, Rx> {
537 let buffer_size = buffer_size as usize;
538 let max_buffer_size = max_buffer_size as usize;
539 let initial_buffer_size = min(buffer_size, max_buffer_size);
540 if initial_buffer_size < buffer_size {
541 warn!(
542 "channel buffer_size ({}) exceeds max_buffer_size ({}); clamping initial \
543 buffer capacity to {}",
544 buffer_size, max_buffer_size, initial_buffer_size
545 );
546 }
547 // Postcondition: the invariant every growth/shrink path in this file assumes.
548 debug_assert!(
549 initial_buffer_size <= max_buffer_size,
550 "initial buffer capacity must never exceed max_buffer_size"
551 );
552 Channel {
553 sock,
554 front_buf: Buffer::with_capacity(initial_buffer_size),
555 back_buf: Buffer::with_capacity(initial_buffer_size),
556 initial_buffer_size,
557 max_buffer_size,
558 readiness: Ready::EMPTY,
559 interest: Ready::READABLE,
560 blocking: false,
561 front_high_watermark_logged: false,
562 back_high_watermark_logged: false,
563 phantom_tx: PhantomData,
564 phantom_rx: PhantomData,
565 }
566 }
567
568 /// Capture every userspace byte and scheduling bit needed to continue this
569 /// channel after a main-process upgrade.
570 pub fn snapshot(&self) -> ChannelSnapshot {
571 debug_assert!(self.initial_buffer_size <= self.max_buffer_size);
572 debug_assert!(self.front_buf.capacity() <= self.max_buffer_size);
573 debug_assert!(self.back_buf.capacity() <= self.max_buffer_size);
574
575 ChannelSnapshot {
576 version: CHANNEL_SNAPSHOT_VERSION,
577 front: SnapshotBytes::copy_of(self.front_buf.data()),
578 back: SnapshotBytes::copy_of(self.back_buf.data()),
579 initial_buffer_size: self.initial_buffer_size,
580 max_buffer_size: self.max_buffer_size,
581 front_buffer_capacity: self.front_buf.capacity(),
582 back_buffer_capacity: self.back_buf.capacity(),
583 readiness: self.readiness.0,
584 interest: self.interest.0,
585 blocking: self.blocking,
586 front_high_watermark_logged: self.front_high_watermark_logged,
587 back_high_watermark_logged: self.back_high_watermark_logged,
588 }
589 }
590
591 /// Rebuild a channel around a received socket without reading, writing, or
592 /// changing flags on the shared open file description.
593 ///
594 /// The receiver supplies the configured buffer limits independently. A
595 /// snapshot cannot use its own size fields to authorize an allocation.
596 /// The descriptor's actual `O_NONBLOCK` flag must likewise agree with the
597 /// captured mode; neither validation changes that shared flag.
598 ///
599 /// The returned wrapper permits mio registration during PREPARED, but only
600 /// [`PausedChannel::resume`] exposes the active channel after COMMIT.
601 pub fn restore_paused(
602 sock: MioUnixStream,
603 snapshot: ChannelSnapshot,
604 expected_initial_buffer_size: usize,
605 expected_max_buffer_size: usize,
606 ) -> Result<PausedChannel<Tx, Rx>, ChannelSnapshotError> {
607 snapshot.validate()?;
608
609 if snapshot.initial_buffer_size != expected_initial_buffer_size
610 || snapshot.max_buffer_size != expected_max_buffer_size
611 {
612 return Err(ChannelSnapshotError::BufferLimitsMismatch {
613 snapshot_initial: snapshot.initial_buffer_size,
614 snapshot_max: snapshot.max_buffer_size,
615 expected_initial: expected_initial_buffer_size,
616 expected_max: expected_max_buffer_size,
617 });
618 }
619
620 let fd = sock.as_raw_fd();
621 // SAFETY: `sock` owns a live descriptor for this call. F_GETFL only
622 // reads flags from its open file description and cannot change the
623 // descriptor shared with the old main process during PREPARED.
624 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
625 if flags < 0 {
626 return Err(ChannelSnapshotError::BlockingModeQuery {
627 fd,
628 error: io::Error::last_os_error().to_string(),
629 });
630 }
631 let fd_blocking = flags & libc::O_NONBLOCK == 0;
632 if snapshot.blocking != fd_blocking {
633 return Err(ChannelSnapshotError::BlockingModeMismatch {
634 snapshot_blocking: snapshot.blocking,
635 fd_blocking,
636 });
637 }
638
639 let mut front_buf = Buffer::from_slice(&snapshot.front);
640 front_buf.grow(snapshot.front_buffer_capacity);
641 let mut back_buf = Buffer::from_slice(&snapshot.back);
642 back_buf.grow(snapshot.back_buffer_capacity);
643
644 debug_assert_eq!(front_buf.capacity(), snapshot.front_buffer_capacity);
645 debug_assert_eq!(back_buf.capacity(), snapshot.back_buffer_capacity);
646 // Not `debug_assert_eq!`: a failure would print both operands, and
647 // these bytes can be protected.
648 debug_assert!(
649 front_buf.data() == &*snapshot.front,
650 "restored front bytes must equal the snapshot's"
651 );
652 debug_assert!(
653 back_buf.data() == &*snapshot.back,
654 "restored back bytes must equal the snapshot's"
655 );
656
657 Ok(PausedChannel {
658 channel: Channel {
659 sock,
660 front_buf,
661 back_buf,
662 initial_buffer_size: snapshot.initial_buffer_size,
663 max_buffer_size: snapshot.max_buffer_size,
664 readiness: Ready(snapshot.readiness),
665 interest: Ready(snapshot.interest),
666 blocking: snapshot.blocking,
667 front_high_watermark_logged: snapshot.front_high_watermark_logged,
668 back_high_watermark_logged: snapshot.back_high_watermark_logged,
669 phantom_tx: PhantomData,
670 phantom_rx: PhantomData,
671 },
672 })
673 }
674
675 pub fn into<Tx2: Debug + ProstMessage + Default, Rx2: Debug + ProstMessage + Default>(
676 self,
677 ) -> Channel<Tx2, Rx2> {
678 Channel {
679 sock: self.sock,
680 front_buf: self.front_buf,
681 back_buf: self.back_buf,
682 initial_buffer_size: self.initial_buffer_size,
683 max_buffer_size: self.max_buffer_size,
684 readiness: self.readiness,
685 interest: self.interest,
686 blocking: self.blocking,
687 front_high_watermark_logged: self.front_high_watermark_logged,
688 back_high_watermark_logged: self.back_high_watermark_logged,
689 phantom_tx: PhantomData,
690 phantom_rx: PhantomData,
691 }
692 }
693
694 // Since MioUnixStream does not have a set_nonblocking method, we have to use the standard library.
695 // We get the file descriptor of the MioUnixStream socket, create a standard library UnixStream,
696 // set it to nonblocking, let go of the file descriptor
697 fn set_nonblocking(&mut self, nonblocking: bool) -> Result<(), ChannelError> {
698 // SAFETY: `fd` is borrowed from `self.sock` for the duration of this
699 // block. We wrap it in a `StdUnixStream` to call `set_nonblocking`,
700 // then immediately release ownership again with `into_raw_fd` so the
701 // descriptor is not closed by `Drop`. `self.sock` retains the
702 // original ownership.
703 unsafe {
704 let fd = self.sock.as_raw_fd();
705 let stream = StdUnixStream::from_raw_fd(fd);
706 stream
707 .set_nonblocking(nonblocking)
708 .map_err(|error| ChannelError::BlockingStatus {
709 fd,
710 error: error.to_string(),
711 })?;
712 let _fd = stream.into_raw_fd();
713 }
714 self.blocking = !nonblocking;
715 Ok(())
716 }
717
718 /// set the read_timeout of the unix stream. This works only temporary, be sure to set the timeout to None afterwards.
719 fn set_timeout(&mut self, timeout: Option<Duration>) -> Result<(), ChannelError> {
720 // SAFETY: `fd` is borrowed from `self.sock` for the duration of this
721 // block. We wrap it in a `StdUnixStream` to call `set_read_timeout`,
722 // then immediately release ownership again with `into_raw_fd` so the
723 // descriptor is not closed by `Drop`. `self.sock` retains the
724 // original ownership.
725 unsafe {
726 let fd = self.sock.as_raw_fd();
727 let stream = StdUnixStream::from_raw_fd(fd);
728 stream
729 .set_read_timeout(timeout)
730 .map_err(|error| ChannelError::SetTimeout {
731 fd,
732 error: error.to_string(),
733 })?;
734 let _fd = stream.into_raw_fd();
735 }
736 Ok(())
737 }
738
739 /// set the channel to be blocking
740 pub fn blocking(&mut self) -> Result<(), ChannelError> {
741 self.set_nonblocking(false)
742 }
743
744 /// set the channel to be nonblocking
745 pub fn nonblocking(&mut self) -> Result<(), ChannelError> {
746 self.set_nonblocking(true)
747 }
748
749 pub fn is_blocking(&self) -> bool {
750 self.blocking
751 }
752
753 /// Get the raw file descriptor of the UNIX socket
754 pub fn fd(&self) -> RawFd {
755 self.sock.as_raw_fd()
756 }
757
758 pub fn handle_events(&mut self, events: Ready) {
759 self.readiness |= events;
760 }
761
762 pub fn readiness(&self) -> Ready {
763 self.readiness & self.interest
764 }
765
766 /// Compute the next buffer size using a doubling strategy, capped at max_buffer_size.
767 /// Returns None if the buffer is already at max capacity.
768 fn grow_size(&self, current_capacity: usize) -> Option<usize> {
769 if current_capacity >= self.max_buffer_size {
770 return None;
771 }
772 // Precondition: we only reach here below the ceiling, so a strictly
773 // larger size always exists within [current+1, max_buffer_size].
774 debug_assert!(
775 current_capacity < self.max_buffer_size,
776 "grow_size must only grow buffers that are strictly below the max ceiling"
777 );
778 // double the capacity, but don't exceed max
779 let new_size = min(current_capacity.saturating_mul(2), self.max_buffer_size);
780 // ensure we grow by at least something (in case current_capacity is 0)
781 let new_size = new_size.max(current_capacity + 1);
782 let new_size = min(new_size, self.max_buffer_size);
783 // Postconditions: the new capacity strictly grows (forward progress,
784 // no spin) and never overshoots the configured ceiling.
785 debug_assert!(
786 new_size > current_capacity,
787 "grow_size must make forward progress (new capacity strictly larger)"
788 );
789 debug_assert!(
790 new_size <= self.max_buffer_size,
791 "grow_size must never exceed the configured max_buffer_size ceiling"
792 );
793 Some(new_size)
794 }
795
796 /// Check if a buffer has exceeded the high watermark and log a warning once
797 fn check_high_watermark(
798 buffer_name: &str,
799 capacity: usize,
800 max: usize,
801 already_logged: &mut bool,
802 ) {
803 if *already_logged {
804 return;
805 }
806 let threshold = (max as f64 * HIGH_WATERMARK_RATIO) as usize;
807 if capacity >= threshold {
808 warn!(
809 "channel {} buffer reached high watermark: {} bytes ({:.0}% of {} max)",
810 buffer_name,
811 capacity,
812 (capacity as f64 / max as f64) * 100.0,
813 max,
814 );
815 *already_logged = true;
816 }
817 }
818
819 /// Check wether we want and can read or write, and calls the appropriate handler.
820 pub fn run(&mut self) -> Result<(), ChannelError> {
821 let interest = self.interest & self.readiness;
822
823 if interest.is_readable() {
824 let _ = self.readable()?;
825 }
826
827 if interest.is_writable() {
828 let _ = self.writable()?;
829 }
830 Ok(())
831 }
832
833 /// Handle readability by filling the front buffer with the socket data.
834 /// Grows the front buffer when full using a doubling strategy, up to max_buffer_size.
835 pub fn readable(&mut self) -> Result<usize, ChannelError> {
836 if !(self.interest & self.readiness).is_readable() {
837 return Err(ChannelError::Connection(None));
838 }
839
840 let mut count = 0usize;
841 loop {
842 let size = self.front_buf.available_space();
843 trace!("channel available space: {}", size);
844 if size == 0 {
845 // try to grow the buffer before giving up
846 if let Some(new_size) = self.grow_size(self.front_buf.capacity()) {
847 Self::check_high_watermark(
848 "front",
849 new_size,
850 self.max_buffer_size,
851 &mut self.front_high_watermark_logged,
852 );
853 self.front_buf.grow(new_size);
854 // The read buffer must never grow past the configured ceiling.
855 debug_assert!(
856 self.front_buf.capacity() <= self.max_buffer_size,
857 "front buffer capacity must stay within the max_buffer_size ceiling"
858 );
859 } else {
860 self.interest.remove(Ready::READABLE);
861 break;
862 }
863 }
864
865 // The slice handed to `read` is exactly the free tail; the kernel
866 // can never write more than that, so a successful read of N bytes
867 // is bounded by the space we just measured (post-grow).
868 let space_before = self.front_buf.available_space();
869 debug_assert!(
870 space_before > 0,
871 "readable must only call read() with non-empty space (zero-space path grows or breaks)"
872 );
873 let data_before = self.front_buf.available_data();
874 match self.sock.read(self.front_buf.space()) {
875 Ok(0) => {
876 self.interest = Ready::EMPTY;
877 self.readiness.remove(Ready::READABLE);
878 self.readiness.insert(Ready::HUP);
879 return Err(ChannelError::NoByteToRead);
880 }
881 Err(read_error) => match read_error.kind() {
882 ErrorKind::WouldBlock => {
883 self.readiness.remove(Ready::READABLE);
884 break;
885 }
886 _ => {
887 // Mark the channel for closing, as the `Ok(0)` arm
888 // does: the owner closes on HUP or ERROR, and an
889 // edge-triggered poller will not report them again
890 // (sozu-proxy/sozu#1560). Assignment, not `insert`:
891 // the socket has failed, so no other bit may stay
892 // armed.
893 self.interest = Ready::EMPTY;
894 self.readiness = Ready::HUP | Ready::ERROR;
895 return Err(ChannelError::Read(read_error));
896 }
897 },
898 Ok(bytes_read) => {
899 // A read can never deliver more bytes than the free space
900 // it was handed; otherwise `fill` would corrupt offsets.
901 debug_assert!(
902 bytes_read <= space_before,
903 "read delivered more bytes than the buffer space it was given"
904 );
905 count += bytes_read;
906 self.front_buf.fill(bytes_read);
907 // `fill` advances `end` by exactly the bytes read, so the
908 // available data grows by that delta — pair-assert the
909 // offset mutation.
910 debug_assert_eq!(
911 self.front_buf.available_data(),
912 data_before + bytes_read,
913 "front buffer available_data must increase by exactly bytes_read"
914 );
915 }
916 };
917 }
918
919 Ok(count)
920 }
921
922 /// Handle writability by writing the content of the back buffer onto the socket.
923 /// Shrinks the back buffer back toward initial size once fully drained.
924 pub fn writable(&mut self) -> Result<usize, ChannelError> {
925 if !(self.interest & self.readiness).is_writable() {
926 return Err(ChannelError::Connection(None));
927 }
928
929 let mut count = 0usize;
930 loop {
931 let size = self.back_buf.available_data();
932 if size == 0 {
933 self.interest.remove(Ready::WRITABLE);
934 self.try_shrink_back_buf();
935 break;
936 }
937
938 let data_before = self.back_buf.available_data();
939 match self.sock.write(self.back_buf.data()) {
940 Ok(0) => {
941 self.interest = Ready::EMPTY;
942 self.readiness.insert(Ready::HUP);
943 return Err(ChannelError::NoByteWritten);
944 }
945 Ok(bytes_written) => {
946 // The kernel cannot accept more than the slice (`data()`)
947 // it was handed; otherwise `consume` would underflow.
948 debug_assert!(
949 bytes_written <= data_before,
950 "write reported more bytes than the buffer data it was given"
951 );
952 count += bytes_written;
953 let consumed = self.back_buf.consume(bytes_written);
954 // `consume` is saturating but the bound above guarantees an
955 // exact consume here; pair-assert the offset mutation.
956 debug_assert_eq!(
957 consumed, bytes_written,
958 "back buffer must consume exactly the bytes written to the socket"
959 );
960 debug_assert_eq!(
961 self.back_buf.available_data(),
962 data_before - bytes_written,
963 "back buffer available_data must shrink by exactly bytes_written"
964 );
965 }
966 Err(write_error) => match write_error.kind() {
967 ErrorKind::WouldBlock => {
968 self.readiness.remove(Ready::WRITABLE);
969 break;
970 }
971 _ => {
972 // Same as the read side: keep the channel marked for
973 // closing instead of wiping HUP and ERROR. WRITABLE
974 // must go: the worker's `Server::send_queue` keeps
975 // looping while it is set and data is pending.
976 self.interest = Ready::EMPTY;
977 self.readiness = Ready::HUP | Ready::ERROR;
978 return Err(ChannelError::Write(write_error));
979 }
980 },
981 }
982 }
983
984 Ok(count)
985 }
986
987 /// Depending on the blocking status:
988 ///
989 /// Blocking: wait for the front buffer to be filled, and parse a message from it
990 ///
991 /// Nonblocking: parse a message from the front buffer, without waiting.
992 /// Prefer using `channel.readable()` before
993 pub fn read_message(&mut self) -> Result<Rx, ChannelError> {
994 if self.blocking {
995 self.read_message_blocking()
996 } else {
997 self.read_message_nonblocking()
998 }
999 }
1000
1001 fn read_message_blocking(&mut self) -> Result<Rx, ChannelError> {
1002 self.read_message_blocking_timeout(None)
1003 }
1004
1005 /// Parse a message from the front buffer, without waiting
1006 fn read_message_nonblocking(&mut self) -> Result<Rx, ChannelError> {
1007 // `NothingRead` is not a special case, it is one row of
1008 // [`rearms_readable`]'s table: an incomplete frame and a frame the
1009 // parser rejected and re-synchronised past are the same situation from
1010 // `readable()`'s point of view, and propagating the second through `?`
1011 // before the re-arm is what stranded the socket bytes behind it
1012 // (sozu-proxy/sozu#1445).
1013 let error = match self.try_read_delimited_message() {
1014 Ok(Some(message)) => {
1015 self.try_shrink_front_buf();
1016 return Ok(message);
1017 }
1018 Ok(None) => ChannelError::NothingRead,
1019 Err(error) => error,
1020 };
1021
1022 if rearms_readable(&error) {
1023 self.interest.insert(Ready::READABLE);
1024 }
1025 Err(error)
1026 }
1027
1028 /// Wait for the front buffer to be filled, and parses a message from it.
1029 ///
1030 /// The wait ends with a message, a read error, or once `timeout` has
1031 /// elapsed since the call. A read interrupted by a signal (`EINTR`) is
1032 /// retried against that same deadline, never restarting it.
1033 pub fn read_message_blocking_timeout(
1034 &mut self,
1035 timeout: Option<Duration>,
1036 ) -> Result<Rx, ChannelError> {
1037 let now = std::time::Instant::now();
1038
1039 // 10 ms = 100 syscalls/sec on idle WouldBlock, pinning a CPU on
1040 // long blocking waits with no payload. 100 ms is
1041 // a usability-acceptable resolution for the outer `timeout`
1042 // deadline check (the wait is bounded by `timeout`, not by this
1043 // value) and drops the steady-state read syscall rate to 10/sec.
1044 self.set_timeout(Some(Duration::from_millis(100)))?;
1045
1046 let status = loop {
1047 if let Some(timeout) = timeout
1048 && now.elapsed() >= timeout
1049 {
1050 break Err(ChannelError::TimeoutReached(timeout));
1051 }
1052
1053 if let Some(message) = self.try_read_delimited_message()? {
1054 self.try_shrink_front_buf();
1055 return Ok(message);
1056 }
1057
1058 match self.sock.read(self.front_buf.space()) {
1059 Ok(0) => return Err(ChannelError::NoByteToRead),
1060 Ok(bytes_read) => self.front_buf.fill(bytes_read),
1061 Err(io_error) => match io_error.kind() {
1062 // The 100 ms `SO_RCVTIMEO` slice elapsed with no byte.
1063 ErrorKind::WouldBlock => continue,
1064 // signal(7): a read with `SO_RCVTIMEO` is never restarted,
1065 // even under `SA_RESTART`, so any handled signal (the main
1066 // process's `SIGTERM`) lands here. Retry: the deadline
1067 // check at the top of the loop still bounds the wait.
1068 ErrorKind::Interrupted => continue,
1069 _ => break Err(ChannelError::Read(io_error)),
1070 },
1071 };
1072 };
1073
1074 self.set_timeout(None)?;
1075
1076 status
1077 }
1078
1079 /// parse a prost message from the front buffer, grow it if necessary
1080 fn try_read_delimited_message(&mut self) -> Result<Option<Rx>, ChannelError> {
1081 // Invariant guarding all the slice indexing below: the front buffer can
1082 // never have grown past the configured ceiling. Every grow site routes
1083 // through `grow_size`/`max_buffer_size`; if this ever fired the length
1084 // checks would be reasoning against a stale bound.
1085 debug_assert!(
1086 self.front_buf.capacity() <= self.max_buffer_size,
1087 "front buffer capacity must never exceed max_buffer_size"
1088 );
1089 let buffer = self.front_buf.data();
1090 // `data()` returns `memory[position..end]`, so its length is exactly the
1091 // available data and can never exceed the buffer capacity.
1092 debug_assert!(
1093 buffer.len() <= self.front_buf.capacity(),
1094 "available data slice cannot exceed buffer capacity"
1095 );
1096 if buffer.len() >= delimiter_size() {
1097 let delimiter = buffer[..delimiter_size()]
1098 .try_into()
1099 .map_err(|_| ChannelError::MismatchBufferSize)?;
1100 let message_len = usize::from_le_bytes(delimiter);
1101
1102 // Defense in depth: bound the parser-side length up-front.
1103 // Without this an attacker who controls the
1104 // first 8 bytes of a frame can declare an arbitrarily large
1105 // message and drive `Buffer::grow` toward the
1106 // `max_buffer_size` ceiling before any byte of payload has
1107 // been read. Reject as `MessageTooLarge` before the doubling
1108 // growth strategy ever runs on attacker-supplied numbers.
1109 //
1110 // Unlike the `MessageLengthUnderDelimiter` case below, this one
1111 // must NOT consume and re-sync. That one can: a declared length
1112 // under `delimiter_size()` is a value no writer can produce for
1113 // any payload, so those bytes are provably not a real header and
1114 // skipping exactly them re-aligns the stream. Here the declared
1115 // length may be honest, in which case the bytes behind the header
1116 // are payload -- dropping the header and re-framing on them would
1117 // decode peer-chosen bytes as a fresh control-plane request. And
1118 // the frame can never complete either way: a length above
1119 // `max_buffer_size` does not fit a buffer bounded by
1120 // `max_buffer_size`, however much more is read.
1121 //
1122 // So it is fatal for that peer, which is already the stance taken
1123 // for the mirror condition on the write side
1124 // (`is_transient_overflow`, `bin/src/command/sessions.rs`: "a
1125 // frame bigger than the ceiling itself, which no amount of
1126 // draining will ever admit"). That is a statement about this end,
1127 // NOT about the peer's good faith: the two ends size their
1128 // channels from their own configuration, so a peer built with a
1129 // larger `max_command_buffer_size` emits frames this end refuses
1130 // while conforming perfectly -- `write_delimited_message` below
1131 // bounds an outgoing frame against the *writer's* ceiling, and the
1132 // two configurations shipped here already disagree tenfold
1133 // (`bin/config.toml` 163_840, `os-build/config.toml` 1_638_400).
1134 // A `sozu ctl --config` pointed at one while the supervisor was
1135 // started from the other, or two binaries built with different
1136 // `SOZU_CONFIG` defaults (`bin/build.rs`, `bin/src/util.rs`), is
1137 // all it takes. Closing is still the only correct exit for this
1138 // end, which can neither complete nor
1139 // re-sync the frame; the log line says what to reconcile instead
1140 // of implying an attack. Mark the channel errored -- the single
1141 // signal `ClientSession::ready`, `WorkerSession::ready` and
1142 // `wants_to_tick` all key on -- so the supervisor drops the
1143 // session. Without it the header is re-parsed forever,
1144 // `extract_messages` swallows the error with a bare `Err(_)`, and
1145 // the session wedges while pinning its file descriptor and buffer
1146 // (sozu-proxy/sozu#1428).
1147 //
1148 // `insert`, never assignment: on the worker's own channel
1149 // (`lib/src/server.rs`) `interest` carries only READABLE and
1150 // WRITABLE, so `readiness()` masks ERROR away and nothing there
1151 // reads `is_error()`. An assignment's only effect on that side
1152 // would be clearing WRITABLE, and `Server::send_queue` gates on
1153 // `channel.readiness.is_writable()`, so already-queued worker
1154 // responses would stall until the next edge-triggered writability
1155 // event.
1156 if message_len > self.max_buffer_size {
1157 error!(
1158 "peer declared a {}-byte frame, above this end's {}-byte ceiling; closing \
1159 the connection. The two ends size their channels independently, so this is \
1160 more often a configuration mismatch than a hostile peer: reconcile \
1161 `max_command_buffer_size` (and `command_buffer_size`, which must stay below \
1162 it) between this end and the peer's",
1163 message_len, self.max_buffer_size
1164 );
1165 self.readiness.insert(Ready::ERROR);
1166 return Err(ChannelError::MessageTooLarge {
1167 message_len,
1168 capacity: self.front_buf.capacity(),
1169 max: self.max_buffer_size,
1170 });
1171 }
1172
1173 // A length-delimited frame is `[delimiter][payload]`. The declared
1174 // `message_len` is the total frame size and MUST therefore be at
1175 // least `delimiter_size()`. A peer-controlled value below that
1176 // ceiling makes `&buffer[delimiter_size()..message_len]` slice
1177 // backwards and panic; reject it the same way as oversized frames.
1178 //
1179 // Drop the bogus delimiter bytes before returning. Without this,
1180 // every subsequent `read_message()` re-reads the same bad header
1181 // from the front buffer and the worker burns CPU on the same error
1182 // until the peer disconnects.
1183 //
1184 // This re-aligns what is already in `front_buf`, and nothing more;
1185 // `rearms_readable` above carries the refill half.
1186 if message_len < delimiter_size() {
1187 self.front_buf.consume(delimiter_size());
1188 return Err(ChannelError::MessageLengthUnderDelimiter {
1189 message_len,
1190 delimiter_size: delimiter_size(),
1191 });
1192 }
1193
1194 if buffer.len() >= message_len {
1195 // By the time we slice, the two guards above have proven the
1196 // length prefix is well-formed: it is at least the delimiter
1197 // (so `delimiter_size()..message_len` runs forward) and at most
1198 // the configured ceiling (so it cannot drive growth). The
1199 // `buffer.len() >= message_len` branch then guarantees the whole
1200 // frame is in the buffer. These are the exact invariants that
1201 // keep the slice and the `consume` in bounds — never reachable
1202 // from a malformed length, which already returned an error.
1203 debug_assert!(
1204 message_len >= delimiter_size(),
1205 "decode path requires a frame at least as large as its delimiter"
1206 );
1207 debug_assert!(
1208 message_len <= self.max_buffer_size,
1209 "decode path requires the declared length within the max ceiling"
1210 );
1211 debug_assert!(
1212 message_len <= buffer.len(),
1213 "decode path requires the full frame to be buffered before slicing"
1214 );
1215 let available_before = self.front_buf.available_data();
1216 debug_assert_eq!(
1217 available_before,
1218 buffer.len(),
1219 "available_data must equal the data slice length we validated against"
1220 );
1221 // Decode first, consume unconditionally, propagate a decode
1222 // failure only afterwards. Returning before the consume (as
1223 // this did until sozu-proxy/sozu#1428) left the whole frame at
1224 // the head of the buffer and moved no readiness bit, and
1225 // `extract_messages` (`bin/src/command/sessions.rs`) swallows
1226 // the error with its bare `Err(_)` arm, so the session
1227 // re-decoded the same bytes on every later read and wedged.
1228 //
1229 // Note what does NOT justify this. `message_len` is only
1230 // range-checked above -- at least `delimiter_size()`, at most
1231 // `max_buffer_size`, no larger than what is buffered. It is
1232 // not proven to be a real frame boundary, so on a
1233 // desynchronised stream consuming it re-frames on bytes the
1234 // sender never framed, which is the very hazard that argues
1235 // against consuming in the `MessageTooLarge` branch above.
1236 // Nor is this the `MessageLengthUnderDelimiter` stance: that
1237 // one consumes exactly `delimiter_size()` on a value no writer
1238 // can emit for any payload, an impossibility proof this branch
1239 // does not have.
1240 //
1241 // What justifies it is coherence with the success path four
1242 // lines down, which already consumes this same peer-supplied
1243 // `message_len` and re-frames on whatever follows. The
1244 // desynchronisation hazard is therefore identical whether the
1245 // payload decodes or not, and it predates this change.
1246 // Closing the peer only on the failing half would punish the
1247 // case more likely to be honest protobuf skew while silently
1248 // accepting the case that is not.
1249 let decoded = Rx::decode(&buffer[delimiter_size()..message_len]);
1250 let consumed = self.front_buf.consume(message_len);
1251 // The whole frame (delimiter + payload) is consumed exactly:
1252 // pair-assert that consume advanced by message_len and the data
1253 // pointer moved forward by the same amount.
1254 debug_assert_eq!(
1255 consumed, message_len,
1256 "must consume exactly the validated frame length"
1257 );
1258 debug_assert_eq!(
1259 self.front_buf.available_data(),
1260 available_before - message_len,
1261 "available_data must drop by exactly the consumed frame length"
1262 );
1263 let message = decoded.map_err(|decode_error| {
1264 error!(
1265 "could not decode a {}-byte frame from the peer; that frame is dropped \
1266 and the channel re-syncs on the next one: {}",
1267 message_len, decode_error
1268 );
1269 ChannelError::InvalidProtobufMessage(decode_error)
1270 })?;
1271 return Ok(Some(message));
1272 }
1273 }
1274
1275 if self.front_buf.available_space() == 0 {
1276 // Compact before concluding anything about the space (fixes
1277 // sozu-proxy/sozu#1436). `available_space()` is `capacity - end`,
1278 // so it measures the free TAIL, not the free room: a buffer whose
1279 // `position` sits mid-way reports zero space while holding
1280 // `position` reusable bytes at its head. `Buffer::consume`
1281 // (`command/src/buffer/growable.rs`) only compacts past
1282 // `capacity / 2`, so decoding one frame of at most half the
1283 // capacity out of a buffer filled to capacity leaves exactly that
1284 // layout, and `Buffer::shift` hands the room straight back.
1285 //
1286 // Without it this arm returned `BufferFull` without consuming and
1287 // without moving a readiness bit -- the shape
1288 // sozu-proxy/sozu#1428 removed from the two branches above -- and
1289 // no later call could recover: `readable()` sees
1290 // `available_space() == 0`, gets `None` from `grow_size` at the
1291 // ceiling and executes `interest.remove(Ready::READABLE)`, so
1292 // every later `readable()` returns `Err(Connection(None))` and
1293 // `fill` is out of reach; `consume` needs a decoded frame or an
1294 // under-delimiter length, which this return path never reaches;
1295 // and `try_shrink_front_buf` is never reached, because both
1296 // `read_message` paths call it only after a frame decoded, and
1297 // both of its own early returns would stop it anyway --
1298 // `capacity <= initial_buffer_size` whenever the channel was
1299 // configured with `command_buffer_size == max_command_buffer_size`,
1300 // and `available_data() * 4 < initial_buffer_size`, which is false
1301 // with that much pending. Not because it shrinks rather than
1302 // shifts: `Buffer::shrink` calls `shift` unconditionally before its
1303 // own size bail, so reaching it WOULD have compacted. The session
1304 // then wedged permanently, holding its file descriptor and a full
1305 // buffer, on a peer that had done nothing wrong.
1306 //
1307 // Compacting here also changes the sub-ceiling path: a front buffer
1308 // still below `max_buffer_size` now compacts on its first zero-space
1309 // parse and grows on the next one, rather than doubling straight
1310 // away. The frame still completes -- the compacted buffer refills to
1311 // `end == capacity` at `position == 0`, where the shift is a no-op
1312 // and the grow below runs -- one read-parse round later, against one
1313 // fewer doubling.
1314 //
1315 // Compaction is the whole recovery, NOT a close: the frame is
1316 // under this end's ceiling and its buffered bytes are intact, so
1317 // only the buffer's internal offsets stand in the way. It cannot
1318 // mask an unsatisfiable frame either -- a declared length above
1319 // `max_buffer_size` returns `MessageTooLarge` at the guard above,
1320 // before control ever arrives here, so the ordering of the two is
1321 // what keeps "too large for the ceiling" and "too large for the
1322 // current layout" apart. The write side already compacts on the
1323 // same reasoning before it considers growing
1324 // (`write_delimited_message` below).
1325 //
1326 // Compacting makes the channel recoverable; it does not by itself
1327 // make a session recover. Freeing space without changing capacity
1328 // ended `extract_messages`' drain loop (`bin/src/command/sessions.rs`)
1329 // on the very parse that made the room, one read short of completing
1330 // the frame, and nothing re-schedules a merely-readable session. That
1331 // loop's termination anchor was corrected in the same changeset; a
1332 // change to what this arm does to the buffer without changing
1333 // capacity has to be checked against it.
1334 self.front_buf.shift();
1335 }
1336
1337 if self.front_buf.available_space() == 0 {
1338 if self.front_buf.capacity() >= self.max_buffer_size {
1339 // Past the compaction above, zero space means `position == 0`
1340 // and `end == capacity`, so the whole capacity is pending data;
1341 // with `capacity == max_buffer_size` any in-range declared
1342 // length would have decoded already. Only a capacity too small
1343 // to hold a length prefix at all reaches this -- a
1344 // configuration that can never frame a message, not a buffer
1345 // that has run out of room.
1346 debug_assert!(
1347 self.front_buf.available_data() < delimiter_size(),
1348 "a compacted, full buffer at the ceiling can only fail to parse when its capacity cannot hold a length prefix"
1349 );
1350 return Err(ChannelError::BufferFull {
1351 capacity: self.front_buf.capacity(),
1352 max: self.max_buffer_size,
1353 });
1354 }
1355 let new_size = self
1356 .grow_size(self.front_buf.capacity())
1357 .unwrap_or(self.max_buffer_size);
1358 Self::check_high_watermark(
1359 "front",
1360 new_size,
1361 self.max_buffer_size,
1362 &mut self.front_high_watermark_logged,
1363 );
1364 self.front_buf.grow(new_size);
1365 }
1366 Ok(None)
1367 }
1368
1369 /// Checks whether the channel is blocking or nonblocking, writes the message.
1370 ///
1371 /// If the channel is nonblocking, you have to flush using `channel.run()` afterwards
1372 pub fn write_message(&mut self, message: &Tx) -> Result<(), ChannelError> {
1373 if self.blocking {
1374 self.write_message_blocking(message)
1375 } else {
1376 self.write_message_nonblocking(message)
1377 }
1378 }
1379
1380 /// Writes the message in the buffer, but NOT on the socket.
1381 /// you have to call channel.run() afterwards
1382 fn write_message_nonblocking(&mut self, message: &Tx) -> Result<(), ChannelError> {
1383 self.write_delimited_message(message)?;
1384
1385 self.interest.insert(Ready::WRITABLE);
1386
1387 Ok(())
1388 }
1389
1390 /// fills the back buffer with data AND writes on the socket
1391 ///
1392 /// Returns `Ok(())` only once the whole back buffer reached the socket.
1393 /// A `write(2)` interrupted by a signal is retried; every other error is
1394 /// returned as `ChannelError::Write`, and the bytes not yet written stay in
1395 /// `back_buf` (sozu-proxy/sozu#1563).
1396 ///
1397 /// `WouldBlock` is not retried. On a blocking socket it only follows a send
1398 /// timeout (`SO_SNDTIMEO`), and this channel never arms one: `set_timeout`
1399 /// sets the read timeout alone. Retrying would spin on a descriptor someone
1400 /// else made nonblocking, and success would claim a delivery that did not
1401 /// happen, so it is reported like any other write failure.
1402 fn write_message_blocking(&mut self, message: &Tx) -> Result<(), ChannelError> {
1403 self.write_delimited_message(message)?;
1404
1405 loop {
1406 let size = self.back_buf.available_data();
1407 if size == 0 {
1408 break;
1409 }
1410
1411 match self.sock.write(self.back_buf.data()) {
1412 Ok(0) => return Err(ChannelError::NoByteWritten),
1413 Ok(bytes_written) => {
1414 self.back_buf.consume(bytes_written);
1415 }
1416 Err(error) if error.kind() == ErrorKind::Interrupted => continue,
1417 Err(error) => return Err(ChannelError::Write(error)),
1418 }
1419 }
1420 Ok(())
1421 }
1422
1423 /// write a message on the back buffer, using our own delimiter (the delimiter of prost
1424 /// is not trustworthy since its size may change)
1425 pub fn write_delimited_message(&mut self, message: &Tx) -> Result<(), ChannelError> {
1426 // The encoded frame can hold protected values: wipe it on every exit,
1427 // including the `MessageTooLarge` refusal below. `encode_to_vec`
1428 // allocates `encoded_len()` bytes once, so no partial copy is left by a
1429 // regrowth.
1430 let payload = Zeroizing::new(message.encode_to_vec());
1431
1432 let payload_len = payload.len() + delimiter_size();
1433
1434 // The framed length is the payload plus a fixed delimiter, so it is by
1435 // construction at least one delimiter wide — the mirror of the
1436 // `MessageLengthUnderDelimiter` invariant the reader enforces.
1437 debug_assert!(
1438 payload_len >= delimiter_size(),
1439 "framed length must include the fixed-size delimiter prefix"
1440 );
1441
1442 let delimiter = payload_len.to_le_bytes();
1443
1444 if payload_len > self.back_buf.available_space() {
1445 self.back_buf.shift();
1446 }
1447
1448 let data_before = self.back_buf.available_data();
1449 if payload_len > self.back_buf.available_space() {
1450 let needed = payload_len - self.back_buf.available_space() + self.back_buf.capacity();
1451 if needed > self.max_buffer_size {
1452 return Err(ChannelError::MessageTooLarge {
1453 message_len: payload_len,
1454 capacity: self.back_buf.capacity(),
1455 max: self.max_buffer_size,
1456 });
1457 }
1458 // Past the ceiling check, the required size is within the ceiling
1459 // and at least the current capacity (we only enter on shortfall).
1460 debug_assert!(
1461 needed <= self.max_buffer_size,
1462 "grow target must be within the max ceiling once the cap check passed"
1463 );
1464
1465 let capacity_before = self.back_buf.capacity();
1466 // use doubling strategy to reach at least `needed`, amortizing future writes
1467 let mut new_length = self.back_buf.capacity();
1468 while new_length < needed {
1469 new_length = new_length.saturating_mul(2).max(new_length + 1);
1470 }
1471 new_length = min(new_length, self.max_buffer_size);
1472 // Post-grow target: large enough to fit the frame yet capped at the
1473 // configured ceiling and never below where we started.
1474 debug_assert!(
1475 new_length >= needed,
1476 "doubling growth must reach at least the needed capacity"
1477 );
1478 debug_assert!(
1479 new_length <= self.max_buffer_size,
1480 "grown back buffer must stay within the max_buffer_size ceiling"
1481 );
1482 debug_assert!(
1483 new_length >= capacity_before,
1484 "growth must never shrink the back buffer"
1485 );
1486 Self::check_high_watermark(
1487 "back",
1488 new_length,
1489 self.max_buffer_size,
1490 &mut self.back_high_watermark_logged,
1491 );
1492 self.back_buf.grow(new_length);
1493 // After the grow the frame must fit in the now-available space.
1494 debug_assert!(
1495 payload_len <= self.back_buf.available_space(),
1496 "back buffer must have room for the full frame after growth"
1497 );
1498 }
1499
1500 self.back_buf
1501 .write_all(&delimiter)
1502 .map_err(ChannelError::Write)?;
1503 self.back_buf
1504 .write_all(&payload)
1505 .map_err(ChannelError::Write)?;
1506
1507 // The two writes appended exactly `payload_len` bytes (delimiter +
1508 // payload) to the back buffer's pending data.
1509 debug_assert_eq!(
1510 self.back_buf.available_data(),
1511 data_before + payload_len,
1512 "back buffer pending data must grow by exactly the framed length"
1513 );
1514 debug_assert!(
1515 self.back_buf.capacity() <= self.max_buffer_size,
1516 "back buffer capacity must never exceed the max_buffer_size ceiling"
1517 );
1518
1519 Ok(())
1520 }
1521
1522 /// Shrink the front buffer back toward initial_buffer_size when it is
1523 /// mostly empty (data consumed) and was previously grown.
1524 fn try_shrink_front_buf(&mut self) {
1525 let capacity = self.front_buf.capacity();
1526 if capacity <= self.initial_buffer_size {
1527 return;
1528 }
1529 // Past the early return, we are strictly above the floor, so a shrink
1530 // back to `initial_buffer_size` is a genuine reduction.
1531 debug_assert!(
1532 capacity > self.initial_buffer_size,
1533 "shrink path only runs when capacity is above the initial floor"
1534 );
1535 // only shrink when the buffer has little pending data
1536 if self.front_buf.available_data() * 4 < self.initial_buffer_size {
1537 let data_before = self.front_buf.available_data();
1538 self.front_buf.shrink(self.initial_buffer_size);
1539 self.front_high_watermark_logged = false;
1540 // Shrink preserves pending data and never drops below the floor.
1541 debug_assert!(
1542 self.front_buf.capacity() >= self.initial_buffer_size,
1543 "front buffer must never shrink below the initial buffer size floor"
1544 );
1545 debug_assert_eq!(
1546 self.front_buf.available_data(),
1547 data_before,
1548 "shrink must preserve all pending front-buffer data"
1549 );
1550 trace!(
1551 "front buffer shrunk from {} to {} bytes",
1552 capacity, self.initial_buffer_size
1553 );
1554 }
1555 }
1556
1557 /// Shrink the back buffer back toward initial_buffer_size when fully drained.
1558 fn try_shrink_back_buf(&mut self) {
1559 let capacity = self.back_buf.capacity();
1560 if capacity <= self.initial_buffer_size {
1561 return;
1562 }
1563 debug_assert!(
1564 capacity > self.initial_buffer_size,
1565 "shrink path only runs when capacity is above the initial floor"
1566 );
1567 if self.back_buf.available_data() == 0 {
1568 self.back_buf.shrink(self.initial_buffer_size);
1569 self.back_high_watermark_logged = false;
1570 // The back buffer is only shrunk once fully drained; it must end at
1571 // the floor with no pending data resurrected.
1572 debug_assert!(
1573 self.back_buf.capacity() >= self.initial_buffer_size,
1574 "back buffer must never shrink below the initial buffer size floor"
1575 );
1576 debug_assert_eq!(
1577 self.back_buf.available_data(),
1578 0,
1579 "back buffer must stay empty across a drained shrink"
1580 );
1581 trace!(
1582 "back buffer shrunk from {} to {} bytes",
1583 capacity, self.initial_buffer_size
1584 );
1585 }
1586 }
1587}
1588
1589/// the payload is prefixed with a delimiter of sizeof(usize) bytes
1590pub const fn delimiter_size() -> usize {
1591 std::mem::size_of::<usize>()
1592}
1593
1594type ChannelResult<Tx, Rx> = Result<(Channel<Tx, Rx>, Channel<Rx, Tx>), ChannelError>;
1595
1596impl<Tx: Debug + ProstMessage + Default, Rx: Debug + ProstMessage + Default> Channel<Tx, Rx> {
1597 /// creates a channel pair: `(blocking_channel, nonblocking_channel)`
1598 pub fn generate(buffer_size: u64, max_buffer_size: u64) -> ChannelResult<Tx, Rx> {
1599 let (command, proxy) = MioUnixStream::pair().map_err(ChannelError::Read)?;
1600 let proxy_channel = Channel::new(proxy, buffer_size, max_buffer_size);
1601 let mut command_channel = Channel::new(command, buffer_size, max_buffer_size);
1602 command_channel.blocking()?;
1603 Ok((command_channel, proxy_channel))
1604 }
1605
1606 /// creates a pair of nonblocking channels
1607 pub fn generate_nonblocking(buffer_size: u64, max_buffer_size: u64) -> ChannelResult<Tx, Rx> {
1608 let (command, proxy) = MioUnixStream::pair().map_err(ChannelError::Read)?;
1609 let proxy_channel = Channel::new(proxy, buffer_size, max_buffer_size);
1610 let command_channel = Channel::new(command, buffer_size, max_buffer_size);
1611 Ok((command_channel, proxy_channel))
1612 }
1613}
1614
1615impl<Tx: Debug + ProstMessage + Default, Rx: Debug + ProstMessage + Default> Iterator
1616 for Channel<Tx, Rx>
1617{
1618 type Item = Rx;
1619 fn next(&mut self) -> Option<Self::Item> {
1620 self.read_message().ok()
1621 }
1622}
1623
1624use mio::{Interest, Registry, Token};
1625impl<Tx, Rx> Source for Channel<Tx, Rx> {
1626 fn register(
1627 &mut self,
1628 registry: &Registry,
1629 token: Token,
1630 interests: Interest,
1631 ) -> io::Result<()> {
1632 self.sock.register(registry, token, interests)
1633 }
1634
1635 fn reregister(
1636 &mut self,
1637 registry: &Registry,
1638 token: Token,
1639 interests: Interest,
1640 ) -> io::Result<()> {
1641 self.sock.reregister(registry, token, interests)
1642 }
1643
1644 fn deregister(&mut self, registry: &Registry) -> io::Result<()> {
1645 self.sock.deregister(registry)
1646 }
1647}
1648
1649#[cfg(test)]
1650mod tests {
1651 use std::{
1652 io::Write,
1653 os::fd::{AsRawFd, FromRawFd, OwnedFd},
1654 thread,
1655 time::Duration,
1656 };
1657
1658 use super::*;
1659 use mio::{Interest, Poll, Token};
1660
1661 #[derive(Clone, PartialEq, prost::Message)]
1662 pub struct ProtobufMessage {
1663 #[prost(uint32, required, tag = "1")]
1664 inner: u32,
1665 }
1666
1667 fn test_channels() -> (
1668 Channel<ProtobufMessage, ProtobufMessage>,
1669 Channel<ProtobufMessage, ProtobufMessage>,
1670 ) {
1671 Channel::generate(1000, 10000).expect("could not generate blocking channels for testing")
1672 }
1673
1674 #[test]
1675 fn channel_snapshot_round_trips_unconsumed_offsets_capacities_bits_and_mode() {
1676 let (mut channel, _peer): (
1677 Channel<ProtobufMessage, ProtobufMessage>,
1678 Channel<ProtobufMessage, ProtobufMessage>,
1679 ) = Channel::generate_nonblocking(8, 64).expect("could not generate channels");
1680
1681 channel.front_buf.grow(32);
1682 channel
1683 .front_buf
1684 .write_all(b"retired-front-pending")
1685 .expect("could not seed front buffer");
1686 channel.front_buf.consume(b"retired-".len());
1687 channel.back_buf.grow(48);
1688 channel
1689 .back_buf
1690 .write_all(b"sent-back-pending")
1691 .expect("could not seed back buffer");
1692 channel.back_buf.consume(b"sent-".len());
1693 channel.readiness =
1694 Ready::READABLE | Ready::WRITABLE | Ready::ERROR | Ready::HUP | Ready::WRITE_CLOSED;
1695 channel.interest = Ready::READABLE | Ready::WRITABLE;
1696 channel.front_high_watermark_logged = true;
1697
1698 let snapshot_json =
1699 serde_json::to_vec(&channel.snapshot()).expect("channel snapshot should serialize");
1700 let snapshot: ChannelSnapshot =
1701 serde_json::from_slice(&snapshot_json).expect("channel snapshot should deserialize");
1702 assert!(snapshot.has_buffered_input());
1703 assert!(snapshot.has_buffered_output());
1704 let original_fd = channel.fd();
1705 let paused: PausedChannel<ProtobufMessage, ProtobufMessage> =
1706 Channel::restore_paused(channel.sock, snapshot, 8, 64)
1707 .expect("valid channel snapshot should restore");
1708
1709 assert_eq!(paused.fd(), original_fd);
1710 let restored = paused.resume();
1711 assert_eq!(restored.front_buf.data(), b"front-pending");
1712 assert_eq!(restored.back_buf.data(), b"back-pending");
1713 assert_eq!(restored.front_buf.capacity(), 32);
1714 assert_eq!(restored.back_buf.capacity(), 48);
1715 assert_eq!(restored.initial_buffer_size, 8);
1716 assert_eq!(restored.max_buffer_size, 64);
1717 assert_eq!(
1718 restored.readiness,
1719 Ready::READABLE | Ready::WRITABLE | Ready::ERROR | Ready::HUP | Ready::WRITE_CLOSED
1720 );
1721 assert_eq!(restored.interest, Ready::READABLE | Ready::WRITABLE);
1722 assert!(!restored.is_blocking());
1723 assert!(restored.front_high_watermark_logged);
1724 assert!(!restored.back_high_watermark_logged);
1725 }
1726
1727 #[test]
1728 fn channel_snapshot_paused_registration_does_not_flush_before_resume() {
1729 let (mut channel, mut peer): (
1730 Channel<ProtobufMessage, ProtobufMessage>,
1731 Channel<ProtobufMessage, ProtobufMessage>,
1732 ) = Channel::generate_nonblocking(32, 128).expect("could not generate channels");
1733 channel
1734 .write_message(&ProtobufMessage { inner: 42 })
1735 .expect("could not queue message");
1736 channel.handle_events(Ready::WRITABLE);
1737
1738 let snapshot = channel.snapshot();
1739 let mut paused: PausedChannel<ProtobufMessage, ProtobufMessage> =
1740 Channel::restore_paused(channel.sock, snapshot, 32, 128)
1741 .expect("valid channel snapshot should restore");
1742 let poll = Poll::new().expect("could not create poll registry");
1743 paused
1744 .register(
1745 poll.registry(),
1746 Token(7),
1747 Interest::READABLE | Interest::WRITABLE,
1748 )
1749 .expect("paused channel should register");
1750
1751 peer.handle_events(Ready::READABLE);
1752 assert_eq!(peer.readable().expect("peer read probe should not fail"), 0);
1753 assert!(matches!(
1754 peer.read_message(),
1755 Err(ChannelError::NothingRead)
1756 ));
1757
1758 let mut restored = paused.resume();
1759 restored.run().expect("resumed channel should flush");
1760 peer.handle_events(Ready::READABLE);
1761 peer.readable().expect("peer should receive resumed bytes");
1762 assert_eq!(
1763 peer.read_message()
1764 .expect("peer should decode queued message"),
1765 ProtobufMessage { inner: 42 }
1766 );
1767 }
1768
1769 #[test]
1770 fn channel_snapshot_rejects_invalid_version_capacities_bytes_and_ready_bits() {
1771 fn snapshot_and_socket() -> (
1772 ChannelSnapshot,
1773 MioUnixStream,
1774 Channel<ProtobufMessage, ProtobufMessage>,
1775 ) {
1776 let (channel, peer): (
1777 Channel<ProtobufMessage, ProtobufMessage>,
1778 Channel<ProtobufMessage, ProtobufMessage>,
1779 ) = Channel::generate_nonblocking(8, 64).expect("could not generate channels");
1780 (channel.snapshot(), channel.sock, peer)
1781 }
1782
1783 let (mut invalid_version, sock, _peer) = snapshot_and_socket();
1784 invalid_version.version += 1;
1785 assert!(matches!(
1786 Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1787 sock,
1788 invalid_version,
1789 8,
1790 64,
1791 ),
1792 Err(ChannelSnapshotError::UnsupportedVersion { .. })
1793 ));
1794
1795 let (mut invalid_bounds, sock, _peer) = snapshot_and_socket();
1796 invalid_bounds.initial_buffer_size = invalid_bounds.max_buffer_size + 1;
1797 assert!(matches!(
1798 Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1799 sock,
1800 invalid_bounds,
1801 8,
1802 64,
1803 ),
1804 Err(ChannelSnapshotError::InvalidBufferBounds { .. })
1805 ));
1806
1807 let (mut invalid_capacity, sock, _peer) = snapshot_and_socket();
1808 invalid_capacity.front_buffer_capacity = invalid_capacity.initial_buffer_size - 1;
1809 assert!(matches!(
1810 Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1811 sock,
1812 invalid_capacity,
1813 8,
1814 64,
1815 ),
1816 Err(ChannelSnapshotError::InvalidBufferCapacity { .. })
1817 ));
1818
1819 let (mut invalid_bytes, sock, _peer) = snapshot_and_socket();
1820 let over_capacity = vec![0; invalid_bytes.front_buffer_capacity + 1];
1821 invalid_bytes.front = SnapshotBytes::copy_of(&over_capacity);
1822 assert!(matches!(
1823 Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(sock, invalid_bytes, 8, 64,),
1824 Err(ChannelSnapshotError::BufferedDataExceedsCapacity { .. })
1825 ));
1826
1827 let (mut invalid_readiness, sock, _peer) = snapshot_and_socket();
1828 invalid_readiness.readiness = 1 << 5;
1829 assert!(matches!(
1830 Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1831 sock,
1832 invalid_readiness,
1833 8,
1834 64,
1835 ),
1836 Err(ChannelSnapshotError::InvalidReadyBits {
1837 field: "readiness",
1838 ..
1839 })
1840 ));
1841
1842 let (mut invalid_interest, sock, _peer) = snapshot_and_socket();
1843 invalid_interest.interest = 1 << 5;
1844 assert!(matches!(
1845 Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1846 sock,
1847 invalid_interest,
1848 8,
1849 64,
1850 ),
1851 Err(ChannelSnapshotError::InvalidReadyBits {
1852 field: "interest",
1853 ..
1854 })
1855 ));
1856 }
1857
1858 #[test]
1859 fn channel_snapshot_accepts_every_defined_ready_subset_in_both_fields() {
1860 let defined_bits = Ready::READABLE.0
1861 | Ready::WRITABLE.0
1862 | Ready::ERROR.0
1863 | Ready::HUP.0
1864 | Ready::WRITE_CLOSED.0;
1865
1866 for bits in 0..=defined_bits {
1867 for field in ["readiness", "interest"] {
1868 let (channel, _peer): (
1869 Channel<ProtobufMessage, ProtobufMessage>,
1870 Channel<ProtobufMessage, ProtobufMessage>,
1871 ) = Channel::generate_nonblocking(8, 64).expect("could not generate channels");
1872 let mut snapshot = channel.snapshot();
1873 if field == "readiness" {
1874 snapshot.readiness = bits;
1875 } else {
1876 snapshot.interest = bits;
1877 }
1878
1879 let restored = Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1880 channel.sock,
1881 snapshot,
1882 8,
1883 64,
1884 )
1885 .unwrap_or_else(|error| {
1886 panic!("{field} rejected defined bits {bits:#04x}: {error}")
1887 })
1888 .resume();
1889 let restored_bits = if field == "readiness" {
1890 restored.readiness.0
1891 } else {
1892 restored.interest.0
1893 };
1894 assert_eq!(restored_bits, bits, "{field} changed bits {bits:#04x}");
1895 }
1896 }
1897 }
1898
1899 #[test]
1900 fn channel_snapshot_rejects_blocking_mode_that_disagrees_with_received_socket() {
1901 let (channel, _peer): (
1902 Channel<ProtobufMessage, ProtobufMessage>,
1903 Channel<ProtobufMessage, ProtobufMessage>,
1904 ) = Channel::generate_nonblocking(8, 64).expect("could not generate channels");
1905 let mut snapshot = channel.snapshot();
1906 snapshot.blocking = true;
1907
1908 // SAFETY: `channel.fd()` is live for this call, and a non-negative
1909 // result is immediately owned so every path closes the duplicate.
1910 let duplicate_fd = unsafe { libc::dup(channel.fd()) };
1911 assert!(duplicate_fd >= 0, "could not duplicate channel descriptor");
1912 // SAFETY: `dup` returned a fresh descriptor owned by this test.
1913 let duplicate = unsafe { OwnedFd::from_raw_fd(duplicate_fd) };
1914 // SAFETY: `duplicate` owns a live descriptor and F_GETFL only reads its
1915 // shared open-file-description flags.
1916 let flags_before = unsafe { libc::fcntl(duplicate.as_raw_fd(), libc::F_GETFL) };
1917 assert!(flags_before >= 0, "could not read channel status flags");
1918 assert_ne!(flags_before & libc::O_NONBLOCK, 0);
1919
1920 let restored = Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1921 channel.sock,
1922 snapshot,
1923 8,
1924 64,
1925 );
1926
1927 // SAFETY: the duplicate remains live even when restoration rejects
1928 // and closes the separately owned descriptor supplied above.
1929 let flags_after = unsafe { libc::fcntl(duplicate.as_raw_fd(), libc::F_GETFL) };
1930 assert_eq!(
1931 flags_after, flags_before,
1932 "restore must not change fd flags"
1933 );
1934 assert!(matches!(
1935 restored,
1936 Err(ChannelSnapshotError::BlockingModeMismatch {
1937 snapshot_blocking: true,
1938 fd_blocking: false,
1939 })
1940 ));
1941 }
1942
1943 #[test]
1944 fn channel_snapshot_rejects_untrusted_limits_before_allocation() {
1945 let (channel, _peer): (
1946 Channel<ProtobufMessage, ProtobufMessage>,
1947 Channel<ProtobufMessage, ProtobufMessage>,
1948 ) = Channel::generate_nonblocking(8, 64).expect("could not generate channels");
1949 let mut snapshot = channel.snapshot();
1950 snapshot.max_buffer_size = usize::MAX;
1951 snapshot.front_buffer_capacity = usize::MAX;
1952 snapshot.back_buffer_capacity = usize::MAX;
1953
1954 assert!(matches!(
1955 Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1956 channel.sock,
1957 snapshot,
1958 8,
1959 64,
1960 ),
1961 Err(ChannelSnapshotError::BufferLimitsMismatch {
1962 snapshot_initial: 8,
1963 snapshot_max: usize::MAX,
1964 expected_initial: 8,
1965 expected_max: 64,
1966 })
1967 ));
1968 }
1969
1970 #[test]
1971 fn channel_snapshot_restores_full_front_buffer_backpressure_and_rearms_after_drain() {
1972 let (mut frame_source, _frame_peer): (
1973 Channel<ProtobufMessage, ProtobufMessage>,
1974 Channel<ProtobufMessage, ProtobufMessage>,
1975 ) = Channel::generate_nonblocking(32, 32).expect("could not generate channels");
1976 frame_source
1977 .write_delimited_message(&ProtobufMessage { inner: 7 })
1978 .expect("could not frame message");
1979 let frame = frame_source.back_buf.data().to_owned();
1980 let (mut reader, _reader_peer): (
1981 Channel<ProtobufMessage, ProtobufMessage>,
1982 Channel<ProtobufMessage, ProtobufMessage>,
1983 ) = Channel::generate_nonblocking(frame.len() as u64, frame.len() as u64)
1984 .expect("could not generate capacity-bound reader");
1985 reader
1986 .front_buf
1987 .write_all(&frame)
1988 .expect("frame should fit");
1989 reader.handle_events(Ready::READABLE);
1990 assert_eq!(
1991 reader.readable().expect("full buffer should backpressure"),
1992 0
1993 );
1994 assert!(!reader.interest.is_readable());
1995
1996 let snapshot = reader.snapshot();
1997 let paused: PausedChannel<ProtobufMessage, ProtobufMessage> =
1998 Channel::restore_paused(reader.sock, snapshot, frame.len(), frame.len())
1999 .expect("backpressured channel should restore");
2000 let mut restored = paused.resume();
2001
2002 assert_eq!(restored.front_buf.data(), frame);
2003 assert!(!restored.interest.is_readable());
2004 assert!(restored.readiness.is_readable());
2005 assert_eq!(
2006 restored
2007 .read_message()
2008 .expect("buffered frame should decode"),
2009 ProtobufMessage { inner: 7 }
2010 );
2011 assert!(matches!(
2012 restored.read_message(),
2013 Err(ChannelError::NothingRead)
2014 ));
2015 assert!(restored.interest.is_readable());
2016 assert_eq!(restored.front_buf.available_data(), 0);
2017 }
2018
2019 #[test]
2020 fn unblock_a_channel() {
2021 let (mut blocking, _nonblocking) = test_channels();
2022 assert!(blocking.nonblocking().is_ok())
2023 }
2024
2025 #[test]
2026 fn generate_blocking_and_nonblocking_channels() {
2027 let (blocking_channel, nonblocking_channel) = test_channels();
2028
2029 assert!(blocking_channel.is_blocking());
2030 assert!(!nonblocking_channel.is_blocking());
2031
2032 let (nonblocking_channel_1, nonblocking_channel_2): (
2033 Channel<ProtobufMessage, ProtobufMessage>,
2034 Channel<ProtobufMessage, ProtobufMessage>,
2035 ) = Channel::generate_nonblocking(1000, 10000)
2036 .expect("could not generatie nonblocking channels");
2037
2038 assert!(!nonblocking_channel_1.is_blocking());
2039 assert!(!nonblocking_channel_2.is_blocking());
2040 }
2041
2042 #[test]
2043 fn write_and_read_message_blocking() {
2044 let (mut blocking_channel, mut nonblocking_channel) = test_channels();
2045
2046 let message_to_send = ProtobufMessage { inner: 42 };
2047
2048 nonblocking_channel
2049 .blocking()
2050 .expect("Could not block channel");
2051 nonblocking_channel
2052 .write_message(&message_to_send)
2053 .expect("Could not write message on channel");
2054
2055 trace!("we wrote a message!");
2056
2057 trace!("reading message..");
2058 // blocking_channel.readable();
2059 let message = blocking_channel
2060 .read_message()
2061 .expect("Could not read message on channel");
2062 trace!("read message!");
2063
2064 assert_eq!(message, ProtobufMessage { inner: 42 });
2065 }
2066
2067 #[test]
2068 fn read_message_blocking_with_timeout_fails() {
2069 let (mut reading_channel, mut writing_channel) = test_channels();
2070 writing_channel.blocking().expect("Could not block channel");
2071
2072 trace!("reading message in a detached thread, with a timeout of 100 milliseconds...");
2073 // Hand the channel back with the result: dropping it when the thread
2074 // ends would close the peer, and the late write below would then fail
2075 // with `EPIPE` instead of arriving too late (sozu-proxy/sozu#1563).
2076 let awaiting_with_timeout = thread::spawn(move || {
2077 let message =
2078 reading_channel.read_message_blocking_timeout(Some(Duration::from_millis(100)));
2079 trace!("read message!");
2080 (message, reading_channel)
2081 });
2082
2083 trace!("Waiting 200 milliseconds…");
2084 thread::sleep(std::time::Duration::from_millis(200));
2085
2086 writing_channel
2087 .write_message(&ProtobufMessage { inner: 200 })
2088 .expect("Could not write message on channel");
2089 trace!("we wrote a message that should arrive too late!");
2090
2091 let (arrived_too_late, _reading_channel) = awaiting_with_timeout
2092 .join()
2093 .expect("error with receiving message from awaiting thread");
2094
2095 assert!(arrived_too_late.is_err());
2096 }
2097
2098 /// Install a no-op `SIGUSR1` handler with `SA_RESTART`, as the main
2099 /// process does for `SIGTERM`. signal(7): a socket read with a receive
2100 /// timeout (`SO_RCVTIMEO`) is never restarted, it fails with `EINTR`.
2101 #[cfg(target_os = "linux")]
2102 fn install_restarting_sigusr1_handler() {
2103 extern "C" fn ignore(_: libc::c_int) {}
2104 // SAFETY: the handler is async-signal-safe (it does nothing), and the
2105 // `sigaction` struct is fully initialised before the call.
2106 unsafe {
2107 let mut action: libc::sigaction = std::mem::zeroed();
2108 action.sa_sigaction = ignore as extern "C" fn(libc::c_int) as libc::sighandler_t;
2109 action.sa_flags = libc::SA_RESTART;
2110 libc::sigemptyset(&mut action.sa_mask);
2111 assert_eq!(
2112 libc::sigaction(libc::SIGUSR1, &action, std::ptr::null_mut()),
2113 0
2114 );
2115 }
2116 }
2117
2118 /// Spawn a reader blocked in `read_message_blocking_timeout`, then
2119 /// interrupt its thread with `SIGUSR1` every 5 ms for `flood`, so at least
2120 /// one signal lands while the 100 ms `SO_RCVTIMEO` read is blocked.
2121 #[cfg(target_os = "linux")]
2122 fn read_blocking_under_signal_flood(
2123 timeout: Duration,
2124 flood: Duration,
2125 message: Option<ProtobufMessage>,
2126 ) -> (Result<ProtobufMessage, ChannelError>, Duration) {
2127 install_restarting_sigusr1_handler();
2128 let (mut reading_channel, mut writing_channel) = test_channels();
2129 writing_channel.blocking().expect("Could not block channel");
2130
2131 let (thread_sender, thread_receiver) = std::sync::mpsc::channel();
2132 let reader = thread::spawn(move || {
2133 // SAFETY: `pthread_self` has no preconditions.
2134 thread_sender
2135 .send(unsafe { libc::pthread_self() })
2136 .expect("send reader thread id");
2137 let started = std::time::Instant::now();
2138 let message = reading_channel.read_message_blocking_timeout(Some(timeout));
2139 (message, started.elapsed(), reading_channel)
2140 });
2141 let reader_thread = thread_receiver.recv().expect("reader thread id");
2142
2143 let flood_end = std::time::Instant::now() + flood;
2144 while std::time::Instant::now() < flood_end {
2145 thread::sleep(Duration::from_millis(5));
2146 // SAFETY: `reader` is joined below, so `reader_thread` names a
2147 // thread that is running or exited but not yet joined.
2148 unsafe { libc::pthread_kill(reader_thread, libc::SIGUSR1) };
2149 }
2150 if let Some(message) = message {
2151 writing_channel
2152 .write_message(&message)
2153 .expect("Could not write message on channel");
2154 }
2155
2156 let (result, elapsed, _reading_channel) = reader.join().expect("reader thread");
2157 (result, elapsed)
2158 }
2159
2160 /// A signal delivered while a blocking read waits (a whole-cgroup
2161 /// `SIGTERM` reaching a replacement main that waits for COMMIT) must not
2162 /// abort the wait: the message sent afterwards is still received.
2163 #[cfg(target_os = "linux")]
2164 #[test]
2165 fn read_message_blocking_with_timeout_survives_interrupting_signals() {
2166 let (result, _) = read_blocking_under_signal_flood(
2167 Duration::from_secs(5),
2168 Duration::from_millis(300),
2169 Some(ProtobufMessage { inner: 7 }),
2170 );
2171 assert_eq!(
2172 result.expect("an interrupted read must keep waiting"),
2173 ProtobufMessage { inner: 7 }
2174 );
2175 }
2176
2177 /// Retrying an interrupted read keeps the caller's deadline: signals
2178 /// arriving for longer than the timeout neither abort nor extend it.
2179 #[cfg(target_os = "linux")]
2180 #[test]
2181 fn read_message_blocking_with_timeout_keeps_its_deadline_under_signals() {
2182 let timeout = Duration::from_millis(300);
2183 let (result, elapsed) =
2184 read_blocking_under_signal_flood(timeout, Duration::from_millis(600), None);
2185 assert!(
2186 matches!(result, Err(ChannelError::TimeoutReached(reached)) if reached == timeout),
2187 "expected the deadline to be reached, got {result:?}"
2188 );
2189 assert!(
2190 elapsed >= timeout,
2191 "returned before the deadline: {elapsed:?}"
2192 );
2193 // One 100 ms `SO_RCVTIMEO` slice of overshoot, plus scheduling slack.
2194 assert!(
2195 elapsed < timeout + Duration::from_millis(400),
2196 "the deadline was extended: {elapsed:?}"
2197 );
2198 }
2199
2200 #[test]
2201 fn read_message_blocking_with_timeout_succeeds() {
2202 let (mut reading_channel, mut writing_channel) = test_channels();
2203 writing_channel.blocking().expect("Could not block channel");
2204
2205 trace!("reading message in a detached thread, with a timeout of 200 milliseconds...");
2206 let awaiting_with_timeout = thread::spawn(move || {
2207 let message = reading_channel
2208 .read_message_blocking_timeout(Some(Duration::from_millis(200)))
2209 .expect("Could not read message with timeout on blocking channel");
2210 trace!("read message!");
2211 message
2212 });
2213
2214 trace!("Waiting 100 milliseconds…");
2215 thread::sleep(std::time::Duration::from_millis(100));
2216
2217 writing_channel
2218 .write_message(&ProtobufMessage { inner: 100 })
2219 .expect("Could not write message on channel");
2220 trace!("we wrote a message that should arrive on time!");
2221
2222 let arrived_on_time = awaiting_with_timeout
2223 .join()
2224 .expect("error with receiving message from awaiting thread");
2225
2226 assert_eq!(arrived_on_time, ProtobufMessage { inner: 100 });
2227 }
2228
2229 #[test]
2230 fn exhaustive_use_of_nonblocking_channels() {
2231 // - two nonblocking channels A and B, identical
2232 let (mut channel_a, mut channel_b) = test_channels();
2233 channel_a.nonblocking().expect("Could not block channel");
2234
2235 // write on A
2236 channel_a
2237 .write_message(&ProtobufMessage { inner: 1 })
2238 .expect("Could not write message on channel");
2239
2240 // set B as readable, normally mio tells when to, by giving events
2241 channel_b.handle_events(Ready::READABLE);
2242
2243 // read on B
2244 let should_err = channel_b.read_message();
2245 assert!(should_err.is_err());
2246
2247 // write another message on A
2248 channel_a
2249 .write_message(&ProtobufMessage { inner: 2 })
2250 .expect("Could not write message on channel");
2251
2252 // insert a handle_events Ready::writable on A
2253 channel_a.handle_events(Ready::WRITABLE);
2254
2255 // flush A with run()
2256 channel_a.run().expect("Failed to run the channel");
2257
2258 // maybe a thread sleep
2259 thread::sleep(std::time::Duration::from_millis(100));
2260
2261 // receive with B using run()
2262 channel_b.run().expect("Failed to run the channel");
2263
2264 // use read_message() twice on B, check them
2265 let message_1 = channel_b
2266 .read_message()
2267 .expect("Could not read message on channel");
2268 assert_eq!(message_1, ProtobufMessage { inner: 1 });
2269
2270 let message_2 = channel_b
2271 .read_message()
2272 .expect("Could not read message on channel");
2273 assert_eq!(message_2, ProtobufMessage { inner: 2 });
2274 }
2275
2276 #[test]
2277 fn buffer_grows_with_doubling_strategy() {
2278 let (writing_channel, _reading_channel): (
2279 Channel<ProtobufMessage, ProtobufMessage>,
2280 Channel<ProtobufMessage, ProtobufMessage>,
2281 ) = Channel::generate(100, 10000).expect("could not generate channels");
2282
2283 assert_eq!(writing_channel.back_buf.capacity(), 100);
2284
2285 assert_eq!(writing_channel.grow_size(100), Some(200));
2286 assert_eq!(writing_channel.grow_size(200), Some(400));
2287 assert_eq!(writing_channel.grow_size(5000), Some(10000));
2288 assert_eq!(writing_channel.grow_size(10000), None);
2289 }
2290
2291 #[test]
2292 fn buffer_cap_returns_error() {
2293 let (mut writing_channel, _reading_channel): (
2294 Channel<ProtobufMessage, ProtobufMessage>,
2295 Channel<ProtobufMessage, ProtobufMessage>,
2296 ) = Channel::generate(50, 50).expect("could not generate channels");
2297
2298 writing_channel.blocking().expect("Could not block channel");
2299
2300 let mut i = 0u32;
2301 let result = loop {
2302 let msg = ProtobufMessage { inner: i };
2303 match writing_channel.write_delimited_message(&msg) {
2304 Ok(()) => i += 1,
2305 Err(e) => break Err(e),
2306 }
2307 if i > 10000 {
2308 break Ok(());
2309 }
2310 };
2311
2312 assert!(result.is_err());
2313 let err = result.unwrap_err();
2314 let err_msg = format!("{err}");
2315 assert!(
2316 err_msg.contains("too large") || err_msg.contains("cannot grow"),
2317 "unexpected error: {err_msg}"
2318 );
2319 }
2320
2321 #[test]
2322 fn back_buffer_shrinks_after_drain() {
2323 let (mut channel, _other): (
2324 Channel<ProtobufMessage, ProtobufMessage>,
2325 Channel<ProtobufMessage, ProtobufMessage>,
2326 ) = Channel::generate(100, 10000).expect("could not generate channels");
2327
2328 // Write directly to the back buffer (without draining to socket)
2329 // to force growth. Each message is ~10 bytes (delimiter + varint).
2330 for i in 0..20 {
2331 channel
2332 .write_delimited_message(&ProtobufMessage { inner: i })
2333 .expect("Could not write message");
2334 }
2335
2336 let grown_capacity = channel.back_buf.capacity();
2337 assert!(
2338 grown_capacity > 100,
2339 "expected buffer growth, got capacity {grown_capacity}"
2340 );
2341
2342 // Simulate full drain by consuming all data
2343 let data_len = channel.back_buf.available_data();
2344 channel.back_buf.consume(data_len);
2345 assert_eq!(channel.back_buf.available_data(), 0);
2346
2347 channel.try_shrink_back_buf();
2348 assert_eq!(
2349 channel.back_buf.capacity(),
2350 100,
2351 "back buffer should shrink to initial size after drain"
2352 );
2353 }
2354
2355 #[test]
2356 fn back_buffer_grows_with_doubling_on_write() {
2357 let (mut channel, _other): (
2358 Channel<ProtobufMessage, ProtobufMessage>,
2359 Channel<ProtobufMessage, ProtobufMessage>,
2360 ) = Channel::generate(32, 10000).expect("could not generate channels");
2361
2362 assert_eq!(channel.back_buf.capacity(), 32);
2363
2364 // Write enough messages to force growth beyond initial capacity.
2365 // Each ProtobufMessage encodes to ~4 bytes + 8-byte delimiter = ~12 bytes.
2366 for i in 0..10 {
2367 channel
2368 .write_delimited_message(&ProtobufMessage { inner: i })
2369 .expect("Could not write message");
2370 }
2371
2372 let grown = channel.back_buf.capacity();
2373 assert!(grown > 32, "expected buffer growth beyond 32, got {grown}");
2374 // doubling from 32 should yield a power-of-two-like size (64, 128, 256, ...)
2375 // rather than the exact needed amount
2376 assert!(
2377 grown.is_power_of_two() || grown == 10000,
2378 "expected doubling growth pattern, got {grown}"
2379 );
2380 }
2381
2382 /// Regression: a peer that writes a length-delimited frame whose
2383 /// declared length is *less than* the delimiter itself must be
2384 /// rejected with `MessageLengthUnderDelimiter`, never panic the
2385 /// reader with `slice index starts at N but ends at M`.
2386 ///
2387 /// Without the bounds check, `&buffer[delimiter_size()..message_len]`
2388 /// at `try_read_delimited_message` panics for any peer-controlled
2389 /// `message_len < delimiter_size()` (= 8 on 64-bit) — a one-packet
2390 /// denial-of-service against the master command socket.
2391 #[test]
2392 fn rejects_declared_length_below_delimiter() {
2393 let (mut reader, mut writer): (
2394 Channel<ProtobufMessage, ProtobufMessage>,
2395 Channel<ProtobufMessage, ProtobufMessage>,
2396 ) = Channel::generate(1000, 10000).expect("could not generate channels");
2397 writer.blocking().expect("writer to block");
2398 reader.blocking().expect("reader to block");
2399
2400 // Craft a delimiter that lies: message_len = 5 (< delimiter_size() = 8).
2401 // Send it as raw bytes, bypassing write_delimited_message.
2402 let bogus: usize = 5;
2403 let bytes = bogus.to_le_bytes();
2404 std::io::Write::write_all(&mut writer.sock, &bytes).expect("raw write of bogus delimiter");
2405
2406 match reader.read_message() {
2407 Err(ChannelError::MessageLengthUnderDelimiter {
2408 message_len,
2409 delimiter_size,
2410 }) => {
2411 assert_eq!(message_len, 5);
2412 assert_eq!(delimiter_size, std::mem::size_of::<usize>());
2413 }
2414 other => panic!(
2415 "expected MessageLengthUnderDelimiter, got {other:?}\n\
2416 NOTE: a panic here means the slice-OOB hardening was reverted",
2417 ),
2418 }
2419 }
2420
2421 /// Regression for sozu-proxy/sozu#1416: `Channel::new`/`generate_nonblocking` took
2422 /// `buffer_size` and `max_buffer_size` without ever comparing them, so a caller
2423 /// passing `buffer_size > max_buffer_size` built a `front_buf` already larger than
2424 /// the ceiling it must respect. `45, 32` is the exact pair the fuzz target's
2425 /// construction generator produced before it was clamped to work around this defect.
2426 /// `try_read_delimited_message` then tripped its own
2427 /// "front buffer capacity must never exceed max_buffer_size" `debug_assert!` on the
2428 /// very first parse, before any wire byte was examined.
2429 #[test]
2430 fn channel_new_clamps_buffer_size_above_max() {
2431 let (mut reader, _writer): (
2432 Channel<ProtobufMessage, ProtobufMessage>,
2433 Channel<ProtobufMessage, ProtobufMessage>,
2434 ) = Channel::generate_nonblocking(45, 32).expect("could not generate channels");
2435
2436 // Parsing an empty buffer must not trip the capacity invariant assertion
2437 // (pre-fix: `try_read_delimited_message`'s own "front buffer capacity must
2438 // never exceed max_buffer_size" `debug_assert!` fires right here, before any
2439 // wire byte is examined).
2440 assert_eq!(
2441 reader
2442 .try_read_delimited_message()
2443 .expect("parsing an empty buffer must not error"),
2444 None
2445 );
2446
2447 assert!(
2448 reader.front_buf.capacity() <= 32,
2449 "front buffer capacity {} must never exceed max_buffer_size 32",
2450 reader.front_buf.capacity()
2451 );
2452 }
2453
2454 /// Regression for sozu-proxy/sozu#1428: a peer that declares a frame
2455 /// length above `max_buffer_size` must mark the channel for closing.
2456 ///
2457 /// The oversize guard rejects the frame but cannot re-sync past it. The
2458 /// declared length is *unsatisfiable* — the frame can never fit a buffer
2459 /// capped at `max_buffer_size`, so no amount of further reading completes
2460 /// it — and the bytes behind the delimiter are of unknown provenance:
2461 /// dropping the eight header bytes and re-framing would decode
2462 /// peer-chosen payload bytes as a fresh control-plane command. That is
2463 /// why this branch must NOT consume, while its sibling
2464 /// `MessageLengthUnderDelimiter` must: a declared length below
2465 /// `delimiter_size()` is one no writer can emit for any payload, so those
2466 /// eight bytes are provably not a real header and skipping exactly them
2467 /// re-aligns the stream.
2468 ///
2469 /// Left unconsumed AND unsignalled, the delimiter is re-parsed on every
2470 /// later read forever: `read_message_nonblocking` propagates the error
2471 /// with no log, `extract_messages` (`bin/src/command/sessions.rs`)
2472 /// discards it with a bare `Err(_)`, and neither `ClientSession::ready`
2473 /// nor `WorkerSession::ready` closes the session because both key on
2474 /// `readiness.is_error() || is_hup()`, which no parse error ever sets.
2475 /// The session then wedges permanently, pinning its file descriptor and
2476 /// up to `max_buffer_size` of buffer, and `doc/configure_admin_ops.md`
2477 /// §5.2's "the supervisor logs and drops the offending peer's session"
2478 /// never happens.
2479 #[test]
2480 fn oversized_declared_length_marks_the_channel_for_closing() {
2481 let (mut reader, mut writer): (
2482 Channel<ProtobufMessage, ProtobufMessage>,
2483 Channel<ProtobufMessage, ProtobufMessage>,
2484 ) = Channel::generate_nonblocking(1000, 10000)
2485 .expect("could not generate nonblocking channels");
2486
2487 // One byte past this reader's `max_buffer_size`. Written raw only
2488 // because both ends of this pair share one ceiling, so the writer's own
2489 // `write_delimited_message` would refuse it. That is not true of a real
2490 // deployment: the two ends size their channels from their own
2491 // `max_command_buffer_size` (`bin/config.toml` ships 163_840,
2492 // `os-build/config.toml` 1_638_400, the compiled default is 2_000_000),
2493 // so a peer configured higher puts this frame on the wire through the
2494 // ordinary write path while conforming perfectly. What follows is
2495 // therefore about what this end can do with the frame, not about the
2496 // peer's good faith.
2497 let oversized: usize = 10_001;
2498 std::io::Write::write_all(&mut writer.sock, &oversized.to_le_bytes())
2499 .expect("raw write of the oversized delimiter");
2500
2501 reader.handle_events(Ready::READABLE);
2502 reader
2503 .readable()
2504 .expect("reader must buffer the delimiter bytes");
2505
2506 let available_before = reader.front_buf.available_data();
2507 assert_eq!(
2508 available_before,
2509 delimiter_size(),
2510 "the delimiter must be buffered before the parse under test"
2511 );
2512
2513 // Arm writability before the parse that marks the channel, so the
2514 // assertion at the end of this test can tell an `insert` from an
2515 // assignment.
2516 reader.handle_events(Ready::WRITABLE);
2517
2518 match reader.read_message() {
2519 Err(ChannelError::MessageTooLarge {
2520 message_len, max, ..
2521 }) => {
2522 assert_eq!(message_len, oversized);
2523 assert_eq!(max, 10000);
2524 }
2525 other => panic!("expected MessageTooLarge, got {other:?}"),
2526 }
2527
2528 // The bytes stay put -- deliberately, see the doc comment above.
2529 assert_eq!(
2530 reader.front_buf.available_data(),
2531 available_before,
2532 "an unsatisfiable declared length must not be consumed: re-framing \
2533 on the bytes behind it would decode peer-chosen bytes as a command"
2534 );
2535
2536 // So a second parse returns the identical error against the identical
2537 // bytes. The parser alone can never make progress on this stream.
2538 match reader.read_message() {
2539 Err(ChannelError::MessageTooLarge { message_len, .. }) => {
2540 assert_eq!(message_len, oversized)
2541 }
2542 other => panic!("expected the identical MessageTooLarge, got {other:?}"),
2543 }
2544 assert_eq!(reader.front_buf.available_data(), available_before);
2545
2546 // Which leaves closing the peer as the only correct exit, and the channel
2547 // must say so through the one signal `ClientSession::ready`,
2548 // `WorkerSession::ready` and `wants_to_tick` all key on
2549 // (`bin/src/command/sessions.rs`).
2550 assert!(
2551 reader.readiness.is_error(),
2552 "an oversized declaration must mark the channel errored so the \
2553 supervisor drops the session; without it the same delimiter is \
2554 re-parsed forever, the error is swallowed by `extract_messages`'s \
2555 `Err(_)` arm, and the session wedges holding its fd and buffer"
2556 );
2557 // And it must INSERT that bit rather than assign it. On the worker's
2558 // own channel (`lib/src/server.rs`) `interest` carries only READABLE
2559 // and WRITABLE, so `readiness()` masks ERROR away and nothing there
2560 // reads `is_error()`: an assignment's only effect on that side is
2561 // clearing WRITABLE, and `Server::send_queue` gates on
2562 // `channel.readiness.is_writable()`, so responses already queued for
2563 // the supervisor would stall until the next edge-triggered writability
2564 // event.
2565 assert!(
2566 reader.readiness.is_writable(),
2567 "marking the channel errored must not clear the other readiness \
2568 bits; wiping WRITABLE stalls `Server::send_queue` until the next \
2569 writability event"
2570 );
2571 }
2572
2573 /// Regression for the second shape of sozu-proxy/sozu#1428: a frame whose
2574 /// declared length is valid but whose payload does not decode must be
2575 /// consumed, so the channel re-syncs on the next frame instead of
2576 /// re-decoding the same bytes for the life of the session.
2577 ///
2578 /// `try_read_delimited_message` propagated `InvalidProtobufMessage` from
2579 /// the `?` on `Rx::decode`, one line *before* the `consume`. The malformed
2580 /// frame therefore stayed at the head of `front_buf`, no readiness bit
2581 /// moved, `extract_messages` (`bin/src/command/sessions.rs`) discarded the
2582 /// error with its bare `Err(_)` arm, and the session wedged exactly as the
2583 /// oversize branch above did. Eight valid length bytes followed by garbage
2584 /// reach it, and so does protobuf skew between two builds.
2585 ///
2586 /// Consuming is coherent with the success path rather than provably safe:
2587 /// `message_len` is only range-checked, but the success path already
2588 /// consumes that same peer-supplied length and re-frames on whatever
2589 /// follows, so the decode-failure path now re-frames on exactly the bytes
2590 /// the decode-success path already did. One undecodable payload therefore
2591 /// costs its frame rather than the peer.
2592 ///
2593 /// SCOPE: this asserts the channel contract, not a session one. Unlike the
2594 /// oversize branch above, this shape has no `ClientSession`-level test, so
2595 /// nothing here proves a session-level property. A frame pipelined behind
2596 /// the malformed one is delivered on the same tick; the session-level proof
2597 /// of that is
2598 /// `client_session_delivers_the_frame_behind_a_malformed_length_prefix`,
2599 /// built on the sibling `MessageLengthUnderDelimiter` shape.
2600 #[test]
2601 fn malformed_payload_is_consumed_so_the_channel_resyncs() {
2602 let (mut reader, mut writer): (
2603 Channel<ProtobufMessage, ProtobufMessage>,
2604 Channel<ProtobufMessage, ProtobufMessage>,
2605 ) = Channel::generate_nonblocking(1000, 10000)
2606 .expect("could not generate nonblocking channels");
2607
2608 // A well-formed frame whose payload is not a `ProtobufMessage`: a
2609 // leading zero byte is field number 0, which no protobuf decoder
2610 // accepts. The length prefix itself is entirely valid. Hand-crafted
2611 // rather than random so the failure is deterministic and independent
2612 // of how much arbitrary input prost happens to accept.
2613 let frame_len: usize = delimiter_size() + 8;
2614 let mut malformed = frame_len.to_le_bytes().to_vec();
2615 malformed.extend_from_slice(&[0x00_u8; 8]);
2616 std::io::Write::write_all(&mut writer.sock, &malformed)
2617 .expect("raw write of the malformed frame");
2618
2619 // ... immediately followed by a perfectly good one.
2620 writer
2621 .write_message(&ProtobufMessage { inner: 7 })
2622 .expect("could not queue the following message");
2623 writer.handle_events(Ready::WRITABLE);
2624 writer.run().expect("could not flush the following message");
2625
2626 reader.handle_events(Ready::READABLE);
2627 reader.readable().expect("reader must buffer both frames");
2628
2629 let available_before = reader.front_buf.available_data();
2630 assert!(
2631 available_before > frame_len,
2632 "both frames must be buffered before the parse under test"
2633 );
2634
2635 match reader.read_message() {
2636 Err(ChannelError::InvalidProtobufMessage(_)) => {}
2637 other => panic!("expected InvalidProtobufMessage, got {other:?}"),
2638 }
2639
2640 assert_eq!(
2641 reader.front_buf.available_data(),
2642 available_before - frame_len,
2643 "a malformed payload behind a valid length must be consumed: the \
2644 frame boundary is exact, so dropping exactly those bytes re-syncs \
2645 the stream"
2646 );
2647
2648 assert_eq!(
2649 reader.read_message().expect("the next frame must decode"),
2650 ProtobufMessage { inner: 7 },
2651 "the channel must make progress past a malformed frame instead of \
2652 re-decoding it on every later read"
2653 );
2654 }
2655
2656 /// Builds the exact front-buffer layout of sozu-proxy/sozu#1436 and returns
2657 /// the reader sitting in it, the writer still holding the pending frame's
2658 /// tail, and that tail.
2659 ///
2660 /// `Buffer::fill` (`command/src/buffer/growable.rs`) compacts whenever a
2661 /// read reaches the end of the buffer, so a front buffer filled to capacity
2662 /// always lands at `position == 0`. `Buffer::consume` compacts only past
2663 /// `capacity / 2`, so decoding one frame of at most half the capacity out of
2664 /// that full buffer advances `position` without reaching the threshold and
2665 /// leaves `available_space() == 0` with `capacity - first.len()` bytes
2666 /// pending. At `capacity == max_buffer_size` nothing can grow out of it
2667 /// either.
2668 ///
2669 /// `declared` is the length the second frame announces; the caller picks it
2670 /// on whichever side of the ceiling it wants to exercise.
2671 fn wedged_reader(
2672 capacity: usize,
2673 declared: usize,
2674 ) -> (
2675 Channel<ProtobufMessage, ProtobufMessage>,
2676 Channel<ProtobufMessage, ProtobufMessage>,
2677 Vec<u8>,
2678 ) {
2679 let (mut reader, mut writer): (
2680 Channel<ProtobufMessage, ProtobufMessage>,
2681 Channel<ProtobufMessage, ProtobufMessage>,
2682 ) = Channel::generate_nonblocking(capacity as u64, capacity as u64)
2683 .expect("could not generate nonblocking channels");
2684
2685 // A real frame, produced by the production write path.
2686 writer
2687 .write_delimited_message(&ProtobufMessage { inner: 1 })
2688 .expect("could not frame the first message");
2689 let first = writer.back_buf.data().to_vec();
2690 writer.back_buf.consume(first.len());
2691 assert!(
2692 first.len() <= capacity / 2,
2693 "the first frame ({} bytes) must stay at or under `Buffer::consume`'s \
2694 {}-byte shift threshold, or the layout under test never forms",
2695 first.len(),
2696 capacity / 2
2697 );
2698
2699 // The second frame. `[0x08, 0x2a]` is field 1 set to 42, repeated: a
2700 // non-repeated scalar takes its last occurrence, so the payload decodes
2701 // at any EVEN length -- callers pass an even `declared`, since an odd
2702 // one would truncate the last pair to a tag with no varint behind it.
2703 // Written raw because both ends of this pair share one ceiling, so
2704 // `write_delimited_message` cannot emit a frame this large -- a peer
2705 // configured with a larger `max_command_buffer_size` puts exactly these
2706 // bytes on the wire through the ordinary write path.
2707 let mut second = declared.to_le_bytes().to_vec();
2708 while second.len() < declared {
2709 second.extend_from_slice(&[0x08, 0x2a]);
2710 }
2711 debug_assert_eq!(
2712 declared % 2,
2713 0,
2714 "an odd declared length truncates the padding to a dangling tag"
2715 );
2716 second.truncate(declared.max(delimiter_size()));
2717
2718 // Fill the front buffer to the brim: the first frame, then as much of
2719 // the second as fits. The rest stays in the writer.
2720 let head = capacity - first.len();
2721 let head = head.min(second.len());
2722 let mut wire = first.clone();
2723 wire.extend_from_slice(&second[..head]);
2724 std::io::Write::write_all(&mut writer.sock, &wire)
2725 .expect("raw write of the first frame and the second frame's head");
2726
2727 reader.handle_events(Ready::READABLE);
2728 assert_eq!(
2729 reader
2730 .readable()
2731 .expect("the reader must fill its front buffer"),
2732 wire.len()
2733 );
2734 assert_eq!(
2735 reader.front_buf.available_space(),
2736 0,
2737 "the front buffer must be filled to the brim before the decode"
2738 );
2739
2740 assert_eq!(
2741 reader.read_message().expect("the first frame must decode"),
2742 ProtobufMessage { inner: 1 }
2743 );
2744 assert_eq!(reader.front_buf.available_data(), capacity - first.len());
2745 assert_eq!(
2746 reader.front_buf.available_space(),
2747 0,
2748 "the layout under test: data pending, zero space, capacity pinned at \
2749 the ceiling -- `Buffer::consume` did not reach its shift threshold"
2750 );
2751
2752 let tail = second[head..].to_vec();
2753 (reader, writer, tail)
2754 }
2755
2756 /// Regression for sozu-proxy/sozu#1436: a frame that fits the ceiling but
2757 /// not the front buffer's current *layout* must be rescued by compaction,
2758 /// not rejected.
2759 ///
2760 /// The `BufferFull` arm of `try_read_delimited_message` returned without
2761 /// consuming and without moving a readiness bit -- the shape
2762 /// sozu-proxy/sozu#1428 removed from its two siblings -- and nothing
2763 /// downstream compensated: `extract_messages` (`bin/src/command/sessions.rs`)
2764 /// discards the error with a bare `Err(_)`, and `ClientSession::ready` /
2765 /// `WorkerSession::ready` close only on `readiness.is_error() || is_hup()`.
2766 /// Worse, it was unrecoverable: the three `front_buf` mutators that can
2767 /// trigger a `Buffer::shift` are all out of reach afterwards. `fill` needs a
2768 /// successful read, but `readable()` finds `available_space() == 0`, gets
2769 /// `None` from `grow_size` at the ceiling and executes
2770 /// `interest.remove(Ready::READABLE)`, so every later `readable()` returns
2771 /// `Err(Connection(None))`; `consume` needs a decoded frame or an
2772 /// under-delimiter length, and the parse returns before either;
2773 /// `try_shrink_front_buf` is only called after a frame decoded, and both
2774 /// of its own early returns would stop it anyway -- `capacity <=
2775 /// initial_buffer_size`, which holds whenever the channel was configured
2776 /// with `command_buffer_size == max_command_buffer_size`, and
2777 /// `available_data() * 4 < initial_buffer_size`, which is false with that
2778 /// much pending. Not because it shrinks rather than shifts: `Buffer::shrink`
2779 /// calls `shift` unconditionally before its own size bail.
2780 ///
2781 /// SCOPE: this is the channel contract. It proves the parser makes room and
2782 /// the frame decodes once its tail is buffered; it proves nothing about the
2783 /// session, whose drain loop had its own reason to stop one read short --
2784 /// see `client_session_completes_a_compacted_frame_without_another_peer_write`
2785 /// in `bin/src/command/sessions.rs`.
2786 ///
2787 /// The recovery is NOT the oversize branch's close. That branch drops the
2788 /// peer because the declared length can never be satisfied by a buffer
2789 /// bounded at `max_buffer_size`. Here the frame is under the ceiling and its
2790 /// buffered bytes are intact: only the buffer's internal offsets are in the
2791 /// way, and a single `Buffer::shift` hands back exactly the space the
2792 /// consumed frame left behind. Closing would drop a conforming peer over an
2793 /// internal buffer-management detail.
2794 #[test]
2795 fn a_frame_within_the_ceiling_is_rescued_by_compaction() {
2796 let capacity = 64usize;
2797 // Exactly at the ceiling: the largest frame this end may legitimately
2798 // be asked to accept, and the boundary case of the guard above.
2799 let (mut reader, mut writer, tail) = wedged_reader(capacity, capacity);
2800 assert!(
2801 !tail.is_empty(),
2802 "the second frame must still be incomplete"
2803 );
2804
2805 std::io::Write::write_all(&mut writer.sock, &tail)
2806 .expect("raw write of the second frame's tail");
2807
2808 // The parse that used to wedge. It must make room and ask for more
2809 // bytes, not declare the buffer unusable.
2810 match reader.read_message() {
2811 Err(ChannelError::NothingRead) => {}
2812 other => panic!(
2813 "expected the parser to compact and wait for the rest of the \
2814 frame, got {other:?}"
2815 ),
2816 }
2817 assert!(
2818 reader.front_buf.available_space() >= tail.len(),
2819 "compaction must hand back the space the consumed frame left behind: \
2820 {} bytes of room for a {}-byte tail",
2821 reader.front_buf.available_space(),
2822 tail.len()
2823 );
2824 assert!(
2825 !reader.readiness.is_error(),
2826 "a frame within the ceiling must not mark the channel for closing"
2827 );
2828
2829 // And the session is alive: it reads again, and the frame completes.
2830 reader.handle_events(Ready::READABLE);
2831 assert_eq!(
2832 reader
2833 .readable()
2834 .expect("the reader must still accept the frame's tail"),
2835 tail.len()
2836 );
2837 assert_eq!(
2838 reader
2839 .read_message()
2840 .expect("the second frame must decode once its tail is buffered"),
2841 ProtobufMessage { inner: 42 },
2842 "a conforming peer's frame must complete rather than wedge the session"
2843 );
2844 }
2845
2846 /// The other side of sozu-proxy/sozu#1436's boundary: compaction must not
2847 /// rescue a frame that is genuinely unsatisfiable.
2848 ///
2849 /// A declared length above `max_buffer_size` does not fit a buffer bounded
2850 /// at `max_buffer_size` however it is compacted, so it must keep taking the
2851 /// sozu-proxy/sozu#1428 path -- `MessageTooLarge`, logged, channel marked
2852 /// errored -- from this layout exactly as from an empty buffer. The ceiling
2853 /// check runs before the zero-space arm precisely so that the two cases
2854 /// cannot be confused.
2855 #[test]
2856 fn a_frame_above_the_ceiling_still_closes_from_the_same_layout() {
2857 let capacity = 64usize;
2858 // Past the ceiling, in the very layout the compaction path rescues at
2859 // `capacity`. Even, because `wedged_reader` pads with two-byte protobuf
2860 // pairs and an odd length would leave a dangling tag; `capacity + 1` is
2861 // rejected by the identical guard, one byte earlier.
2862 let (mut reader, _writer, _tail) = wedged_reader(capacity, capacity + 2);
2863
2864 let pending = reader.front_buf.available_data();
2865 match reader.read_message() {
2866 Err(ChannelError::MessageTooLarge {
2867 message_len, max, ..
2868 }) => {
2869 assert_eq!(message_len, capacity + 2);
2870 assert_eq!(max, capacity);
2871 }
2872 other => panic!(
2873 "expected MessageTooLarge: compaction must not rescue a frame the \
2874 ceiling can never admit, got {other:?}"
2875 ),
2876 }
2877 assert_eq!(
2878 reader.front_buf.available_data(),
2879 pending,
2880 "an unsatisfiable declared length must still not be consumed"
2881 );
2882 assert!(
2883 reader.readiness.is_error(),
2884 "an unsatisfiable declared length must still mark the channel for \
2885 closing, whatever the buffer layout it was parsed from"
2886 );
2887 }
2888
2889 /// A peer that dies with bytes still queued towards it makes the next
2890 /// `read(2)` fail with `ECONNRESET` rather than return EOF. That failure
2891 /// must leave the channel marked for closing exactly as EOF does: the
2892 /// supervisor's `WorkerSession::ready` closes a session on HUP or ERROR
2893 /// only, and an edge-triggered poller reports the hangup once, so a read
2894 /// error that wiped the readiness left a dead worker's session open for
2895 /// good (sozu-proxy/sozu#1560).
2896 #[test]
2897 fn a_read_error_keeps_the_channel_marked_for_closing() {
2898 for initial in [
2899 Ready::READABLE | Ready::WRITABLE | Ready::HUP | Ready::ERROR,
2900 Ready::READABLE | Ready::WRITABLE,
2901 ] {
2902 let (mut local, peer) =
2903 Channel::<ProtobufMessage, ProtobufMessage>::generate_nonblocking(1000, 10000)
2904 .expect("could not generate nonblocking channels");
2905 // Leave a byte unread in the peer's receive queue, then close the
2906 // peer: the kernel resets the connection instead of sending EOF.
2907 assert_eq!(local.sock.write(b"x").expect("write to the live peer"), 1);
2908 drop(peer);
2909
2910 local.readiness = initial;
2911 match local.readable() {
2912 Err(ChannelError::Read(error)) => {
2913 assert_eq!(error.kind(), ErrorKind::ConnectionReset)
2914 }
2915 other => panic!("expected a connection reset, got {other:?}"),
2916 }
2917 assert!(
2918 local.readiness.is_hup() && local.readiness.is_error(),
2919 "a read error must leave HUP and ERROR set (from {initial:?}), got {:?}",
2920 local.readiness
2921 );
2922 assert!(!local.readiness.is_readable() && !local.readiness.is_writable());
2923 assert_eq!(local.interest, Ready::EMPTY);
2924 }
2925 }
2926
2927 /// Same contract on the write side: `EPIPE` towards a closed peer must
2928 /// mark the channel for closing, as `Ok(0)` already does, and is reported
2929 /// as `ChannelError::Write`, not `Read` (sozu-proxy/sozu#1563).
2930 #[test]
2931 fn a_write_error_keeps_the_channel_marked_for_closing() {
2932 for initial in [
2933 Ready::READABLE | Ready::WRITABLE | Ready::HUP | Ready::ERROR,
2934 Ready::READABLE | Ready::WRITABLE,
2935 ] {
2936 let (mut local, peer) =
2937 Channel::<ProtobufMessage, ProtobufMessage>::generate_nonblocking(1000, 10000)
2938 .expect("could not generate nonblocking channels");
2939 drop(peer);
2940
2941 local
2942 .write_delimited_message(&ProtobufMessage { inner: 7 })
2943 .expect("queue a message in the back buffer");
2944 local.interest.insert(Ready::WRITABLE);
2945 local.readiness = initial;
2946 match local.writable() {
2947 Err(ChannelError::Write(error)) => {
2948 assert_eq!(error.kind(), ErrorKind::BrokenPipe)
2949 }
2950 other => panic!("expected a broken pipe reported as a write error, got {other:?}"),
2951 }
2952 assert!(
2953 local.readiness.is_hup() && local.readiness.is_error(),
2954 "a write error must leave HUP and ERROR set (from {initial:?}), got {:?}",
2955 local.readiness
2956 );
2957 assert!(!local.readiness.is_readable() && !local.readiness.is_writable());
2958 assert_eq!(local.interest, Ready::EMPTY);
2959 }
2960 }
2961
2962 /// A blocking write towards a closed peer must fail with the socket error.
2963 /// It used to return `Ok(())` for any `write(2)` error, leaving the frame
2964 /// in `back_buf` while the caller believed it delivered
2965 /// (sozu-proxy/sozu#1563).
2966 #[test]
2967 fn a_blocking_write_to_a_closed_peer_is_an_error() {
2968 let (mut blocking, peer) = test_channels();
2969 assert!(blocking.is_blocking());
2970 drop(peer);
2971
2972 match blocking.write_message(&ProtobufMessage { inner: 9 }) {
2973 Err(ChannelError::Write(error)) => assert_eq!(error.kind(), ErrorKind::BrokenPipe),
2974 other => panic!("expected a broken pipe reported as a write error, got {other:?}"),
2975 }
2976 assert!(
2977 blocking.back_buf.available_data() > 0,
2978 "the unsent frame must stay in the back buffer"
2979 );
2980 }
2981}