swactor/crates/data-plane/tests/bootstrap_channel_guarantees.rs

155 lines
5.1 KiB
Rust
Raw Normal View History

#![cfg(target_os = "linux")]
use std::ffi::CString;
use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use data_plane::bootstrap::channel::{
AttachmentResult, BootstrapChannelError, GuestBootstrap, SessionBootstrap, bootstrap_channel,
};
use data_plane::protocol::SessionCapability;
use swactor::actor::ActorAddress;
fn arena_fd() -> OwnedFd {
let name = CString::new("swactor-bootstrap-test").expect("memfd name");
// SAFETY: name is NUL-terminated and memfd_create returns a fresh descriptor.
let fd = unsafe { libc::memfd_create(name.as_ptr(), libc::MFD_CLOEXEC) };
assert!(fd >= 0, "memfd_create: {}", io::Error::last_os_error());
// SAFETY: fd is fresh and uniquely owned.
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
let bytes = b"arena";
// SAFETY: fd is writable and bytes is a valid input buffer.
assert_eq!(
unsafe { libc::pwrite(fd.as_raw_fd(), bytes.as_ptr().cast(), bytes.len(), 0) },
bytes.len() as isize
);
fd
}
fn material() -> SessionBootstrap {
SessionBootstrap {
host_session: ActorAddress([0x11; 32]),
session_capability: SessionCapability::new([0x22; 32]),
routing: br#"{"endpoint":"host"}"#.to_vec(),
}
}
#[test]
fn one_use_claim_transfers_arena_and_reports_attachment() {
let (mut host, child) = bootstrap_channel().expect("bootstrap channel");
let arena = arena_fd();
let expected = material();
let pending = GuestBootstrap::begin_claim(child).expect("begin guest claim");
let mut attachment = host
.accept_claim(&expected, arena.as_raw_fd())
.expect("accept claim");
let guest = pending.receive().expect("guest claim");
assert_eq!(guest.material, expected);
// SAFETY: F_GETFD only reads flags for the received live descriptor.
let flags = unsafe { libc::fcntl(guest.arena_fd.as_raw_fd(), libc::F_GETFD) };
assert_ne!(flags & libc::FD_CLOEXEC, 0);
let mut bytes = [0_u8; 5];
// SAFETY: descriptor is readable and bytes is writable.
assert_eq!(
unsafe {
libc::pread(
guest.arena_fd.as_raw_fd(),
bytes.as_mut_ptr().cast(),
bytes.len(),
0,
)
},
bytes.len() as isize
);
assert_eq!(&bytes, b"arena");
guest.attachment_succeeded().expect("notify attachment");
assert_eq!(
attachment.receive_result().expect("attachment result"),
AttachmentResult::Succeeded
);
let duplicate = host.accept_claim(&expected, arena.as_raw_fd());
assert!(matches!(
duplicate,
Err(BootstrapChannelError::AlreadyClaimed)
));
}
#[test]
fn rejected_claim_is_reported_without_descriptor() {
let (mut host, child) = bootstrap_channel().expect("bootstrap channel");
let pending = GuestBootstrap::begin_claim(child).expect("begin guest claim");
host.reject_claim("session revoked")
.expect("host rejection");
let result = pending.receive();
assert!(matches!(
result,
Err(BootstrapChannelError::Rejected(reason)) if reason == "session revoked"
));
}
#[test]
fn incompatible_and_truncated_claims_are_rejected() {
for packet in [
vec![b'S', b'W', b'C', b'L', 99, 0, 0, 0, 0, 0, 0, 0],
vec![b'S', b'W', b'C'],
] {
let (mut host, child) = bootstrap_channel().expect("bootstrap channel");
// SAFETY: child is connected and packet is a valid readable buffer.
assert_eq!(
unsafe {
libc::send(
child.as_raw_fd(),
packet.as_ptr().cast(),
packet.len(),
libc::MSG_NOSIGNAL,
)
},
packet.len() as isize
);
let result = host.accept_claim(&material(), arena_fd().as_raw_fd());
if packet.len() == 12 {
assert!(matches!(
result,
Err(BootstrapChannelError::UnsupportedVersion {
found: 99,
supported: 1
})
));
} else {
assert!(matches!(
result,
Err(BootstrapChannelError::Malformed("short frame prefix"))
));
}
}
}
#[test]
fn peer_closure_resolves_blocked_claim_as_closed() {
let (mut host, child) = bootstrap_channel().expect("bootstrap channel");
drop(child);
assert!(matches!(
host.accept_claim(&material(), arena_fd().as_raw_fd()),
Err(BootstrapChannelError::ChannelClosed)
));
}
#[test]
fn guest_failure_reason_reaches_host() {
let (mut host, child) = bootstrap_channel().expect("bootstrap channel");
let arena = arena_fd();
let pending = GuestBootstrap::begin_claim(child).expect("begin guest claim");
let mut attachment = host
.accept_claim(&material(), arena.as_raw_fd())
.expect("accept claim");
pending
.receive()
.expect("guest claim")
.attachment_failed("route rejected")
.expect("notify failure");
assert_eq!(
attachment.receive_result().expect("attachment result"),
AttachmentResult::Failed("route rejected".to_owned())
);
}