use core::cell::Cell;
use core::future::{Future, pending};
use core::task::{Context, Poll, Waker};
use cloud_sdk::operation::{
CheckedResponseGuard, ContentTypePolicy, RequestIdPolicy, ResponseBodyPolicy, ResponsePolicy,
ResponsePolicyError,
};
use cloud_sdk::transport::{
HeaderSensitivity, MediaType, ResponseBuffer, ResponseMetadata, ResponseStorageSanitizer,
RetainedMetadataError, StatusCode,
};
static OK: [StatusCode; 1] = [StatusCode::OK];
static JSON: [MediaType<'static>; 1] = [MediaType::JSON];
#[test]
fn every_request_id_policy_has_an_explicit_lifecycle() -> Result<(), &'static str> {
let mut protected_storage = [0xa5_u8; 32];
let mut protected_headers = [0xa5_u8; 8192];
let mut protected = checked_with_request_id(
&mut protected_storage,
&mut protected_headers,
RequestIdPolicy::Protected,
b"protected-id",
)?;
assert!(protected.with_borrowed(|view| {
view.with_request_id(|request_id| request_id == Some(b"protected-id".as_slice()))
}));
let mut forbidden_retention = [0xa5_u8; 64];
assert!(matches!(
protected.retain_metadata_into(&mut forbidden_retention, 64),
Err(RetainedMetadataError::RetentionForbidden)
));
drop(protected);
assert_eq!(protected_storage, [0_u8; 32]);
assert_eq!(protected_headers, [0_u8; 8192]);
let mut discarded_storage = [0xa5_u8; 32];
let mut discarded_headers = [0xa5_u8; 8192];
let discarded = checked_with_request_id(
&mut discarded_storage,
&mut discarded_headers,
RequestIdPolicy::Discard,
b"discarded-id",
)?;
assert!(
discarded.with_borrowed(|view| { view.with_request_id(|request_id| request_id.is_none()) })
);
drop(discarded);
assert_eq!(discarded_storage, [0_u8; 32]);
assert_eq!(discarded_headers, [0_u8; 8192]);
let mut retained_body = [0xa5_u8; 32];
let mut retained_headers = [0xa5_u8; 8192];
let mut retained_guard = checked_with_request_id(
&mut retained_body,
&mut retained_headers,
RequestIdPolicy::Retain,
b"retained-id",
)?;
let mut retained_storage = [0xa5_u8; 64];
let retained_pointer = retained_storage.as_ptr();
let retained = retained_guard
.retain_metadata_into(&mut retained_storage, 64)
.map_err(|_| "retainable request ID did not transfer")?;
assert!(
retained_guard
.with_borrowed(|view| { view.with_request_id(|request_id| request_id.is_none()) })
);
assert!(
retained.with_request_id(|request_id| { request_id == Some(b"retained-id".as_slice()) })
);
assert!(retained.with_request_id(|request_id| {
request_id.is_some_and(|request_id| request_id.as_ptr() == retained_pointer)
}));
drop(retained_guard);
assert_eq!(retained_body, [0_u8; 32]);
assert_eq!(retained_headers, [0_u8; 8192]);
drop(retained);
assert_eq!(retained_storage, [0_u8; 64]);
Ok(())
}
#[test]
fn rejected_retention_clears_the_source_and_invalid_ids_fail_closed() -> Result<(), &'static str> {
let mut storage = [0xa5_u8; 32];
let mut headers = [0xa5_u8; 8192];
let mut checked = checked_with_request_id(
&mut storage,
&mut headers,
RequestIdPolicy::Retain,
b"too-long",
)?;
let mut retention = [0xa5_u8; 64];
assert!(matches!(
checked.retain_metadata_into(&mut retention, 3),
Err(RetainedMetadataError::RetentionLimitExceeded)
));
assert!(
checked.with_borrowed(|view| { view.with_request_id(|request_id| request_id.is_none()) })
);
drop(checked);
assert_eq!(storage, [0_u8; 32]);
assert_eq!(headers, [0_u8; 8192]);
let mut invalid_storage = [0xa5_u8; 32];
let mut invalid_headers = [0xa5_u8; 8192];
let invalid = checked_with_request_id(
&mut invalid_storage,
&mut invalid_headers,
RequestIdPolicy::Protected,
b"",
);
assert!(matches!(
invalid,
Err("response policy rejected request identifier")
));
drop(invalid);
assert_eq!(invalid_storage, [0_u8; 32]);
assert_eq!(invalid_headers, [0_u8; 8192]);
Ok(())
}
#[test]
fn mandatory_clear_survives_noop_and_recontaminating_hooks() {
for hook in [
&NoopHook as &dyn ResponseStorageSanitizer,
&RecontaminatingHook as &dyn ResponseStorageSanitizer,
] {
let mut storage = [0xa5_u8; 32];
let mut headers = [0xa5_u8; 8192];
{
let mut response =
ResponseBuffer::with_additive_sanitizer(&mut storage, 16, &mut headers, hook);
if let Ok(mut attempt) = response.writer().begin_attempt()
&& let Ok(body) = attempt.body_mut()
{
body.fill(0x42);
}
}
assert_eq!(storage, [0_u8; 32]);
assert_eq!(headers, [0_u8; 8192]);
}
}
#[test]
fn mandatory_final_clear_runs_when_an_additive_hook_panics() {
let mut admission_storage = [0xa5_u8; 32];
let mut admission_headers = [0xa5_u8; 8192];
let admission_unwind = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _response = ResponseBuffer::with_additive_sanitizer(
&mut admission_storage,
16,
&mut admission_headers,
&PanickingHook,
);
}));
assert!(admission_unwind.is_err());
assert_eq!(admission_storage, [0_u8; 32]);
assert_eq!(admission_headers, [0_u8; 8192]);
let hook = PanicOnDropHook {
admitted: Cell::new(false),
};
let mut drop_storage = [0xa5_u8; 32];
let mut drop_headers = [0xa5_u8; 8192];
let drop_unwind = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let response = ResponseBuffer::with_additive_sanitizer(
&mut drop_storage,
16,
&mut drop_headers,
&hook,
);
drop(response);
}));
assert!(drop_unwind.is_err());
assert_eq!(drop_storage, [0_u8; 32]);
assert_eq!(drop_headers, [0_u8; 8192]);
}
#[test]
fn panic_unwind_clears_response_attempt_state() {
let mut storage = [0xa5_u8; 16];
let mut headers = [0xa5_u8; 8192];
let mut response = ResponseBuffer::new(&mut storage, 8, &mut headers);
let unwind = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let mut attempt = response
.writer()
.begin_attempt()
.unwrap_or_else(|_| unreachable!());
attempt
.body_mut()
.unwrap_or_else(|_| unreachable!())
.fill(0x5a);
attempt
.headers_mut()
.unwrap_or_else(|_| unreachable!())
.try_push("x-request-id", b"partial", HeaderSensitivity::Sensitive)
.unwrap_or_else(|_| unreachable!());
std::panic::resume_unwind(std::boxed::Box::new("intentional response-attempt unwind"));
}));
assert!(unwind.is_err());
let mut attempt = response
.writer()
.begin_attempt()
.unwrap_or_else(|_| unreachable!());
assert!(
attempt
.body_mut()
.is_ok_and(|body| body.iter().all(|byte| *byte == 0))
);
assert!(attempt.headers().is_empty());
}
#[test]
fn dirty_decode_workspace_clears_on_error_and_panic_unwind() -> Result<(), &'static str> {
let mut success_storage = [0xa5_u8; 32];
let mut success_headers = [0xa5_u8; 8192];
let checked = checked_with_request_id(
&mut success_storage,
&mut success_headers,
RequestIdPolicy::Discard,
b"ignored",
)?;
let decoded = checked.decode_owned_with_workspace(|_response, workspace| {
workspace.decoder_scratch_mut().fill(0x01);
workspace.cursor_scratch_mut().fill(0x02);
workspace.provider_link_scratch_mut().fill(0x03);
Ok::<_, &'static str>(())
});
assert_eq!(decoded, Ok(()));
assert_eq!(success_storage, [0_u8; 32]);
let mut error_storage = [0xa5_u8; 32];
let mut error_headers = [0xa5_u8; 8192];
let checked = checked_with_request_id(
&mut error_storage,
&mut error_headers,
RequestIdPolicy::Discard,
b"ignored",
)?;
let decoded = checked.decode_owned_with_workspace(|_response, workspace| {
workspace.decoder_scratch_mut().fill(0x11);
workspace.cursor_scratch_mut().fill(0x22);
workspace.provider_link_scratch_mut().fill(0x33);
Err::<(), _>("decode failed")
});
assert_eq!(decoded, Err("decode failed"));
assert_eq!(error_storage, [0_u8; 32]);
let mut panic_storage = [0xa5_u8; 32];
let mut panic_headers = [0xa5_u8; 8192];
let checked = checked_with_request_id(
&mut panic_storage,
&mut panic_headers,
RequestIdPolicy::Discard,
b"ignored",
)?;
let unwind = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _: Result<(), ()> = checked.decode_owned_with_workspace(|_response, workspace| {
workspace.decoder_scratch_mut().fill(0x44);
std::panic::resume_unwind(std::boxed::Box::new("intentional cleanup unwind"));
});
}));
assert!(unwind.is_err());
assert_eq!(panic_storage, [0_u8; 32]);
Ok(())
}
#[test]
fn dropping_a_pending_future_clears_its_owned_response_buffer() {
let mut storage = [0xa5_u8; 32];
let mut headers = [0xa5_u8; 8192];
{
let mut future = core::pin::pin!(pending_response(&mut storage, &mut headers));
let mut context = Context::from_waker(Waker::noop());
assert!(matches!(
Future::poll(future.as_mut(), &mut context),
Poll::Pending
));
}
assert_eq!(storage, [0_u8; 32]);
assert_eq!(headers, [0_u8; 8192]);
}
async fn pending_response(storage: &mut [u8], header_storage: &mut [u8]) {
let mut response = ResponseBuffer::new(storage, 16, header_storage);
if let Ok(mut attempt) = response.writer().begin_attempt() {
if let Ok(body) = attempt.body_mut() {
body.fill(0x5a);
}
pending::<()>().await;
}
}
fn checked_with_request_id<'a>(
storage: &'a mut [u8],
header_storage: &'a mut [u8],
request_id_policy: RequestIdPolicy,
request_id: &[u8],
) -> Result<CheckedResponseGuard<'a>, &'static str> {
let mut response = ResponseBuffer::new(storage, 16, header_storage);
{
let mut attempt = response
.writer()
.begin_attempt()
.map_err(|_| "response attempt was unavailable")?;
let headers = attempt
.headers_mut()
.map_err(|_| "response headers were unavailable")?;
headers
.try_push("x-request-id", request_id, HeaderSensitivity::Sensitive)
.map_err(|_| "request identifier header was invalid")?;
headers
.try_push(
"content-type",
b"application/json",
HeaderSensitivity::Public,
)
.map_err(|_| "content type header was invalid")?;
attempt
.body_mut()
.map_err(|_| "response body was unavailable")?
.get_mut(..2)
.ok_or("response body was too small")?
.copy_from_slice(b"{}");
attempt
.commit(StatusCode::OK, 2, ResponseMetadata::EMPTY)
.map_err(|_| "response commitment failed")?;
}
json_policy()
.validate(response, request_id_policy)
.map_err(|error| match error {
ResponsePolicyError::InvalidRequestId => "response policy rejected request identifier",
_ => "response policy rejected fixture",
})
}
fn json_policy() -> ResponsePolicy {
ResponsePolicy::new(
&OK,
ContentTypePolicy::Required(&JSON),
ResponseBodyPolicy::Required,
16,
)
.unwrap_or_else(|_| unreachable!())
}
struct NoopHook;
impl ResponseStorageSanitizer for NoopHook {
fn sanitize_response_storage(&self, _response_storage: &mut [u8]) {}
}
struct RecontaminatingHook;
impl ResponseStorageSanitizer for RecontaminatingHook {
fn sanitize_response_storage(&self, response_storage: &mut [u8]) {
response_storage.fill(0xa5);
}
}
struct PanickingHook;
impl ResponseStorageSanitizer for PanickingHook {
fn sanitize_response_storage(&self, response_storage: &mut [u8]) {
response_storage.fill(0x5a);
std::panic::resume_unwind(std::boxed::Box::new("intentional additive cleanup panic"));
}
}
struct PanicOnDropHook {
admitted: Cell<bool>,
}
impl ResponseStorageSanitizer for PanicOnDropHook {
fn sanitize_response_storage(&self, response_storage: &mut [u8]) {
if self.admitted.replace(true) {
response_storage.fill(0x7a);
std::panic::resume_unwind(std::boxed::Box::new("intentional drop cleanup panic"));
}
}
}