Request
The payload is the tag byte 19 alone; the server ignores any bytes that follow the tag.
The payload of the request is exactly one byte:
[19]
- No parameter. The request carries nothing after the tag. The tag is the whole request; what is asked about is said on each channel, by digest.
- Bytes after the tag. A server ignores every byte that follows the tag. A request whose payload extends past the tag is not malformed.
- Malformed. A payload with no byte is not a request of any endpoint. Endpoints states how a server answers a request it cannot read.
The request is defined by
diverge-sdk-rs/src/provider/endpoints/images/oci/client/request/frame.rs:
//! What a client's request frame carries for an image pull.
use std::convert::Infallible;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
/// Open a pull on a provider: a scope to ask, on channels, what the
/// provider holds for the caller.
///
/// A unit struct, because the scope has no parameters: what is
/// asked about is said on each channel, by digest, and the scope
/// itself names nothing.
///
/// # A frame that is only a tag
///
/// It still has to go on the wire, because
/// [`ClientFrame::Request`](crate::wire::frame::client::ClientFrame::Request)
/// carries one type of frame and the payload's leading byte is what
/// says which request it is. So this encodes to exactly one byte and
/// decodes by reading exactly one byte — the tag and nothing after it.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Frame;
/// 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 = 19;
impl Encode for Frame {
/// [`Infallible`]: writing one known byte has no failure mode.
type Error = Infallible;
fn encode(&self, out: &mut Writer<'_>) -> Result<(), Self::Error> {
out.extend_from_slice(&[TAG]);
Ok(())
}
}
impl Decode<'_> for Frame {
/// Only the tag can be wrong, because only the tag is read.
type Error = FrameError;
fn decode(bytes: &[u8]) -> Result<Self, Self::Error> {
match bytes.first() {
Some(&TAG) => Ok(Frame),
Some(&tag) => Err(FrameError::UnexpectedTag(tag)),
None => Err(FrameError::Empty),
}
}
}
/// An image pull request that could not be read.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
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),
}
impl std::fmt::Display for FrameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FrameError::Empty => f.write_str("image pull request frame is empty"),
FrameError::UnexpectedTag(tag) => write!(f, "expected image pull request tag {TAG}, found {tag}"),
}
}
}
impl std::error::Error for FrameError {}