# dissonance
An async-friendly Rust library for generating Noise-encrypted transport
protocols. It provides tools for:
- creating an [`AsyncRead`] + [`AsyncWrite`] Noise socket
- creating a [`Stream`] + [`Sink`] abstraction layer on top of it (with
separate sender and responder message types).
[`AsyncRead`]: tokio::io::AsyncRead
[`AsyncWrite`]: tokio::io::AsyncWrite
[`Stream`]: futures::stream::Stream
[`Sink`]: futures::sink::Sink
## Quickstart - Encrypted raw byte streams with [`NoiseSocket`]
In order to create a proper transport you first need to obtain a
[`NoiseSocket`]. This is done through the [`NoiseBuilder`] interface.
[`NoiseSocket`]: crate::noise_session::NoiseSocket
[`NoiseBuilder`]: crate::noise_session::NoiseBuilder
NoiseBuilder requires a reliable underlying transport protocol to work!
Suppose you want to upgrade your plain [`TcpStream`] to a Noise one as an
initiator by performing an IK Noise handshake. For this, you can use the
[`NoiseBuilder::build_as_initiator()`] method after supplying the necessary
handshake data as follows:
[`NoiseBuilder::build_as_initiator()`]:
crate::noise_session::NoiseBuilder::build_as_initiator
[`TcpStream`]: https://docs.rs/tokio/latest/tokio/net/struct.TcpStream.html
```rust
let socket = NoiseBuilder::::new(my_keys, my_tcp_stream)
.set_my_type(NoiseSelfType::I)
.set_peer_type(NoisePeerType::K(peer_key))
.build_as_initiator().await?;
```
The resulting `socket` provides a unified `AsyncRead + AsyncWrite` interface
for transporting raw bytes over an encrypted channel.
## Abstracting away the bytes with [`NoiseTransport`]
An [`AsyncRead`] + [`AsyncWrite`] interface is often not enough for creating
a proper communication protocol. We want to send and receive messages of known
types that aren't necessarily the same between the sender and the responder.
[`NoiseTransport`]: crate::noise_transport::NoiseTransport
As a toy example - suppose the sender is a client and the responder is
a server. Each client can request the current date from the server. The
server can then respond with a formatted date encoded in a [`String`].
Let's encode each message type by leveraging Rust's type system:
```rust
#[derive(Serialize, Deserialize)]
enum Request {
GetDateTime
}
#[derive(Serialize, Deserialize)]
enum Response {
DateTime(String)
}
```
By encoding each message in an `enum` we can add more requests and more
responses later. By separating request and response types from each other we
won't have to match requests when waiting for a response and vice-versa.
Both `Request` and `Response` need to be serializable and deserializable in
order to send them through a socket. This is done through Serde's
[`Serialize`] and [`Deserialize`] traits.
[`Serialize`]: serde::Serialize
[`Deserialize`]: serde::Deserialize
We can now create a [`NoiseTransport`] that abstracts away the bytes and
turns the [`AsyncRead`] + [`AsyncWrite`] Noise socket into a unified
[`Stream`] + [`Sink`] interface:
```rust
let transport = NoiseTransport::::new(socket);
```
That was easy! We can now use the [`StreamExt`] and [`SinkExt`] traits to
send and receive our messages:
```rust
transport.send(Request::GetDateTime).await?;
let response: Response = transport.next().await?;
```
[`StreamExt`]: futures::stream::StreamExt
[`SinkExt`]: futures::sink::SinkExt
## Using the underlying socket of a [`NoiseTransport`] directly
Suppose you want to send a large file (a couple of gigabytes) to the server.
Encoding this file as a vector of bytes would not only be extremely inefficient,
it would also be impossible since either the sender or receiver could run out
of RAM. In this case the best way to send this file would be to copy it with
[`tokio::io::copy()`](https://docs.rs/tokio/latest/tokio/io/fn.copy.html)
by using the underlying socket directly.
You can get a temporary mutable reference to the underlying socket by calling
[`NoiseTransport::get_mut()`]. You can then copy the file as you normally
would by using the `copy()` method:
```rust
let mut socket = transport.get_mut();
tokio::io::copy(&mut my_file, &mut socket).await?
```
[`NoiseTransport::get_mut()`]:
crate::noise_transport::NoiseTransport::get_mut()
## Technical details
Sending messages over a stream requires serialization. This is done using
a combination of [Serde](https://docs.rs/serde/latest/serde/) with the
[Postcard](https://docs.rs/postcard/latest/postcard/) serializer.
Individual Noise messages are framed with a big endian `u16`
[`LengthDelimitedCodec`]. Then, each Noise message pack (a set of consecutive
Noise frames) gets framed with a big endian `u32` [`LengthDelimitedCodec`].
This double framing is required for sending large byte streams, as per the
previous section.
[`LengthDelimitedCodec`]: tokio_util::codec::LengthDelimitedCodec
License: BSD-3-Clause