-
Notifications
You must be signed in to change notification settings - Fork 912
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
2 changed files
with
245 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 */ |