ModelConnector/crates/model-connector/src/envelope.rs
Meta Vens ec0519d9ff connector-kit-lift: the provider-neutral connector kit, lifted from CodexConnector fd7cec4 (squash of eureka-body/connector-kit-lift 1cf879f..ba1f7eb)
The v2 contract, encrypted envelope and framing, client, caller admission
and replay, and the serve loop move into the model-connector crate behind
one ProviderBackend trait, relicensed AGPL-3.0-only by the sole copyright
holder (ruling connector-kit-relicense-agpl). Same schema ids and wire bytes
as v2, pinned by golden fixtures generated from fd7cec4. The Codex backend
is not ported (ruling codex-connector-park).

Serve owns the frame bounds: it measures the sealed envelope, refuses
configurations that cannot deliver their payload bound, answers over-bound
payloads and over-long ids with v2 refusals, and never records or replays
an undeliverable result.

Spec eureka-body:cut_spec:cut-connector-kit-lift.r1; reports h1-h5;
Soul s5 holds (verdict eureka-body:verdict:cut-connector-kit-lift.s5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 07:12:42 +01:00

514 lines
18 KiB
Rust

use std::io::{Read, Write};
use aes_gcm::aead::{Aead, Payload};
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
use rand_core::{OsRng, RngCore};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::contract::{
ContractError, ENVELOPE_SCHEMA_ID, TransportInvocation, TransportKey, TransportResult,
canonical_invocation_bytes, require_id,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum EnvelopeKind {
Invocation,
Result,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TransportEnvelope {
pub schema_id: String,
pub caller_runtime_id: String,
pub request_id: String,
pub message_kind: EnvelopeKind,
pub nonce: [u8; 12],
pub ciphertext: Vec<u8>,
}
/// The longest caller id, request id or model name a service admits. The
/// frame arithmetic below depends on it. A request whose id is longer is
/// refused on the wire as malformed.
pub const MAX_IDENTITY_BYTES: usize = 256;
/// The most bytes a serialized envelope holding `ciphertext_len` ciphertext
/// bytes can occupy, with ids at their longest: the frame a caller payload
/// bound (`CallerAdmission`'s `max_payload_bytes`, a bound on ciphertext
/// length) needs. The v2 wire serializes the ciphertext as a MessagePack array
/// of integers: a byte of 128 or more takes two bytes, so the worst case is
/// twice the ciphertext, plus the array header and the envelope's other
/// fields at their longest. Measured from the real encoding, not assumed.
pub fn frame_bytes_for_payload(ciphertext_len: usize) -> usize {
let shell = rmp_serde::to_vec(&TransportEnvelope {
schema_id: ENVELOPE_SCHEMA_ID.to_string(),
caller_runtime_id: "x".repeat(MAX_IDENTITY_BYTES),
request_id: "x".repeat(MAX_IDENTITY_BYTES),
message_kind: EnvelopeKind::Invocation,
nonce: [0xff; 12],
ciphertext: Vec::new(),
})
.map_or(usize::MAX, |bytes| bytes.len());
// The shell holds an empty array header of one byte.
let header = match ciphertext_len {
0..=15 => 1,
16..=65_535 => 3,
_ => 5,
};
shell
.saturating_sub(1)
.saturating_add(header)
.saturating_add(ciphertext_len.saturating_mul(2))
}
/// The largest payload bound whose worst-case frame fits `max_frame_bytes`:
/// the inverse of [`frame_bytes_for_payload`]. `None` when the frame cannot
/// carry even an empty envelope.
pub fn payload_bytes_for_frame(max_frame_bytes: usize) -> Option<usize> {
// frame_bytes_for_payload(0) is the shell plus a one-byte array header.
let fixed = frame_bytes_for_payload(0) - 1;
let room = max_frame_bytes.checked_sub(fixed)?;
// Each array header width bounds the payloads it can describe; the
// widest payload any width admits is the answer.
let by_header = |header: usize, cap: usize| room.checked_sub(header).map(|r| (r / 2).min(cap));
[
by_header(5, usize::MAX),
by_header(3, 65_535),
by_header(1, 15),
]
.into_iter()
.flatten()
.max()
}
pub fn encrypt_invocation(
invocation: &TransportInvocation,
security: &TransportKey,
) -> Result<TransportEnvelope, ServiceError> {
let plaintext = canonical_invocation_bytes(invocation)?;
encrypt_message(
&invocation.caller_runtime_id,
invocation.request_id(),
EnvelopeKind::Invocation,
&plaintext,
security,
)
}
pub fn decrypt_invocation(
envelope: &TransportEnvelope,
security: &TransportKey,
) -> Result<TransportInvocation, ServiceError> {
validate_envelope(envelope, EnvelopeKind::Invocation)?;
let plaintext = decrypt_message(envelope, security)?;
let invocation: TransportInvocation =
rmp_serde::from_slice(&plaintext).map_err(|_| ServiceError::Encoding)?;
if invocation.caller_runtime_id != envelope.caller_runtime_id
|| invocation.request_id() != envelope.request_id
{
return Err(ServiceError::OuterIdentity);
}
Ok(invocation)
}
pub fn encrypt_result(
result: &TransportResult,
security: &TransportKey,
) -> Result<TransportEnvelope, ServiceError> {
let plaintext = rmp_serde::to_vec(result).map_err(|_| ServiceError::Encoding)?;
encrypt_message(
&result.caller_runtime_id,
&result.request_id,
EnvelopeKind::Result,
&plaintext,
security,
)
}
pub fn decrypt_result(
envelope: &TransportEnvelope,
security: &TransportKey,
invocation: &TransportInvocation,
) -> Result<TransportResult, ServiceError> {
validate_envelope(envelope, EnvelopeKind::Result)?;
let plaintext = decrypt_message(envelope, security)?;
let result: TransportResult =
rmp_serde::from_slice(&plaintext).map_err(|_| ServiceError::Encoding)?;
if result.caller_runtime_id != envelope.caller_runtime_id
|| result.request_id != envelope.request_id
{
return Err(ServiceError::OuterIdentity);
}
result.validate_against(invocation)?;
Ok(result)
}
#[derive(Debug)]
pub(crate) enum TransportFrameError {
Connection(std::io::Error),
Size,
}
pub(crate) fn read_transport_frame(
reader: &mut impl Read,
max_frame_bytes: usize,
) -> Result<Vec<u8>, TransportFrameError> {
let mut length = [0_u8; 4];
reader
.read_exact(&mut length)
.map_err(TransportFrameError::Connection)?;
let length = u32::from_be_bytes(length) as usize;
if length == 0 || length > max_frame_bytes {
return Err(TransportFrameError::Size);
}
let mut payload = vec![0_u8; length];
reader
.read_exact(&mut payload)
.map_err(TransportFrameError::Connection)?;
Ok(payload)
}
pub(crate) fn write_transport_frame(
writer: &mut impl Write,
payload: &[u8],
max_frame_bytes: usize,
) -> Result<(), TransportFrameError> {
if payload.is_empty() || payload.len() > max_frame_bytes || payload.len() > u32::MAX as usize {
return Err(TransportFrameError::Size);
}
writer
.write_all(&(payload.len() as u32).to_be_bytes())
.and_then(|()| writer.write_all(payload))
.and_then(|()| writer.flush())
.map_err(TransportFrameError::Connection)
}
pub(crate) fn encrypt_message(
caller_runtime_id: &str,
request_id: &str,
message_kind: EnvelopeKind,
plaintext: &[u8],
security: &TransportKey,
) -> Result<TransportEnvelope, ServiceError> {
require_id(caller_runtime_id, "caller_runtime_id")?;
require_id(request_id, "request_id")?;
let mut nonce = [0; 12];
OsRng.fill_bytes(&mut nonce);
let cipher = Aes256Gcm::new_from_slice(&security.0).map_err(|_| ServiceError::Encryption)?;
let aad = envelope_aad(
ENVELOPE_SCHEMA_ID,
caller_runtime_id,
request_id,
message_kind,
)?;
let ciphertext = cipher
.encrypt(
Nonce::from_slice(&nonce),
Payload {
msg: plaintext,
aad: &aad,
},
)
.map_err(|_| ServiceError::Encryption)?;
Ok(TransportEnvelope {
schema_id: ENVELOPE_SCHEMA_ID.to_string(),
caller_runtime_id: caller_runtime_id.to_string(),
request_id: request_id.to_string(),
message_kind,
nonce,
ciphertext,
})
}
pub(crate) fn decrypt_message(
envelope: &TransportEnvelope,
security: &TransportKey,
) -> Result<Vec<u8>, ServiceError> {
let cipher = Aes256Gcm::new_from_slice(&security.0).map_err(|_| ServiceError::Encryption)?;
let aad = envelope_aad(
&envelope.schema_id,
&envelope.caller_runtime_id,
&envelope.request_id,
envelope.message_kind,
)?;
cipher
.decrypt(
Nonce::from_slice(&envelope.nonce),
Payload {
msg: &envelope.ciphertext,
aad: &aad,
},
)
.map_err(|_| ServiceError::Encryption)
}
pub(crate) fn envelope_aad(
schema_id: &str,
caller_runtime_id: &str,
request_id: &str,
message_kind: EnvelopeKind,
) -> Result<Vec<u8>, ServiceError> {
rmp_serde::to_vec(&(schema_id, caller_runtime_id, request_id, message_kind))
.map_err(|_| ServiceError::Encoding)
}
pub(crate) fn validate_envelope(
envelope: &TransportEnvelope,
expected_kind: EnvelopeKind,
) -> Result<(), ServiceError> {
if envelope.schema_id != ENVELOPE_SCHEMA_ID || envelope.message_kind != expected_kind {
return Err(ServiceError::Envelope);
}
require_id(&envelope.caller_runtime_id, "caller_runtime_id")?;
require_id(&envelope.request_id, "request_id")?;
if envelope.ciphertext.is_empty() {
return Err(ServiceError::Envelope);
}
Ok(())
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum ServiceError {
#[error(transparent)]
Contract(#[from] ContractError),
#[error("invalid caller admission")]
InvalidAdmission,
#[error("caller runtime was admitted twice")]
DuplicateCaller,
#[error("callers must not share transport keys")]
SharedCallerKey,
#[error("connector replay store failed: {0}")]
ReplayStore(String),
#[error("connector replay record is malformed")]
InvalidReplayRecord,
#[error("caller key changed while durable replay records still exist")]
ReplayCallerKeyMismatch,
#[error("caller runtime is not admitted")]
CallerNotAdmitted,
#[error("unexpected transport envelope")]
Envelope,
#[error("transport envelope identity substitution")]
OuterIdentity,
#[error("transport encryption or authentication failed")]
Encryption,
#[error("transport MessagePack encoding failed")]
Encoding,
#[error("completion did not carry a transported provider outcome")]
InvalidCompletion,
#[error("completion has no active request")]
NoActiveRequest,
#[error("completion does not match the active request")]
ActiveRequestMismatch,
#[error("sealed result does not fit the frame bound")]
FrameBound,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fixtures::*;
#[test]
fn encrypted_invocation_hides_content_and_binds_outer_identity() {
let invocation = invocation();
let epiphany_key = security("epiphany-distinct-test-key");
let envelope = encrypt_invocation(&invocation, &epiphany_key).unwrap();
assert!(
!envelope
.ciphertext
.windows("Projected state".len())
.any(|window| window == b"Projected state")
);
assert_eq!(
decrypt_invocation(&envelope, &epiphany_key).unwrap(),
invocation
);
assert_eq!(
decrypt_invocation(&envelope, &security("wrong-caller-key")),
Err(ServiceError::Encryption)
);
let mut substituted = envelope;
substituted.request_id = "request-2".to_string();
assert_eq!(
decrypt_invocation(&substituted, &epiphany_key),
Err(ServiceError::Encryption)
);
}
fn sealed_invocation(caller: &str, request_id: &str, kind: EnvelopeKind) -> TransportEnvelope {
let invocation = invocation();
let plaintext = canonical_invocation_bytes(&invocation).unwrap();
encrypt_message(
caller,
request_id,
kind,
&plaintext,
&security("epiphany-distinct-test-key"),
)
.unwrap()
}
#[test]
fn the_outer_identity_must_match_the_sealed_invocation_on_each_field() {
let key = security("epiphany-distinct-test-key");
let invocation = invocation();
let caller = invocation.caller_runtime_id.clone();
let request_id = invocation.request_id().to_string();
let matching = sealed_invocation(&caller, &request_id, EnvelopeKind::Invocation);
assert_eq!(decrypt_invocation(&matching, &key).unwrap(), invocation);
for envelope in [
sealed_invocation("other-caller", &request_id, EnvelopeKind::Invocation),
sealed_invocation(&caller, "other-request", EnvelopeKind::Invocation),
] {
assert_eq!(
decrypt_invocation(&envelope, &key),
Err(ServiceError::OuterIdentity)
);
}
}
#[test]
fn the_outer_identity_must_match_the_sealed_result_on_each_field() {
let key = security("epiphany-distinct-test-key");
let invocation = invocation();
let result = TransportResult::transported(&invocation, Vec::new(), receipt(&invocation));
let plaintext = rmp_serde::to_vec(&result).unwrap();
let seal = |caller: &str, request_id: &str| {
encrypt_message(caller, request_id, EnvelopeKind::Result, &plaintext, &key).unwrap()
};
let caller = result.caller_runtime_id.clone();
let request_id = result.request_id.clone();
assert_eq!(
decrypt_result(&seal(&caller, &request_id), &key, &invocation).unwrap(),
result
);
for envelope in [
seal("other-caller", &request_id),
seal(&caller, "other-request"),
] {
assert_eq!(
decrypt_result(&envelope, &key, &invocation),
Err(ServiceError::OuterIdentity)
);
}
}
#[test]
fn a_malformed_envelope_is_refused_before_any_decryption() {
let key = security("epiphany-distinct-test-key");
let invocation = invocation();
let good = encrypt_invocation(&invocation, &key).unwrap();
let mut schema = good.clone();
schema.schema_id = "gamecult.codex.transport_envelope.v1".to_string();
let wrong_kind = sealed_invocation("epiphany-yggdrasil", "request-1", EnvelopeKind::Result);
let mut empty = good.clone();
empty.ciphertext.clear();
for envelope in [schema, wrong_kind, empty] {
assert_eq!(
decrypt_invocation(&envelope, &key),
Err(ServiceError::Envelope)
);
}
for field in ["caller_runtime_id", "request_id"] {
let mut blank = good.clone();
if field == "caller_runtime_id" {
blank.caller_runtime_id = " ".to_string();
} else {
blank.request_id = String::new();
}
assert_eq!(
decrypt_invocation(&blank, &key),
Err(ServiceError::Contract(ContractError::Invalid(field)))
);
}
}
#[test]
fn a_frame_is_bounded_inclusively_and_never_empty() {
use std::io::Cursor;
let read = |length: u32, body: usize, max: usize| {
let mut frame = length.to_be_bytes().to_vec();
frame.extend(vec![1; body]);
read_transport_frame(&mut Cursor::new(frame), max)
};
assert_eq!(read(16, 16, 16).unwrap(), vec![1; 16]);
assert!(matches!(read(17, 17, 16), Err(TransportFrameError::Size)));
assert!(matches!(read(0, 0, 16), Err(TransportFrameError::Size)));
// A bound above the length never turns an empty frame into an empty payload.
assert!(matches!(read(0, 0, 1_000), Err(TransportFrameError::Size)));
let write = |payload: &[u8], max: usize| {
let mut out = Vec::new();
write_transport_frame(&mut out, payload, max).map(|()| out)
};
assert_eq!(write(&[1; 16], 16).unwrap().len(), 20);
assert!(matches!(
write(&[1; 17], 16),
Err(TransportFrameError::Size)
));
assert!(matches!(write(&[], 16), Err(TransportFrameError::Size)));
assert!(matches!(write(&[], 1_000), Err(TransportFrameError::Size)));
}
#[test]
fn the_worst_frame_is_the_real_encoding_of_an_all_high_ciphertext() {
let long = "x".repeat(MAX_IDENTITY_BYTES);
for length in [0, 1, 15, 16, 17, 255, 256, 65_535, 65_536, 70_000] {
let envelope = TransportEnvelope {
schema_id: ENVELOPE_SCHEMA_ID.to_string(),
caller_runtime_id: long.clone(),
request_id: long.clone(),
message_kind: EnvelopeKind::Invocation,
nonce: [0xff; 12],
ciphertext: vec![0xff; length],
};
assert_eq!(
rmp_serde::to_vec(&envelope).unwrap().len(),
frame_bytes_for_payload(length),
"{length}"
);
}
}
#[test]
fn a_sealed_envelope_never_exceeds_the_worst_frame_for_its_ciphertext() {
let key = security("epiphany-distinct-test-key");
for id in ["a".to_string(), "i".repeat(MAX_IDENTITY_BYTES)] {
for plaintext in [1, 100, 5_000, 70_000] {
let sealed =
encrypt_message(&id, &id, EnvelopeKind::Result, &vec![0x41; plaintext], &key)
.unwrap();
let real = rmp_serde::to_vec(&sealed).unwrap().len();
let worst = frame_bytes_for_payload(sealed.ciphertext.len());
assert!(real <= worst, "{real} > {worst}");
// Real ciphertext is random, so about half its bytes take two
// bytes: the worst case is not wildly above what is written.
if id.len() == MAX_IDENTITY_BYTES {
assert!(
worst <= real + sealed.ciphertext.len() + 64,
"{worst} {real}"
);
}
}
}
}
#[test]
fn the_payload_bound_a_frame_allows_inverts_the_frame_a_payload_needs() {
assert_eq!(
payload_bytes_for_frame(frame_bytes_for_payload(0) - 1),
None
);
assert_eq!(payload_bytes_for_frame(frame_bytes_for_payload(0)), Some(0));
for bytes in [1, 15, 16, 4096, 65_535, 65_536, 100_000, 2_000_000] {
let frame = frame_bytes_for_payload(bytes);
assert_eq!(payload_bytes_for_frame(frame), Some(bytes), "{bytes}");
assert!(
payload_bytes_for_frame(frame - 1).unwrap() < bytes,
"{bytes}"
);
}
}
}