blob: a913985034037846b1a1ecfccf8ff731c77c5212 [file] [log] [blame]
/*
* Copyright (c) 2017 Intel Corporation.
* Copyright (c) 2017 Linaro Ltd.
*
* SPDX-License-Identifier: Apache-2.0
*/
#if 1
#define SYS_LOG_DOMAIN "irc"
#define NET_LOG_ENABLED 1
#endif
#include <board.h>
#include <drivers/rand32.h>
#include <errno.h>
#include <gpio.h>
#include <net/nbuf.h>
#include <net/net_context.h>
#include <net/net_core.h>
#include <net/net_if.h>
#include <stdio.h>
#include <zephyr.h>
#include <net/dns_client.h>
#if !defined(CONFIG_NET_IPV6) && !defined(CONFIG_NET_IPV4)
#error "CONFIG_NET_IPV6 or CONFIG_NET_IPV4 must be enabled for irc_bot"
#endif
#define STACK_SIZE 2048
uint8_t stack[STACK_SIZE];
#define CMD_BUFFER_SIZE 256
static uint8_t cmd_buf[CMD_BUFFER_SIZE];
/* LED */
#if defined(LED0_GPIO_PORT)
#define LED_GPIO_NAME LED0_GPIO_PORT
#define LED_PIN LED0_GPIO_PIN
#else
#define LED_GPIO_NAME "(fail)"
#define LED_PIN 0
#endif
static struct device *led0;
static bool fake_led;
/* Network Config */
#if defined(CONFIG_NET_IPV6)
#define ZIRC_AF_INET AF_INET6
#define ZIRC_SOCKADDR_IN sockaddr_in6
#if defined(CONFIG_NET_APP_MY_IPV6_ADDR)
#define ZIRC_LOCAL_IP_ADDR CONFIG_NET_APP_MY_IPV6_ADDR
#else
#define ZIRC_LOCAL_IP_ADDR "2001:db8::1"
#endif /* CONFIG_NET_APP_MY_IPV6_ADDR */
#if defined(CONFIG_NET_APP_PEER_IPV6_ADDR)
#define ZIRC_PEER_IP_ADDR CONFIG_NET_APP_PEER_IPV6_ADDR
#else
#define ZIRC_PEER_IP_ADDR "2001:db8::2"
#endif /* CONFIG_NET_APP_PEER_IPV6_ADDR */
/* Google DNS server for IPv6 */
#define DNS_SERVER "2001:4860:4860::8888"
#else /* CONFIG_NET_IPV4 */
#define ZIRC_AF_INET AF_INET
#define ZIRC_SOCKADDR_IN sockaddr_in
#if defined(CONFIG_NET_APP_MY_IPV4_ADDR)
#define ZIRC_LOCAL_IP_ADDR CONFIG_NET_APP_MY_IPV4_ADDR
#else
#define ZIRC_LOCAL_IP_ADDR "192.168.0.1"
#endif /* CONFIG_NET_APP_MY_IPV4_ADDR */
#if defined(CONFIG_NET_APP_PEER_IPV4_ADDR)
#define ZIRC_PEER_IP_ADDR CONFIG_NET_APP_PEER_IPV4_ADDR
#else
#define ZIRC_PEER_IP_ADDR "192.168.0.2"
#endif /* CONFIG_NET_APP_PEER_IPV4_ADDR */
/* Google DNS server for IPv4 */
#define DNS_SERVER "8.8.8.8"
#endif
/* DNS API */
#define DNS_PORT 53
#define DNS_SLEEP_MSECS 400
/* IRC API */
#define DEFAULT_SERVER "irc.freenode.net"
#define DEFAULT_PORT 6667
#define DEFAULT_CHANNEL "#zephyrbot"
struct zirc_chan;
typedef void (*on_privmsg_rcvd_cb_t)(void *data, struct zirc_chan *chan,
char *umask, char *msg);
struct zirc {
struct net_context *conn;
struct zirc_chan *chans;
void *data;
};
struct zirc_chan {
struct zirc *irc;
struct zirc_chan *next;
const char *chan;
on_privmsg_rcvd_cb_t on_privmsg_rcvd;
void *data;
};
static void on_msg_rcvd(void *data, struct zirc_chan *chan, char *umask,
char *msg);
static void
panic(const char *msg)
{
NET_ERR("Panic: %s", msg);
for (;;) {
k_sleep(K_FOREVER);
}
}
static int
transmit(struct net_context *ctx, char buffer[], size_t len)
{
struct net_buf *send_buf;
send_buf = net_nbuf_get_tx(ctx, K_FOREVER);
if (!send_buf) {
return -ENOMEM;
}
if (!net_nbuf_append(send_buf, len, buffer, K_FOREVER)) {
return -EINVAL;
}
return net_context_send(send_buf, NULL, K_NO_WAIT, NULL, NULL);
}
static void
on_cmd_ping(struct zirc *irc, char *umask, char *cmd, size_t len)
{
char pong[32];
int ret;
NET_INFO("Got PING command from server: %s", cmd);
ret = snprintk(pong, 32, "PONG :%s", cmd + 1);
if (ret < sizeof(pong)) {
ret = transmit(irc->conn, pong, ret);
if (ret < 0) {
NET_INFO("Transmit error: %d", ret);
}
}
}
static void
on_cmd_privmsg(struct zirc *irc, char *umask, char *cmd, size_t len)
{
struct zirc_chan *chan;
char *space;
if (!umask) {
/* Don't know how this got here, so ignore */
return;
}
NET_INFO("Got message from umask %s: %s", umask, cmd);
space = memchr(cmd, ' ', len);
if (!space) {
return;
}
*space = '\0';
if (*(space + 1) != ':') {
/* Just ignore messages without a ':' after the space */
return;
}
space += 2; /* Jump over the ':', pointing to the message itself */
for (chan = irc->chans; chan; chan = chan->next) {
if (!strncmp(chan->chan, cmd, space - cmd)) {
chan->on_privmsg_rcvd(chan->data, chan, umask, space);
return;
}
}
/* TODO: could be a private message (from another user) */
NET_INFO("Unknown privmsg received: %.*s\n", (int)len, cmd);
}
#define CMD(cmd_, cb_) { \
.cmd = cmd_ " ", \
.cmd_len = sizeof(cmd_ " ") - 1, \
.func = on_cmd_ ## cb_ \
}
static void
process_command(struct zirc *irc, char *cmd, size_t len)
{
static const struct {
const char *cmd;
size_t cmd_len;
void (*func)(struct zirc *zirc, char *umask, char *cmd,
size_t len);
} commands[] = {
CMD("PING", ping),
CMD("PRIVMSG", privmsg),
};
char *umask;
int i;
if (*cmd == ':') {
char *space = memchr(cmd, ' ', len);
if (!space) {
return;
}
umask = cmd + 1;
*space = '\0';
len -= (space - cmd) + 1;
if (!len) {
return;
}
cmd = space + 1;
NET_INFO("Received from server, umask=%s: %s", umask, cmd);
} else {
umask = NULL;
NET_INFO("Received from server (no umask): %s", cmd);
}
for (i = 0; i < ARRAY_SIZE(commands); i++) {
if (len < commands[i].cmd_len) {
continue;
}
if (!strncmp(cmd, commands[i].cmd, commands[i].cmd_len)) {
NET_INFO("Command has handler, executing");
cmd += commands[i].cmd_len;
len -= commands[i].cmd_len;
commands[i].func(irc, umask, cmd, len);
return;
}
}
/* TODO: handle notices, CTCP, etc */
NET_INFO("Could not find handler to handle %s, ignoring", cmd);
}
#undef CMD
static void
on_context_recv(struct net_context *ctx, struct net_buf *buf,
int status, void *data)
{
struct zirc *irc = data;
struct net_buf *tmp;
uint8_t *end_of_line;
size_t len;
uint16_t pos = 0, cmd_len = 0;
if (!buf) {
/* TODO: notify of disconnection, maybe reconnect? */
NET_ERR("Disconnected\n");
return;
}
if (status) {
/* TODO: handle connection error */
NET_ERR("Connection error: %d\n", -status);
net_nbuf_unref(buf);
return;
}
/* tmp points to fragment containing IP header */
tmp = buf->frags;
/* skip pos to the first TCP payload */
pos = net_nbuf_appdata(buf) - tmp->data;
while (tmp) {
len = tmp->len - pos;
end_of_line = memchr(tmp->data + pos, '\r', len);
if (end_of_line) {
len = end_of_line - (tmp->data + pos);
}
if (cmd_len + len > sizeof(cmd_buf)) {
/* overrun cmd_buf - bail out */
NET_ERR("CMD BUFFER OVERRUN!! %zu > %zu",
cmd_len + len,
sizeof(cmd_buf));
break;
}
tmp = net_nbuf_read(tmp, pos, &pos, len, cmd_buf + cmd_len);
cmd_len += len;
if (end_of_line) {
/* skip the /n char after /r */
if (tmp) {
tmp = net_nbuf_read(tmp, pos, &pos, 1, NULL);
}
cmd_buf[cmd_len] = '\0';
process_command(irc, cmd_buf, cmd_len);
cmd_len = 0;
}
}
net_nbuf_unref(buf);
/* TODO: handle messages that spans multiple packets? */
}
static int
zirc_nick_set(struct zirc *irc, const char *nick)
{
char buffer[32];
int ret;
NET_INFO("Setting nickname to: %s", nick);
ret = snprintk(buffer, sizeof(buffer), "NICK %s\r\n", nick);
if (ret < 0 || ret >= sizeof(buffer)) {
return -EINVAL;
}
return transmit(irc->conn, buffer, ret);
}
static int
zirc_user_set(struct zirc *irc, const char *user, const char *realname)
{
char buffer[64];
int ret;
NET_INFO("Setting user to: %s, real name to: %s", user, realname);
ret = snprintk(buffer, sizeof(buffer), "USER %s * * :%s\r\n",
user, realname);
if (ret < 0 || ret >= sizeof(buffer)) {
return -EINVAL;
}
return transmit(irc->conn, buffer, ret);
}
static int
zirc_chan_join(struct zirc *irc, struct zirc_chan *chan,
const char *channel,
on_privmsg_rcvd_cb_t on_privmsg_rcvd,
void *data)
{
char buffer[32];
int ret;
NET_INFO("Joining channel: %s", channel);
if (!on_privmsg_rcvd) {
return -EINVAL;
}
ret = snprintk(buffer, sizeof(buffer), "JOIN %s\r\n", channel);
if (ret < 0 || ret >= sizeof(buffer)) {
return -EINVAL;
}
ret = transmit(irc->conn, buffer, ret);
if (ret < 0) {
return ret;
}
chan->irc = irc;
chan->chan = channel;
chan->on_privmsg_rcvd = on_privmsg_rcvd;
chan->data = data;
chan->next = irc->chans;
irc->chans = chan;
return 0;
}
static int
zirc_chan_part(struct zirc_chan *chan)
{
struct zirc_chan **cc, *c;
char buffer[32];
int ret;
NET_INFO("Leaving channel: %s", chan->chan);
ret = snprintk(buffer, sizeof(buffer), "PART %s\r\n", chan->chan);
if (ret < 0 || ret >= sizeof(buffer)) {
return -EINVAL;
}
ret = transmit(chan->irc->conn, buffer, ret);
if (ret < 0) {
return ret;
}
for (cc = &chan->irc->chans, c = chan->irc->chans;
c; cc = &c->next, c = c->next) {
if (c == chan) {
*cc = c->next;
return 0;
}
}
return -ENOENT;
}
static int in_addr_set(sa_family_t family,
const char *ip_addr,
int port,
struct sockaddr *_sockaddr)
{
int rc = 0;
_sockaddr->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) {
NET_ERR("Invalid IP address: %s", 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 int
zirc_dns_lookup(const char *host,
struct sockaddr *src_addr,
struct sockaddr *dst_addr)
{
struct net_context *net_ctx;
struct dns_context dns_ctx;
struct sockaddr dns_addr = { 0 };
int ret;
/* query DNS for server IP */
dns_init(&dns_ctx);
ret = net_context_get(ZIRC_AF_INET, SOCK_DGRAM, IPPROTO_UDP, &net_ctx);
if (ret < 0) {
NET_ERR("Cannot get network context for IP UDP: %d\n", -ret);
return ret;
}
ret = net_context_bind(net_ctx, src_addr,
sizeof(struct ZIRC_SOCKADDR_IN));
if (ret < 0) {
NET_ERR("Cannot bind: %d\n", -ret);
goto dns_exit;
}
in_addr_set(ZIRC_AF_INET, DNS_SERVER, DNS_PORT, &dns_addr);
dns_ctx.net_ctx = net_ctx;
dns_ctx.timeout = DNS_SLEEP_MSECS;
dns_ctx.dns_server = &dns_addr;
dns_ctx.elements = 1;
#ifdef CONFIG_NET_IPV6
dns_ctx.query_type = DNS_QUERY_TYPE_AAAA;
dns_ctx.address.ipv6 = &net_sin6(dst_addr)->sin6_addr;
#else
dns_ctx.query_type = DNS_QUERY_TYPE_A;
dns_ctx.address.ipv4 = &net_sin(dst_addr)->sin_addr;
#endif
dns_ctx.name = DEFAULT_SERVER;
ret = dns_resolve(&dns_ctx);
if (ret != 0) {
NET_ERR("dns_resolve ERROR %d\n", -ret);
}
dns_exit:
net_context_put(net_ctx);
return ret;
}
static int
zirc_connect(struct zirc *irc, const char *host, int port, void *data)
{
struct net_if *iface;
struct sockaddr dst_addr, src_addr;
struct zirc_chan *chan;
int ret;
char name_buf[32];
NET_INFO("Connecting to %s:%d...", host, port);
ret = net_context_get(ZIRC_AF_INET, SOCK_STREAM, IPPROTO_TCP,
&irc->conn);
if (ret < 0) {
NET_DBG("Could not get new context: %d", -ret);
return ret;
}
iface = net_if_get_default();
if (!iface) {
NET_DBG("Could not get new context: %d", -ret);
return -EIO;
}
#if defined(CONFIG_NET_IPV6) && defined(CONFIG_NET_DHCPV6)
/* TODO: IPV6 DHCP */
#elif defined(CONFIG_NET_IPV4) && defined(CONFIG_NET_DHCPV4)
net_ipaddr_copy(&net_sin(&src_addr)->sin_addr,
&iface->dhcpv4.requested_ip);
ret = in_addr_set(ZIRC_AF_INET, NULL, 0, &src_addr);
#else
ret = in_addr_set(ZIRC_AF_INET, ZIRC_LOCAL_IP_ADDR,
0, &src_addr);
if (ret < 0) {
goto connect_exit;
}
#endif
#if defined(CONFIG_DNS_RESOLVER)
in_addr_set(ZIRC_AF_INET, NULL, port, &dst_addr);
/* set the IP address for the dst_addr via DNS */
ret = zirc_dns_lookup(host, &src_addr, &dst_addr);
if (ret < 0) {
NET_ERR("Could not peform DNS lookup on host %s: %d",
host, -ret);
goto connect_exit;
}
#else
ret = in_addr_set(ZIRC_AF_INET, ZIRC_PEER_IP_ADDR,
port, &dst_addr);
if (ret < 0) {
goto connect_exit;
}
#endif
ret = net_context_bind(irc->conn, &src_addr,
sizeof(struct ZIRC_SOCKADDR_IN));
if (ret < 0) {
NET_DBG("Could not bind to local address: %d", -ret);
goto connect_exit;
}
irc->data = data;
ret = net_context_connect(irc->conn, &dst_addr,
sizeof(struct ZIRC_SOCKADDR_IN),
NULL, K_FOREVER, irc);
if (ret < 0) {
NET_DBG("Could not connect, errno %d", -ret);
goto connect_exit;
}
net_context_recv(irc->conn, on_context_recv, K_NO_WAIT, irc);
chan = irc->data;
ret = snprintk(name_buf, sizeof(name_buf), "zephyrbot%u",
sys_rand32_get());
if (ret < 0 || ret >= sizeof(name_buf)) {
panic("Can't fill name buffer");
}
if (zirc_nick_set(irc, name_buf) < 0) {
panic("Could not set nick");
}
if (zirc_user_set(irc, name_buf, "Zephyr IRC Bot") < 0) {
panic("Could not set user");
}
if (zirc_chan_join(irc, chan, DEFAULT_CHANNEL, on_msg_rcvd, NULL) < 0) {
panic("Could not join channel");
}
return ret;
connect_exit:
net_context_put(irc->conn);
return ret;
}
static int
zirc_disconnect(struct zirc *irc)
{
NET_INFO("Disconnecting");
irc->chans = NULL;
return net_context_put(irc->conn);
}
static int
zirc_chan_send_msg(const struct zirc_chan *chan, const char *msg)
{
char buffer[128];
NET_INFO("Sending to channel %s: %s", chan->chan, msg);
while (*msg) {
int msglen, txret;
msglen = snprintk(buffer, sizeof(buffer), "PRIVMSG %s :%s\r\n",
chan->chan, msg);
if (msglen < 0) {
return msglen;
}
txret = transmit(chan->irc->conn, buffer, msglen);
if (txret < 0) {
return txret;
}
if (msglen < sizeof(buffer)) {
return 0;
}
msg += msglen - sizeof("PRIVMSG :\r\n") - 1 +
strlen(chan->chan);
}
return 0;
}
static void
on_cmd_hello(struct zirc_chan *chan, const char *nick, const char *msg)
{
char buf[64];
int ret;
ret = snprintk(buf, sizeof(buf), "Hello, %s!", nick);
if (ret < 0 || ret >= sizeof(buf)) {
zirc_chan_send_msg(chan, "Hello, world! (Your nick is "
"larger than my stack allows.)");
} else {
zirc_chan_send_msg(chan, buf);
}
}
static void
on_cmd_random(struct zirc_chan *chan, const char *nick, const char *msg)
{
char buf[128];
int32_t num = sys_rand32_get();
int ret;
switch (num & 3) {
case 0:
ret = snprintk(buf, sizeof(buf), "Here's a fresh, random "
"32-bit integer, %s: %d", nick, num);
break;
case 1:
ret = snprintk(buf, sizeof(buf), "Another number, fresh off "
"the PRNG: %d", num);
break;
case 2:
ret = snprintk(buf, sizeof(buf), "Some calculations "
"with senseless constants yielded %d", num);
break;
case 3:
ret = snprintk(buf, sizeof(buf), "I rolled a fair dice and "
"the result is... %d", num);
break;
default:
/* Shut up the compiler, as this condition is impossible. */
ret = -1;
}
if (ret < 0 || ret >= sizeof(buf)) {
zirc_chan_send_msg(chan, "I rolled a fair dice and the "
"number is... 7.3");
} else {
zirc_chan_send_msg(chan, buf);
}
}
static bool
read_led(void)
{
uint32_t led = 0;
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(bool led)
{
if (!led0) {
fake_led = led;
} else {
gpio_pin_write(led0, LED_PIN, !led);
}
}
static void
on_cmd_led_off(struct zirc_chan *chan, const char *nick, const char *msg)
{
zirc_chan_send_msg(chan, "The LED should be *off* now");
write_led(false);
}
static void
on_cmd_led_on(struct zirc_chan *chan, const char *nick, const char *msg)
{
zirc_chan_send_msg(chan, "The LED should be *on* now");
write_led(true);
}
static void
on_cmd_led_toggle(struct zirc_chan *chan, const char *nick, const char *msg)
{
if (read_led()) {
on_cmd_led_off(chan, nick, msg);
} else {
on_cmd_led_on(chan, nick, msg);
}
}
static void
on_cmd_rejoin(struct zirc_chan *chan, const char *nick, const char *msg)
{
zirc_chan_part(chan);
zirc_chan_join(chan->irc, chan, DEFAULT_CHANNEL, on_msg_rcvd, NULL);
}
static void
on_cmd_disconnect(struct zirc_chan *chan, const char *nick, const char *msg)
{
zirc_disconnect(chan->irc);
}
#define CMD(c) { \
.cmd = "!" #c, \
.cmd_len = sizeof(#c) - 1, \
.func = on_cmd_ ## c \
}
static void
on_msg_rcvd(void *data, struct zirc_chan *chan, char *umask, char *msg)
{
static const struct {
const char *cmd;
size_t cmd_len;
void (*func)(struct zirc_chan *chan, const char *nick,
const char *msg);
} commands[] = {
CMD(hello),
CMD(random),
CMD(led_toggle),
CMD(led_off),
CMD(led_on),
CMD(rejoin),
CMD(disconnect),
};
char *nick, *end;
int i;
if (!umask) {
return;
}
NET_INFO("Received from umask %s: %s", umask, msg);
end = strchr(umask, '!');
if (!end) {
nick = NULL;
} else {
*end = '\0';
nick = umask;
}
for (i = 0; i < ARRAY_SIZE(commands); i++) {
if (!strncmp(msg, commands[i].cmd, commands[i].cmd_len)) {
msg += commands[i].cmd_len;
commands[i].func(chan, nick, msg);
return;
}
}
if (!strncmp(msg, "!help", 5)) {
char msg[32];
int ret;
/* TODO: loop through commands[] and create help text */
/* TODO: add help message to command, "!help command"
* sends it back
*/
ret = snprintk(msg, sizeof(msg), "%s, you're a grown up, figure"
" it out", nick);
if (ret < 0 || ret >= sizeof(msg)) {
zirc_chan_send_msg(chan, "Your nick is too long, and my"
" stack is limited. Can't help you");
} else {
zirc_chan_send_msg(chan, msg);
}
}
}
#undef CMD
static void
initialize_network(void)
{
struct net_if *iface;
NET_INFO("Initializing network");
iface = net_if_get_default();
if (!iface) {
panic("No default network interface");
}
#if defined(CONFIG_NET_IPV6) && defined(CONFIG_NET_DHCPV6)
/* TODO: IPV6 DHCP */
#elif defined(CONFIG_NET_IPV4) && defined(CONFIG_NET_DHCPV4)
net_dhcpv4_start(iface);
/* delay so DHCPv4 can assign IP */
/* TODO: add a timeout/retry */
NET_INFO("Waiting for DHCP ...");
do {
k_sleep(K_SECONDS(1));
} while (net_is_ipv4_addr_unspecified(&iface->dhcpv4.requested_ip));
NET_INFO("Done!");
/* TODO: add a timeout */
NET_INFO("Waiting for IP assginment ...");
do {
k_sleep(K_SECONDS(1));
} while (!net_is_my_ipv4_addr(&iface->dhcpv4.requested_ip));
NET_INFO("Done!");
#else
struct sockaddr addr;
if (in_addr_set(ZIRC_AF_INET, ZIRC_LOCAL_IP_ADDR, 0,
&addr) < 0) {
NET_ERR("Invalid IP address: %s",
ZIRC_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_IPV6 && CONFIG_NET_DHCPV6 */
}
static void
initialize_hardware(void)
{
NET_INFO("Initializing hardware");
led0 = device_get_binding(LED_GPIO_NAME);
if (led0) {
gpio_pin_configure(led0, LED_PIN, GPIO_DIR_OUT);
}
}
static void irc_bot(void)
{
struct zirc irc = { };
struct zirc_chan chan = { };
NET_INFO("Zephyr IRC bot sample");
initialize_network();
initialize_hardware();
if (zirc_connect(&irc, DEFAULT_SERVER, DEFAULT_PORT, &chan) < 0) {
panic("Could not connect");
}
}
void main(void)
{
k_thread_spawn(stack, STACK_SIZE, (k_thread_entry_t)irc_bot,
NULL, NULL, NULL, K_PRIO_COOP(7), 0, 0);
}