Request

The payload is the tag byte 23 followed by the acceptor’s identity and the connector’s mode as JSON.

The payload of the request is the tag byte followed by the request as JSON, read as Notation provides:

[23][connect JSON …]
  • daemon. The identity an acceptor is known by on this server: the string the server answered that acceptor’s accept. The server compares it and does not read it.
  • mode. How the connector authenticates to the acceptor: one object whose one member names the mode, unbrokered, whose value is an object with the member credential, a string. The server relays the mode verbatim and does not read it.
  • Malformed. A payload with no byte, a payload whose first byte is not 23, 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/daemons/connect/client/request/frame.rs:

//! What a client's request frame carries for a connect.

use serde::{Deserialize, Serialize};

use crate::shared::daemons::Mode;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};

/// Ask the provider to connect this daemon to another.
///
/// The acceptor by the identity the provider authorized its
/// connection under — the one its accept was answered with — and the
/// mode the connector authenticates to it in, which the provider
/// relays to the acceptor unread.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Frame {
    /// The daemon to reach, by its identity at this provider.
    pub daemon: String,
    /// How the connector authenticates to it: see [`Mode`].
    pub mode: Mode,
}

/// 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 = 23;

/// JSON, like every other structured 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 daemons connect 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.
    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("daemons connect request frame is empty"),
            FrameError::UnexpectedTag(tag) => write!(f, "expected daemons connect request tag {TAG}, found {tag}"),
            FrameError::Body(error) => write!(f, "daemons connect 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,
        }
    }
}

and by diverge-sdk-rs/src/shared/daemons/mode.rs:

//! How a connecting daemon authenticates to the daemon it connects
//! to.

use serde::{Deserialize, Serialize};

/// The connector's way in: what it would present as the first frame
/// of a daemon connection, carried here instead, since a connection
/// relayed through a provider has no `Auth` frame of its own. One
/// object whose one member is the mode, by name:
/// `{"unbrokered":{"credential":…}}`. One mode today; a brokered mode
/// is a second member when the wire defines it, and nothing moves.
/// The provider relays it to the acceptor unread, and the acceptor
/// judges it as it judges a credential presented on a socket.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Mode {
    /// The two daemons already know each other: the connector
    /// presents a credential of an account it holds on the acceptor.
    Unbrokered {
        /// The credential, as the connector would present it in an
        /// `Auth::Unbrokered`: the key the acceptor minted for the
        /// account, byte for byte.
        credential: String,
    },
}