#include "sock_tcp.h"
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
/*
* Our basic socket event handler for driving our callbacks
*/
static void sock_tcp_event_handler (evutil_socket_t fd, short what, void *arg)
{
struct sock_tcp *sock = arg;
// invoke appropriate callback
sock_stream_invoke_callbacks(SOCK_TCP_BASE(sock), what);
}
/*
* Our sock_stream_methods.read method
*/
static err_t sock_tcp_read (struct sock_stream *base_sock, void *buf, size_t *len)
{
struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp);
int ret;
// 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);
if (ret < 0) {
// EAGAIN -> zero bytes
*len = 0;
} else {
// normal -> bytes read
*len = ret;
}
// ok
return SUCCESS;
}
/*
* Our sock_stream_methods.write method
*/
static err_t sock_tcp_write (struct sock_stream *base_sock, const void *buf, size_t *len)
{
struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp);
int ret;
// 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);
if (ret < 0) {
// EAGAIN -> zero bytes
*len = 0;
} else {
// normal -> bytes read
*len = ret;
}
return SUCCESS;
}
static err_t sock_tcp_event_init (struct sock_stream *base_sock)
{
struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp);
err_t err;
// set nonblocking
if ((err = sock_tcp_set_nonblock(sock, 1)))
return err;
// add ourselves as the event handler
if ((err = sock_tcp_init_ev(sock, &sock_tcp_event_handler, sock)))
return err;
// done
return SUCCESS;
}
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);
// implemented in sock_tcp_add_event
return sock_tcp_add_event(sock, mask);
}
/*
* Our sock_stream_type
*/
struct sock_stream_type sock_tcp_type = {
.methods.read = &sock_tcp_read,
.methods.write = &sock_tcp_write,
.methods.event_init = &sock_tcp_event_init,
.methods.event_enable = &sock_tcp_event_enable,
};
err_t sock_tcp_alloc (struct sock_tcp **sock_ptr)
{
// alloc
if ((*sock_ptr = calloc(1, sizeof(**sock_ptr))) == NULL)
return ERR_CALLOC;
// initialize base with sock_tcp_type
sock_stream_init(SOCK_TCP_BASE(*sock_ptr), &sock_tcp_type);
// invalid fds are <0
(*sock_ptr)->fd = -1;
// done
return SUCCESS;
}
err_t sock_tcp_init_fd (struct sock_tcp *sock, int fd)
{
// valid fd -XXX: err instead?
assert(fd >= 0);
// initialize
sock->fd = fd;
// done
return SUCCESS;
}
err_t sock_tcp_init_ev (struct sock_tcp *sock, void (*ev_cb)(evutil_socket_t, short, void *), void *cb_arg)
{
// require valid fd
assert(sock->fd >= 0);
// this is initialization
assert(sock->ev_read == NULL && sock->ev_write == NULL);
// create new event
if ((sock->ev_read = event_new(_sock_stream_ctx.ev_base, sock->fd, EV_READ, ev_cb, cb_arg)) == NULL)
return SET_ERROR(SOCK_TCP_ERR(sock), ERR_EVENT_NEW);
if ((sock->ev_write = event_new(_sock_stream_ctx.ev_base, sock->fd, EV_WRITE, ev_cb, cb_arg)) == NULL)
return SET_ERROR(SOCK_TCP_ERR(sock), ERR_EVENT_NEW);
// ok
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;
int err;
RESET_ERROR(SOCK_TCP_ERR(sock));
// hints
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
// resolve
if ((err = getaddrinfo(hostname, service, &hints, &res)))
RETURN_SET_ERROR_EXTRA(SOCK_TCP_ERR(sock), ERR_GETADDRINFO, err);
// try each result in turn
for (r = res; r; r = r->ai_next) {
// create the socket
if ((sock->fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol)) < 0) {
// remember error
SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_SOCKET);
// skip to next one
continue;
}
// connect to remote address
if (connect(sock->fd, r->ai_addr, r->ai_addrlen)) {
// remember error
SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_CONNECT);
// close/invalidate socket
close(sock->fd);
sock->fd = -1;
// skip to next one
continue;
}
// valid socket, use this
break;
}
// ensure we got some valid socket, else return last error code
if (sock->fd < 0) {
// did we hit some error?
if (IS_ERROR(SOCK_TCP_ERR(sock)))
// return last error
return ERROR_CODE(SOCK_TCP_ERR(sock));
else
// no results
return SET_ERROR(SOCK_TCP_ERR(sock), ERR_GETADDRINFO_EMPTY);
}
// ok, done
return 0;
}
err_t sock_tcp_set_nonblock (struct sock_tcp *sock, int nonblock)
{
// fcntl it
// XXX: maintain old flags?
if (fcntl(sock->fd, F_SETFL, nonblock ? O_NONBLOCK : 0) < 0)
RETURN_SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_FCNTL);
// ok
return SUCCESS;
}
void sock_tcp_release (struct sock_tcp *sock)
{
// must not be connected
assert(sock->fd < 0);
// free
free(sock);
}
err_t sock_tcp_connect (struct sock_stream **sock_ptr, const char *host, const char *service, struct error_info *err_info)
{
struct sock_tcp *sock;
err_t err;
// allocate
if ((err = sock_tcp_alloc(&sock)))
return err;
// connect
if ((err = sock_tcp_init_connect(sock, host, service))) {
// copy error_info
SET_ERROR_INFO(err_info, sock_stream_error(SOCK_TCP_BASE(sock)));
// cleanup
sock_tcp_release(sock);
// return error code
return err;
}
// good
*sock_ptr = SOCK_TCP_BASE(sock);
return 0;
}