# HG changeset patch # User Tero Marttila # Date 1235849955 -7200 # Node ID 5001564ac5fc2368606946edb4d6643ca3754ef5 # Parent 20ce0029e4a03c37269c514425f90c19113db329 irc_line implementation diff -r 20ce0029e4a0 -r 5001564ac5fc Makefile --- a/Makefile Sat Feb 28 20:23:37 2009 +0200 +++ b/Makefile Sat Feb 28 21:39:15 2009 +0200 @@ -34,6 +34,7 @@ SOCK_OBJS = obj/sock.o obj/sock_tcp.o SOCK_GNUTLS_OBJS = obj/sock_gnutls.o LINEPROTO_OBJS = obj/line_proto.o +IRC_OBJS = obj/irc_line.o # XXX: not yet there #CORE_OBJS = obj/lib/log.o obj/lib/signals.o @@ -42,7 +43,7 @@ all: ${BIN_PATHS} # binaries -bin/nexus: ${CORE_OBJS} ${SOCK_OBJS} ${SOCK_GNUTLS_OBJS} ${LINEPROTO_OBJS} +bin/nexus: ${CORE_OBJS} ${SOCK_OBJS} ${SOCK_GNUTLS_OBJS} ${LINEPROTO_OBJS} ${IRC_OBJS} # computed CFLAGS = ${MODE_CFLAGS} ${FIXED_CFLAGS} ${LIBEVENT_CFLAGS} ${GNUTLS_CFLAGS} diff -r 20ce0029e4a0 -r 5001564ac5fc src/error.h --- a/src/error.h Sat Feb 28 20:23:37 2009 +0200 +++ b/src/error.h Sat Feb 28 21:39:15 2009 +0200 @@ -67,6 +67,10 @@ /* Libevent errors */ _ERROR_CODE( ERR_EVENT_NEW, 0x010201, NONE ), _ERROR_CODE( ERR_EVENT_ADD, 0x010202, NONE ), + + /* irc_line errors */ + _ERROR_CODE( ERR_LINE_TOO_LONG, 0x100101, NONE ), + _ERROR_CODE( ERR_LINE_INVALID_TOKEN, 0x100102, NONE ), // mask of bits used for the error_code value _ERROR_CODE_MASK = 0xffffff, diff -r 20ce0029e4a0 -r 5001564ac5fc src/irc_line.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/irc_line.c Sat Feb 28 21:39:15 2009 +0200 @@ -0,0 +1,118 @@ + +#include "irc_line.h" + +#include +#include + +err_t irc_line_parse (struct irc_line *line, char *data) +{ + int i; + + // prefix? + if (data && *data == ':') { + // consume as token + line->prefix = strsep(&data, " ") + 1; + } + + // command + line->command = strsep(&data, " "); + + // args + for (i = 0; i < IRC_ARG_MAX; i++) { + // trailing? + if (data && *data == ':') { + // consume the rest of the line + line->args[i] = data + 1; + data = NULL; + + } else { + // either NULL or a normal token + line->args[i] = strsep(&data, " "); + } + } + + // parse errors? What are those? + return SUCCESS; +} + +/* + * Tokens for output_token + */ +enum { + /* No space before token */ + TOK_NOSPACE = 0x01, + + /* Prefix with :, may contain spaces, last token */ + TOK_TRAILING = 0x02, +}; + +static err_t output_token (char *buf, size_t *offset, const char *token, int flags) +{ + size_t token_len = strlen(token), len; + char *dest = buf + *offset; + + // overall length + len = token_len + (flags & TOK_NOSPACE ? 0 : 1) + (flags & TOK_TRAILING ? 1 : 0); + + // check overflow + if (*offset + len > IRC_LINE_MAX) + return ERR_LINE_TOO_LONG; + + // check invalid tokens + if (strpbrk(token, (flags & TOK_TRAILING) ? IRC_TOKEN_TRAILING_INVALID : IRC_TOKEN_INVALID)) + return ERR_LINE_INVALID_TOKEN; + + // delimit with space? + if (!(flags & TOK_NOSPACE)) + *dest++ = ' '; + + // prefix trailing? + if (flags & TOK_TRAILING) + *dest++= ':'; + + // copy to buffer + memcpy(dest, token, token_len); + + // update offset + *offset += len; + + // ok + return 0; +} + +err_t irc_line_build (const struct irc_line *line, char *buf) +{ + size_t off = 0; + int i; + err_t err; + + // XXX: no need for prefix on client + assert(line->prefix == NULL); + + // command + if ((err = output_token(buf, &off, line->command, TOK_NOSPACE))) + return err; + + // args + for (i = 0; i < IRC_ARG_MAX; i++) { + // skip unused args at end + if (line->args[i] == NULL) + break; + + // if last and contains a space, format as a trailing param + if ((i < IRC_ARG_MAX - 1) && line->args[i + 1] == NULL && strchr(line->args[i], ' ')) { + // output using TOK_TRAILING + if ((err = output_token(buf, &off, line->args[i], TOK_TRAILING))) + return err; + + } else { + // output as normal token + if ((err = output_token(buf, &off, line->args[i], 0))) + return err; + } + } + + // done + return SUCCESS; +} + diff -r 20ce0029e4a0 -r 5001564ac5fc src/irc_line.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/irc_line.h Sat Feb 28 21:39:15 2009 +0200 @@ -0,0 +1,47 @@ +#ifndef IRC_LINE_H +#define IRC_LINE_H + +#include "error.h" + +/* + * The maximum length of a line, including terminating CRLF + */ +#define IRC_LINE_MAX 512 + +/* + * The maximum number of arguments for a single command + */ +#define IRC_ARG_MAX 15 + +/* + * Chars that are invalid inside of tokens + */ +#define IRC_TOKEN_INVALID "\r\n\n " +#define IRC_TOKEN_TRAILING_INVALID "\r\n\n" + +/* + * Low-level IRC protocol unit is a line with its bits + */ +struct irc_line { + /* The message source, either a server name or a nickmask */ + const char *prefix; + + /* The command, either a numeric or a primary command */ + const char *command; + + /* The arguments, with unused ones set to NULL */ + const char *args[IRC_ARG_MAX]; +}; + +/* + * Parse an IRC message to fill in an irc_line. This mutates the value of data (to insert NULs between tokens), and + * stores pointers into this data into the irc_line. + */ +err_t irc_line_parse (struct irc_line *line, char *data); + +/* + * Formats an irc_line as a protocol line into the given buffer (which should hold at least IRC_LINE_MAX bytes). + */ +err_t irc_line_build (const struct irc_line *line, char *buf); + +#endif /* IRC_LINE_H */ diff -r 20ce0029e4a0 -r 5001564ac5fc src/line_proto.c --- a/src/line_proto.c Sat Feb 28 20:23:37 2009 +0200 +++ b/src/line_proto.c Sat Feb 28 21:39:15 2009 +0200 @@ -43,7 +43,7 @@ static void line_proto_on_read (struct sock_stream *sock, void *arg) { struct line_proto *lp = arg; - const char *line; + char *line; // sanity-check assert(lp->tail_offset < lp->buf_len); @@ -155,7 +155,7 @@ return next; } -err_t line_proto_read (struct line_proto *lp, const char **line_ptr) +err_t line_proto_read (struct line_proto *lp, char **line_ptr) { // offset to recv() new data into, offset to _parse_line hint, offset to next line from _parse_line size_t recv_offset = 0, peek_offset = 0, next_offset = 0; diff -r 20ce0029e4a0 -r 5001564ac5fc src/line_proto.h --- a/src/line_proto.h Sat Feb 28 20:23:37 2009 +0200 +++ b/src/line_proto.h Sat Feb 28 21:39:15 2009 +0200 @@ -15,7 +15,7 @@ /* * The callback for receiving lines */ -typedef void (*line_proto_read_cb)(const char *line, void *arg); +typedef void (*line_proto_read_cb)(char *line, void *arg); /* * Create a new line_proto off the the given sock_stream. The newly allocated line_proto will be returned via *lp_ptr. @@ -32,7 +32,7 @@ * returned via *line_ptr, and we return SUCCESS. If we don't yet have a full line, and receiving more would block, * NULL is returned via *line_ptr instead. Otherwise, nonzero error return code. */ -err_t line_proto_read (struct line_proto *lp, const char **line_ptr); +err_t line_proto_read (struct line_proto *lp, char **line_ptr); /* * Signify that the line read with line_proto_read() was handled and can be discarded diff -r 20ce0029e4a0 -r 5001564ac5fc src/nexus.c --- a/src/nexus.c Sat Feb 28 20:23:37 2009 +0200 +++ b/src/nexus.c Sat Feb 28 21:39:15 2009 +0200 @@ -9,6 +9,7 @@ #include "sock.h" #include "line_proto.h" +#include "irc_line.h" #define CONNECT_HOST "irc.fixme.fi" #define CONNECT_SERV "6697" @@ -32,9 +33,19 @@ printf(" --ssl / -S use SSL\n"); } -void on_line (const char *line, void *arg) +void on_line (char *line_buf, void *arg) { - printf("<<< %s\n", line); + struct irc_line line; + int err; + + // parse + if ((err = irc_line_parse(&line, line_buf))) + printf("!!! Invalid line: %s: %s\n", line_buf, error_name(err)); + + else + printf("<<< prefix=%s, command=%s, args={%s, %s, %s, ...}\n", + line.prefix, line.command, line.args[0], line.args[1], line.args[2] + ); } int main (int argc, char **argv)