# Request

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

Canonical: https://protocol.diverge.network/2.3.0/endpoints/daemons-connect/request/
Specification revision: 2.3.0

The payload of the request is the tag byte followed by the request as
JSON, read as [Notation](/2.3.0/endpoints/#notation) provides:

```text
[23][connect JSON …]
```

- **`daemon`.** The identity an acceptor is known by on this server:
  the string the server answered that acceptor's
  [accept](/2.3.0/endpoints/daemons-accept/response/). 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](/2.3.0/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`:

```rust
//! 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`:

```rust
//! 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,
    },
}
```
