irc_line implementation
authorTero Marttila <terom@fixme.fi>
Sat, 28 Feb 2009 21:39:15 +0200
changeset 17 5001564ac5fc
parent 16 20ce0029e4a0
child 18 dedf137b504f
irc_line implementation
Makefile
src/error.h
src/irc_line.c
src/irc_line.h
src/line_proto.c
src/line_proto.h
src/nexus.c
--- 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)