Index

Go CBOR encoder: Episode 1, getting started

Let’s write a CBOR encoder in Go. We’ll learn more about type switching and type manipulation with the reflect package. This is going to be a series of posts, each building on the previous one. It requires a good understanding of the Golang syntax.

CBOR is a data format described in RFC 7049, it’s like JSON but binary instead of text. Its design goals are:

We use an interface similar to the encoding/json package. If you are unfamiliar with this encoding sub-packages, I recommend you read the JSON and Go article.

To start our empty package we’ll create a file named cbor.go like this:

// Implements CBOR encoding:
//
//   https://tools.ietf.org/html/rfc7049
//
package cbor

import (
        "io"
)

type Encoder struct {
        w   io.Writer
}

func NewEncoder(w io.Writer) *Encoder {
        return &Encoder{w: w}
}

func (enc *Encoder)Encode(v interface{}) error {
        return nil
}

Here’s how we use the encoder:

var output = bytes.Buffer{}
var encoder = NewEncoder(&output)
var myvalue = 1234
// write the integer 1234 CBOR encoded into output
if err := encoder.Encode(&myvalue); err != nil {
    ...
}

We have our basic structure we can now start working on the encoder’s implementation. In the previous example we encoded the integer 1234, but we won’t start with integers, instead we will encode the value nil to start because it’s the easiest value to encode.

According the CBOR specification the nil value is represented by a single byte: 0xf6

Let’s write a test with the testing package, we’ll verify the encoder outputs the single byte 0xf6 into the result buffer when we pass nil. We create a new file cbor_test.go beside cbor.go for our tests:

package cbor

import (
    "bytes"
    "testing"
)

func TestNil(t *testing.T) {
    var buffer = bytes.Buffer{}
    var err = NewEncoder(&buffer).Encode(nil)

    if !(err == nil && bytes.Equal(result.Bytes(), []byte{0xf6})) {
        t.Fatalf(
            "%#v != %#v or %#v != %#v",
            err, nil, result, []byte{0xf6},
        )
    }
}

If we run the test with what we currently have we’ll get an error. No surprise: we haven’t implemented anything yet, so the encoder won’t write to the output buffer:

$ go test .
--- FAIL: TestNil (0.00 seconds)
    cbor_test.go:15: <nil> != nil or []byte{} != []bytes{0xf6}
FAIL
FAIL    _/home/henry/essays/cbor    0.011s

To implement the nil value encoding: we write the byte in the result when calling Encode() and we’ll return an error if the value isn’t nil since we haven’t implemented anything else yet:

var ErrNotImplemented = errors.New("Not Implemented")

// Can only encode nil
func (enc *Encoder) Encode(v interface{}) error {
    switch v.(type) {
    case nil:
        var _, err = enc.w.Write([]byte{0xf6})
        return err
    }
    return ErrNotImplemented
}

Here we’re using a type switch to determine the type of the value we got. There’s only one case for now: nil, where we write the 0xf6 value in the output and return the error to the caller.

And now the test succeeds:

=== RUN   TestNil
--- PASS: TestNil (0.00s)
PASS
ok  	_/home/henry/essays/cbor	0.027s

We created the initial encoder that can encode a single value successfully. In the next episode we’ll implement CBOR encoding for more basic Go types.