sim-platform-linux 0.1.0

Bounded Linux mechanics for SIM platform capsules
Documentation
// conformance: Linux transport realizes typed socket, DNS, IPC, and clock ports.

use sim_kernel::{Cx, DefaultFactory, EagerPolicy, HandleSeed};
use sim_lib_server::{ServerAddress, ServerTransport, TcpServerTransport};
use sim_platform_linux::{LinuxDnsPort, LinuxIpcPort, LinuxSocketPort, bind_transport_services};
use sim_transport_ports::{
    DnsPort, Half, IpcAddress, IpcPort, SocketAddress, SocketPort, TransportErrorKind,
};
use std::{
    io::{Read, Write},
    net::{IpAddr, Ipv4Addr},
    sync::Arc,
    time::Duration,
};

#[test]
fn server_tcp_transport_uses_the_explicit_linux_capsule() {
    bind_transport_services().unwrap();
    let listener = TcpServerTransport::bind(ServerAddress::Tcp {
        host: "127.0.0.1".to_owned(),
        port: 0,
    })
    .unwrap();
    let ServerAddress::Tcp { host, port } = listener.address() else {
        panic!("expected the bound TCP address");
    };
    let client = LinuxSocketPort
        .connect_tcp(&SocketAddress::Ip {
            address: host.parse().unwrap(),
            port: *port,
        })
        .unwrap();
    let mut cx = Cx::new(
        Arc::new(EagerPolicy),
        Arc::new(DefaultFactory),
        HandleSeed::new(0x5a4f_50a1_71a0_0001),
    );
    let _server = loop {
        if let Some(connection) = listener
            .accept_timeout(&mut cx, Duration::from_millis(25))
            .unwrap()
        {
            break connection;
        }
    };
    client.shutdown(Half::Both).unwrap();
    listener.shutdown(&mut cx).unwrap();
}

#[test]
fn tcp_udp_dns_and_native_errors_are_capsule_owned() {
    let port = LinuxSocketPort;
    let loopback = SocketAddress::Ip {
        address: IpAddr::V4(Ipv4Addr::LOCALHOST),
        port: 0,
    };
    let listener = port.listen_tcp(&loopback).unwrap();
    let bound = listener.local_address().unwrap();
    let mut client = port.connect_tcp(&bound).unwrap();
    let mut server = loop {
        if let Some(stream) = listener.accept().unwrap() {
            break stream;
        }
    };
    client.write_all(b"same-frame-bytes").unwrap();
    let mut capture = [0; 16];
    server.read_exact(&mut capture).unwrap();
    assert_eq!(&capture, b"same-frame-bytes");

    let udp = port.bind_udp(&loopback).unwrap();
    assert_ne!(udp.local_address().unwrap(), loopback);
    assert!(!LinuxDnsPort.resolve("localhost", 9).unwrap().is_empty());

    let collision = port.listen_tcp(&bound).err().unwrap();
    assert_eq!(collision.kind, TransportErrorKind::AddressInUse);
}

#[test]
fn ipc_variants_do_not_share_a_path_contract() {
    let error = LinuxIpcPort
        .connect(&IpcAddress::WindowsPipe("sim-test".into()))
        .err()
        .unwrap();
    assert_eq!(error.kind, TransportErrorKind::Unsupported);

    #[cfg(unix)]
    {
        let path = std::env::temp_dir().join(format!("sim-platform-ipc-{}", std::process::id()));
        let _ = std::fs::remove_file(&path);
        let listener = LinuxIpcPort
            .listen(&IpcAddress::UnixPath(path.clone()))
            .unwrap();
        let mut client = LinuxIpcPort
            .connect(&IpcAddress::UnixPath(path.clone()))
            .unwrap();
        let mut server = loop {
            if let Some(stream) = listener.accept().unwrap() {
                break stream;
            }
        };
        client.write_all(b"ipc").unwrap();
        let mut bytes = [0; 3];
        server.read_exact(&mut bytes).unwrap();
        assert_eq!(&bytes, b"ipc");
        listener.close().unwrap();
        std::fs::remove_file(path).unwrap();
    }
}