#include "sock_tcp.h"
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
/*
* 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;
// map directly to read(2)
if ((ret = read(sock->fd, buf, len)) < 0)
// errno
RETURN_SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_READ);
else
// bytes read
return ret;
}
/*
* 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;
// map directly to write(2)
if ((ret = write(sock->fd, buf, len)) < 0)
// errno
RETURN_SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_WRITE);
else
// bytes read
return ret;
}
/*
* Our sock_stream_type
*/
struct sock_stream_type sock_tcp_type = {
.methods.read = &sock_tcp_read,
.methods.write = &sock_tcp_write,
};
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);
// 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_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_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))) {
// set *err_info
sock_stream_error(SOCK_TCP_BASE(sock), err_info);
// cleanup
sock_tcp_release(sock);
// return error code
return err;
}
// good
*sock_ptr = SOCK_TCP_BASE(sock);
return 0;
}
void sock_tcp_release (struct sock_tcp *sock)
{
// must not be connected
assert(sock->fd < 0);
// free
free(sock);
}