net: remove sample implementing NATS
A sample implementing NATS protocol that is not part of the Zephyr networking subsystem. The implementation is not maintained and only served as a proof of concept. Related to #20017 Signed-off-by: Anas Nashif <anas.nashif@intel.com>
This commit is contained in:
parent
937c9c215f
commit
91920268e0
7 changed files with 0 additions and 1210 deletions
|
@ -1,8 +0,0 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.13.1)
|
||||
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
|
||||
project(nats)
|
||||
|
||||
FILE(GLOB app_sources src/*.c)
|
||||
target_sources(app PRIVATE ${app_sources})
|
|
@ -1,163 +0,0 @@
|
|||
.. _NATS_Client_Sample:
|
||||
|
||||
|
||||
NATS Client Implementation Sample
|
||||
#################################
|
||||
|
||||
|
||||
Overview
|
||||
********
|
||||
|
||||
`NATS <http://nats.io/documentation/internals/nats-protocol/>`__ is a
|
||||
publisher/subscriber protocol implemented on top of TCP. It is specified in
|
||||
`NATS Protocol documentation <http://nats.io/documentation/internals/nats-protocol/>`__,
|
||||
and this is a sample implementation for Zephyr using the new IP stack.
|
||||
The API is loosely based off of the `Golang API
|
||||
<https://github.com/nats-io/go-nats>`__.
|
||||
|
||||
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
|
||||
<https://github.com/nats-io/gnatsd>`__ for the server, and the official
|
||||
`go-nats <https://github.com/nats-io/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
|
||||
<https://github.com/nats-io/go-nats/blob/e6bb81b5a5f37ef7bf364bb6276e13813086c6ee/README.md#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.
|
|
@ -1,23 +0,0 @@
|
|||
CONFIG_INIT_STACKS=y
|
||||
|
||||
CONFIG_NET_DHCPV4=y
|
||||
CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=3
|
||||
CONFIG_NET_IPV6=y
|
||||
CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3
|
||||
CONFIG_NET_LOG=y
|
||||
CONFIG_NET_MAX_CONTEXTS=10
|
||||
CONFIG_NET_SHELL=y
|
||||
CONFIG_NET_STATISTICS=y
|
||||
CONFIG_NET_TCP=y
|
||||
CONFIG_NETWORKING=y
|
||||
CONFIG_DNS_RESOLVER=n
|
||||
|
||||
CONFIG_PRINTK=y
|
||||
|
||||
CONFIG_NET_CONFIG_SETTINGS=y
|
||||
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
|
||||
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
|
||||
|
||||
CONFIG_TEST_RANDOM_GENERATOR=y
|
||||
|
||||
CONFIG_JSON_LIBRARY=y
|
|
@ -1,7 +0,0 @@
|
|||
sample:
|
||||
name: NATS Client
|
||||
tests:
|
||||
sample.net.nats:
|
||||
harness: net
|
||||
depends_on: netif
|
||||
tags: net nats
|
|
@ -1,330 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <logging/log.h>
|
||||
LOG_MODULE_REGISTER(net_nats_sample, LOG_LEVEL_DBG);
|
||||
|
||||
#include <drivers/gpio.h>
|
||||
#include <net/net_context.h>
|
||||
#include <net/net_core.h>
|
||||
#include <net/net_if.h>
|
||||
#include <zephyr.h>
|
||||
|
||||
#include "nats.h"
|
||||
|
||||
/* LED */
|
||||
#ifndef DT_ALIAS_LED0_GPIOS_CONTROLLER
|
||||
#ifdef LED0_GPIO_PORT
|
||||
#define DT_ALIAS_LED0_GPIOS_CONTROLLER LED0_GPIO_PORT
|
||||
#else
|
||||
#define DT_ALIAS_LED0_GPIOS_CONTROLLER "(fail)"
|
||||
#define DT_ALIAS_LED0_GPIOS_PIN 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define LED_GPIO_NAME DT_ALIAS_LED0_GPIOS_CONTROLLER
|
||||
#define LED_PIN DT_ALIAS_LED0_GPIOS_PIN
|
||||
|
||||
static struct device *led0;
|
||||
static bool fake_led;
|
||||
|
||||
/* Network Config */
|
||||
|
||||
#if defined(CONFIG_NET_IPV6)
|
||||
|
||||
#define NATS_AF_INET AF_INET6
|
||||
#define NATS_SOCKADDR_IN sockaddr_in6
|
||||
|
||||
#if defined(CONFIG_NET_CONFIG_MY_IPV6_ADDR)
|
||||
#define NATS_LOCAL_IP_ADDR CONFIG_NET_CONFIG_MY_IPV6_ADDR
|
||||
#else
|
||||
#define NATS_LOCAL_IP_ADDR "2001:db8::1"
|
||||
#endif /* CONFIG_NET_CONFIG_MY_IPV6_ADDR */
|
||||
|
||||
#if defined(CONFIG_NET_CONFIG_PEER_IPV6_ADDR)
|
||||
#define NATS_PEER_IP_ADDR CONFIG_NET_CONFIG_PEER_IPV6_ADDR
|
||||
#else
|
||||
#define NATS_PEER_IP_ADDR "2001:db8::2"
|
||||
#endif /* CONFIG_NET_CONFIG_PEER_IPV6_ADDR */
|
||||
|
||||
#else /* CONFIG_NET_IPV4 */
|
||||
|
||||
#define NATS_AF_INET AF_INET
|
||||
#define NATS_SOCKADDR_IN sockaddr_in
|
||||
|
||||
#if defined(CONFIG_NET_CONFIG_MY_IPV4_ADDR)
|
||||
#define NATS_LOCAL_IP_ADDR CONFIG_NET_CONFIG_MY_IPV4_ADDR
|
||||
#else
|
||||
#define NATS_LOCAL_IP_ADDR "192.168.0.1"
|
||||
#endif /* CONFIG_NET_CONFIG_MY_IPV4_ADDR */
|
||||
|
||||
#if defined(CONFIG_NET_CONFIG_PEER_IPV4_ADDR)
|
||||
#define NATS_PEER_IP_ADDR CONFIG_NET_CONFIG_PEER_IPV4_ADDR
|
||||
#else
|
||||
#define NATS_PEER_IP_ADDR "192.168.0.2"
|
||||
#endif /* CONFIG_NET_CONFIG_PEER_IPV4_ADDR */
|
||||
|
||||
#endif
|
||||
|
||||
/* DNS API */
|
||||
#define DNS_PORT 53
|
||||
#define DNS_SLEEP_MSECS 400
|
||||
|
||||
/* Default server */
|
||||
#define DEFAULT_PORT 4222
|
||||
|
||||
static void panic(const char *msg)
|
||||
{
|
||||
LOG_ERR("Panic: %s", msg);
|
||||
for (;;) {
|
||||
k_sleep(K_FOREVER);
|
||||
}
|
||||
}
|
||||
|
||||
static int in_addr_set(sa_family_t family,
|
||||
const char *ip_addr,
|
||||
int port,
|
||||
struct sockaddr *_sockaddr)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
_sockaddr->sa_family = family;
|
||||
|
||||
if (ip_addr) {
|
||||
if (family == AF_INET6) {
|
||||
rc = net_addr_pton(family,
|
||||
ip_addr,
|
||||
&net_sin6(_sockaddr)->sin6_addr);
|
||||
} else {
|
||||
rc = net_addr_pton(family,
|
||||
ip_addr,
|
||||
&net_sin(_sockaddr)->sin_addr);
|
||||
}
|
||||
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Invalid IP address: %s", log_strdup(ip_addr));
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
if (family == AF_INET6) {
|
||||
net_sin6(_sockaddr)->sin6_port = htons(port);
|
||||
} else {
|
||||
net_sin(_sockaddr)->sin_port = htons(port);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void initialize_network(void)
|
||||
{
|
||||
struct net_if *iface;
|
||||
|
||||
LOG_INF("Initializing network");
|
||||
|
||||
iface = net_if_get_default();
|
||||
if (!iface) {
|
||||
panic("No default network interface");
|
||||
}
|
||||
|
||||
/* TODO: IPV6 DHCP */
|
||||
#if defined(CONFIG_NET_IPV4) && defined(CONFIG_NET_DHCPV4)
|
||||
net_dhcpv4_start(iface);
|
||||
|
||||
/* delay so DHCPv4 can assign IP */
|
||||
/* TODO: add a timeout/retry */
|
||||
LOG_INF("Waiting for DHCP ...");
|
||||
do {
|
||||
k_sleep(K_SECONDS(1));
|
||||
} while (net_ipv4_is_addr_unspecified(&iface->dhcpv4.requested_ip));
|
||||
|
||||
LOG_INF("Done!");
|
||||
|
||||
/* TODO: add a timeout */
|
||||
LOG_INF("Waiting for IP assginment ...");
|
||||
do {
|
||||
k_sleep(K_SECONDS(1));
|
||||
} while (!net_ipv4_is_my_addr(&iface->dhcpv4.requested_ip));
|
||||
|
||||
LOG_INF("Done!");
|
||||
#else
|
||||
struct sockaddr addr;
|
||||
|
||||
if (in_addr_set(NATS_AF_INET, NATS_LOCAL_IP_ADDR, 0,
|
||||
&addr) < 0) {
|
||||
LOG_ERR("Invalid IP address: %s",
|
||||
NATS_LOCAL_IP_ADDR);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_NET_IPV6)
|
||||
net_if_ipv6_addr_add(iface,
|
||||
&net_sin6(&addr)->sin6_addr,
|
||||
NET_ADDR_MANUAL, 0);
|
||||
#else
|
||||
net_if_ipv4_addr_add(iface,
|
||||
&net_sin(&addr)->sin_addr,
|
||||
NET_ADDR_MANUAL, 0);
|
||||
#endif
|
||||
#endif /* CONFIG_NET_IPV4 && CONFIG_NET_DHCPV4 */
|
||||
}
|
||||
|
||||
static bool read_led(void)
|
||||
{
|
||||
u32_t led = 0U;
|
||||
int r;
|
||||
|
||||
if (!led0) {
|
||||
return fake_led;
|
||||
}
|
||||
|
||||
r = gpio_pin_read(led0, LED_PIN, &led);
|
||||
if (r < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !led;
|
||||
}
|
||||
|
||||
static void write_led(const struct nats *nats,
|
||||
const struct nats_msg *msg,
|
||||
bool state)
|
||||
{
|
||||
char *pubstate;
|
||||
int ret;
|
||||
|
||||
if (!led0) {
|
||||
fake_led = state;
|
||||
} else {
|
||||
gpio_pin_write(led0, LED_PIN, !state);
|
||||
}
|
||||
|
||||
pubstate = state ? "on" : "off";
|
||||
ret = nats_publish(nats, "led0", 0, msg->reply_to, 0,
|
||||
pubstate, strlen(pubstate));
|
||||
if (ret < 0) {
|
||||
printk("Failed to publish: %d\n", ret);
|
||||
} else {
|
||||
printk("*** Turning LED %s\n", pubstate);
|
||||
}
|
||||
}
|
||||
|
||||
static int on_msg_received(const struct nats *nats,
|
||||
const struct nats_msg *msg)
|
||||
{
|
||||
if (!strcmp(msg->subject, "led0")) {
|
||||
if (msg->payload_len == 2 && !strcmp(msg->payload, "on")) {
|
||||
write_led(nats, msg, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (msg->payload_len == 3 && !strcmp(msg->payload, "off")) {
|
||||
write_led(nats, msg, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (msg->payload_len == 6 && !strcmp(msg->payload, "toggle")) {
|
||||
write_led(nats, msg, !read_led());
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static void initialize_hardware(void)
|
||||
{
|
||||
LOG_INF("Initializing hardware");
|
||||
|
||||
led0 = device_get_binding(LED_GPIO_NAME);
|
||||
if (led0) {
|
||||
gpio_pin_configure(led0, LED_PIN, GPIO_DIR_OUT);
|
||||
}
|
||||
}
|
||||
|
||||
static int connect(struct nats *nats, u16_t port)
|
||||
{
|
||||
#if defined(CONFIG_NET_IPV4) && defined(CONFIG_NET_DHCPV4)
|
||||
struct net_if *iface;
|
||||
#endif
|
||||
struct sockaddr dst_addr, src_addr;
|
||||
int ret;
|
||||
|
||||
LOG_INF("Connecting...");
|
||||
|
||||
ret = net_context_get(NATS_AF_INET, SOCK_STREAM, IPPROTO_TCP,
|
||||
&nats->conn);
|
||||
if (ret < 0) {
|
||||
LOG_DBG("Could not get new context: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* TODO: IPV6 DHCP */
|
||||
#if defined(CONFIG_NET_IPV4) && defined(CONFIG_NET_DHCPV4)
|
||||
iface = net_if_get_default();
|
||||
|
||||
net_ipaddr_copy(&net_sin(&src_addr)->sin_addr,
|
||||
&iface->dhcpv4.requested_ip);
|
||||
ret = in_addr_set(NATS_AF_INET, NULL, 0, &src_addr);
|
||||
#else
|
||||
ret = in_addr_set(NATS_AF_INET, NATS_LOCAL_IP_ADDR,
|
||||
0, &src_addr);
|
||||
if (ret < 0) {
|
||||
goto connect_exit;
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = in_addr_set(NATS_AF_INET, NATS_PEER_IP_ADDR,
|
||||
port, &dst_addr);
|
||||
if (ret < 0) {
|
||||
goto connect_exit;
|
||||
}
|
||||
|
||||
ret = net_context_bind(nats->conn, &src_addr,
|
||||
sizeof(struct NATS_SOCKADDR_IN));
|
||||
if (ret < 0) {
|
||||
LOG_DBG("Could not bind to local address: %d", -ret);
|
||||
goto connect_exit;
|
||||
}
|
||||
|
||||
ret = nats_connect(nats, &dst_addr, sizeof(struct NATS_SOCKADDR_IN));
|
||||
if (!ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
connect_exit:
|
||||
net_context_put(nats->conn);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void nats_client(void)
|
||||
{
|
||||
struct nats nats = {
|
||||
.on_message = on_msg_received
|
||||
};
|
||||
|
||||
LOG_INF("NATS Client Sample");
|
||||
|
||||
initialize_network();
|
||||
initialize_hardware();
|
||||
|
||||
if (connect(&nats, DEFAULT_PORT) < 0) {
|
||||
panic("Could not connect to NATS server");
|
||||
}
|
||||
|
||||
if (nats_subscribe(&nats, "led0", 0, NULL, 0,
|
||||
"sub132984012384098", 0) < 0) {
|
||||
panic("Could not subscribe to `led0` topic");
|
||||
}
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
nats_client();
|
||||
}
|
|
@ -1,632 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <logging/log.h>
|
||||
LOG_MODULE_DECLARE(net_nats_sample, LOG_LEVEL_DBG);
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <data/json.h>
|
||||
#include <sys/printk.h>
|
||||
#include <sys/util.h>
|
||||
#include <net/net_pkt.h>
|
||||
#include <net/net_context.h>
|
||||
#include <net/net_core.h>
|
||||
#include <net/net_if.h>
|
||||
#include <stdbool.h>
|
||||
#include <zephyr/types.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <zephyr.h>
|
||||
|
||||
#include "nats.h"
|
||||
|
||||
#define CMD_BUF_LEN 256
|
||||
|
||||
struct nats_info {
|
||||
const char *server_id;
|
||||
const char *version;
|
||||
const char *go;
|
||||
const char *host;
|
||||
size_t max_payload;
|
||||
u16_t port;
|
||||
bool ssl_required;
|
||||
bool auth_required;
|
||||
};
|
||||
|
||||
struct io_vec {
|
||||
const void *base;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
static bool is_subject_valid(const char *subject, size_t len)
|
||||
{
|
||||
size_t pos;
|
||||
char last = '\0';
|
||||
|
||||
if (!subject) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (pos = 0; pos < len; last = subject[pos++]) {
|
||||
switch (subject[pos]) {
|
||||
case '>':
|
||||
if (subject[pos + 1] != '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
case '.':
|
||||
case '*':
|
||||
if (last == subject[pos]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
if (isalnum((unsigned char)subject[pos])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_sid_valid(const char *sid, size_t len)
|
||||
{
|
||||
size_t pos;
|
||||
|
||||
if (!sid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (pos = 0; pos < len; pos++) {
|
||||
if (!isalnum((unsigned char)sid[pos])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define TRANSMITV_LITERAL(lit_) { .base = lit_, .len = sizeof(lit_) - 1 }
|
||||
|
||||
static int transmitv(struct net_context *conn, int iovcnt,
|
||||
struct io_vec *iov)
|
||||
{
|
||||
u8_t buf[1024];
|
||||
int i, pos;
|
||||
|
||||
for (i = 0, pos = 0; i < iovcnt; pos += iov[i].len, i++) {
|
||||
memcpy(&buf[pos], iov[i].base, iov[i].len);
|
||||
}
|
||||
|
||||
return net_context_send(conn, buf, pos, NULL, K_NO_WAIT, NULL);
|
||||
}
|
||||
|
||||
static inline int transmit(struct net_context *conn, const char buffer[],
|
||||
size_t len)
|
||||
{
|
||||
return transmitv(conn, 1, (struct io_vec[]) {
|
||||
{ .base = buffer, .len = len },
|
||||
});
|
||||
}
|
||||
|
||||
#define FIELD(struct_, member_, type_) { \
|
||||
.field_name = #member_, \
|
||||
.field_name_len = sizeof(#member_) - 1, \
|
||||
.offset = offsetof(struct_, member_), \
|
||||
.type = type_ \
|
||||
}
|
||||
static int handle_server_info(struct nats *nats, char *payload, size_t len,
|
||||
struct net_buf *buf, u16_t offset)
|
||||
{
|
||||
static const struct json_obj_descr descr[] = {
|
||||
FIELD(struct nats_info, server_id, JSON_TOK_STRING),
|
||||
FIELD(struct nats_info, version, JSON_TOK_STRING),
|
||||
FIELD(struct nats_info, go, JSON_TOK_STRING),
|
||||
FIELD(struct nats_info, host, JSON_TOK_STRING),
|
||||
FIELD(struct nats_info, port, JSON_TOK_NUMBER),
|
||||
FIELD(struct nats_info, auth_required, JSON_TOK_TRUE),
|
||||
FIELD(struct nats_info, ssl_required, JSON_TOK_TRUE),
|
||||
FIELD(struct nats_info, max_payload, JSON_TOK_NUMBER),
|
||||
};
|
||||
struct nats_info info = {};
|
||||
char user[32], pass[64];
|
||||
size_t user_len = sizeof(user), pass_len = sizeof(pass);
|
||||
int ret;
|
||||
|
||||
ret = json_obj_parse(payload, len, descr, ARRAY_SIZE(descr), &info);
|
||||
if (ret < 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (info.ssl_required) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (!info.auth_required) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!nats->on_auth_required) {
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
ret = nats->on_auth_required(nats, user, &user_len, pass, &pass_len);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = json_escape(user, &user_len, sizeof(user));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = json_escape(pass, &pass_len, sizeof(pass));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return transmitv(nats->conn, 5, (struct io_vec[]) {
|
||||
TRANSMITV_LITERAL("CONNECT {\"user\":\""),
|
||||
{ .base = user, .len = user_len },
|
||||
TRANSMITV_LITERAL("\",\"pass\":\""),
|
||||
{ .base = pass, .len = pass_len },
|
||||
TRANSMITV_LITERAL("\"}\r\n"),
|
||||
});
|
||||
}
|
||||
#undef FIELD
|
||||
|
||||
static bool char_in_set(char chr, const char *set)
|
||||
{
|
||||
const char *ptr;
|
||||
|
||||
for (ptr = set; *ptr; ptr++) {
|
||||
if (*ptr == chr) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static char *strsep(char *strp, const char *delims)
|
||||
{
|
||||
const char *delim;
|
||||
char *ptr;
|
||||
|
||||
if (!strp) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (delim = delims; *delim; delim++) {
|
||||
ptr = strchr(strp, *delim);
|
||||
if (ptr) {
|
||||
*ptr = '\0';
|
||||
|
||||
for (ptr++; *ptr; ptr++) {
|
||||
if (!char_in_set(*ptr, delims)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int copy_pkt_to_buf(struct net_buf *src, u16_t offset,
|
||||
char *dst, size_t dst_size, size_t n_bytes)
|
||||
{
|
||||
u16_t to_copy;
|
||||
u16_t copied;
|
||||
|
||||
if (dst_size < n_bytes) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
while (src && offset >= src->len) {
|
||||
offset -= src->len;
|
||||
src = src->frags;
|
||||
}
|
||||
|
||||
for (copied = 0U; src && n_bytes > 0; offset = 0U) {
|
||||
to_copy = MIN(n_bytes, src->len - offset);
|
||||
|
||||
memcpy(dst + copied, (char *)src->data + offset, to_copy);
|
||||
copied += to_copy;
|
||||
|
||||
n_bytes -= to_copy;
|
||||
src = src->frags;
|
||||
}
|
||||
|
||||
if (n_bytes > 0) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_server_msg(struct nats *nats, char *payload, size_t len,
|
||||
struct net_buf *buf, u16_t offset)
|
||||
{
|
||||
char *subject, *sid, *reply_to, *bytes, *end_ptr;
|
||||
char prev_end = payload[len];
|
||||
size_t payload_size;
|
||||
|
||||
/* strsep() uses strchr(), ensure payload is NUL-terminated */
|
||||
payload[len] = '\0';
|
||||
|
||||
/* Slice the tokens */
|
||||
subject = payload;
|
||||
sid = strsep(subject, " \t");
|
||||
reply_to = strsep(sid, " \t");
|
||||
bytes = strsep(reply_to, " \t");
|
||||
|
||||
if (!bytes) {
|
||||
if (!reply_to) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bytes = reply_to;
|
||||
reply_to = NULL;
|
||||
}
|
||||
|
||||
/* Parse the payload size */
|
||||
errno = 0;
|
||||
payload_size = strtoul(bytes, &end_ptr, 10);
|
||||
payload[len] = prev_end;
|
||||
if (errno != 0) {
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (!end_ptr) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (payload_size >= CMD_BUF_LEN - len) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (copy_pkt_to_buf(buf, offset, end_ptr, CMD_BUF_LEN - len,
|
||||
payload_size) < 0) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
end_ptr[payload_size] = '\0';
|
||||
|
||||
return nats->on_message(nats, &(struct nats_msg) {
|
||||
.subject = subject,
|
||||
.sid = sid,
|
||||
.reply_to = reply_to,
|
||||
.payload = end_ptr,
|
||||
.payload_len = payload_size,
|
||||
});
|
||||
}
|
||||
|
||||
static int handle_server_ping(struct nats *nats, char *payload, size_t len,
|
||||
struct net_buf *buf, u16_t offset)
|
||||
{
|
||||
static const char pong[] = "PONG\r\n";
|
||||
|
||||
return transmit(nats->conn, pong, sizeof(pong) - 1);
|
||||
}
|
||||
|
||||
static int ignore(struct nats *nats, char *payload, size_t len,
|
||||
struct net_buf *buf, u16_t offset)
|
||||
{
|
||||
/* FIXME: Notify user of success/errors. This would require
|
||||
* maintaining information of what was the last sent command in
|
||||
* order to provide the best error information for the user.
|
||||
* Without VERBOSE set, these won't be sent -- but be cautious and
|
||||
* ignore them just in case.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CMD(cmd_, handler_) { \
|
||||
.op = cmd_, \
|
||||
.len = sizeof(cmd_) - 1, \
|
||||
.handle = handler_ \
|
||||
}
|
||||
static int handle_server_cmd(struct nats *nats, char *cmd, size_t len,
|
||||
struct net_buf *buf, u16_t offset)
|
||||
{
|
||||
static const struct {
|
||||
const char *op;
|
||||
size_t len;
|
||||
int (*handle)(struct nats *nats, char *payload, size_t len,
|
||||
struct net_buf *buf, u16_t offset);
|
||||
} cmds[] = {
|
||||
CMD("INFO", handle_server_info),
|
||||
CMD("MSG", handle_server_msg),
|
||||
CMD("PING", handle_server_ping),
|
||||
CMD("+OK", ignore),
|
||||
CMD("-ERR", ignore),
|
||||
};
|
||||
size_t i;
|
||||
char *payload;
|
||||
size_t payload_len;
|
||||
|
||||
payload = strsep(cmd, " \t");
|
||||
if (!payload) {
|
||||
payload = strsep(cmd, "\r");
|
||||
if (!payload) {
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
payload_len = len - (size_t)(payload - cmd);
|
||||
len = (size_t)(payload - cmd - 1);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(cmds); i++) {
|
||||
if (len != cmds[i].len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!strncmp(cmds[i].op, cmd, len)) {
|
||||
return cmds[i].handle(nats, payload, payload_len,
|
||||
buf, offset);
|
||||
}
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
#undef CMD
|
||||
|
||||
int nats_subscribe(const struct nats *nats,
|
||||
const char *subject, size_t subject_len,
|
||||
const char *queue_group, size_t queue_group_len,
|
||||
const char *sid, size_t sid_len)
|
||||
{
|
||||
if (!is_subject_valid(subject, subject_len)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!is_sid_valid(sid, sid_len)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (queue_group) {
|
||||
return transmitv(nats->conn, 7, (struct io_vec[]) {
|
||||
TRANSMITV_LITERAL("SUB "),
|
||||
{
|
||||
.base = subject,
|
||||
.len = subject_len ?
|
||||
subject_len : strlen(subject)
|
||||
},
|
||||
TRANSMITV_LITERAL(" "),
|
||||
{
|
||||
.base = queue_group,
|
||||
.len = queue_group_len ?
|
||||
queue_group_len : strlen(queue_group)
|
||||
},
|
||||
TRANSMITV_LITERAL(" "),
|
||||
{
|
||||
.base = sid,
|
||||
.len = sid_len ? sid_len : strlen(sid)
|
||||
},
|
||||
TRANSMITV_LITERAL("\r\n")
|
||||
});
|
||||
}
|
||||
|
||||
return transmitv(nats->conn, 5, (struct io_vec[]) {
|
||||
TRANSMITV_LITERAL("SUB "),
|
||||
{
|
||||
.base = subject,
|
||||
.len = subject_len ? subject_len : strlen(subject)
|
||||
},
|
||||
TRANSMITV_LITERAL(" "),
|
||||
{
|
||||
.base = sid,
|
||||
.len = sid_len ? sid_len : strlen(sid)
|
||||
},
|
||||
TRANSMITV_LITERAL("\r\n")
|
||||
});
|
||||
}
|
||||
|
||||
int nats_unsubscribe(const struct nats *nats,
|
||||
const char *sid, size_t sid_len, size_t max_msgs)
|
||||
{
|
||||
if (!is_sid_valid(sid, sid_len)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (max_msgs) {
|
||||
char max_msgs_str[3 * sizeof(size_t)];
|
||||
int ret;
|
||||
|
||||
ret = snprintk(max_msgs_str, sizeof(max_msgs_str),
|
||||
"%zu", max_msgs);
|
||||
if (ret < 0 || ret >= (int)sizeof(max_msgs_str)) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return transmitv(nats->conn, 5, (struct io_vec[]) {
|
||||
TRANSMITV_LITERAL("UNSUB "),
|
||||
{
|
||||
.base = sid,
|
||||
.len = sid_len ? sid_len : strlen(sid)
|
||||
},
|
||||
TRANSMITV_LITERAL(" "),
|
||||
{ .base = max_msgs_str, .len = ret },
|
||||
TRANSMITV_LITERAL("\r\n"),
|
||||
});
|
||||
}
|
||||
|
||||
return transmitv(nats->conn, 3, (struct io_vec[]) {
|
||||
TRANSMITV_LITERAL("UNSUB "),
|
||||
{
|
||||
.base = sid,
|
||||
.len = sid_len ? sid_len : strlen(sid)
|
||||
},
|
||||
TRANSMITV_LITERAL("\r\n")
|
||||
});
|
||||
}
|
||||
|
||||
int nats_publish(const struct nats *nats,
|
||||
const char *subject, size_t subject_len,
|
||||
const char *reply_to, size_t reply_to_len,
|
||||
const char *payload, size_t payload_len)
|
||||
{
|
||||
char payload_len_str[3 * sizeof(size_t)];
|
||||
int ret;
|
||||
|
||||
if (!is_subject_valid(subject, subject_len)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = snprintk(payload_len_str, sizeof(payload_len_str), "%zu",
|
||||
payload_len);
|
||||
if (ret < 0 || ret >= (int)sizeof(payload_len_str)) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (reply_to) {
|
||||
return transmitv(nats->conn, 9, (struct io_vec[]) {
|
||||
TRANSMITV_LITERAL("PUB "),
|
||||
{
|
||||
.base = subject,
|
||||
.len = subject_len ?
|
||||
subject_len : strlen(subject)
|
||||
},
|
||||
TRANSMITV_LITERAL(" "),
|
||||
{
|
||||
.base = reply_to,
|
||||
.len = reply_to_len ?
|
||||
reply_to_len : strlen(reply_to)
|
||||
},
|
||||
TRANSMITV_LITERAL(" "),
|
||||
{ .base = payload_len_str, .len = ret },
|
||||
TRANSMITV_LITERAL("\r\n"),
|
||||
{ .base = payload, .len = payload_len },
|
||||
TRANSMITV_LITERAL("\r\n"),
|
||||
});
|
||||
}
|
||||
|
||||
return transmitv(nats->conn, 7, (struct io_vec[]) {
|
||||
TRANSMITV_LITERAL("PUB "),
|
||||
{
|
||||
.base = subject,
|
||||
.len = subject_len ? subject_len : strlen(subject)
|
||||
},
|
||||
TRANSMITV_LITERAL(" "),
|
||||
{ .base = payload_len_str, .len = ret },
|
||||
TRANSMITV_LITERAL("\r\n"),
|
||||
{ .base = payload, .len = payload_len },
|
||||
TRANSMITV_LITERAL("\r\n"),
|
||||
});
|
||||
}
|
||||
|
||||
static void receive_cb(struct net_context *ctx,
|
||||
struct net_pkt *pkt,
|
||||
union net_ip_header *ip_hdr,
|
||||
union net_proto_header *proto_hdr,
|
||||
int status,
|
||||
void *user_data)
|
||||
{
|
||||
struct nats *nats = user_data;
|
||||
char cmd_buf[CMD_BUF_LEN];
|
||||
struct net_buf *tmp;
|
||||
u16_t pos = 0U, cmd_len = 0U;
|
||||
size_t len;
|
||||
u8_t *end_of_line;
|
||||
|
||||
if (!pkt) {
|
||||
/* FIXME: How to handle disconnection? */
|
||||
return;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
/* FIXME: How to handle connectio error? */
|
||||
net_pkt_unref(pkt);
|
||||
return;
|
||||
}
|
||||
|
||||
tmp = pkt->cursor.buf;
|
||||
if (!tmp) {
|
||||
net_pkt_unref(pkt);
|
||||
return;
|
||||
}
|
||||
|
||||
pos = pkt->cursor.pos - tmp->data;
|
||||
|
||||
while (tmp) {
|
||||
len = tmp->len - pos;
|
||||
|
||||
end_of_line = memchr((u8_t *)tmp->data + pos, '\n', len);
|
||||
if (end_of_line) {
|
||||
len = end_of_line - ((u8_t *)tmp->data + pos);
|
||||
}
|
||||
|
||||
if (cmd_len + len > sizeof(cmd_buf)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (net_pkt_read(pkt, (u8_t *)(cmd_buf + cmd_len), len)) {
|
||||
break;
|
||||
}
|
||||
|
||||
cmd_len += len;
|
||||
|
||||
if (end_of_line) {
|
||||
u8_t dummy;
|
||||
int ret;
|
||||
|
||||
if (net_pkt_read_u8(pkt, &dummy)) {
|
||||
break;
|
||||
}
|
||||
|
||||
cmd_buf[cmd_len] = '\0';
|
||||
|
||||
ret = handle_server_cmd(nats, cmd_buf, cmd_len,
|
||||
tmp, pos);
|
||||
if (ret < 0) {
|
||||
/* FIXME: What to do with unhandled messages? */
|
||||
break;
|
||||
}
|
||||
|
||||
cmd_len = 0U;
|
||||
}
|
||||
|
||||
tmp = pkt->cursor.buf;
|
||||
pos = pkt->cursor.pos - tmp->data;
|
||||
}
|
||||
|
||||
net_pkt_unref(pkt);
|
||||
}
|
||||
|
||||
int nats_connect(struct nats *nats, struct sockaddr *addr, socklen_t addrlen)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = net_context_connect(nats->conn, addr, addrlen,
|
||||
NULL, K_FOREVER, NULL);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return net_context_recv(nats->conn, receive_cb, K_NO_WAIT, nats);
|
||||
}
|
||||
|
||||
int nats_disconnect(struct nats *nats)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = net_context_put(nats->conn);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
nats->conn = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef __NATS_H
|
||||
#define __NATS_H
|
||||
|
||||
#include <zephyr/types.h>
|
||||
#include <net/net_context.h>
|
||||
|
||||
struct nats_msg {
|
||||
const char *subject;
|
||||
const char *sid;
|
||||
const char *reply_to;
|
||||
const char *payload;
|
||||
size_t payload_len;
|
||||
};
|
||||
|
||||
struct nats {
|
||||
struct net_context *conn;
|
||||
|
||||
int (*on_auth_required)(const struct nats *nats,
|
||||
char *user, size_t *user_len,
|
||||
char *pass, size_t *pass_len);
|
||||
int (*on_message)(const struct nats *nats,
|
||||
const struct nats_msg *msg);
|
||||
};
|
||||
|
||||
int nats_connect(struct nats *nats, struct sockaddr *addr, socklen_t addrlen);
|
||||
int nats_disconnect(struct nats *nats);
|
||||
|
||||
int nats_subscribe(const struct nats *nats,
|
||||
const char *subject, size_t subject_len,
|
||||
const char *queue_group, size_t queue_group_len,
|
||||
const char *sid, size_t sid_len);
|
||||
int nats_unsubscribe(const struct nats *nats,
|
||||
const char *sid, size_t sid_len,
|
||||
size_t max_msgs);
|
||||
|
||||
int nats_publish(const struct nats *nats,
|
||||
const char *subject, size_t subject_len,
|
||||
const char *reply_to, size_t reply_to_len,
|
||||
const char *payload, size_t payload_len);
|
||||
|
||||
#endif /* __NATS_H */
|
Loading…
Add table
Add a link
Reference in a new issue