Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions tests/rust/wasm32-wasip3/src/bin/sockets-tcp-connect.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"proposals": [
"sockets"
],
"operations": [
{
"type": "run"
},
{
"type": "wait"
}
]
}
185 changes: 185 additions & 0 deletions tests/rust/wasm32-wasip3/src/bin/sockets-tcp-connect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
use test_wasm32_wasip3::sockets::{
self,
wasi::sockets::types::{ErrorCode, IpAddress, IpAddressFamily, IpSocketAddress, TcpSocket},
};

const PORT: u16 = 42;

struct Component;

sockets::export!(Component);

async fn test_invalid_address_family(family: IpAddressFamily) {
let sock = TcpSocket::create(family).unwrap();

let addr = match family {
IpAddressFamily::Ipv4 => IpSocketAddress::localhost(IpAddressFamily::Ipv6, PORT),
IpAddressFamily::Ipv6 => IpSocketAddress::localhost(IpAddressFamily::Ipv4, PORT),
};

let result = sock.connect(addr).await;
assert!(matches!(result, Err(ErrorCode::InvalidArgument)));
}

async fn test_non_unicast(family: IpAddressFamily) {
let mut non_unicast_addresses = Vec::new();

match family {
IpAddressFamily::Ipv4 => {
// Multicast
for nibble in 224..=239 {
non_unicast_addresses.push(IpAddress::Ipv4((nibble, 0, 0, 1)));
}
// Limited broadcast
non_unicast_addresses.push(IpAddress::Ipv4((255, 255, 255, 255)));
}
IpAddressFamily::Ipv6 => {
// Multicast
for b in 0xff00..=0xffff {
non_unicast_addresses.push(IpAddress::Ipv6((b, 0, 0, 0, 0, 0, 0, 1)));
}
}
};

for addr in non_unicast_addresses {
let sock = TcpSocket::create(family).unwrap();
let socket_addr = IpSocketAddress::new(addr, PORT);
let result = sock.connect(socket_addr).await;

assert!(matches!(result, Err(ErrorCode::InvalidArgument)));
}
}

async fn test_dual_stack_support() {
let sock = TcpSocket::create(IpAddressFamily::Ipv6).unwrap();
let addr = IpSocketAddress::ipv6_mapped_localhost(PORT);
let result = sock.connect(addr).await;

assert!(matches!(result, Err(ErrorCode::InvalidArgument)));
}

async fn test_unspecified_remote_addr(family: IpAddressFamily) {
let sock = TcpSocket::create(family).unwrap();
let addr = IpSocketAddress::unspecified(family, PORT);
let result = sock.connect(addr).await;

assert!(matches!(result, Err(ErrorCode::InvalidArgument)));
}

async fn test_connect_0_port(family: IpAddressFamily) {
let sock = TcpSocket::create(family).unwrap();
let addr = IpSocketAddress::localhost(family, 0);
let result = sock.connect(addr).await;

assert!(matches!(result, Err(ErrorCode::InvalidArgument)));
}

async fn test_connected_state(family: IpAddressFamily) {
let listener = TcpSocket::create(family).unwrap();
listener
.bind(IpSocketAddress::localhost(family, 0))
.unwrap();
let server_addr = listener.get_local_address().unwrap();
listener.listen().unwrap();

let sock = TcpSocket::create(family).unwrap();
futures::join!(
async {
let result = sock.connect(server_addr).await;
assert!(result.is_ok());
},
async {
let result = sock.connect(server_addr).await;
assert!(matches!(result, Err(ErrorCode::InvalidState)));
}
);
}

async fn test_listening_state(family: IpAddressFamily) {
let listener = TcpSocket::create(family).unwrap();
listener
.bind(IpSocketAddress::localhost(family, 0))
.unwrap();
listener.listen().unwrap();

let result = listener
.connect(IpSocketAddress::localhost(family, PORT))
.await;
assert!(matches!(result, Err(ErrorCode::InvalidState)));
}

async fn test_connection_refused(family: IpAddressFamily) {
let sock = TcpSocket::create(family).unwrap();
let addr = IpSocketAddress::localhost(family, 59999);
let result = sock.connect(addr).await;

assert!(matches!(result, Err(ErrorCode::ConnectionRefused)));
}

async fn test_explicit_bind(family: IpAddressFamily) {
let (listener, mut accept) = {
let bind_address = IpSocketAddress::localhost(family, 0);
let listener = TcpSocket::create(family).unwrap();
listener.bind(bind_address).unwrap();
let accept = listener.listen().unwrap();
(listener, accept)
};

let listener_address = listener.get_local_address().unwrap();
let client = TcpSocket::create(family).unwrap();

client.bind(IpSocketAddress::localhost(family, 0)).unwrap();

futures::join!(
async {
client.connect(listener_address).await.unwrap();
},
async {
accept.next().await.unwrap();
}
);
}

async fn test_explicit_bind_addrinuse(family: IpAddressFamily) {
let listener = {
let bind_address = IpSocketAddress::localhost(family, 0);
let listener = TcpSocket::create(family).unwrap();
listener.bind(bind_address).unwrap();
listener
};

let listener_address = listener.get_local_address().unwrap();
let client = TcpSocket::create(family).unwrap();

let result = client.bind(listener_address);
assert!(matches!(result, Err(ErrorCode::AddressInUse)));
}

impl sockets::exports::wasi::cli::run::Guest for Component {
async fn run() -> Result<(), ()> {
test_invalid_address_family(IpAddressFamily::Ipv4).await;
test_invalid_address_family(IpAddressFamily::Ipv6).await;
test_non_unicast(IpAddressFamily::Ipv4).await;
test_non_unicast(IpAddressFamily::Ipv6).await;
test_dual_stack_support().await;
test_unspecified_remote_addr(IpAddressFamily::Ipv4).await;
test_unspecified_remote_addr(IpAddressFamily::Ipv6).await;
test_connect_0_port(IpAddressFamily::Ipv4).await;
test_connect_0_port(IpAddressFamily::Ipv6).await;
test_connection_refused(IpAddressFamily::Ipv4).await;
test_connection_refused(IpAddressFamily::Ipv6).await;
test_connected_state(IpAddressFamily::Ipv4).await;
test_connected_state(IpAddressFamily::Ipv6).await;
test_listening_state(IpAddressFamily::Ipv4).await;
test_listening_state(IpAddressFamily::Ipv6).await;
test_explicit_bind(IpAddressFamily::Ipv4).await;
test_explicit_bind(IpAddressFamily::Ipv6).await;
test_explicit_bind_addrinuse(IpAddressFamily::Ipv4).await;
test_explicit_bind_addrinuse(IpAddressFamily::Ipv6).await;
Ok(())
}
}

fn main() {
unreachable!()
}
13 changes: 13 additions & 0 deletions tests/rust/wasm32-wasip3/src/bin/sockets-tcp-listen.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"proposals": [
"sockets"
],
"operations": [
{
"type": "run"
},
{
"type": "wait"
}
]
}
85 changes: 85 additions & 0 deletions tests/rust/wasm32-wasip3/src/bin/sockets-tcp-listen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use test_wasm32_wasip3::sockets::{
self,
wasi::sockets::types::{ErrorCode, IpAddressFamily, IpSocketAddress, TcpSocket},
};

struct Component;

sockets::export!(Component);

fn test_with_bind(family: IpAddressFamily) {
let addr = IpSocketAddress::localhost(family, 0);
let sock = TcpSocket::create(family).unwrap();
sock.bind(addr).unwrap();
assert!(sock.listen().is_ok());
}

fn test_without_bind(family: IpAddressFamily) {
// Without an explicit bind, `listen` will assign an ephemeral
// port.
let sock = TcpSocket::create(family).unwrap();
assert!(sock.get_local_address().is_err());
assert!(sock.listen().is_ok());
assert!(sock.get_local_address().is_ok());
}

async fn test_inherited_properties(family: IpAddressFamily) {
let addr = IpSocketAddress::localhost(family, 0);
let sock = TcpSocket::create(family).unwrap();
sock.bind(addr).unwrap();
let client = TcpSocket::create(family).unwrap();
let local_addr = sock.get_local_address().unwrap();

let mut accept = sock.listen().unwrap();
futures::join!(
async {
let next = accept.next().await.unwrap();
assert_eq!(next.get_address_family(), sock.get_address_family());
assert_eq!(next.get_keep_alive_enabled(), sock.get_keep_alive_enabled());
assert_eq!(
next.get_keep_alive_idle_time(),
sock.get_keep_alive_idle_time()
);
assert_eq!(
next.get_keep_alive_interval(),
sock.get_keep_alive_interval()
);
assert_eq!(next.get_keep_alive_count(), sock.get_keep_alive_count());
assert_eq!(next.get_hop_limit(), sock.get_hop_limit());
// TODO: In wasmtime, these values are different, even though according to the
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to investigate a bit more prior to creating an issue upstream. See spec: https://github.com/WebAssembly/WASI/blob/main/proposals/sockets/wit-0.3.0-draft/types.wit#L239

// spec they should be inherited.
// assert_eq!(next.get_receive_buffer_size(), sock.get_receive_buffer_size());
// assert_eq!(next.get_send_buffer_size(), sock.get_send_buffer_size());
},
async {
client.connect(local_addr).await.unwrap();
}
);
}

fn test_listening(fam: IpAddressFamily) {
let addr = IpSocketAddress::localhost(fam, 0);
let sock = TcpSocket::create(fam).unwrap();
sock.bind(addr).unwrap();
sock.listen().unwrap();
let result = sock.listen();
assert!(matches!(result, Err(ErrorCode::InvalidState)));
}

impl sockets::exports::wasi::cli::run::Guest for Component {
async fn run() -> Result<(), ()> {
test_with_bind(IpAddressFamily::Ipv4);
test_with_bind(IpAddressFamily::Ipv6);
test_without_bind(IpAddressFamily::Ipv4);
test_without_bind(IpAddressFamily::Ipv6);
test_inherited_properties(IpAddressFamily::Ipv4).await;
test_inherited_properties(IpAddressFamily::Ipv6).await;
test_listening(IpAddressFamily::Ipv4);
test_listening(IpAddressFamily::Ipv6);
Ok(())
}
}

fn main() {
unreachable!()
}
13 changes: 13 additions & 0 deletions tests/rust/wasm32-wasip3/src/bin/sockets-tcp-receive.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"proposals": [
"sockets"
],
"operations": [
{
"type": "run"
},
{
"type": "wait"
}
]
}
Loading
Loading