mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-18 05:12:45 +01:00
routing: Added simple IRC library based on io_loop
The IRC library can login and keep its connection alive by replying to PING messages. It also exposes a callback that handles PRIVMSG commands and can inject messages from outside, e.g., based on a timer or a lightning event.
This commit is contained in:
parent
fce9ee29e3
commit
96839290fb
178
irc.c
Normal file
178
irc.c
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
#include "irc.h"
|
||||||
|
#include "daemon/dns.h"
|
||||||
|
#include "daemon/log.h"
|
||||||
|
|
||||||
|
void (*irc_privmsg_cb)(struct ircstate *, const struct privmsg *) = NULL;
|
||||||
|
void (*irc_disconnect_cb)(struct ircstate *) = NULL;
|
||||||
|
|
||||||
|
static struct io_plan *irc_connected(struct io_conn *conn, struct lightningd_state *dstate, struct ircstate *state);
|
||||||
|
static void irc_disconnected(struct io_conn *conn, struct ircstate *state);
|
||||||
|
|
||||||
|
bool irc_send_msg(struct ircstate *state, struct privmsg *m)
|
||||||
|
{
|
||||||
|
return irc_send(state, "PRIVMSG", "%s :%s", m->channel, m->msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send a raw irccommand to the IRC server. */
|
||||||
|
bool irc_send(struct ircstate *state, const char *command, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
struct irccommand *c = tal(state, struct irccommand);
|
||||||
|
|
||||||
|
c->prefix = NULL;
|
||||||
|
|
||||||
|
if (!state->connected)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
c->command = tal_strdup(c, command);
|
||||||
|
c->params = tal_vfmt(c, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
list_add_tail(&state->writequeue, &c->list);
|
||||||
|
io_wake(state);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write buffered irccommands to the IRC connection. Commands can be
|
||||||
|
buffered using irc_send. */
|
||||||
|
static struct io_plan *irc_write_loop(struct io_conn *conn, struct ircstate *state)
|
||||||
|
{
|
||||||
|
state->writebuffer = tal_free(state->writebuffer);
|
||||||
|
|
||||||
|
struct irccommand *m = list_pop(&state->writequeue, struct irccommand, list);
|
||||||
|
if (m == NULL)
|
||||||
|
return io_out_wait(conn, state, irc_write_loop, state);
|
||||||
|
|
||||||
|
bool hasprefix = m->prefix == NULL;
|
||||||
|
state->writebuffer = tal_fmt(
|
||||||
|
state, "%s%s%s %s\r\n",
|
||||||
|
hasprefix ? "" : m->prefix,
|
||||||
|
hasprefix ? "" : " ",
|
||||||
|
m->command,
|
||||||
|
m->params);
|
||||||
|
|
||||||
|
tal_free(m);
|
||||||
|
|
||||||
|
log_debug(state->log, "Sending: \"%s\"", state->writebuffer);
|
||||||
|
|
||||||
|
return io_write(
|
||||||
|
conn,
|
||||||
|
state->writebuffer, strlen(state->writebuffer),
|
||||||
|
irc_write_loop, state
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called by the read loop to handle individual lines. This splits the
|
||||||
|
* line into a struct irccommand and passes it on to the specific
|
||||||
|
* handlers for the irccommand type. It silently drops any irccommand
|
||||||
|
* that has an unhandled type.
|
||||||
|
*/
|
||||||
|
static void handle_irc_command(struct ircstate *state, const char *line)
|
||||||
|
{
|
||||||
|
log_debug(state->log, "Received: \"%s\"", line);
|
||||||
|
|
||||||
|
struct irccommand *m = talz(state, struct irccommand);
|
||||||
|
char** splits = tal_strsplit(m, line, " ", STR_NO_EMPTY);
|
||||||
|
int numsplits = tal_count(splits) - 1;
|
||||||
|
|
||||||
|
if (numsplits > 2 && strstarts(splits[0], ":")) {
|
||||||
|
m->prefix = splits[0];
|
||||||
|
splits++;
|
||||||
|
}
|
||||||
|
m->command = splits[0];
|
||||||
|
m->params = tal_strjoin(m, splits + 1, " ", STR_NO_TRAIL);
|
||||||
|
|
||||||
|
if (streq(m->command, "PING")) {
|
||||||
|
irc_send(state, "PONG", "%s", m->params);
|
||||||
|
|
||||||
|
} else if (streq(m->command, "PRIVMSG")) {
|
||||||
|
struct privmsg *pm = talz(m, struct privmsg);
|
||||||
|
pm->sender = m->prefix;
|
||||||
|
pm->channel = splits[1];
|
||||||
|
pm->msg = tal_strjoin(m, splits + 2, " ", STR_NO_TRAIL);
|
||||||
|
irc_privmsg_cb(state, pm);
|
||||||
|
}
|
||||||
|
tal_free(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read incoming data and split it along the newline boundaries. Takes
|
||||||
|
* care of buffering incomplete lines and passes the lines to the
|
||||||
|
* handle_irc_command handler.
|
||||||
|
*/
|
||||||
|
static struct io_plan *irc_read_loop(struct io_conn *conn, struct ircstate *state)
|
||||||
|
{
|
||||||
|
|
||||||
|
size_t len = state->readlen + state->buffered;
|
||||||
|
char *start = state->buffer, *end;
|
||||||
|
|
||||||
|
while ((end = memchr(start, '\n', len)) != NULL) {
|
||||||
|
/* Strip "\r\n" from lines. */
|
||||||
|
const char *line = tal_strndup(state, start, end - 1 - start);
|
||||||
|
handle_irc_command(state, line);
|
||||||
|
tal_free(line);
|
||||||
|
len -= (end + 1 - start);
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Move any partial data back down. */
|
||||||
|
memmove(state->buffer, start, len);
|
||||||
|
state->buffered = len;
|
||||||
|
|
||||||
|
return io_read_partial(conn, state->buffer + state->buffered,
|
||||||
|
sizeof(state->buffer) - state->buffered,
|
||||||
|
&state->readlen, irc_read_loop, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void irc_failed(struct lightningd_state *dstate, struct ircstate *state)
|
||||||
|
{
|
||||||
|
irc_disconnected(state->conn, state);
|
||||||
|
state->connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void irc_disconnected(struct io_conn *conn, struct ircstate *state)
|
||||||
|
{
|
||||||
|
log_debug(state->log, "Lost connection to IRC server");
|
||||||
|
state->connected = false;
|
||||||
|
state->conn = NULL;
|
||||||
|
state->readlen = 0;
|
||||||
|
state->buffered = 0;
|
||||||
|
memset(state->buffer, 0, sizeof(state->buffer));
|
||||||
|
|
||||||
|
/* Clear any pending commands, they're no longer useful */
|
||||||
|
while (!list_empty(&state->writequeue))
|
||||||
|
tal_free(list_pop(&state->writequeue, struct irccommand, list));
|
||||||
|
|
||||||
|
/* Same goes for partially written commands */
|
||||||
|
state->writebuffer = tal_free(state->writebuffer);
|
||||||
|
|
||||||
|
if (irc_disconnect_cb != NULL)
|
||||||
|
irc_disconnect_cb(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void irc_connect(struct ircstate *state)
|
||||||
|
{
|
||||||
|
state->connected = false;
|
||||||
|
list_head_init(&state->writequeue);
|
||||||
|
|
||||||
|
log_debug(state->log, "Connecting to IRC server %s", state->server);
|
||||||
|
dns_resolve_and_connect(state->dstate, state->server, "6667", irc_connected, irc_failed, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct io_plan *irc_connected(struct io_conn *conn, struct lightningd_state *dstate, struct ircstate *state)
|
||||||
|
{
|
||||||
|
io_set_finish(conn, irc_disconnected, state);
|
||||||
|
state->conn = conn;
|
||||||
|
state->connected = true;
|
||||||
|
irc_send(state, "USER", "%s 0 * :A lightning node", state->nick);
|
||||||
|
irc_send(state, "NICK", "%s", state->nick);
|
||||||
|
irc_send(state, "JOIN", "#lightning-nodes");
|
||||||
|
|
||||||
|
return io_duplex(conn,
|
||||||
|
io_read_partial(conn,
|
||||||
|
state->buffer, sizeof(state->buffer),
|
||||||
|
&state->readlen, irc_read_loop, state),
|
||||||
|
irc_write_loop(conn, state));
|
||||||
|
}
|
67
irc.h
Normal file
67
irc.h
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#ifndef LIGHTNING_IRC_H
|
||||||
|
#define LIGHTNING_IRC_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <ccan/io/io.h>
|
||||||
|
#include <ccan/short_types/short_types.h>
|
||||||
|
#include <ccan/str/str.h>
|
||||||
|
#include <ccan/tal/str/str.h>
|
||||||
|
#include <ccan/time/time.h>
|
||||||
|
#include <ccan/timer/timer.h>
|
||||||
|
|
||||||
|
#include "daemon/lightningd.h"
|
||||||
|
|
||||||
|
struct irccommand {
|
||||||
|
struct list_node list;
|
||||||
|
const char *prefix;
|
||||||
|
const char *command;
|
||||||
|
const char *params;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct privmsg {
|
||||||
|
const char *channel;
|
||||||
|
const char *sender;
|
||||||
|
const char *msg;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ircstate {
|
||||||
|
/* Meta information */
|
||||||
|
const char *nick;
|
||||||
|
const char *server;
|
||||||
|
|
||||||
|
/* Connection and reading */
|
||||||
|
struct io_conn *conn;
|
||||||
|
char buffer[512];
|
||||||
|
size_t readlen;
|
||||||
|
size_t buffered;
|
||||||
|
|
||||||
|
/* Write queue related */
|
||||||
|
struct list_head writequeue;
|
||||||
|
char *writebuffer;
|
||||||
|
|
||||||
|
/* Pointer to external state, making it available to callbacks */
|
||||||
|
struct lightningd_state *dstate;
|
||||||
|
|
||||||
|
struct log *log;
|
||||||
|
|
||||||
|
/* Are we currently connected? */
|
||||||
|
bool connected;
|
||||||
|
|
||||||
|
/* Time to wait after getting disconnected before reconnecting. */
|
||||||
|
struct timerel reconnect_timeout;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Callback to register for incoming messages */
|
||||||
|
extern void (*irc_privmsg_cb)(struct ircstate *, const struct privmsg *);
|
||||||
|
extern void (*irc_disconnect_cb)(struct ircstate *);
|
||||||
|
|
||||||
|
/* Send messages to IRC */
|
||||||
|
bool irc_send(struct ircstate *state, const char *command, const char *fmt, ...) PRINTF_FMT(3,4);
|
||||||
|
bool irc_send_msg(struct ircstate *state, struct privmsg *m);
|
||||||
|
|
||||||
|
/* Register IRC connection with io */
|
||||||
|
void irc_connect(struct ircstate *state);
|
||||||
|
|
||||||
|
#endif /* LIGHTNING_IRC_H */
|
Loading…
Reference in New Issue
Block a user