--- 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}
--- 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,
--- /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 <string.h>
+#include <assert.h>
+
+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;
+}
+
--- /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 */
--- 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;
--- 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
--- 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)