Separate provider contract from connector client

This commit is contained in:
Meta Vens 2026-08-24 23:29:15 +02:00
commit 6dc80f6d26
2 changed files with 40 additions and 4 deletions

View file

@ -17,8 +17,14 @@ path = "src/main.rs"
required-features = ["daemon"]
[features]
default = []
default = ["client"]
client = [
"dep:aes-gcm",
"dep:rand_core",
"dep:zeroize",
]
daemon = [
"client",
"dep:anyhow",
"dep:chrono",
"dep:cultcache-rs",
@ -30,14 +36,14 @@ daemon = [
]
[dependencies]
aes-gcm = "0.10"
aes-gcm = { version = "0.10", optional = true }
anyhow = { version = "1", optional = true }
base64 = "0.23"
chrono = { version = "0.4", features = ["serde"], optional = true }
cultcache-rs = { git = "https://github.com/GameCult/cultcache-rs.git", rev = "fdbf3bf0a4b3f6b617c224d7439feb8e69d4474c", optional = true }
cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "75c180782aeba7cfd22d6412877397708a4ed28f", optional = true }
ed25519-dalek = { version = "2.2", optional = true }
rand_core = { version = "0.6", features = ["getrandom"] }
rand_core = { version = "0.6", features = ["getrandom"], optional = true }
rmp-serde = "1"
serde = { version = "1", features = ["derive"] }
serde_bytes = { version = "0.11", optional = true }
@ -46,4 +52,4 @@ sha2 = "0.10"
thiserror = "2"
ureq = { version = "3.4", default-features = false, features = ["rustls"], optional = true }
uuid = { version = "1", features = ["v4"], optional = true }
zeroize = "1"
zeroize = { version = "1", optional = true }

View file

@ -1,6 +1,9 @@
use std::collections::HashSet;
#[cfg(feature = "client")]
use std::io::{Read, Write};
#[cfg(feature = "client")]
use std::net::{SocketAddr, TcpStream};
#[cfg(feature = "client")]
use std::time::Duration;
#[cfg(feature = "daemon")]
@ -8,14 +11,18 @@ use std::collections::HashMap;
#[cfg(feature = "daemon")]
use std::path::Path;
#[cfg(feature = "client")]
use aes_gcm::aead::{Aead, Payload};
#[cfg(feature = "client")]
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
#[cfg(feature = "daemon")]
use cultcache_rs::{CultCache, DatabaseEntry, OwnedRedbMessagePackBackingStore};
#[cfg(feature = "client")]
use rand_core::{OsRng, RngCore};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use thiserror::Error;
#[cfg(feature = "client")]
use zeroize::{Zeroize, Zeroizing};
#[cfg(feature = "daemon")]
@ -59,9 +66,11 @@ pub const RESULT_SCHEMA_ID: &str = "gamecult.codex.transport_result.v2";
pub const RECEIPT_SCHEMA_ID: &str = "gamecult.codex.transport_receipt.v2";
pub const ENVELOPE_SCHEMA_ID: &str = "gamecult.codex.transport_envelope.v2";
#[cfg(feature = "client")]
#[derive(Clone, PartialEq, Eq)]
pub struct CodexTransportKey([u8; 32]);
#[cfg(feature = "client")]
impl CodexTransportKey {
pub fn from_connection_secret(secret: &str) -> Result<Self, ServiceError> {
if secret.trim().is_empty() || secret.trim() != secret {
@ -71,6 +80,7 @@ impl CodexTransportKey {
}
}
#[cfg(feature = "client")]
impl Drop for CodexTransportKey {
fn drop(&mut self) {
self.0.zeroize();
@ -634,12 +644,14 @@ pub fn canonical_invocation_bytes(
rmp_serde::to_vec(invocation).map_err(|_| ContractError::Encoding)
}
#[cfg(feature = "client")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CodexEnvelopeKind {
Invocation,
Result,
}
#[cfg(feature = "client")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodexTransportEnvelope {
pub schema_id: String,
@ -650,6 +662,7 @@ pub struct CodexTransportEnvelope {
pub ciphertext: Vec<u8>,
}
#[cfg(feature = "client")]
pub fn encrypt_invocation(
invocation: &CodexTransportInvocation,
security: &CodexTransportKey,
@ -664,6 +677,7 @@ pub fn encrypt_invocation(
)
}
#[cfg(feature = "client")]
pub fn decrypt_invocation(
envelope: &CodexTransportEnvelope,
security: &CodexTransportKey,
@ -680,6 +694,7 @@ pub fn decrypt_invocation(
Ok(invocation)
}
#[cfg(feature = "client")]
pub fn encrypt_result(
result: &CodexTransportResult,
security: &CodexTransportKey,
@ -694,6 +709,7 @@ pub fn encrypt_result(
)
}
#[cfg(feature = "client")]
pub fn decrypt_result(
envelope: &CodexTransportEnvelope,
security: &CodexTransportKey,
@ -712,6 +728,7 @@ pub fn decrypt_result(
Ok(result)
}
#[cfg(feature = "client")]
#[derive(Clone)]
pub struct CodexConnectorClient {
endpoint: SocketAddr,
@ -720,6 +737,7 @@ pub struct CodexConnectorClient {
response_timeout: Option<Duration>,
}
#[cfg(feature = "client")]
impl CodexConnectorClient {
pub fn new(
endpoint: SocketAddr,
@ -770,6 +788,7 @@ impl CodexConnectorClient {
}
}
#[cfg(feature = "client")]
#[derive(Debug, Error)]
pub enum CodexConnectorClientError {
#[error("invalid connector client configuration")]
@ -784,12 +803,14 @@ pub enum CodexConnectorClientError {
Transport(#[from] ServiceError),
}
#[cfg(feature = "client")]
#[derive(Debug)]
enum TransportFrameError {
Connection(std::io::Error),
Size,
}
#[cfg(feature = "client")]
fn client_frame_error(error: TransportFrameError) -> CodexConnectorClientError {
match error {
TransportFrameError::Connection(error) => CodexConnectorClientError::Connection(error),
@ -797,6 +818,7 @@ fn client_frame_error(error: TransportFrameError) -> CodexConnectorClientError {
}
}
#[cfg(feature = "client")]
fn read_transport_frame(
reader: &mut impl Read,
max_frame_bytes: usize,
@ -816,6 +838,7 @@ fn read_transport_frame(
Ok(payload)
}
#[cfg(feature = "client")]
fn write_transport_frame(
writer: &mut impl Write,
payload: &[u8],
@ -831,6 +854,7 @@ fn write_transport_frame(
.map_err(TransportFrameError::Connection)
}
#[cfg(feature = "client")]
fn encrypt_message(
caller_runtime_id: &str,
request_id: &str,
@ -868,6 +892,7 @@ fn encrypt_message(
})
}
#[cfg(feature = "client")]
fn decrypt_message(
envelope: &CodexTransportEnvelope,
security: &CodexTransportKey,
@ -890,6 +915,7 @@ fn decrypt_message(
.map_err(|_| ServiceError::Encryption)
}
#[cfg(feature = "client")]
fn envelope_aad(
schema_id: &str,
caller_runtime_id: &str,
@ -900,6 +926,7 @@ fn envelope_aad(
.map_err(|_| ServiceError::Encoding)
}
#[cfg(feature = "client")]
fn validate_envelope(
envelope: &CodexTransportEnvelope,
expected_kind: CodexEnvelopeKind,
@ -1643,6 +1670,7 @@ mod tests {
.unwrap()
}
#[cfg(feature = "client")]
fn security(key: &str) -> CodexTransportKey {
CodexTransportKey::from_connection_secret(key).unwrap()
}
@ -1654,6 +1682,7 @@ mod tests {
CodexTransportInvocation::new(caller_runtime_id, 2_000, [7; 32], request).unwrap()
}
#[cfg(feature = "client")]
#[test]
fn encrypted_invocation_hides_content_and_binds_outer_identity() {
let invocation = invocation();
@ -1682,6 +1711,7 @@ mod tests {
);
}
#[cfg(feature = "client")]
#[test]
fn shared_client_transports_one_exact_encrypted_request_and_result() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();