feat(core): run bounded real gateway sessions
This commit is contained in:
+153
-34
@@ -2,17 +2,31 @@
|
||||
unsafe_code,
|
||||
clippy::borrow_as_ptr,
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::items_after_statements
|
||||
clippy::items_after_statements,
|
||||
clashing_extern_declarations
|
||||
)]
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::c_void;
|
||||
use std::mem::{offset_of, size_of};
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
|
||||
use std::sync::{Condvar, Mutex};
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use rustls::sign::SigningKey;
|
||||
use rustls::SignatureScheme;
|
||||
|
||||
#[path = "gateway_oracle.rs"]
|
||||
mod gateway_oracle;
|
||||
|
||||
thread_local! {
|
||||
static CORE_CONTEXTS: RefCell<HashMap<usize, usize>> = RefCell::new(HashMap::new());
|
||||
static ORACLES: RefCell<HashMap<usize, gateway_oracle::Oracle>> = RefCell::new(HashMap::new());
|
||||
}
|
||||
|
||||
const ABI_V1: u32 = 1;
|
||||
const OK: u32 = 0;
|
||||
const INVALID_ARGUMENT: u32 = 1;
|
||||
@@ -167,6 +181,8 @@ struct Context {
|
||||
cancel_on_connecting: AtomicBool,
|
||||
reentry_target: AtomicUsize,
|
||||
reentry_results: Mutex<Vec<u32>>,
|
||||
admission_key: Mutex<Option<Arc<dyn SigningKey>>>,
|
||||
tls_key: Mutex<Option<Arc<dyn SigningKey>>>,
|
||||
}
|
||||
|
||||
impl Default for Context {
|
||||
@@ -191,6 +207,8 @@ impl Default for Context {
|
||||
cancel_on_connecting: AtomicBool::new(false),
|
||||
reentry_target: AtomicUsize::new(0),
|
||||
reentry_results: Mutex::new(Vec::new()),
|
||||
admission_key: Mutex::new(None),
|
||||
tls_key: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,9 +224,22 @@ unsafe extern "C" fn sign_admission(raw: *mut c_void, input: BytesView, output:
|
||||
let ctx = unsafe { context(raw) };
|
||||
ctx.admission_calls.fetch_add(1, Ordering::SeqCst);
|
||||
*ctx.admission_input.lock().expect("admission lock") = bytes.to_vec();
|
||||
let status = ctx.admission_status.load(Ordering::SeqCst);
|
||||
if status != OK {
|
||||
return status;
|
||||
}
|
||||
let key = ctx.admission_key.lock().expect("admission key");
|
||||
let Some(key) = key.as_ref() else {
|
||||
return INTERNAL;
|
||||
};
|
||||
let signer = key
|
||||
.choose_scheme(&[SignatureScheme::ED25519])
|
||||
.expect("Ed25519 admission signer");
|
||||
let signature = signer.sign(bytes).expect("admission signature");
|
||||
assert_eq!(signature.len(), 64);
|
||||
// Test invariant: ABI promises a writable 64-byte Rust-owned signature buffer.
|
||||
unsafe { ptr::write_bytes(output, 0xA5, 64) };
|
||||
ctx.admission_status.load(Ordering::SeqCst)
|
||||
unsafe { ptr::copy_nonoverlapping(signature.as_ptr(), output, signature.len()) };
|
||||
OK
|
||||
}
|
||||
|
||||
unsafe extern "C" fn sign_tls(raw: *mut c_void, input: BytesView, output: *mut u8) -> u32 {
|
||||
@@ -217,9 +248,22 @@ unsafe extern "C" fn sign_tls(raw: *mut c_void, input: BytesView, output: *mut u
|
||||
let ctx = unsafe { context(raw) };
|
||||
ctx.tls_calls.fetch_add(1, Ordering::SeqCst);
|
||||
*ctx.tls_input.lock().expect("tls lock") = bytes.to_vec();
|
||||
let status = ctx.tls_status.load(Ordering::SeqCst);
|
||||
if status != OK {
|
||||
return status;
|
||||
}
|
||||
let key = ctx.tls_key.lock().expect("TLS key");
|
||||
let Some(key) = key.as_ref() else {
|
||||
return INTERNAL;
|
||||
};
|
||||
let signer = key
|
||||
.choose_scheme(&[SignatureScheme::ED25519])
|
||||
.expect("Ed25519 TLS signer");
|
||||
let signature = signer.sign(bytes).expect("TLS signature");
|
||||
assert_eq!(signature.len(), 64);
|
||||
// Test invariant: ABI promises a writable 64-byte Rust-owned signature buffer.
|
||||
unsafe { ptr::write_bytes(output, 0x5A, 64) };
|
||||
ctx.tls_status.load(Ordering::SeqCst)
|
||||
unsafe { ptr::copy_nonoverlapping(signature.as_ptr(), output, signature.len()) };
|
||||
OK
|
||||
}
|
||||
|
||||
unsafe extern "C" fn sign_admission_probes_global_reentry(
|
||||
@@ -391,14 +435,66 @@ fn create(ctx: &mut Context) -> *mut Core {
|
||||
// Test invariant: config/out pointers remain valid for the synchronous create call.
|
||||
assert_eq!(unsafe { verse_core_create_v1(&config, &mut core) }, OK);
|
||||
assert!(!core.is_null());
|
||||
ctx.core.store(core as usize, Ordering::SeqCst);
|
||||
register(core, ctx);
|
||||
core
|
||||
}
|
||||
|
||||
fn register(core: *mut Core, ctx: &mut Context) {
|
||||
ctx.core.store(core as usize, Ordering::SeqCst);
|
||||
CORE_CONTEXTS.with(|contexts| {
|
||||
contexts
|
||||
.borrow_mut()
|
||||
.insert(core as usize, ptr::from_mut(ctx) as usize);
|
||||
});
|
||||
}
|
||||
|
||||
fn connect(core: *mut Core, manifest: &[u8], credential: &[u8]) -> u32 {
|
||||
let request = request(manifest, credential);
|
||||
// Test invariant: request and backing byte slices remain valid for the synchronous call.
|
||||
unsafe { verse_core_connect_v1(core, &request) }
|
||||
connect_mode(core, "", manifest, credential)
|
||||
}
|
||||
|
||||
fn connect_mode(core: *mut Core, mode: &str, manifest: &[u8], credential: &[u8]) -> u32 {
|
||||
connect_with_oracle(core, mode, |oracle| {
|
||||
let manifest = if manifest == MANIFEST {
|
||||
oracle.ready.manifest.as_bytes()
|
||||
} else {
|
||||
manifest
|
||||
};
|
||||
let credential = if credential == CREDENTIAL {
|
||||
oracle.ready.credential.as_bytes()
|
||||
} else {
|
||||
credential
|
||||
};
|
||||
let request = request(manifest, credential);
|
||||
// Test invariant: request and backing byte slices remain valid for the synchronous call.
|
||||
unsafe { verse_core_connect_v1(core, &request) }
|
||||
})
|
||||
}
|
||||
|
||||
fn connect_with_oracle(
|
||||
core: *mut Core,
|
||||
mode: &str,
|
||||
action: impl FnOnce(&gateway_oracle::Oracle) -> u32,
|
||||
) -> u32 {
|
||||
let oracle = gateway_oracle::Oracle::start(mode);
|
||||
CORE_CONTEXTS.with(|contexts| {
|
||||
let raw = *contexts
|
||||
.borrow()
|
||||
.get(&(core as usize))
|
||||
.expect("registered ABI context");
|
||||
// Test invariant: context outlives the core and is removed only after destroy succeeds.
|
||||
let ctx = unsafe { &*(raw as *const Context) };
|
||||
*ctx.admission_key.lock().expect("admission key") =
|
||||
Some(gateway_oracle::test_key(&oracle.ready.admission_key));
|
||||
*ctx.tls_key.lock().expect("TLS key") =
|
||||
Some(gateway_oracle::test_key(&oracle.ready.client_key));
|
||||
});
|
||||
let status = action(&oracle);
|
||||
if status == OK {
|
||||
ORACLES.with(|oracles| {
|
||||
oracles.borrow_mut().insert(core as usize, oracle);
|
||||
});
|
||||
}
|
||||
status
|
||||
}
|
||||
|
||||
fn wait_for(ctx: &Context, predicate: impl Fn(&[u32]) -> bool) {
|
||||
@@ -419,7 +515,16 @@ fn wait_for(ctx: &Context, predicate: impl Fn(&[u32]) -> bool) {
|
||||
|
||||
fn destroy(core: *mut Core) -> u32 {
|
||||
// Test invariant: caller retains the handle until destroy reports success.
|
||||
unsafe { verse_core_destroy_v1(core, 2_000) }
|
||||
let status = unsafe { verse_core_destroy_v1(core, 2_000) };
|
||||
if status == OK {
|
||||
ORACLES.with(|oracles| {
|
||||
oracles.borrow_mut().remove(&(core as usize));
|
||||
});
|
||||
CORE_CONTEXTS.with(|contexts| {
|
||||
contexts.borrow_mut().remove(&(core as usize));
|
||||
});
|
||||
}
|
||||
status
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -500,8 +605,11 @@ fn connect_copies_inputs_and_calls_purpose_specific_signers_once() {
|
||||
|
||||
assert_eq!(ctx.admission_calls.load(Ordering::SeqCst), 1);
|
||||
assert_eq!(ctx.tls_calls.load(Ordering::SeqCst), 1);
|
||||
assert_eq!(&*ctx.admission_input.lock().expect("admission"), MANIFEST);
|
||||
assert_eq!(&*ctx.tls_input.lock().expect("tls"), CREDENTIAL);
|
||||
let admission = ctx.admission_input.lock().expect("admission");
|
||||
let tls = ctx.tls_input.lock().expect("tls");
|
||||
assert!(admission.starts_with(b"versevdi/tunnel-admission/v1"));
|
||||
assert!(!tls.is_empty());
|
||||
assert_ne!(&*admission, &*tls);
|
||||
assert_eq!(destroy(core), OK);
|
||||
}
|
||||
|
||||
@@ -564,13 +672,18 @@ fn tables_and_slices_reject_short_unsupported_null_and_oversized_inputs() {
|
||||
base: ConnectRequest,
|
||||
ignored: [u8; 24],
|
||||
}
|
||||
let mut trailing_request = ExtendedRequest {
|
||||
base: request(MANIFEST, CREDENTIAL),
|
||||
ignored: [0xEE; 24],
|
||||
};
|
||||
trailing_request.base.struct_size = size_of::<ExtendedRequest>() as u32;
|
||||
assert_eq!(
|
||||
unsafe { verse_core_connect_v1(core, &trailing_request.base) },
|
||||
connect_with_oracle(core, "", |oracle| {
|
||||
let mut trailing_request = ExtendedRequest {
|
||||
base: request(
|
||||
oracle.ready.manifest.as_bytes(),
|
||||
oracle.ready.credential.as_bytes(),
|
||||
),
|
||||
ignored: [0xEE; 24],
|
||||
};
|
||||
trailing_request.base.struct_size = size_of::<ExtendedRequest>() as u32;
|
||||
unsafe { verse_core_connect_v1(core, &trailing_request.base) }
|
||||
}),
|
||||
OK
|
||||
);
|
||||
|
||||
@@ -616,7 +729,7 @@ fn signer_callbacks_cannot_reenter_even_cancel() {
|
||||
cfg.sign_admission = Some(sign_admission_reenters);
|
||||
let mut core = ptr::null_mut();
|
||||
assert_eq!(unsafe { verse_core_create_v1(&cfg, &mut core) }, OK);
|
||||
ctx.core.store(core as usize, Ordering::SeqCst);
|
||||
register(core, &mut ctx);
|
||||
assert_eq!(connect(core, MANIFEST, CREDENTIAL), OK);
|
||||
assert_eq!(ctx.reentry_cancel.load(Ordering::SeqCst), REENTRANT);
|
||||
assert_eq!(destroy(core), OK);
|
||||
@@ -634,7 +747,7 @@ fn signer_callback_rejects_every_stateful_api_across_handles() {
|
||||
cfg.sign_admission = Some(sign_admission_probes_global_reentry);
|
||||
let mut core = ptr::null_mut();
|
||||
assert_eq!(unsafe { verse_core_create_v1(&cfg, &mut core) }, OK);
|
||||
ctx.core.store(core as usize, Ordering::SeqCst);
|
||||
register(core, &mut ctx);
|
||||
assert_eq!(connect(core, MANIFEST, CREDENTIAL), OK);
|
||||
assert_eq!(
|
||||
ctx.reentry_results
|
||||
@@ -644,8 +757,8 @@ fn signer_callback_rejects_every_stateful_api_across_handles() {
|
||||
[ABI_V1, REENTRANT, REENTRANT, REENTRANT, REENTRANT, REENTRANT, REENTRANT]
|
||||
);
|
||||
|
||||
assert_eq!(connect(other, MANIFEST, CREDENTIAL), OK);
|
||||
assert_eq!(destroy(core), OK);
|
||||
assert_eq!(connect(other, MANIFEST, CREDENTIAL), OK);
|
||||
assert_eq!(destroy(other), OK);
|
||||
}
|
||||
|
||||
@@ -661,7 +774,7 @@ fn event_callback_allows_only_originating_handle_cancel() {
|
||||
cfg.on_state = Some(on_state_probes_global_reentry);
|
||||
let mut core = ptr::null_mut();
|
||||
assert_eq!(unsafe { verse_core_create_v1(&cfg, &mut core) }, OK);
|
||||
ctx.core.store(core as usize, Ordering::SeqCst);
|
||||
register(core, &mut ctx);
|
||||
assert_eq!(connect(core, MANIFEST, CREDENTIAL), OK);
|
||||
wait_for(&ctx, |states| states.contains(&STATE_CANCELLED));
|
||||
assert_eq!(
|
||||
@@ -672,8 +785,8 @@ fn event_callback_allows_only_originating_handle_cancel() {
|
||||
[ABI_V1, REENTRANT, REENTRANT, REENTRANT, REENTRANT, REENTRANT, REENTRANT, OK]
|
||||
);
|
||||
|
||||
assert_eq!(connect(other, MANIFEST, CREDENTIAL), OK);
|
||||
assert_eq!(destroy(core), OK);
|
||||
assert_eq!(connect(other, MANIFEST, CREDENTIAL), OK);
|
||||
assert_eq!(destroy(other), OK);
|
||||
}
|
||||
|
||||
@@ -720,7 +833,7 @@ fn cancel_during_connect_preserves_state_order_and_stops_before_tls_signing() {
|
||||
cfg.sign_admission = Some(sign_admission_waits_for_cancel);
|
||||
let mut core = ptr::null_mut();
|
||||
assert_eq!(unsafe { verse_core_create_v1(&cfg, &mut core) }, OK);
|
||||
ctx.core.store(core as usize, Ordering::SeqCst);
|
||||
register(core, &mut ctx);
|
||||
|
||||
assert_eq!(connect(core, MANIFEST, CREDENTIAL), CANCELLED);
|
||||
wait_for(&ctx, |states| states.contains(&STATE_CANCELLED));
|
||||
@@ -737,16 +850,22 @@ fn send_input_is_nonblocking_bounded_and_cancel_is_idempotent() {
|
||||
let mut ctx = Context::default();
|
||||
ctx.reentry_cancel.store(OK, Ordering::SeqCst);
|
||||
let core = create(&mut ctx);
|
||||
assert_eq!(connect(core, MANIFEST, CREDENTIAL), OK);
|
||||
assert_eq!(connect_mode(core, "slow-input", MANIFEST, CREDENTIAL), OK);
|
||||
let event = keyboard_event();
|
||||
for _ in 0..64 {
|
||||
assert_eq!(unsafe { verse_core_send_input_v1(core, &event) }, OK);
|
||||
}
|
||||
assert_eq!(
|
||||
unsafe { verse_core_send_input_v1(core, &event) },
|
||||
QUEUE_FULL
|
||||
);
|
||||
assert_eq!(unsafe { verse_core_request_idr_v1(core) }, OK);
|
||||
let started = Instant::now();
|
||||
let mut sent = 0;
|
||||
let saturated = loop {
|
||||
match unsafe { verse_core_send_input_v1(core, &event) } {
|
||||
OK => sent += 1,
|
||||
QUEUE_FULL => break true,
|
||||
status => panic!("unexpected input status {status}"),
|
||||
}
|
||||
if sent == 10_000 || started.elapsed() > Duration::from_secs(1) {
|
||||
break false;
|
||||
}
|
||||
};
|
||||
assert!(saturated, "real slow consumer did not expose bounded queue");
|
||||
assert!(started.elapsed() < Duration::from_secs(1));
|
||||
assert_eq!(unsafe { verse_core_cancel_v1(core) }, OK);
|
||||
assert_eq!(unsafe { verse_core_cancel_v1(core) }, OK);
|
||||
assert_eq!(unsafe { verse_core_send_input_v1(core, &event) }, CANCELLED);
|
||||
|
||||
Reference in New Issue
Block a user