I have been writing about 9p for a long time, preaching about its allegedly simplicity but without having any actual experience with it. Recently, I have taken on to learn the basics of parsing 9p messages and I am afraid to say, it took me way too long to start learning about it.

I had always put it off learning how to parse 9p messages and I have no idea why. Everywhere I searched, programmers spoke passionately about the protocol and its alleged benefits.

So really, I had no reason not to start experimenting but I put it off for about 2 to 3 years now. Luckily, in that time, I got to experience 9p very intimately as I had made the choice to switch to acme.

acme required that you learn a command line utility based on 9p that allows you to manipulate acme like you would by writing Elisp scripts in Emacs or Vimscript in Vim.

I learned that 9p is basically a virtual file system. You write, read, open, clunk(close), stat on and on.

Fun Fact: Learning how to parse 9p messages only required one manual page.

Usually, programs that use 9p use it to manipulate the program’s internal state. Maybe, you want to read events by a program and so you’d open and read the /event file and clunk it once you are done.

9p is binary. This means that you only deal with bytes. Sure, there are some strings in the protocol but, in general, you deal with bytes.

All 9p messages have three components in all of them:

  • size: 4 byte unsigned integer that represents the size of message(including the size component)
  • type: a byte unsigned integer that represents the type of message. this is helpful when parsing the other parameters sent in the message.
  • tag: 2 byte unsigned integer that represents the ID for the message. this is helpful when sending multiple requests and assigning responses to those requests.

parsing little endian unsigned integers

How do I parse unsigned integers? Well, you have to figured out if the unsigned integers are represented in big endian or little endian. In 9p, they are little endian.

Don’t bother searching your favorite search engine for how to parse numbers in little endian, here’s a way implemented in Go and Javascript:

func power(x uint64, y uint64) uint64 {
	for i := 1; i <= y; i++ {
		x = x * x
	}

	return x
}

func parseInteger(array []byte) uint64 {
	var ret uint64 = 0
	for index, bite := range array {
		ret += bite * power(2, index * 8)
	}

	return ret
}
function parseInteger(array) {
	return array.map((bite, index) => bite * Math.pow(2, index * 8)).reduce((a, b) => a + b);
}

Basically, you go through each element in the byte array, multiple each element by 2 to index(from 0) multipled by 8, and add them all together.

To test it out if your code is making sense or not, I suggest putting different hexadecimal values in your browser’s console and contrast those with your implementation.

Here is how the implementation above is doing:

// browser console
> parseInteger([255, 255])
65535
> 0xffff
65536
> 0xf0ff
61695
> parseInteger([255, 240])
61695
> 0xc9
201
> 0x3f
63
> 0x3fc9
16329
> parseInteger([201, 63])
16329

So far so good, now, let’s go onto the message notation.

9p manual message notation

The 9p protocol has a manual that describes every single request in the intro(5) man page.

Components of a message are seperated by spaces.

size[4] Tversion tag[2] msize[4] version[s]

Up above, we have five components. size[4], Tversion, tag[2], msize[4], and version[s].

Some variables have a brackets at the end and some don’t. The brackets represent the size.

With that, we can defer that size[4] means that the size is made out of 4 bytes. The tag[2] made out of 2 bytes and the msize[4] out of 4 bytes.

What about version[s]? Any brackets that have s in them represent a string.

How do we know the size of the string? There is a two byte unsigned integer before the string content.

Essentially, version[s] means nversion[2] version[nversion].

One more thing, sometimes a bracket has n inside it. That means that the data that should be string is instead an array of bytes. This is important and will come up later on.

how to read dynamic 9p data

Remember that function we wrote before? You’ll need it in a couple of minutes.

Get a 2 byte array and make it into an integer. Afterwards, read that amount of bytes. If the thing you want to read is supposed to be a string, i.e it has [s] instead of [n], just convert that byte array to a UTF-8 string.

All text in Plan 9 is UTF-8 and does not end with a NUL byte.

Here’s some Javascript code that reads dynamic 9p data:

// reads any variable with `n` in brackets
// e.g. stat[n]
function read9pData(array) {
	let size = parseInteger(array.slice(0, 2))
	return array.slice(2, array);
}

// reads any variable with `s` in brackets
// e.g. version[s]
function read9pString(array) {
	return new TextDecoder().decode(array);
}

We first get the size then read size amount of bytes. Simple, eh?

nwqid and wqid: how to read 9p arrays

This one took me a good 30 minutes but it doesn’t have to take you 30 minutes to undestand.

In 9p, nwqid and nwqid*(wqid[13]) are included in the response of a RWalk (9p’s readdir). When I first looked at the Rwalk message, I immediately got confused and wondered how could a readDir return a bunch of random bytes. Turns out it wasn’t random data instead it was an array.

Anyhoo, nwqid represents the number of wqid[13].

To parse nwqid*(wqid[13]), you just read nwqid*13 bytes while seperating each 13 bytes as their own elements in the array.

Proverbial Javascript demo below:

// building off of the previous functions
function read9pQidArray(array) {
	let nwqid = parseInteger(array.slice(0, 2));
	let array = [];

	for(let i = 0; i < nwqid; i++) {
		array.push(parseInteger(array.slice(2+(i*13), 15+(i*13))));
	}

	return array;
}

use streams instead of array

Try to use streams, or in Go io.Reader and io.Writer, as much as possible. Since allocating and deallocating array is often unnecessary in parsing 9p messages, streams are more preferrable over arrays.

Examples written in this article used arrays for illustration purposes only.

conclusion

9p is a simple protocol to parse. Feel free to contact me about any questions with 9p as I am currently gaining proficiency in it.

Hopefully, some day soon, I’ll release a 9p2000 library.