Response
The server sends the bytes of the file as responses of at most CHUNK_SIZE bytes each and the finish; a file of zero bytes is one empty response; an error is the last response.
[0][bytes …] a piece of the file
[1][error JSON …] the file was not read, or not all of it
- The sequence. Zero or more responses whose first byte is
0, each a piece of the file in order, followed by the response finish; or the responses sent so far, exactly one response whose first byte is1, and the response finish. A file of zero bytes is exactly one response whose first byte is0and nothing after it. - The piece. After the byte
0, the bytes of the piece, verbatim, running to the end of the payload. Each piece is at mostCHUNK_SIZEbytes,2 * 1024 * 1024, the constantdiverge-sdk-rs/src/lib.rsdefines; the pieces concatenated are the file, and a receiver measures none. - A finish with nothing. A response finish that no response precedes states that the server could not read the request.
The response is defined by diverge-sdk-rs/src/container_proxy/outside/endpoints/filesystem/read/server/response/frame.rs:
//! What a server's response frame carries for a read.
use std::fmt;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
use crate::shared::containers::read;
use crate::shared::error::Error;
/// A piece of the file, or the news that there will not be one.
///
/// A payload leads with one byte saying which — `0` for
/// [`Body`](Self::Body), `1` for [`Error`](Self::Error) — and the rest
/// is that variant's own bytes.
///
/// # How it ends
///
/// | the scope ends with | means |
/// |---------------------|-------|
/// | bodies, then a finish | the bytes are the file |
/// | an [`Error`](Self::Error), then a finish | the file was not read, or not all of it |
///
/// Only a finish ends it. A zero-byte file is one empty body, then
/// the finish, so that a finish with nothing before it keeps its
/// standing meaning: could not serve, with nothing to say.
///
/// # The failure is this scope's, the bytes are not
///
/// [`Body`](Self::Body) carries
/// [`read::response::Frame`](crate::shared::containers::read::response::Frame),
/// which means the same thing wherever the exchange happens. The
/// [`Error`](Self::Error) beside it is the exchange's own.
#[derive(Debug, Clone, PartialEq)]
pub enum Frame<'a> {
/// A piece of the file. Tag `0`.
///
/// Each at most [`CHUNK_SIZE`](crate::CHUNK_SIZE), in order, and
/// every one appends to the file.
Body(read::response::Frame<'a>),
/// A failure. Tag `1`.
///
/// See [`shared::error::Error`](crate::shared::error::Error) for
/// why it says so little.
Error(Error),
}
/// Tag for [`Frame::Body`].
const BODY: u8 = 0;
/// Tag for [`Frame::Error`].
const ERROR: u8 = 1;
impl Encode for Frame<'_> {
/// The ordinary JSON failure, from the half that has one. The
/// other cannot fail.
type Error = serde_json::Error;
// Spelled out rather than `Self::Error`: this enum has a variant
// called `Error`, so the associated type is ambiguous by that name.
fn encode(
&self,
out: &mut Writer<'_>,
) -> Result<(), serde_json::Error> {
match self {
Frame::Body(inner) => {
out.extend_from_slice(&[BODY]);
// Its error is `Infallible`, and an empty match on one
// is how you say so: there is no value to handle.
inner.encode(out).map_err(|error| match error {})
}
Frame::Error(error) => {
out.extend_from_slice(&[ERROR]);
error.encode(out)
}
}
}
}
impl<'a> Decode<'a> for Frame<'a> {
/// Three ways to fail, and only one of them is a parse.
type Error = FrameError;
// Spelled out for the same reason as `encode` above.
fn decode(bytes: &'a [u8]) -> Result<Self, FrameError> {
let (tag, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
match *tag {
BODY => read::response::Frame::decode(rest)
.map(Frame::Body)
.map_err(|error| match error {}),
ERROR => {
Error::decode(rest).map(Frame::Error).map_err(FrameError::Error)
}
tag => Err(FrameError::UnknownTag(tag)),
}
}
}
/// A read response frame that could not be read.
#[derive(Debug)]
pub enum FrameError {
/// No bytes at all, so not even a tag.
///
/// Distinct from a zero-length body, which is a tag followed by
/// nothing and is a file.
Empty,
/// A tag that is neither of this frame's two.
UnknownTag(u8),
/// The error did not parse.
Error(serde_json::Error),
}
impl fmt::Display for FrameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FrameError::Empty => f.write_str("read response frame is empty"),
FrameError::UnknownTag(tag) => {
write!(f, "unknown read response frame tag {tag}")
}
FrameError::Error(error) => {
write!(f, "read error did not parse: {error}")
}
}
}
}
impl std::error::Error for FrameError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FrameError::Error(error) => Some(error),
FrameError::Empty | FrameError::UnknownTag(_) => None,
}
}
}
and by diverge-sdk-rs/src/shared/containers/read/response/frame.rs:
//! What a response frame carries on a read channel.
use std::convert::Infallible;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
/// A piece of the file.
///
/// Bytes and nothing else — no tag, because there is nothing to
/// discriminate. A read channel carries one kind of traffic from the
/// first byte to the last, and the requester knew what it was when it
/// asked.
///
/// # How it ends
///
/// With a finish, and only with a finish. There is no `Complete` and
/// there is no failure: a read that finished cleanly is one whose
/// channel finished —
/// [`ChannelResponseFinish`](crate::wire::frame::server::ServerFrame::ChannelResponseFinish)
/// says so at the frame layer, and saying it again in the payload
/// would be two signals for one fact.
///
/// # A silent tear is possible here
///
/// A file being read can be written underneath the reader, and the
/// bytes already sent are then a mix: whatever was there before the
/// write, and whatever is there after.
///
/// A provider can DETECT it — `fstat` on its own descriptor before the
/// first byte and after the last, comparing size and mtime — and
/// cannot prevent it. Linux advisory locks bind only processes that
/// opt in, and mandatory locking was removed in 5.15. But there is no
/// frame here that means "here are the bytes, and they moved while I
/// sent them", so a provider that detects one has nowhere to say so.
///
/// # No length, anywhere
///
/// Not in a head, not in the request, not implied by anything. A file
/// being written to can change size in both directions after a sender
/// has looked at it, so any length stated up front is a promise made
/// about a number that has already moved. It is the commitment that
/// makes `tar` corrupt a whole archive when one entry shifts, and this
/// declines to make it.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Frame<'a>(
/// The bytes, borrowed from the frame they arrived in.
///
/// Sent as they are read. A sender holds no more than one buffer's
/// worth, so a file larger than memory crosses without either end
/// ever holding it whole.
pub &'a [u8],
);
/// Straight through, and identical to
/// [`write_bytes`](crate::shared::containers::write_bytes::response::Frame)
/// — which is what makes piping a read into a write cost nothing. One
/// frame out is one frame in, with no shape to translate between
/// them; whatever tag an endpoint puts in front is a byte, not a
/// copy.
impl Encode for Frame<'_> {
/// [`Infallible`]: copying a slice into a buffer has no failure
/// mode.
type Error = Infallible;
fn encode(&self, out: &mut Writer<'_>) -> Result<(), Self::Error> {
out.extend_from_slice(self.0);
Ok(())
}
}
impl<'a> Decode<'a> for Frame<'a> {
/// [`Infallible`]: there is nothing to get wrong about a slice
/// that is already the answer.
type Error = Infallible;
fn decode(bytes: &'a [u8]) -> Result<Self, Self::Error> {
Ok(Frame(bytes))
}
}