CGO

Gopher

Here I will describe how I built a TCP Server with protobuf and NaCL using ASSA ABLOY saltchannel implementation.

This is hyperthetical used for communication between IoT devices and backend services.

This description is an idea, a concept of how to use go and c together.

My application will integrate saltchannel using its own library and some Go wrappers.

Directory layout would look something like this

| gatewayserver/
| -------------/saltwrapper/
| -------------/-----------/export.go
| -------------/-----------/salt_wrapper.go
| -------------/-----------/type.go
| -------------/-----------/salt_wrapper.h
| -------------/-----------/salt_wrapper.c
| -------------/main.go
| -------------/handle.go
| -------------/conn.go

The idea of this hyperthetical application is to communicate with an IoT device another group is building.

The idea is the IoT device would communicate using Protobuf to a backend service and the backend service would deliver information to the IoT device for whatever needs it would want.

Possible use-cases for this device would be a remote key, monitoring of gauges, etc.

To make this work we would need to download and build the salt-channel-c library from Github, then to make that happen we choose libsodium, at the moment we just have everything in our Golang project.

MAIN APPLICATION (GO)

Our main (Go) application would start a net. Listener on a port of your choice, pass this off to our wrapper, an example code of this would be something like this


localAddr, _ := net.ResolveTCPAddr("tcp", ":7777")
// Check for error in your own code, for my sanity I will skip all error checking if not strictly needed for logic, when I'm explaining stuff.
listener, _ := net.ListenTCP("tcp", localAddr)

// acceptance loop for incoming connections
for {
    conn, _ := listener.AcceptTCP()
    go handleConnection(conn)
}

This should be pretty self-explaining, we try to resolve a port on our machine, we get back the object pass it to our Listener which in turn will become a socket on our O/S and we start a main loop just waiting for incoming connections.

In our loop, when we receive an incoming connection, we pass it to our go routine. A side-note you could do something better or clever, but my choice is naivë solution.

Our handleConnection function starts up the salt-channel library by using a pre-defined saltwrapper. SaltServer(conn) and then we use the library in another for-loop to read the bytes going over the wire.

func handleConnection(conn *net.TCPConn) {
    defer conn.Close()

    sc, _ := saltwrapper.SaltServer(conn)

    fromReadChannel := make(chan []byte, 2) // buffered channel 
LOOP:
    for {
        iotPublicKey := sc.SaltGetKey() // The device will present its Public Key
        _, err := sc.SaltReceiveMessage() // we check if we can receive from the salt-channel
        if err != nil {
           break LOOP
        }

        buffer := <-fromReadChannel
        var request iotdevice.Request // this would the Protobuf as golang source code
        proto.Unmarshal(buffer, &request)
        switch request.Action.(type) {
            case iotdevice.AnAction1:
            // We do not really care about these actions for this article
            case iotdevice.AnAction2:
            // We do not really care about these actions for this article
        }
    }
}

C? GO ? CGO!

Above code describe with much simplicity the action of our application.

Now we will turn our focus on the salt-channel wrapper implementation and how we integrate into go.

As described by the beautiful directory layout ASCII art, we know we are going to have a folder inside our Go project that will include all the wrapper material to communicate between C and Go.

Our Go interface

As salt-channel would have a server and client mode we need two functions for that.

We do that by creating a type SaltChannel and depending on the need we have two constructors that will return a SaltChannel with the proper mode configuration.

The salt-channel-c implementation would have a Read, Send, Disconnect, GetKey (public key)


type SaltChannel struct {}

func (sc *SaltChannel) Read(b []byt) (int, error) {}
func (sc *SaltChannel) SaltSendMessage(b []byte) (int, error) {
    return sc.Write(b)
}

func (sc *SaltChannel) Write(b []byte) (int, error) {
    buffer := C.CBytes(b)
    ptr := (C.salt_handle_t)(sc.Ptr)

    rv := C.salt_send_message(ptr, (*C.uint8_t)(buffer), (C.uint16_t)(len(b)))
    if rv != C.SALT_SUCCESS || C.salt_error(ptr) != C.SALT_ERR_NONE {
        return 0, fmt.Errorf("sendmessage error: %d %d\n", rv, C.salt_error(ptr))
    }
    return len(b), nil
}

func (sc *SaltChannel) SaltReceiveMessage(outCh chan []byte) (int, error) {
    var offset C.uint16_t
    var msglength C.uint16_t
    ptr := (C.salt_handle_t)(sc.Ptr)

    tmp := make([]byte, 1600) // 1600 is a bit bigger than what SaltChannel needs, I think they require 1522 bytes.
    bufferC := C.CBytes(tmp) // Here we allocate a C byte array

    rv := C.salt_receive_message(ptr, (*C.uint8_t)(bufferC), (C.uint16_t)(1600), &offset, &msglength)

    if rv != 0 || C.salt_error(ptr) != C.SALT_ERR_NONE {
        return 0, fmt.Errorf("some clever message")
    }

    b := C.GoBytes(bufferC,1600) // here we translate back to Go bytes

    buf := b[offset: offset+msglength]
    outCh <- buf
    return (int)(len(buf)), nil

}

// Freeloader function (global) disconnect

func SaltDisconnect() {
    C.salt_disconnect(nil)
}

// Constructors for SaltChannel
func SaltServer() *SaltChannel {}
func SaltClient() *SaltChannel {}

As our folder includes .c and .h files we would need to enable C compilation from our .go source code, at the very top of our salt_wrapper.go we would include GCC compilation options.

We also include some proxy functions to pass data between C and Go.


/*
#cgo CFLAGS: -I${SRCDIR}/../salt-channel-c/src
#cgo LDFLAGS: -L${SRCDIR}/../ -lsalt
#cgo LDFLAGS: -lsodium
#include "salt_wrapper.h"

// These will be defined in Go and used in C
extern salt_ret_t ProxyWrite(void*, const uint8_t *, uint16_t); 
extern salt_ret_t ProxyRead(void*, const uint8_t *, uint16_t);

uint16_t socket_write(void *socket, const uint8_t *buffer, uint16_t size)
{
    return ProxyWrite(socket, buffer, size);
}

uint16_t socket_read(void *socket, const uint8_t *buffer, uint16_t size)
{
    return ProxyRead(socket, buffer, size);
}

*/

import "C"

CONCLUSION

As you probably can see, you need to move data back and forth between Go and C layers, and to point out where the required files are for the compiler to do its job.

CGO is nice when you have a unified C library your company is using and building, so all have the same interface and expectations, but it is not quite Go.

REFERENCES