I implemented Twitter in the woods using military radios

2018-02-0415 minute read

If you've been in the woods communicating with others using radios, you'll be quite familiar with the term hot micing.

If someone is talking on the radio, it broadcasts to everyone. The next person can speak only when the first person is done.

So if someone puts their radio in their not-so-spacious backpack, the "press to talk"-button might be pushed down permanently. And there you have it. Backpack noises broadcasting live. The entire radio network is rendered useless.

Quite fragile? Well, there are many solutions to this, and the problem can be easily eliminated.

However, the consept of "one person broadcasts at a time" is still the standard for radios. This is useful in some cases, but also has some shortcomings

  • Missing messages 🔎 – If you're out of reach when something is said, you'll miss it. Period. You cannot replay. The information is lost into the ether, as radio waves gets absorbed by the trees and atmosphere.
  • Waiting for your turn ⏳ – If there's an ongoing conversation, and you have some information that is not urgent, you probably don't want to disturb the ongoing conversation. As there is no other option but to wait, you go to make some coffee. By the end of it, you forgot what it was all about.
  • No message history 🗂️ – Your radio beeps with a "small battery"-warning. You decide to change batteries. Waiting for the radio to reboot you start to wonder: "Did I miss anything while my radio was offline?"

These shortcoming becomes more noticable as the group grows and more radios are added to the network.

Moving to text based communication

I was tinkering with my radio and found a RJ-45 interface. The radio I was using was a Kongsberg MH600 VHF radio. It's a bit heavy, but quite reliable. Luckily, it supports sending and receiving IP traffic.

So I went to the drawing board and roughly sketched out something that kind of looked like Twitter. A message input, a message queue and a message feed.

The design is simple, yet effective for solving the issues described above:

  • Missing messages 🔎 – The message queue with automatic retries will ensure that your messages are received by others.
  • Waiting for your turn ⏳ – You no longer have to wait for conversations to finish before sending message.
  • No message history 🗂️ – The feed will give you a better overview of the ongoing conversation.

So how do we implement this?

Now let's dive into the technical details. The first thing to ask ourselves is: how do we even send a message to the other radios? We'll need to have some sort of reliable data transfer (RDT), so let's try out a simple TCP socket.

Hmm, looks like we're having a lot of spurious retransmissions. It's taking up a lot of our bandwith actually. We have to get rid of this.

Looking further into it, it seems like the TCP implementation of Windows (as we we're testing on here) simply cannot handle the extreme volatility in round trip time (RTT) between the radios. At one point, RTT was 100ms, the next second, it was 1300ms. The network congestion avoidance mechanism in TCP does not tolerate this variance in RTT, resulting in retransmission of packets that are in transmission.

Okey, so we either ditch TCP or reimplement TCP in user space.

We ditch TCP.

So then we're opting for UDP instead. This requires us to implement some mechanism to ensure RDT, as UDP is simply "fire and forget". Packet loss is simply not handled by UDP, and packet loss will happen with radios in the forest.

How could we implement reliable data transfer (RDT)?

In my case, there is always at least one radio that has a direct connection to all other radios. In the sketch below, it is radio A.

This makes everything easier, as one radio has to be the authrotitative source of truth. This way, deciding "what has been sent or not" can be left to just asking this radio. If radio A has not received your message, it is not considered "received".

If your network does not have a radio with connection to everyone, I would advise you to establish a repeater or something like that. It just makes everything a lot easier if you have an authoritative source of truth. Or let's just call this "the server".

If all radios have a direct connection to every other radio, then we'll just pick one. Preferrably the one in the center geographically.

How to reliably send messages to the server radio?

As we now have one radio to act as a server, we'll implement the RDT for sending messages to this server. The rudimentary implementation of this is simple: client keeps sending message until a receival receipt is returned from the server, more commonly known as ACK-messages.

How to reliably distribute the messages received at the server?

Since the sending radio might not have a direct connection to every other radio in the network, the server radio should broadcast the received message once more. This way, everyone should receive it at least once.

However, packet loss can happen at this point also. And we don't want an ACK from every radio in the network. That simply doesn't scale.

Hmm, well, if a packet is lost, it will likely be followed by subsequent packet losses. Packet losses could be caused by empty batteries, passing a geographic obstacle, or any other "time framed" reason. It rarely happens randomly without any following packet losses. They tend to bundle up, like dust bunnies.

What if the receiving radios were responsible for the RDT? The server simply repeats the incoming message, and does not care if anyone was offline. Then it's left as a task for the client radios to figure out if they missed a packet. We don’t want the clients polling the server however, we dont have enough bandwidth for that.

… wait a second. If the client has not received any messages, and are not allowed to poll the server, it's simply not possible for it to be aware of the loss? Or is it?

What if messages includes a reference to the previous message. This way, the clients will be able to discover packet losses by passively listening for new messages! 💡

Okey, so let's implement this mechanism by hashing new messages together with the previous message. Then a client can do the same procedure and compare the hash with the received one. If they differ, it knows its message history diverges from the server. Just like a blockchain.

So let's imagine a radio is behind some mountain, and not receiving any packets at the moment.

At a later point, when that radio gets back online, a new message will be sent. When the radio receives this message, it will notice that it has lost a previous packet thanks to our blockchain. This will trigger a "SYNC"-request to the server radio so the message history can be fetched.

But what if no new messages are sent?

Our server to client RDT mechanism now relies on some traffic in order to function. If a packet is lost, and followed by a long pause in traffic, the unlucky radio will never discover the packet loss.

So, let's fix this by ensuring at least some traffic every minute or so.

If no message has been received by the server for the last 60 seconds, then the server sends a "HEARTBEAT"-message, allowing the clients to

  1. determine if they're online (can hear the server radio)
  2. discover packet loss

Demonstration

I implemented the described RDT mechanisms, and a simple user interface to go with it. Time for some videos demonstrating the application!

First video, I'll be testing the "happy path". Everyone is online, messages are sucesfully broadcasted. To spice things up, let's send a special "ALARM"-message that makes all clients beep 🔔

Next up, let's test out the "retry until ACK received" from client to server

Lastly, let's test the packet loss detection mechanism when new messages are broadcasted.

Summary

In this project I implemented a very simple Twitter-like application for use in networks with very low bandwidth and high packet loss rate.

In the end, our application is very reliant on a server being always available. If the server disappears, then application will stop functioning for everyone until its online again. This is a weakness with this design choice. When the server comes back online however, messages will eventually be stored in history and broadcasted to the network.

If you have any questions about this project, or just want to reach out to me, send a message on LinkedIn.

Hey, I'm Magnus, a developer from Norway.

I'm currently employed at Fink AS.

I also write about technical stuff in general

Newtype or newtrait? Ways around the orphan rule

2024-10-203 minute read

Can we work around the orphan rule in Rust using traits instead of newtypes?

Read more

I made an AI chatbot answering questions for employees at our company

2023-10-219 minute read

In this article, I'll explain to you how I used OpenAI embeddings and completions API to implement an AI chatbot answering questions for employees at our company. The code examples I'll show are written in Typescript, but the same principles apply to any programming language.

Read more

Kunstig Humor – Improv theater meets AI

2023-10-184 minute read

This fall, I've been assisting the comedy group Vrøvl in setting up a show where improvisers and the audience interact with AI live on stage.

Read more

My first stab at 3D game development

2023-08-233 minute read

I wanted to learn about 3D game development, so I set out on a small project inspired by a friend of mine who lives on a farm. His name is Gunnar and he lives on a farm called Steinseth Gård.

Read more

How most Rust projects are organized (Part 2)

2022-01-102 minute read

In part 2, I manually inspected a selection of Rust projects looking for patterns in how files and folders usually are structured.

Read more

How most Rust projects are organized

2022-01-072 minute read

I collected data from GitHub.com to see what resides in most Rust projects src folder.

Read more