# HG changeset patch # User Tero Marttila # Date 1235839690 -7200 # Node ID 4147fae232d9fcee2ca2f3ff528c447102aead9c # Parent 14e79683c48cbaf7e4b9d215fabd1c310f1b9acb update sock_stream_read/write semantics for EOF/EAGAIN, tentative event-based gnutls code diff -r 14e79683c48c -r 4147fae232d9 src/error.h --- a/src/error.h Sat Feb 28 17:39:37 2009 +0200 +++ b/src/error.h Sat Feb 28 18:48:10 2009 +0200 @@ -50,7 +50,8 @@ _ERROR_CODE( ERR_READ, 0x000401, ERRNO ), _ERROR_CODE( ERR_READ_EOF, 0x000402, NONE ), _ERROR_CODE( ERR_WRITE, 0x000403, ERRNO ), - _ERROR_CODE( ERR_FCNTL, 0x000404, ERRNO ), + _ERROR_CODE( ERR_WRITE_EOF, 0x000404, NONE ), + _ERROR_CODE( ERR_FCNTL, 0x000405, ERRNO ), /* GnuTLS errors */ _ERROR_CODE( ERR_GNUTLS_CERT_ALLOC_CRED, 0x010101, GNUTLS ), @@ -61,6 +62,7 @@ _ERROR_CODE( ERR_GNUTLS_HANDSHAKE, 0x010106, GNUTLS ), _ERROR_CODE( ERR_GNUTLS_RECORD_SEND, 0x010107, GNUTLS ), _ERROR_CODE( ERR_GNUTLS_RECORD_RECV, 0x010108, GNUTLS ), + _ERROR_CODE( ERR_GNUTLS_RECORD_GET_DIRECTION, 0x010109, GNUTLS ), /* Libevent errors */ _ERROR_CODE( ERR_EVENT_NEW, 0x010201, NONE ), diff -r 14e79683c48c -r 4147fae232d9 src/line_proto.c --- a/src/line_proto.c Sat Feb 28 17:39:37 2009 +0200 +++ b/src/line_proto.c Sat Feb 28 18:48:10 2009 +0200 @@ -154,7 +154,7 @@ size_t recv_offset = 0, peek_offset = 0, next_offset = 0; int ret; - // adjust offset from previous data + // adjust offset to beyond previous data (as will be moved next) recv_offset = lp->tail_len; // move trailing data from previous line to front of buffer @@ -183,23 +183,16 @@ assert(recv_offset < lp->buf_len); // otherwise, read more data - if ((ret = sock_stream_read(lp->sock, lp->buf + recv_offset, lp->buf_len - recv_offset)) < 0) { - // we can special-case EAGAIN, as it's expected - if (MATCH_ERROR(sock_stream_error(lp->sock), ERR_READ, EAGAIN)) { - // return a NULL *line_ptr - *line_ptr = NULL; - break; + if ((ret = sock_stream_read(lp->sock, lp->buf + recv_offset, lp->buf_len - recv_offset)) < 0) + // store and return NULL on errors + RETURN_SET_ERROR_INFO(&lp->err, sock_stream_error(lp->sock)); - } else { - // store and return NULL on errors - RETURN_SET_ERROR_INFO(&lp->err, sock_stream_error(lp->sock)); - - } + // EAGAIN? + if (ret == 0) { + // return a NULL *line_ptr + *line_ptr = NULL; + break; } - - // EOF? - if (ret == 0) - return SET_ERROR(&lp->err, ERR_READ_EOF); // update recv_offset recv_offset += ret; diff -r 14e79683c48c -r 4147fae232d9 src/line_proto.h --- a/src/line_proto.h Sat Feb 28 17:39:37 2009 +0200 +++ b/src/line_proto.h Sat Feb 28 18:48:10 2009 +0200 @@ -35,6 +35,11 @@ err_t line_proto_read (struct line_proto *lp, const char **line_ptr); /* + * Signify that the line read with line_proto_read() was handled and can be discarded + */ +void line_proto_discard (struct line_proto *lp); + +/* * Get current error_info* */ const struct error_info* line_proto_error (struct line_proto *lp); diff -r 14e79683c48c -r 4147fae232d9 src/nexus.c --- a/src/nexus.c Sat Feb 28 17:39:37 2009 +0200 +++ b/src/nexus.c Sat Feb 28 18:48:10 2009 +0200 @@ -12,8 +12,8 @@ #include "sock.h" #include "line_proto.h" -#define CONNECT_HOST "irc.fixme.fi" -#define CONNECT_SERV "6667" +#define CONNECT_HOST "localhost" +#define CONNECT_SERV "5001" #define LINE_LENGTH 512 void on_line (const char *line, void *arg) { diff -r 14e79683c48c -r 4147fae232d9 src/sock.c --- a/src/sock.c Sat Feb 28 17:39:37 2009 +0200 +++ b/src/sock.c Sat Feb 28 18:48:10 2009 +0200 @@ -74,3 +74,12 @@ // return pointer return SOCK_ERR(sock); } + +void sock_stream_invoke_callbacks (struct sock_stream *sock, short what) +{ + if (what & EV_READ && sock->cb_info->on_read) + sock->cb_info->on_read(sock, sock->cb_arg); + + if (what & EV_WRITE && sock->cb_info->on_write) + sock->cb_info->on_read(sock, sock->cb_arg); +} diff -r 14e79683c48c -r 4147fae232d9 src/sock.h --- a/src/sock.h Sat Feb 28 17:39:37 2009 +0200 +++ b/src/sock.h Sat Feb 28 18:48:10 2009 +0200 @@ -51,7 +51,10 @@ err_t sock_gnutls_connect (struct sock_stream **sock_ptr, const char *host, const char *service, struct error_info *err); /* - * The generic read/write API for stream sockets. + * The generic read/write API for stream sockets. These are mostly identical to the equivalent read/write syscalls, but + * the handling of EOF and EAGAIN is different. Normally, these return the (positive) number of bytes written. For + * EAGAIN, these return zero. For EOF, these return -ERR_READ_EOF/ERR_WRITE_EOF. Otherwise, these return the -ERR_* + * code. */ int sock_stream_read (struct sock_stream *sock, void *buf, size_t len); int sock_stream_write (struct sock_stream *sock, const void *buf, size_t len); @@ -65,10 +68,11 @@ err_t sock_stream_event_init (struct sock_stream *sock, const struct sock_stream_callbacks *callbacks, void *arg); /* - * Prime the callbacks set up earlier with sock_stream_event_init to fire once ready. + * Enable the events for this sock, as set up earlier with event_init. Mask should contain EV_READ/EV_WRITE. * - * \a mask is a bitmask of EV_* bits, such as EV_READ, EV_WRITE or EV_PERSIST. See event_set() for more info about the - * behaviour of those. + * The implementation of this is slightly hazy for complex protocols; this should only be used to map from + * sock_stream_read/write to the corresponding sock_stream_callback. That is, if sock_stream_read returns zero, then + * call event_enable(EV_READ), wherepon on_read will later be called. */ err_t sock_stream_event_enable (struct sock_stream *sock, short mask); diff -r 14e79683c48c -r 4147fae232d9 src/sock_gnutls.c --- a/src/sock_gnutls.c Sat Feb 28 17:39:37 2009 +0200 +++ b/src/sock_gnutls.c Sat Feb 28 18:48:10 2009 +0200 @@ -8,14 +8,29 @@ static err_t sock_gnutls_read (struct sock_stream *base_sock, void *buf, size_t *len) { struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls); + struct error_info *err = SOCK_GNUTLS_ERR(sock); int ret; - // just map to gnutls_record_recv - if ((ret = gnutls_record_recv(sock->session, buf, *len)) < 0) - RETURN_SET_ERROR_ERRNO(SOCK_GNUTLS_ERR(sock), ERR_GNUTLS_RECORD_RECV); + // read gnutls record + ret = gnutls_record_recv(sock->session, buf, *len); - // updated length - *len = ret; + // errors + if (ret < 0 && ret != GNUTLS_E_AGAIN) + RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_RECV, ret); + + else if (ret == 0) + return SET_ERROR(err, ERR_READ_EOF); + + + // eagain? + if (ret == 0) { + *len = 0; + + } else { + // updated length + *len = ret; + + } return SUCCESS; } @@ -23,29 +38,87 @@ static err_t sock_gnutls_write (struct sock_stream *base_sock, const void *buf, size_t *len) { struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls); + struct error_info *err = SOCK_GNUTLS_ERR(sock); int ret; + + // read gnutls record + ret = gnutls_record_send(sock->session, buf, *len); - // just map to gnutls_record_send - if ((ret = gnutls_record_send(sock->session, buf, *len)) < 0) - RETURN_SET_ERROR_ERRNO(SOCK_GNUTLS_ERR(sock), ERR_GNUTLS_RECORD_SEND); + // errors + if (ret < 0 && ret != GNUTLS_E_AGAIN) + RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_RECV, ret); - // updated length - *len = ret; + else if (ret == 0) + return SET_ERROR(err, ERR_READ_EOF); + + + // eagain? + if (ret == 0) { + *len = 0; + + } else { + // updated length + *len = ret; + } return SUCCESS; } +static void sock_gnutls_event_handler (int fd, short what, void *arg) +{ + struct sock_gnutls *sock = arg; + + // gnutls might be able to proceed now, so ask user to try what didn't work before now, using the mask given to + // event_enable(). + sock_stream_invoke_callbacks(SOCK_GNUTLS_BASE(sock), sock->ev_mask); +} + static err_t sock_gnutls_event_init (struct sock_stream *base_sock) { struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls); - + + err_t err; + + // set nonblocking + if ((err = sock_tcp_set_nonblock(SOCK_GNUTLS_TCP(sock), 1))) + return err; + + // add ourselves as the event handler + if ((err = sock_tcp_init_ev(SOCK_GNUTLS_TCP(sock), &sock_gnutls_event_handler, sock))) + return err; + + // ok return SUCCESS; } static err_t sock_gnutls_event_enable (struct sock_stream *base_sock, short mask) { struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls); + int ret; + // store the ev_mask. We don't care about it here, because we assume that event_enable is only called once read or + // write, respectively, return zero. This is really the only case we can handle with gnutls. + sock->ev_mask = mask; + + // gnutls_record_get_direction tells us what I/O operation gnutls would have required for the last + // operation, so we can use that to determine what events to register + switch ((ret = gnutls_record_get_direction(sock->session))) { + case 0: + // read more data + sock_tcp_add_event(SOCK_GNUTLS_TCP(sock), EV_READ); + break; + + case 1: + // write buffer full + sock_tcp_add_event(SOCK_GNUTLS_TCP(sock), EV_WRITE); + break; + + default: + // random error + RETURN_SET_ERROR_EXTRA(SOCK_GNUTLS_ERR(sock), ERR_GNUTLS_RECORD_GET_DIRECTION, ret); + } + + // ok... wait return SUCCESS; } diff -r 14e79683c48c -r 4147fae232d9 src/sock_gnutls.h --- a/src/sock_gnutls.h Sat Feb 28 17:39:37 2009 +0200 +++ b/src/sock_gnutls.h Sat Feb 28 18:48:10 2009 +0200 @@ -31,6 +31,9 @@ /* The GnuTLS session for this connection */ gnutls_session_t session; + + /* The current event_enable mask */ + int ev_mask; }; #define SOCK_GNUTLS_BASE(sock_ptr) (&(sock_ptr)->base_tcp.base) diff -r 14e79683c48c -r 4147fae232d9 src/sock_internal.h --- a/src/sock_internal.h Sat Feb 28 17:39:37 2009 +0200 +++ b/src/sock_internal.h Sat Feb 28 18:48:10 2009 +0200 @@ -63,4 +63,9 @@ */ void sock_stream_init (struct sock_stream *sock, struct sock_stream_type *type); +/* + * Invoke the appropriate callbacks for the given EV_* bitmask. + */ +void sock_stream_invoke_callbacks (struct sock_stream *sock, short what); + #endif /* SOCK_INTERNAL_H */ diff -r 14e79683c48c -r 4147fae232d9 src/sock_tcp.c --- a/src/sock_tcp.c Sat Feb 28 17:39:37 2009 +0200 +++ b/src/sock_tcp.c Sat Feb 28 18:48:10 2009 +0200 @@ -18,11 +18,7 @@ struct sock_tcp *sock = arg; // invoke appropriate callback - if (what & EV_READ && SOCK_TCP_BASE(sock)->cb_info->on_read) - SOCK_TCP_BASE(sock)->cb_info->on_read(SOCK_TCP_BASE(sock), SOCK_TCP_BASE(sock)->cb_arg); - - if (what & EV_WRITE && SOCK_TCP_BASE(sock)->cb_info->on_write) - SOCK_TCP_BASE(sock)->cb_info->on_read(SOCK_TCP_BASE(sock), SOCK_TCP_BASE(sock)->cb_arg); + sock_stream_invoke_callbacks(SOCK_TCP_BASE(sock), what); } /* @@ -33,14 +29,26 @@ struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp); int ret; - // map directly to read(2) - if ((ret = read(sock->fd, buf, *len)) < 0) - // errno + // read(), and detect non-EAGAIN or EOF + if ((ret = read(sock->fd, buf, *len)) < 0 && errno != EAGAIN) + // unexpected error RETURN_SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_READ); + + else if (ret == 0) + // EOF + return SET_ERROR(SOCK_TCP_ERR(sock), ERR_READ_EOF); - // bytes read - *len = ret; + if (ret < 0) { + // EAGAIN -> zero bytes + *len = 0; + + } else { + // normal -> bytes read + *len = ret; + } + + // ok return SUCCESS; } @@ -52,13 +60,24 @@ struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp); int ret; - // map directly to write(2) - if ((ret = write(sock->fd, buf, *len)) < 0) - // errno + // write(), and detect non-EAGAIN or EOF + if ((ret = write(sock->fd, buf, *len)) < 0 && errno != EAGAIN) + // unexpected error RETURN_SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_WRITE); + + else if (ret == 0) + // EOF + return SET_ERROR(SOCK_TCP_ERR(sock), ERR_WRITE_EOF); - // bytes read - *len = ret; + + if (ret < 0) { + // EAGAIN -> zero bytes + *len = 0; + + } else { + // normal -> bytes read + *len = ret; + } return SUCCESS; } @@ -83,16 +102,9 @@ static err_t sock_tcp_event_enable (struct sock_stream *base_sock, short mask) { struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp); - - // just add the appropraite events - if (mask & EV_READ && event_add(sock->ev_read, NULL)) - return SET_ERROR(SOCK_TCP_ERR(sock), ERR_EVENT_ADD); - - if (mask & EV_WRITE && event_add(sock->ev_write, NULL)) - return SET_ERROR(SOCK_TCP_ERR(sock), ERR_EVENT_ADD); - // done - return SUCCESS; + // implemented in sock_tcp_add_event + return sock_tcp_add_event(sock, mask); } /* @@ -149,6 +161,19 @@ return SUCCESS; } +err_t sock_tcp_add_event (struct sock_tcp *sock, short mask) +{ + // just add the appropraite events + if (mask & EV_READ && event_add(sock->ev_read, NULL)) + return SET_ERROR(SOCK_TCP_ERR(sock), ERR_EVENT_ADD); + + if (mask & EV_WRITE && event_add(sock->ev_write, NULL)) + return SET_ERROR(SOCK_TCP_ERR(sock), ERR_EVENT_ADD); + + // done + return SUCCESS; +} + err_t sock_tcp_init_connect (struct sock_tcp *sock, const char *hostname, const char *service) { struct addrinfo hints, *res, *r; diff -r 14e79683c48c -r 4147fae232d9 src/sock_tcp.h --- a/src/sock_tcp.h Sat Feb 28 17:39:37 2009 +0200 +++ b/src/sock_tcp.h Sat Feb 28 18:48:10 2009 +0200 @@ -49,6 +49,11 @@ err_t sock_tcp_set_nonblock (struct sock_tcp *sock, int nonblock); /* + * event_add the specified ev_* events. + */ +err_t sock_tcp_add_event (struct sock_tcp *sock, short mask); + +/* * Release a non-connected sock_tcp */ void sock_tcp_release (struct sock_tcp *sock);