Response

The server answers with exactly one channel response, the byte 0, or the byte 1 followed by the repository path it holds the image under, and the channel response finish; a finish with nothing before it states that it does not hold the image for the client.

[1][name: utf8 …]      once: the server holds the image for the client, under that repository path
[0]                    once: it does not
  • The sequence. Exactly one channel response, followed by the channel response finish. A channel response finish that no channel response precedes states that the server does not hold the image for the client.
  • The frame. The byte 0 and nothing after it: the server does not hold the image for the client. The byte 1 followed by one or more bytes of UTF-8: the server holds the image the digest names for the client, and the bytes are the repository path it holds it under, with no host and no tag. A frame whose first byte is neither, a frame of the byte 0 followed by anything, a frame of the byte 1 followed by nothing, and a frame whose bytes after 1 are not UTF-8 are malformed.
  • What follows. A client that was answered 1 may ask for the manifest and blobs of the image on oci-manifest and oci-blob channels.

The channel response is defined by diverge-sdk-rs/src/shared/containers/oci/has/response/frame.rs:

//! The byte `0`, or the byte `1` and a name.

use std::convert::Infallible;

use super::FrameError;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};

/// Whether the caller holds the image, and under what repository
/// path: the byte `0` and nothing after it when it does not; the byte
/// `1` followed by the path as UTF-8 when it does.
///
/// The path is the one the caller holds the image under, which the
/// provider's registry serves it from: a runtime pulls
/// `<registry>/<repository>/<name>@<digest>`, and `name` is this. The
/// caller's store is keyed by digest, so the name is the caller's to
/// answer and not the provider's to ask. A provider refuses a path
/// that is not a repository path, since it lands in a reference by
/// concatenation. A name that is empty is not a name: it encodes as
/// not held.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Frame {
    /// The repository path the image is held under, or `None` for not
    /// held.
    pub name: Option<String>,
}

impl Encode for Frame {
    /// A byte and bytes: nothing to fail.
    type Error = Infallible;

    fn encode(&self, out: &mut Writer<'_>) -> Result<(), Infallible> {
        match self.name.as_deref() {
            Some(name) if !name.is_empty() => {
                out.extend_from_slice(&[1]);
                out.extend_from_slice(name.as_bytes());
            }
            _ => out.extend_from_slice(&[0]),
        }
        Ok(())
    }
}

impl Decode<'_> for Frame {
    /// No byte, a first byte that is neither, a `0` with bytes after
    /// it, or a `1` with none after it or with bytes that are not
    /// UTF-8.
    type Error = FrameError;

    fn decode(bytes: &[u8]) -> Result<Self, FrameError> {
        match bytes {
            [] => Err(FrameError::Empty),
            [0] => Ok(Frame { name: None }),
            [0, ..] => Err(FrameError::Trailing),
            [1] => Err(FrameError::Name),
            [1, name @ ..] => std::str::from_utf8(name)
                .map(|name| Frame {
                    name: Some(name.to_string()),
                })
                .map_err(|_| FrameError::Name),
            [byte, ..] => Err(FrameError::Byte(*byte)),
        }
    }
}