# Container Proxy Endpoints

> A request on the container proxy connection opens a scope for exactly one of seven endpoints, designated by the tag byte that begins its payload; a request the server cannot read is answered by a bare response finish.

Canonical: https://protocol.diverge.network/2.3.0/container-proxy-endpoints/
Specification revision: 2.3.0

A [request](/2.3.0/frames/scopes/) the client sends on the connection of
the [Container Proxy](/2.3.0/container-proxy/) opens a scope for exactly one
**endpoint**. The request's payload begins with a single tag byte
designating the endpoint; the remainder of the payload is the
endpoint's request content, in the form the endpoint defines.

| Tag | Endpoint |
|-----|----------|
| `0` | [agents::begin](/2.3.0/container-proxy-endpoints/agents-begin/) |
| `1` | [tools::begin](/2.3.0/container-proxy-endpoints/tools-begin/) |
| `2` | [fuse::mount](/2.3.0/container-proxy-endpoints/fuse-mount/) |
| `3` | [filesystem::tree](/2.3.0/container-proxy-endpoints/filesystem-tree/) |
| `4` | [filesystem::read](/2.3.0/container-proxy-endpoints/filesystem-read/) |
| `5` | [filesystem::write](/2.3.0/container-proxy-endpoints/filesystem-write/) |
| `6` | [filesystem::fuse_serve](/2.3.0/container-proxy-endpoints/filesystem-fuse-serve/) |

The tag values are normative as tabulated. Each endpoint has its own
section. Every section of this layer states requirements of the server
— the proxy — only: a sentence describing what the client sends states
the input the server serves, and imposes no requirement on the client
beyond those the [Endpoints](/2.3.0/endpoints/) layer states of the
provider's server.

- **The parties.** The provider's server is the **client** of the
  connection and the proxy is the **server**, in the sense of
  [Frames](/2.3.0/frames/): the client sends the frame types that layer
  defines for a client, and the server sends the frame types that
  layer defines for a server. Every page of this layer uses the two
  words so, and the crate names the client's traffic `client` and the
  server's traffic `server` under every endpoint.
- **The channels.** The channels the client opens on a scope are
  listed under the endpoint's Client Channels section; the channels
  the server opens are listed under its Server Channels section, as
  the [Endpoints](/2.3.0/endpoints/) layer lists them. Each
  channel is its own section, as [Channels](/2.3.0/frames/channels/)
  provides. The numbering of a channel is its opener's, per scope,
  and a channel number of one opener means nothing to the other.
- **An unreadable request is answered, not dropped.** A request whose
  payload the server cannot read — no tag, a tag this table does not
  list, or content that does not conform to the endpoint's form — is
  answered by a response finish that no response precedes. The server
  sends nothing else for it.
- **Growth is new tag values.** A future endpoint is a new tag value
  in this table. No tag value listed here changes its endpoint.
- **Notation.** The JSON forms and the byte layouts of this layer are
  read as [Notation](/2.3.0/endpoints/#notation) provides.

The requests are defined by `diverge-sdk-rs/src/container_proxy/outside/endpoints/client_request.rs`:

```rust
//! Every request the server can open a scope with.

use std::convert::Infallible;

use super::{agents, filesystem, fuse, tools};
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};

/// The payload of a
/// [`Request`](crate::wire::frame::client::ClientFrame::Request).
///
/// One variant per scope, in tag order, plus
/// [`Invalid`](Self::Invalid). This is the only place the tag values
/// meet: each request states its own in its own module, and nothing
/// else can see all of them at once — so this is what the table in
/// [`container_proxy_endpoints`](super) is a table OF.
///
/// # Decoding cannot fail
///
/// There is no error type. A payload this does not understand — an
/// unknown tag, a body that will not parse, no bytes at all — becomes
/// [`Invalid`](Self::Invalid) rather than a failure, so the proxy
/// always has a request to answer and a malformed one is a scope like
/// any other.
///
/// The alternative was refusing to decode, which would leave the proxy
/// holding bytes it could not name and no scope to complain in. The
/// only thing left would be closing the connection, which is the
/// container's whole life over one bad frame.
#[derive(Debug, Clone, PartialEq)]
pub enum ClientRequest<'a> {
    /// Tag `0`. Begin the server's work on an agent container.
    AgentsBegin(agents::begin::client::request::Frame),
    /// Tag `1`. Begin the server's work on a tool container.
    ToolsBegin(tools::begin::client::request::Frame),
    /// Tag `2`. Make one FUSE mount.
    FuseMount(fuse::mount::client::request::Frame),
    /// Tag `3`. Watch the container's tree.
    FilesystemTree(filesystem::tree::client::request::Frame),
    /// Tag `4`. Read one file out of the container.
    FilesystemRead(filesystem::read::client::request::Frame),
    /// Tag `5`. Write one file into the container.
    FilesystemWrite(filesystem::write::client::request::Frame),
    /// Tag `6`. Serve a subtree of the container live.
    FilesystemFuseServe(filesystem::fuse_serve::client::request::Frame),
    /// Something this version cannot read, kept as it arrived.
    ///
    /// No tag of its own. It is not a request the server sends — it is
    /// what a request BECOMES when the proxy cannot make one of the
    /// others out of it, so it carries the payload verbatim and
    /// encodes back to exactly those bytes.
    ///
    /// # It is answered, not dropped
    ///
    /// The proxy finishes the scope over it, with nothing in front:
    /// seven scopes have seven error vocabularies, and an invalid request
    /// names none of them — where a finish with nothing before it is
    /// already what the wire means by a request that could not be
    /// served. Which is the point all the same: a server that sent
    /// something wrong learns so, in the scope it opened, and its
    /// other work carries on.
    ///
    /// # Why it carries no reason
    ///
    /// The proxy has the bytes and can ask the specific request to
    /// decode them, which answers precisely: an unknown tag, a body
    /// that would not parse, or nothing at all. Storing a reason here
    /// would mean this type choosing which of seven error vocabularies
    /// to speak, and choosing wrong for six of them.
    Invalid(&'a [u8]),
}

impl Encode for ClientRequest<'_> {
    /// The ordinary JSON failure: every request is JSON after its
    /// tag.
    type Error = serde_json::Error;

    fn encode(
        &self,
        out: &mut Writer<'_>,
    ) -> Result<(), serde_json::Error> {
        match self {
            ClientRequest::AgentsBegin(frame) => frame.encode(out),
            ClientRequest::ToolsBegin(frame) => frame.encode(out),
            ClientRequest::FuseMount(frame) => frame.encode(out),
            ClientRequest::FilesystemTree(frame) => frame.encode(out),
            ClientRequest::FilesystemRead(frame) => frame.encode(out),
            ClientRequest::FilesystemWrite(frame) => frame.encode(out),
            ClientRequest::FilesystemFuseServe(frame) => frame.encode(out),
            ClientRequest::Invalid(bytes) => {
                out.extend_from_slice(bytes);
                Ok(())
            }
        }
    }
}

/// Dispatch on the tag, and fall back to
/// [`Invalid`](ClientRequest::Invalid) rather than refusing.
///
/// Each request is handed the WHOLE payload, tag included, because
/// each checks its own tag — this reads the byte to choose a decoder
/// and does not strip it.
impl<'a> Decode<'a> for ClientRequest<'a> {
    /// [`Infallible`]: a payload that cannot be read is a request all
    /// the same.
    type Error = Infallible;

    fn decode(bytes: &'a [u8]) -> Result<Self, Infallible> {
        let Some(tag) = bytes.first() else {
            return Ok(ClientRequest::Invalid(bytes));
        };
        let request = match *tag {
            0 => agents::begin::client::request::Frame::decode(bytes)
                .map(ClientRequest::AgentsBegin)
                .ok(),
            1 => tools::begin::client::request::Frame::decode(bytes)
                .map(ClientRequest::ToolsBegin)
                .ok(),
            2 => fuse::mount::client::request::Frame::decode(bytes)
                .map(ClientRequest::FuseMount)
                .ok(),
            3 => filesystem::tree::client::request::Frame::decode(bytes)
                .map(ClientRequest::FilesystemTree)
                .ok(),
            4 => filesystem::read::client::request::Frame::decode(bytes)
                .map(ClientRequest::FilesystemRead)
                .ok(),
            5 => filesystem::write::client::request::Frame::decode(bytes)
                .map(ClientRequest::FilesystemWrite)
                .ok(),
            6 => filesystem::fuse_serve::client::request::Frame::decode(bytes)
                .map(ClientRequest::FilesystemFuseServe)
                .ok(),
            _ => None,
        };
        Ok(request.unwrap_or(ClientRequest::Invalid(bytes)))
    }
}
```
