Request
The payload is the tag byte 2 followed by the id of a running container and the path of a directory of it as JSON.
The payload of the request is the tag byte followed by the request as JSON, read as Notation provides:
[2][request JSON …]
id. The id the response of a run carried. The server resolves it among the containers it is running; it means nothing outside the server that minted it.path. An array of strings: the components of the directory to serve, from the root of the container. An empty array names the root of the container. No component is empty,.or.., or contains/or NUL; a path with such a component is answered with an error, not as malformed.- Malformed. A payload with no byte, a payload whose first byte
is not
2, and a payload whose bytes after the tag do not parse as the request are malformed. Endpoints states how a server answers a request it cannot read.
The request is defined by diverge-sdk-rs/src/provider/endpoints/containers/fuse_serve/client/request/frame.rs:
//! What a client's request frame carries for a container FUSE serve.
use serde::{Deserialize, Serialize};
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
/// Serve a subtree of a running container: answer the FUSE asks the
/// caller opens on the scope from the container's own filesystem.
///
/// # An id, and the same access model as a connect
///
/// The id is the one the container's run answered with, a capability
/// the provider minted and nobody can guess. A caller names only a
/// container it is running: an id under which nothing runs, and an
/// id whose runner the caller is not, are refused alike, so a caller
/// cannot learn from the answer whether an id it holds is somebody
/// else's.
///
/// # A path, as components
///
/// The subtree is named as path components from the container's
/// root — the same meaning of "path" as everywhere else in this
/// crate, and the frame of reference a
/// [`filetree`](crate::shared::filetree) stream uses. Empty is the
/// root itself. Each component is a name: not empty, not `.` or
/// `..`, holding no `/` and no NUL. A path that is not a directory
/// of the container, or that lies under `/proc`, `/sys` or `/dev`,
/// is the FUSE serve refused.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct Frame {
/// Which container, by the id its run answered with.
pub id: String,
/// The subtree, as components from the container's root; empty
/// for the root.
pub path: Vec<String>,
}
/// This frame's tag among the scope-opening requests.
///
/// One byte at the front of the payload, which is what tells a reader
/// which request it holds. The frame layer does not discriminate them
/// — [`ClientFrame::Request`](crate::wire::frame::client::ClientFrame::Request)
/// is one type carrying bytes — so the distinction has to be in the
/// bytes, and each request owns the value that names it.
///
/// See the table in [`endpoints`](crate::provider::endpoints) for the whole
/// allocation. The values are chosen across modules that do not know
/// about each other, so the table is the only place they can be seen
/// at once.
const TAG: u8 = 2;
/// JSON, like every other container request.
impl Encode for Frame {
/// The ordinary JSON failure. The tag cannot fail.
type Error = serde_json::Error;
fn encode(&self, out: &mut Writer<'_>) -> Result<(), Self::Error> {
out.extend_from_slice(&[TAG]);
serde_json::to_writer(out, self)
}
}
impl Decode<'_> for Frame {
/// Three ways to fail, and only one of them is JSON.
type Error = FrameError;
fn decode(bytes: &[u8]) -> Result<Self, Self::Error> {
let (tag, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
if *tag != TAG {
return Err(FrameError::UnexpectedTag(*tag));
}
serde_json::from_slice(rest).map_err(FrameError::Body)
}
}
/// A container FUSE serve request that could not be read.
#[derive(Debug)]
pub enum FrameError {
/// No bytes at all, so not even a tag.
Empty,
/// A tag naming some other request.
///
/// A reader that dispatched on the tag will not see this. One that
/// assumed which request it held, and was wrong, will — which is
/// the point of checking a tag rather than skipping it.
UnexpectedTag(u8),
/// The request did not parse.
Body(serde_json::Error),
}
impl std::fmt::Display for FrameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FrameError::Empty => {
f.write_str("container FUSE serve request frame is empty")
}
FrameError::UnexpectedTag(tag) => {
write!(f, "expected container FUSE serve request tag {TAG}, found {tag}")
}
FrameError::Body(error) => {
write!(f, "container FUSE serve request did not parse: {error}")
}
}
}
}
impl std::error::Error for FrameError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FrameError::Body(error) => Some(error),
FrameError::Empty | FrameError::UnexpectedTag(_) => None,
}
}
}