sansio 1.0.1

sansio — an architectural pattern for writing protocol implementations that are completely decoupled from I/O operations
Documentation
<h1 align="center">
 <a href="https://webrtc.rs"><img src="https://raw.githubusercontent.com/webrtc-rs/sansio/master/sansio-black.png" alt="sansio"></a>
 <br>
</h1>
<p align="center">
 <a href="https://github.com/webrtc-rs/sansio/actions">
  <img src="https://github.com/webrtc-rs/sansio/workflows/cargo/badge.svg">
 </a>
 <a href="https://crates.io/crates/sansio">
  <img src="https://img.shields.io/crates/v/sansio.svg">
 </a>
 <a href="https://docs.rs/sansio">
  <img src="https://docs.rs/sansio/badge.svg">
 </a>
 <a href="https://doc.rust-lang.org/1.6.0/complement-project-faq.html#why-dual-mitasl2-license">
  <img src="https://img.shields.io/badge/license-MIT%2FApache--2.0-blue" alt="License: MIT/Apache 2.0">
 </a>
</p>
<p align="center">
 Rust in Sans-IO
</p>

# What is Sans-IO?

**Sans-IO** (French for "without I/O") is an architectural pattern for writing protocol implementations that are
completely decoupled from I/O operations. This makes protocols:

- **Testable**: Test protocol logic without mocking sockets or async runtimes
- **Portable**: Run the same protocol code in sync, async, embedded, or WASM environments
- **Composable**: Stack and combine protocol layers easily
- **Debuggable**: Step through protocol state machines without I/O side effects

## Features

- Clean push-pull API for message handling
- Zero-cost abstractions with generic type parameters
- Support for custom events and timeout handling
- Works with any I/O backend (sync, async, embedded)
- No dependencies, minimal footprint
- Fully documented with examples
- **`no_std` by default** - works in any environment

## no_std Support

This crate is `no_std` by default and works seamlessly in any environment - embedded systems,
bare-metal applications, WASM, or standard applications.

### Time Handling

The `Time` associated type is fully generic, so you can use any time representation that fits
your environment:

**Using `std::time::Instant`:**

```rust
impl Protocol<Vec<u8>, Vec<u8>, ()> for MyProtocol {
    type Time = std::time::Instant;
    // ...
}
```

**Using tick counts (embedded):**

```rust
impl Protocol<Vec<u8>, Vec<u8>, ()> for MyProtocol {
    type Time = u64;  // System ticks
    // ...
}
```

**Using milliseconds:**

```rust
impl Protocol<Vec<u8>, Vec<u8>, ()> for MyProtocol {
    type Time = i64;  // Milliseconds since epoch
    // ...
}
```

**No timeout needed:**

```rust
impl Protocol<Vec<u8>, Vec<u8>, ()> for MyProtocol {
    type Time = ();  // Unit type when timeouts aren't used
    // ...
}
```

## The Protocol Trait

The `Protocol` trait provides a simplified Sans-IO interface for building network protocols:

```rust
pub trait Protocol<Rin, Win, Ein> {
    type Rout;  // Output read type
    type Wout;  // Output write type
    type Eout;  // Output event type
    type Error; // Error type
    type Time;  // Time type (u64, Instant, i64, (), etc.)

    // Push data into protocol
    fn handle_read(&mut self, msg: Rin) -> Result<(), Self::Error>;
    fn handle_write(&mut self, msg: Win) -> Result<(), Self::Error>;
    fn handle_event(&mut self, evt: Ein) -> Result<(), Self::Error>;
    fn handle_timeout(&mut self, now: Self::Time) -> Result<(), Self::Error>;

    // Pull results from protocol
    fn poll_read(&mut self) -> Option<Self::Rout>;
    fn poll_write(&mut self) -> Option<Self::Wout>;
    fn poll_event(&mut self) -> Option<Self::Eout>;
    fn poll_timeout(&mut self) -> Option<Self::Time>;

    // Lifecycle
    fn close(&mut self) -> Result<(), Self::Error>;
}
```

## Quick Start

Add `sansio` to your `Cargo.toml`:

```toml
[dependencies]
sansio = "1"
```

## Example: Uppercase Protocol

A simple protocol that converts incoming strings to uppercase:

```rust
use sansio::Protocol;
use std::collections::VecDeque;

#[derive(Debug)]
struct MyError;
impl std::fmt::Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "MyError")
    }
}
impl std::error::Error for MyError {}

/// A simple uppercase protocol: converts incoming strings to uppercase
struct UppercaseProtocol {
    routs: VecDeque<String>,
    wouts: VecDeque<String>,
}

impl UppercaseProtocol {
    fn new() -> Self {
        Self {
            routs: VecDeque::new(),
            wouts: VecDeque::new(),
        }
    }
}

impl Protocol<String, String, ()> for UppercaseProtocol {
    type Rout = String;
    type Wout = String;
    type Eout = ();
    type Error = MyError;
    type Time = ();  // No timeout handling needed

    fn handle_read(&mut self, msg: String) -> Result<(), Self::Error> {
        // Process incoming message
        self.routs.push_back(msg.to_uppercase());
        Ok(())
    }

    fn poll_read(&mut self) -> Option<Self::Rout> {
        // Return processed message
        self.routs.pop_front()
    }

    fn handle_write(&mut self, msg: String) -> Result<(), Self::Error> {
        // For this simple protocol, just pass through
        self.wouts.push_back(msg);
        Ok(())
    }

    fn poll_write(&mut self) -> Option<Self::Wout> {
        self.wouts.pop_front()
    }
}

// Usage example
fn main() {
    let mut protocol = UppercaseProtocol::new();

    // Push data in
    protocol.handle_read("hello world".to_string()).unwrap();

    // Pull results out
    assert_eq!(protocol.poll_read(), Some("HELLO WORLD".to_string()));
}
```

## Testing Made Easy

Sans-IO protocols are trivial to test because they don't involve any I/O:

```rust
#[test]
fn test_uppercase_protocol() {
    let mut protocol = UppercaseProtocol::new();

    // Test single message
    protocol.handle_read("test".to_string()).unwrap();
    assert_eq!(protocol.poll_read(), Some("TEST".to_string()));

    // Test multiple messages
    protocol.handle_read("hello".to_string()).unwrap();
    protocol.handle_read("world".to_string()).unwrap();
    assert_eq!(protocol.poll_read(), Some("HELLO".to_string()));
    assert_eq!(protocol.poll_read(), Some("WORLD".to_string()));
    assert_eq!(protocol.poll_read(), None);
}
```

## Use Cases

- **Network Protocols**: HTTP, WebSocket, custom TCP/UDP protocols
- **Message Parsers**: Protocol buffers, JSON-RPC, custom formats
- **State Machines**: Connection handling, handshakes, negotiations
- **Protocol Testing**: Unit test protocol logic without network I/O
- **Embedded Systems**: Protocols that need to work with both blocking and non-blocking I/O
- **WASM**: Browser-based protocol implementations

## Why Sans-IO?

Traditional protocol implementations mix I/O and protocol logic:

```rust
// Traditional approach - tightly coupled to async I/O
async fn handle_connection(mut stream: TcpStream) {
    let mut buf = [0u8; 1024];
    while let Ok(n) = stream.read(&mut buf).await {
        // Protocol logic mixed with I/O
        let response = process(&buf[..n]);
        stream.write_all(&response).await.unwrap();
    }
}
```

Sans-IO separates concerns:

```rust
// Sans-IO approach - protocol logic is independent
struct MyProtocol {
    /* ... */
}
impl Protocol<Vec<u8>, Vec<u8>, ()> for MyProtocol { /* ... */ }

// I/O layer is separate and can be swapped
async fn handle_connection(mut stream: TcpStream, mut protocol: MyProtocol) {
    let mut buf = [0u8; 1024];
    while let Ok(n) = stream.read(&mut buf).await {
        protocol.handle_read(buf[..n].to_vec()).unwrap();
        while let Some(response) = protocol.poll_write() {
            stream.write_all(&response).await.unwrap();
        }
    }
}
```

Benefits:

- Protocol logic can be tested without async runtime
- Same protocol works with sync, async, or embedded I/O
- Easier to debug and reason about
- Protocol layers can be composed and reused

## Documentation

Full API documentation is available at [docs.rs/sansio](https://docs.rs/sansio)

## License

Licensed under either of:

- Apache License, Version 2.0 ([LICENSE-APACHE]../LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT]../LICENSE-MIT or http://opensource.org/licenses/MIT)

at your option.