Vault
Kinds 5 to 9: the proxy reads, writes, deletes, locks and unlocks keys of a store the caller holds; the server answers each ask with exactly one message, and the proxy repeats no ask.
Five asks carry the operations of the container on a key-value store the caller holds. The server answers each with exactly one message.
| Kind | Ask | Payload after the kind | Answered on | Answer |
|---|---|---|---|---|
5 | get | [key …] | /vault/get/{channel} | the value, its absence, or an error |
6 | set | [key_len: u16, big-endian][key …][value …] | /vault/set/{channel} | ok or an error |
7 | delete | [key …] | /vault/delete/{channel} | ok or an error |
8 | lock | [ttl: u32, big-endian][key …] | /vault/lock/{channel} | ok, sent when the lock is held, or an error |
9 | unlock | [key …] | /vault/unlock/{channel} | ok or an error |
- Keys and values. A key is UTF-8. Where nothing follows the
key, it runs to the end of the payload; in
set, a length prefix precedes it because a value follows. A value is bytes, carried verbatim. - The namespace. The payload names a key and no store. The server determines which store the key belongs to, from what it knows of the container it serves. The proxy has no means of naming another container’s store.
- One message. The server sends exactly one message, the frame its page defines, and closes the connection. A path that the proxy or the server closes cleanly with no message before the close states that the ask was not served.
- No repetition. The proxy does not ask an operation again. An
operation whose path ended abruptly, and an operation whose
/requestsconnection ended before the server opened its path, are reported inside the container as failed. A lock the server granted survives such an ending until its TTL expires.
Four of the five answers are defined by
diverge-provider-sdk/src/shared/containers/vault/response/frame.rs:
//! The answer shared by the four operations that either happen or do
//! not.
use std::convert::Infallible;
use super::super::ResponseError;
use crate::encode::{Encode, Writer};
/// The one message that answers a set, a delete, a lock or an unlock
/// — each of those operations re-exports it as its own
/// `response::Frame`.
///
/// ```text
/// [kind: u8][message…]
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Frame<'a> {
/// Kind `0`. It happened: the key is written or removed, the lock
/// is held or released.
Ok,
/// Kind `1`. It did not, and this says why, for a reader rather
/// than a program: nothing here is enumerated, because what a
/// vault can refuse is the caller's policy and not this
/// specification's.
Error(&'a str),
}
impl Encode for Frame<'_> {
/// [`Infallible`]: a kind byte and bytes copied.
type Error = Infallible;
fn encode(&self, out: &mut Writer<'_>) -> Result<(), Infallible> {
match self {
Frame::Ok => out.extend_from_slice(&[0]),
Frame::Error(message) => {
out.extend_from_slice(&[1]);
out.extend_from_slice(message.as_bytes());
}
}
Ok(())
}
}
impl<'a> Frame<'a> {
/// Decode the one message. The message borrows from `bytes`.
pub fn decode(bytes: &'a [u8]) -> Result<Self, ResponseError> {
let (kind, rest) = bytes.split_first().ok_or(ResponseError::Empty)?;
match *kind {
0 => Ok(Frame::Ok),
1 => std::str::from_utf8(rest)
.map(Frame::Error)
.map_err(|_| ResponseError::MessageUtf8),
other => Err(ResponseError::UnknownKind(other)),
}
}
}
The ways in which an ask or an answer fails to decode are defined by
diverge-provider-sdk/src/shared/containers/vault/error.rs:
//! Why a vault ask or answer could not be read or written.
use std::error;
use std::fmt;
/// A vault ask that could not be read.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RequestError {
/// Fewer bytes than the ask's fixed part promises — a length
/// prefix, a TTL, or the key a prefix said was there.
Truncated,
/// A key that is not UTF-8.
KeyUtf8,
}
impl fmt::Display for RequestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RequestError::Truncated => {
f.write_str("vault request is shorter than it promises")
}
RequestError::KeyUtf8 => f.write_str("vault key is not utf-8"),
}
}
}
impl error::Error for RequestError {}
/// A vault ask that could not be written.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RequestEncodeError {
/// A key of more bytes than a two-byte length prefix can say,
/// carrying how many there were. Only [`set`](super::set)
/// prefixes its key; the others take any length.
KeyLength(usize),
}
impl fmt::Display for RequestEncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RequestEncodeError::KeyLength(len) => {
write!(f, "vault key is {len} bytes, more than 65535")
}
}
}
}
impl error::Error for RequestEncodeError {}
/// A vault answer that could not be read.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResponseError {
/// No bytes at all, so not even a kind.
Empty,
/// A kind this answer does not define.
UnknownKind(u8),
/// An error message that is not UTF-8.
MessageUtf8,
}
impl fmt::Display for ResponseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ResponseError::Empty => f.write_str("vault response is empty"),
ResponseError::UnknownKind(kind) => {
write!(f, "unknown vault response kind {kind}")
}
ResponseError::MessageUtf8 => {
f.write_str("vault error message is not utf-8")
}
}
}
}
impl error::Error for ResponseError {}