Skip to content
Merged
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
18 changes: 9 additions & 9 deletions src/app/blog/message-framing-tutorial/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Message framing is a generally applicable concept, and by no means limited to wo

# Framed Messages

QUIC connections are made of streams and those stream can be bi-directional or uni-directional. Bi-directional streams are streams you can read from and write to in both directions. Uni directional streams are streams that can only be written to on one side and read from by the other.
QUIC connections are made of streams and those streams can be bi-directional or uni-directional. Bi-directional streams are streams you can read from and write to in both directions. Uni-directional streams are streams that can only be written to on one side and read from by the other.

A single QUIC connection to another node can have many streams, some uni-directional, some bi-directional.

Expand All @@ -60,7 +60,7 @@ The first thing you might do when trying to get familiar with working with strea

This is fine while you are getting familiar, but when you go to write your protocols you will want something more sophisticated.

What we really want to do is send multiple logical message on the stream, and have some format for figuring out how to separate out those messages. We also must deal with the fact that not all messages are going to be the same length.
What we really want to do is send multiple logical messages on the stream, and have some format for figuring out how to separate out those messages. We also must deal with the fact that not all messages are going to be the same length.

That's where message framing comes in. In message framing, you prepend the *length*, in bytes, of your message data to your message before sending over the data. On the receiving side, you read that *length* data first, then you create or adjust your buffer to accommodate that length, and finally read the exact number of bytes off your stream and into your buffer.

Expand Down Expand Up @@ -95,7 +95,7 @@ async fn main() -> anyhow::Result<()> {

This doesn't do anything yet, but run `cargo run` to make sure everything is peachy!

For this example, we are are going to show the *send* and *receive* sides in the same main function. This just makes the example easier to illustrate and keeps it down to a single file. Normally, we would create a whole CLI with different commands associated with the send and receive sides. Our examples of [dumbpipe](https://github.com/n0-computer/dumbpipe) and [sendme](https://github.com/n0-computer/sendme) show this pattern off nicely.
For this example, we are going to show the *send* and *receive* sides in the same main function. This just makes the example easier to illustrate and keeps it down to a single file. Normally, we would create a whole CLI with different commands associated with the send and receive sides. Our examples of [dumbpipe](https://github.com/n0-computer/dumbpipe) and [sendme](https://github.com/n0-computer/sendme) show this pattern off nicely.

For this demo, we are going to set up the receiving side, set up the sending side, write on the sending side, read from the receiving side, and then clean up and close down.

Expand Down Expand Up @@ -136,7 +136,7 @@ The `addr` is the `NodeAddr` of the receive endpoint, which contains all the add

Now that we have a connection, let's open a stream and send some data!

We can use the `Connection::open_bi` to create a bi-directional stream, or `Connection::open_uni` to create a uni-directional stream. In this case, we will create a uni-directional stream.
We can use the `Connection::open_bi` to create a bi-directional stream, or `Connection::open_uni` to create a uni-directional stream. In this case, we will create a uni-directional stream.

To be more explicit, using `open_bi` would give us both a `SendStream` and `RecvStream`, while using `open_uni` will give us only a `SendStream`. In our example, we will be using `open_uni`.

Expand Down Expand Up @@ -225,7 +225,7 @@ impl ProtocolHandler for SmolProtocol {
connection: Connection,
) -> Result<(), AcceptError> {
todo!();
};
}
}
```

Expand Down Expand Up @@ -309,7 +309,7 @@ Thankfully, message framing is so popular that `tokio` provides extension traits

You'll note that by using a `u8`, we are limiting our message size to 255 bytes. In a protocol that sends larger messages, you would want to use a larger integer size. (Check out [varints](https://en.wikipedia.org/wiki/Variable-length_quantity) that use space-saving techniques when sending smaller numbers, but allow you to express very large numbers as well.)

But it's `u8`s for us in this exampler! So first, we will ensure that the length of the message is smaller than an 8-bit integer. Then, we write the `u8` using the `write_u8` method, and then we will use `write_all` to write the entire contents of the message onto the stream:
But it's `u8`s for us in this example! So first, we will ensure that the length of the message is smaller than an 8-bit integer. Then, we write the `u8` using the `write_u8` method, and then we will use `write_all` to write the entire contents of the message onto the stream:

```rust
use iroh::endpoint::SendStream;
Expand All @@ -332,7 +332,7 @@ Next, we create a buffer the size of the `u8` that we read. Then, we read that n
We then need to create a UTF-8 string from the bytes in the buffer. Finally, we return the `String`:

```rust
use iroh::endpoint::ReadStream
use iroh::endpoint::RecvStream;
use tokio::io::AsyncReadExt;

async fn read_frame(stream: &mut RecvStream) -> anyhow::Result<Option<String>> {
Expand All @@ -348,7 +348,7 @@ async fn read_frame(stream: &mut RecvStream) -> anyhow::Result<Option<String>> {
}
```

# implementing `SmolProtocol`
# Implementing `SmolProtocol`

Time to implement the accept handler on `SmolProtocol`. This is what will get called each time we get an incoming connection to the `Router`.

Expand Down Expand Up @@ -474,4 +474,4 @@ impl ProtocolHandler for SmolProtocol {

We hope you've learned a bit about writing protocols on this journey, specifically how framed messages are an incredibly useful technique.

In this example, we sent simple strings on our streams, but in a real-world use case, we often send structured data. For a more in-depth example exploring how you might send rtructured data, including how we at n0 like to serialize and deserialize data to and from the wire, take a look at the [framed messages](https://github.com/n0-computer/iroh-examples/tree/main/framed-messages) example in `iroh-examples`.
In this example, we sent simple strings on our streams, but in a real-world use case, we often send structured data. For a more in-depth example exploring how you might send structured data, including how we at n0 like to serialize and deserialize data to and from the wire, take a look at the [framed messages](https://github.com/n0-computer/iroh-examples/tree/main/framed-messages) example in `iroh-examples`.
Loading