stun-agent 0.1.1

Rust Sans I/O framwework to implement STUN agents
Documentation
# Crate stun-agent

## STUN Agent library for Rust.

This crate provides a STUN I/O-free protocol implementation. An I/O-free protocol implementation,
often referred to as a [Sans-IO](https://sans-io.readthedocs.io/index.html) implementation, is a
network protocol implementation that contains no code for network I/O or asynchronous flow control.
This means the protocol implementation is agnostic to the underlying networking stack and can be
used in any environment that provides the necessary network I/O and asynchronous flow control.

These STUN agents are desi∫gned for use in a client-server architecture where the client sends a
request and the server responds. This sans-IO protocol implementation is defined entirely in terms
of synchronous functions returning synchronous results, without blocking or waiting for any form of
I/O. This makes it suitable for a wide range of environments, enhancing testing, flexibility,
correctness, re-usability and simplicity.

This library currently provides support for writing STUN clients. Support for writing servers is not
yet implemented. The main element of this library is:

# StunClient

A STUN client is an entity that sends STUN requests and receives STUN responses and STUN indications.
A STUN client can also send indications. This is the main entity used to interact with the STUN server.
This class provides the tools required to implement different STUN
[usages](https://datatracker.ietf.org/doc/html/rfc8489#section-13) over the STUN protocol
in an easy and efficient way.

# API considerations

Since the **StunClient** abstains from performing any I/O operations, the controller assumes responsibility
for managing input and output buffers, timeouts, and client-generated events. The implementation of this
controller is entirely at the user’s discretion and does not enforce the use of any specific I/O stack or
asynchronous framework. This abstraction imposes certain guidelines to ensure the protocol’s proper
functioning. Consequently, users must consider the following technical aspects:

* The controller must capture and handle any events that the client may generate after interacting with
the library.
* The controller must handle the input and output buffers that the client will use to send and receive data
from the server.
* Timing management falls under the controller’s jurisdiction, as the client lacks internal time-handling
mechanisms. The controller must define transaction timeouts and inform the client upon their expiration.
For supporting timed events, the API exposes an **Instant** parameter to the controller, facilitating specification
of event occurrence times.

# Design considerations

Most Sans I/O implementations are structured around a state machine that responds to events generated by both
the client and the server. Each event triggers the generation of output buffers, timers, or additional events.
This foundational concept is illustrated in the following API:

```rust
let events = handle_data(&in_bytes);
let out_bytes = perform_action();
```

However, the STUN requirements introduce complexity to the API. The aforementioned API alone does not suffice
to manage STUN intricacies. For instance, the handle_data function might fail and trigger events even in case of
failures. The STUN client needs to manage these events and generate further events for the controller. This
implementation could have been realized as follows:

```rust
fn handle_data(in_bytes: &[u8])
-> Result<Vec<StuntClientEvent>, (StunAgentError, Vec<StuntClientEvent>)> {
// Implementation
}
```

The design of this API necessitates that the caller manages both errors and the events they generate. This approach
can lead to increased complexity and maintenance challenges in the caller’s code. For instance, the caller may employ
a match expression when invoking the function to handle both success outcomes and the errors and resulting events in
case of failure:

```rust
let response = handle_data(&in_bytes);
match response {
  Ok(events) => {
    handle_events(events);
  },
  Err((error, events)) => {
    handle_error(error);
    handle_events(events);
  },
}
```

As observed, managing events in both success and failure scenarios indicates a sub-optimal design. Consequently,
the STUN client API is structured to enable the caller to pull events generated by the client. While this approach
offers a more ergonomic event handling mechanism, it requires the caller to actively retrieve and process events from
the client.

```rust
fn handle_data(in_bytes: &[u8]) -> Result<ClientData, StunAgentError> {
// Implementation
}
```

And the controller’s code would look like this:

```rust
let data = handle_data(&in_bytes)?;
// Now we can pull events from the client
let events = pull_events();
```

Moreover, this type of API not only facilitates the retrieval of events but also allows for the retrieval of data
generated by the client. For instance, the **send_request** method returns the **TransactionId** of the request,
which the controller can use to manage outgoing transactions.

[!WARNING]
Events are overwritten whenever a new operation is performed on the client. Therefore, the controller must ensure
that all events are processed before initiating any new operations. In multi-threaded environments, the controller
must also synchronize operations and event retrieval to maintain consistency and prevent data loss.

# Input and Output

The STUN client does not perform any I/O operations. Instead, the controller is responsible for managing input and
output buffers. Memory allocation is delegated to the controller, which must provide the buffers used by the client.
This approach reduces the client’s memory footprint and enhances performance by enabling more sophisticated memory
management strategies, such as memory pools, where buffers can be reused to minimize memory allocation overhead.

# Timing Management
The STUN client does not manage timing internally. Instead, the controller is responsible for setting timeouts and
managing transaction timing. The API provides an **Instant** parameter to the controller, allowing it to specify event
occurrence times. Timing consistency across operations is crucial, meaning that time must monotonically increase to
ensure the proper functioning of the client.

Exposing the **Instant** parameter in the API might seem counter intuitive, as it requires the controller to manage time.
However, this design choice ensures that the client remains agnostic to time management, granting the controller full
control over the internal state machine. This approach facilitates comprehensive testing of complex scenarios by
enabling deterministic time control without the need to mock time.

# Timeouts
Timeouts specify the maximum duration the client will wait for an event to occur. The STUN client uses timeouts to
manage transactions and prevent indefinite waiting for responses. If a response is not received within the designated
timeout period, the client generates a timeout event, marking the transaction as failed. Timeouts are also employed to
manage re-transmissions of requests sent over unreliable transports. When the client needs to set a timeout for a
re-transmission, it generates a **RestransmissionTimeOut** event, which is then notified to the controller when the events
are pulled.

If multiple timeouts are scheduled, the client will only notify the controller of the most recent timeout. This approach
allows the controller to manage timeouts more efficiently, ensuring that only one timeout needs to be handled at a time.

Managing timeouts is the responsibility of the controller; the STUN client will only provide the timeout duration.
If the timeout is not canceled, the controller must call the **on_timeout** method to inform the client that the timeout
has been reached.

Timeouts are identified by a **TransactionId**. When a timeout is canceled for any reason, the client will notify the
controller either by setting a new timeout with a different **TransactionId** or by not setting any timeout event at all.

# Usage

The following example demonstrates how to create a STUN client and send a BINDING indication to a STUN server.

```rust
// We use a client builder to create a STUN client, for this example,
// the client will be used over an unreliable transport such as UDP.
// This client will no use any credential mechanism nor the FINGERPRINT
// attributes. Besides, we configure the default parameters for the
// re-transmission timeout.
let mut client = StunClienteBuilder::new(
    TransportReliability::Unreliable(RttConfig::default()))
    .build()
    .unwrap();

// We create a STUN BINDING indication to send to the server.
// According to the RFC8489, the BINDING indications does not require
// any attributes.
let mut attributes = StunAttributes::default();

// Since this is a library implementation without direct I/O operations,
// no input or output will be handled by the stack. Instead, we need to
// access the output buffer event provided by the client to send the data
// through the socket.
// Besides, no buffer allocations will be performed by the library, so the
// controller must provide the buffer that will be used to send the data.
// This allow the library to reduce the memory footprint and improve the
// performance, being flexible to allow more complex usages of memory such
// as memory pools where buffers can be reused to minimize the memory
// allocation overhead.
let buffer = vec![0; 1024];
client.send_indication(BINDING, attributes, buffer).unwrap();

// Pull events from the client
let events = client.events();

// Only one output packect event is expected. This event must contain the
// buffer that will be sent to the server. Because indications do not require
// a response, no timeouts will be set for this transaction.
assert_eq!(events.len(), 1);
let mut iter = events.iter();

// Next event already contains the buffer that needs to be send to the server.
let StuntClientEvent::OutputPacket(buffer) = iter
    .next()
    .expect("Expected event")
else {
    panic!("Expected OutputBuffer event");
};
```

In the following example we are going to use the STUN client to send a BINDING request to a STUN server. Requests
require a response from the server, so the client will set a timeout for the transaction. The response must arrive
before the timeout is reached, otherwise the client will generate a timeout event and will mark the transaction
as failed.

```rust
// We create a STUN BINDING request to send to the server.
// According to the RFC8489, the BINDING request does not require
// any attributes.
let instant = std::time::Instant::now();
let mut attributes = StunAttributes::default();
let buffer = vec![0; 1024];
let transaction_id = client
    .send_request(BINDING, attributes, buffer, instant)
    .unwrap();

// Pull events from the client
let events = client.events();

// Two events are expected, the first one is the output buffer event
// and the second one is the timeout event.
assert_eq!(events.len(), 2);
let mut iter = events.iter();
// Next event already contains the buffer that needs to be send to the server.
let StuntClientEvent::OutputPacket(buffer) = iter
    .next()
    .expect("Expected event")
else {
    panic!("Expected OutputBuffer event");
};
// Next event indicates that the user must set a timeout for the transaction
// identified by the transaction_id.
let StuntClientEvent::RestransmissionTimeOut((id, duration)) = iter
    .next()
    .expect("Expected event")
else {
    panic!("Expected RestransmissionTimeOut event");
};
assert_eq!(id, &transaction_id);

// Now the controller should set a timout of `duration` for the transaction
// identified by `id`. After the timeout is reached, the controller must call
// the `on_timeout` method to notify the client that the time has expired.

// We re going to simulate the timeout event by calling the `on_timeout` method.
let instant = instant + *duration;
client.on_timeout(instant);

// Pull events from the client
let events = client.events();

// Two events are expected, the first one is the retransmission of the requests,
// and the second one is the new timeout set for the transaction.
assert_eq!(events.len(), 2);
let mut iter = events.iter();

// Next event contains the buffer that needs to be retransmitted.
let StuntClientEvent::OutputPacket(buffer) = iter
    .next()
    .expect("Expected event")
else {
        panic!("Expected OutputBuffer event");
};
let StuntClientEvent::RestransmissionTimeOut((id, duration)) = iter
    .next()
    .expect("Expected event")
else {
   panic!("Expected RestransmissionTimeOut event");
};
assert_eq!(id, &transaction_id);
```

When sending over an unreliable transport, the client SHOULD re-transmit a STUN request message starting with an
interval of RTO (“Re-transmission TimeOut”), doubling after each re-transmission until a final timeout is reached.
By default, if the controller does not set a different value, the default timeout is 39500 ms for both, reliable and
not reliable transports. If the client has not received a response after that time, the client will consider the
transaction to have timed out, and an event of type **TransactionFailed** will be generated the next time that events
were pulled with the error **TimedOut** for the transaction.

To finish, the next example shows how to handle buffers received from the server. Raw buffers will be processed by
the client to generate events that can be pulled by the controller.

```rust
// Buffer received from the server
 let buffer = [
    0x00, 0x11, 0x00, 0x00, // BINDING Indication type and message length
    0x21, 0x12, 0xA4, 0x42, // Magic cookie
    0xB8, 0xC2, 0x8E, 0x1A, // }
    0x41, 0x05, 0x18, 0x56, // }  Transaction ID
    0x3E, 0xFC, 0xCF, 0x5D, // }
 ];

 // Process buffer
 client.on_buffer_recv(&buffer, Instant::now()).unwrap();

 // Pull events from the client
 let events = client.events();

 // There must be only one events with the STUN message received
 assert_eq!(events.len(), 1);

 let mut iter = events.iter();
 let StuntClientEvent::StunMessageReceived(msg) = iter
     .next()
     .expect("Expected event")
 else {
    panic!("Expected StunMessageReceived event");
 };
 assert_eq!(msg.method(), BINDING);
 assert_eq!(msg.class(), Indication);

 // No attributes in the message
 assert_eq!(msg.attributes().len(), 0);
```

# Contributing

Patches and feedback are welcome.

# Donations

If you find this project helpful, you may consider making a donation:

<img src="https://www.bitcoinqrcodemaker.com/api/?style=bitcoin&amp;address=bc1qx258lwvgzlg5zt2xsns2nr75dhvxuzk3wkqmnh" height="150" width="150" alt="Bitcoin QR Code">
<img src="https://www.bitcoinqrcodemaker.com/api/?style=ethereum&amp;address=0xefa6404e5A50774117fd6204cbD33cf4454c67Fb" height="150" width="150" alt="Ethereum QR Code">

# License

This project is licensed under either of
* [Apache License, Version 2.0]https://www.apache.org/licenses/LICENSE-2.0
* [MIT license]https://opensource.org/licenses/MIT

[![say thanks](https://img.shields.io/badge/Say%20Thanks-👍-1EAEDB.svg)](https://github.com/sancane/rustun/stargazers)