NATS Client Implementation Sample

Overview

NATS is a publisher/subscriber protocol implemented on top of TCP. It is specified in NATS Protocol documentation, and this is a sample implementation for Zephyr using the new IP stack. The API is loosely based off of the Golang API.

With this sample, it’s possible to subscribe/unsubscribe to a given subject, and be notified of changes asynchronously. In order to conserve resources, the implementation does not keep track of subscribed subjects; that must be performed by the application itself, so it can ignore unknown/undesired subjects.

TLS is not supported yet, although basic authentication is. The client will indicate if it supports username/password if a certain callback is set in the struct nats. This callback will then be called, and the user must copy the username/password to the supplied user/pass buffers.

Content might be also published for a given subject.

The sample application lets one observe the subject “led0”, and turn it “on”, “off”, or “toggle” its value. Changing the value will, if supported, act on a status LED on the development board. The new status will be published.

Also worth noting is that most of the networking and GPIO boilerplate has been shamelessly copied from the IRC bot example. (Curiously, both protocols are similar.)

Requirements

To test the sample, build the Zephyr application for your platform. This has only been tested with the QEMU emulator as provided by the Zephyr SDK, but it should work with other supported hardware as long as they have enough memory, the network stack has TCP enabled, and the connectivity hardware is supported.

As far as the software goes, this has been tested with the official gnatsd for the server, and the official go-nats client library. Both the server and clients were set up as per instructions found in their respective README.md files.

The client was a one-off test that is basically the same code provided in the Basic Usage section as found in the go-nats README file, however, subscribing to the topic used in this sample: led0, and publishing values as described above (on, off, and toggle).

Library Usage

Allocate enough space for a struct nats, setting a few callbacks so that you’re notified as events happen:

struct nats nats_ctx = {
    .on_auth_required = on_auth_required,
    .on_message = on_message
};

The on_auth_required() and on_message() functions are part of your application, and each must have these signatures:

int on_auth_required(struct nats *nats, char **user, char **pass);
int on_message(struct nats *nats, struct nats_msg *msg);

Both functions should return 0 to signal that they could successfully handle their role, and a negative value, if they couldn’t for any reason. It’s recommended to use a negative integer as provided by errno.h in order to ease debugging.

The first function, on_auth_required(), is called if the server notifies that it requires authentication. It’s not going to be called if that’s not the case, so it is optional. However, if the server asks for credentials and this function is not provided, the connection will be closed and an error will be returned by nats_connect().

The second function, on_message(), will be called whenever the server has been notified of a value change. The struct nats_msg has the following fields:

struct nats_msg {
    const char *subject;
    const char *sid;
    const char *reply_to;
};

The field reply_to may be passed directly to nats_publish(), in order to publish a reply to this message. If it’s NULL (no reply-to field in the message from the server), the nats_publish() function will not reply to a specific mailbox and will just update the topic value.

In order to manage topic subscription, these functions can be used:

int nats_subscribe(struct nats *nats, const char *subject,
    const char *queue_group, const char *sid);

subject and sid are validated so that they’re actually valid per the protocol rules. -EINVAL is returned if they’re not.

If queue_group is NULL, it’s not sent to the server.

int nats_unsubscribe(struct nats *nats, const char *sid,
    size_t max_msgs);

sid is validated so it’s actually valid per the protocol rules. -EINVAL is returned if it’s not.

max_msgs specifies the number of messages that the server will send before actually unsubscribing the message. Can be 0 to immediately unsubscribe. (See note below.)

Both of these functions will return -ENOMEM if they couldn’t build the message to transmit to the server. They can also return any error that net_context_send() can return.

Note: In order to conserve resources, the library implementation will not make note of subscribed topics. Both nats_subscribe() and nats_unsubscribe() functions will only notify the server that the client is either interested or uninterested in a particular topic. The on_message() callback may be called to notify of changes in topics that have not been subscribed to (or have been recently unsubscribed). It’s up to the application to decide to ignore the message.

Topics can be published by using the following function:

int nats_publish(struct nats *nats, const char *subject,
    const char *reply_to, const char *payload,
    size_t payload_len);

As usual, subject is validated and -EINVAL will be returned if it’s in an invalid format. The reply_to field can be NULL, in which case, subscribers to this topic won’t receive this information as well.

As net_subscribe() and net_unsubscribe(), this function can return -ENOMEM or any other errors that net_context_send() returns.