lrc

I’d like to super informally introduce live relay chat.

overview

If you think about using Google Docs with a friend, as you type, they see it immediately, and as they type, you see it immediately. At the highest level, LRC is a protocol that allows for that same kind of live communication, except with the minimal amount of structure for it to look like every other chat app that you were forced to install on your phone and computer for no good reason.

A little bit more technically speaking, Live relay chat (LRC) is an application-layer protocol for realtime, text-based communication, inspired by the internet relay chat (IRC) protocol. To recap how IRC works, clients connect to servers, clients send servers messages, servers relay the message to all other clients. LRC has this same basic relay model, but every time you edit your message message, your client sends a small update event to the server, which is then relayed to all other clients.

At the time of writing this blog post, LRC has one client, LunaRC, and server, lrcd, both of which I wrote. You can look at and play around with the source code on github, and there also executables if you don’t want to work with the source code. (Don’t judge me it’s messy, this is my first project in go & basically my first larger scope totally solo project, and there were probably better ways of making a CLI but I am a bit nuts and it’s fun to learn about escape codes)

specs

protocol

Server binds to port 927, and listens for TCP connections.
Communication occurs through LRC events.
Every LRC event originating from a client has a 2 byte header, and every event from a server has a 6 byte header. The first and last bytes are always the length of the event (including header), and the event type. The middle four bytes of the header are the message id that the event refers to.
there are currently 8 LRC event types:
0 - ping. Requests a pong, and also, if it comes from a server, can contain a status update. Payload (status update) is a string that can be 50 unicode code points at most (up to 200 bytes).
1 - pong. Response to a ping. No payload
2 - init. Initializes a message. Has a payload that consists of if the message originated in this client (1 byte, 1 if the event is being sent from a server to the client which sent the server the init event, and 0 otherwise), a color (1 byte, using the ansi color table), and a name (up to 12 unicode code points, which can be up to 48 bytes)
3 - pub. Publishes a message. No payload
4 - insert. Inserts a string into a message at a position. Has a payload that consists of a position (2 bytes), and a utf-8 string (as many complete unicode code points as you can fit into 247 bytes)
5 - delete. Deletes the character before the position. Has a payload that consists of a position (2 bytes)
6 - mute. Mutes all future events originating from the connection associated with a message. Has a payload that consists of a message id (4 bytes)
7 - unmute. Unmutes the connection associated with a message, if applicable. Has a payload that consists of a message id (4 bytes)

I hope that’s enough detail, I might’ve left out some intricacies. You can email me rachel@moth11.net, or I’m sure you can figure out some other way to contact me

additional notes on client and servers

This initial implementation of LRC has servers echo all messages back to the clients from which they originate. For backwards compatibility, LRC will likely continue to require this, and LunaRC currently requires this to render your messages, however this obviously introduces what feels like input latency on the client side (though really the input is processed immediately, we are just waiting for the round-trip to server before we render it) and it’s considered best practice to ignore your echoed messages and have a separate rendering path for user input.
LRC is designed in the image of IRC, so LRC servers are supposed to be super lightweight with no database. This means that if a client connects to a server while a different client is speaking, it’s possible that the new client will recieve events with message ids that do not correspond to initialized posts. It is considered best practice to render these posts to the best of your capabilities. In general, clients should support insertions outside the current bounds of the clients current model for the message’s text, they should not allow users to insert outside the current bounds of the client’s current model, and servers should be agnostic to where insertions occur.
The current protocol takes place on unencrypted TCP. I will add TLS soon™.

notes on LunaRC

When you see the big glowing moth, press enter to connect to connect to lrc://moth11.net/. You can also press q to quit, and / to search for a different server.
When connected to a server, the keybinds are kinda inspired by vim. Press j to scroll down and k to scroll up. Press i to enter insert mode. You’re now live! While the bottom bar is glowing, everything you type will be relayed to everyone else who is connected to the server. Press enter while in insert mode to publish your message. You stay in insert mode after a publish
Press escape to go back to normal mode. Press q to quit LunaRC, press d to dump the event log, press r to rerender the chat (this sometimes can be helpful whenever I wrote a bug in my codes). Press c and then type a number and then press enter, and you’ve now picked a color from here for your status bar, name, and live messages. Press n and then type your new name (12 characters at most) to change your name. Changing both color and name only apply when you initialize a post, but you don’t need to worry about namespace collision, identity theft is not a crime in LRC.
Soon™ you will be able to insert and delete at parts of the message besides the very end, but right now that’s unimplemented. Later™ you will be able to use your mouse to help with editing, but that might take some time.
When I sent this to my best friend last night, her terminal did not support ansi codes unfortunately. If you see the text wall when you run the program above the moth, then that means your terminal doesn’t either. Theoretically you might not have the worst experience if you press r a bunch, but I think if at a minimum you download git, it comes with the terminal emulator called git bash, which should be sufficient to see the user interface correctly.
Likely right now everything in the user interface of LunaRC will break if you use non ASCII unicode code points. Luckily, I have it hard coded to only send ASCII printable characters, and I haven’t implemented copy and paste, so it should be ok for now. The protocol and servers should be agnostic to this however, and it’s best practice for clients to recieve and properly render the full UTF-8 standard.
If you want to run or build from source, install the go programming language if you don’t have it installed already, and then open a terminal in the LunaRC directory, and type “go run ./cmd” or “go build ./cmd”

notes on lrcd

Right now you need to hard code the welcome message haha, that’s somewhere on the horizon between soon™ and later™. Even if you don’t know go or how to program in general, I’m sure you can figure it out if you want to run your own server, lrcd is just barely over 200 lines right now, and you can contact me somehow. I might hardcode the prod version of it, better logging is also somewhere on the schedule, so much to do…
If you want to run or build from source, install the go programming language if you don’t have it installed already, and then open a terminal in the lrcd directory, and type “go run .” or “go build .”

onwards!

As it should be clear, I have plenty of things on the short and medium term horizon for LunaRC and lrcd. On longer timescales, I’d like to learn more about atproto, because I think that in order to scale past a community server dark forest model, it would be really helpful to have better tools for identities and moderation.