Response
The sequence of channel responses the client answers with, what ends it, what a finish with nothing before it means, and what the server does with the answer.
[path_len: u32, big-endian][path JSON …][bytes …] zero or more
- The sequence. Zero or more channel responses, each one file or
one piece of one file, in any order of files. Each carries a
u32, big-endian, the byte length of the path; the path, a JSON array of strings, the components of the file relative to the directory; and the bytes, verbatim, running to the end of the payload. Consecutive channel responses with one path are one file, their bytes appended in order; a channel response with a new path begins a new file. A file of zero bytes is one channel response with no bytes. The channel response finish follows the last. A channel response finish that no channel response precedes states that the client does not hold the identity, and the run fails. - A frame that does not decode. The server skips it, and the verification against the identity fails.
The channel response is defined by diverge-provider-sdk/src/shared/containers/fetch_directory/response/frame.rs:
//! One file — or one chunk of one — of the directory being fetched.
use std::error;
use std::fmt;
use crate::decode::Decode;
use crate::encode::{Encode, Writer};
/// One file's placement and bytes.
///
/// A fetched directory arrives as one of these per file, in no
/// promised order, and the channel's finish is what says the
/// directory is whole. Zero frames before the finish is the client
/// saying it does not hold the identity at all.
///
/// The body is borrowed from the frame it arrived in: the receiver
/// is about to write these bytes somewhere, and copying them first
/// would double every chunk's memory for nothing. The path is owned —
/// it is parsed out of its JSON, and it is small.
///
/// # Adjacency is the chunking
///
/// A file larger than [`CHUNK_SIZE`](crate::CHUNK_SIZE) is
/// sent as
/// consecutive frames with an EQUAL path, in order, and the receiver
/// concatenates — chunk-naive by design: same path, next frame,
/// append; a new path begins a new file. No index, no offset, no
/// "last one" marker. (A sender only splits what exceeds the chunk
/// size, so a zero-byte continuation frame cannot occur; a
/// legitimately empty file is one frame with no bytes.)
///
/// # The path is parts, and the last part is the filename
///
/// Relative to the directory being fetched — the request named the
/// directory, so the frames do not repeat it. Parts rather than a
/// joined string, because a separator convention is a thing two ends
/// can disagree about and an array is not.
///
/// # Three parts on the wire
///
/// `[u32 BE: byte length of the path JSON][path, a JSON array of
/// strings][everything after: the bytes, verbatim]`. The body is raw
/// rather than JSON because a file's bytes are not anybody's
/// document — encoding them would mean base64 and a third more wire
/// for nothing.
///
/// # A short set is detectable, and that is enough
///
/// A client that dies mid-directory leaves the server with some
/// frames and a finish it cannot tell from completion. No frame says
/// "last one" — the identity does: the server hashes and measures
/// what arrived, and a partial set fails both.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Frame<'a> {
/// The file's path relative to the fetched directory,
/// one component per element, the final element the filename.
pub path: Vec<String>,
/// The bytes — this frame's chunk of them — borrowed from the
/// frame they arrived in.
pub body: &'a [u8],
}
/// The bytes the path length occupies.
const PATH_LEN: usize = 4;
impl Encode for Frame<'_> {
type Error = EncodeError;
fn encode(&self, out: &mut Writer<'_>) -> Result<(), EncodeError> {
let path =
serde_json::to_vec(&self.path).map_err(EncodeError::Path)?;
let len = u32::try_from(path.len())
.map_err(|_| EncodeError::PathLength(path.len()))?;
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(&path);
out.extend_from_slice(self.body);
Ok(())
}
}
impl<'a> Decode<'a> for Frame<'a> {
type Error = FrameError;
fn decode(bytes: &'a [u8]) -> Result<Self, FrameError> {
if bytes.len() < PATH_LEN {
return Err(FrameError::Short(bytes.len()));
}
let (len, rest) = bytes.split_at(PATH_LEN);
let len = u32::from_be_bytes(
<[u8; PATH_LEN]>::try_from(len).expect("split_at gave 4 bytes"),
) as usize;
if rest.len() < len {
return Err(FrameError::Truncated {
need: len,
have: rest.len(),
});
}
let (path, body) = rest.split_at(len);
Ok(Frame {
path: serde_json::from_slice(path).map_err(FrameError::Path)?,
body,
})
}
}
/// A file frame that could not be written.
#[derive(Debug)]
pub enum EncodeError {
/// The path would not serialize.
Path(serde_json::Error),
/// The path's JSON is longer than the four-byte length can say.
/// Carried rather than truncated, because a length that lies is a
/// frame that reads somebody else's bytes as its own.
PathLength(usize),
}
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EncodeError::Path(error) => {
write!(f, "fetch file path did not serialize: {error}")
}
EncodeError::PathLength(len) => {
write!(
f,
"fetch file path is {len} bytes of JSON, more than a \
u32 can say"
)
}
}
}
}
impl error::Error for EncodeError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
EncodeError::Path(error) => Some(error),
EncodeError::PathLength(_) => None,
}
}
}
/// A file frame that could not be read.
#[derive(Debug)]
pub enum FrameError {
/// Fewer bytes than the path length itself, carrying however many
/// there were.
Short(usize),
/// A path length pointing past the end of the frame.
Truncated {
/// What the length claimed.
need: usize,
/// What was actually there.
have: usize,
},
/// The path did not parse as a JSON array of strings.
Path(serde_json::Error),
}
impl fmt::Display for FrameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FrameError::Short(len) => {
write!(
f,
"fetch file frame is {len} bytes, not even a path \
length"
)
}
FrameError::Truncated { need, have } => {
write!(
f,
"fetch file path claims {need} bytes and {have} follow"
)
}
FrameError::Path(error) => {
write!(f, "fetch file path did not parse: {error}")
}
}
}
}
impl error::Error for FrameError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
FrameError::Path(error) => Some(error),
FrameError::Short(_) | FrameError::Truncated { .. } => None,
}
}
}