hio/stio/lib/stio-tcp.c

451 lines
9.5 KiB
C

#include "stio-prv.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
static int tcp_make (stio_dev_t* dev, void* ctx)
{
/* NOTE: this can be extended to use ctx to tell between INET and INET6 or other types of sockets without creating a new dev method set. */
stio_dev_tcp_t* tcp = (stio_dev_tcp_t*)dev;
struct sockaddr* saddr = (struct sockaddr*)ctx;
tcp->sck = stio_openasyncsck (AF_INET, SOCK_STREAM);
if (tcp->sck == STIO_SCKHND_INVALID) goto oops;
if (saddr)
{
stio_scklen_t len;
int iv;
if (saddr->sa_family == AF_INET)
len = STIO_SIZEOF(struct sockaddr_in);
else if (saddr->sa_family == AF_INET6)
len = STIO_SIZEOF(struct sockaddr_in6);
else
{
dev->stio->errnum = STIO_EINVAL;
goto oops;
}
//setsockopt (udp->sck, SOL_SOCKET, SO_REUSEADDR, ...);
// TRANSPARENT, ETC.
iv = 1;
if (setsockopt (tcp->sck, SOL_SOCKET, SO_REUSEADDR, &iv, STIO_SIZEOF(iv)) == -1 ||
bind (tcp->sck, saddr, len) == -1)
{
//dev->stio->errnum = STIO_EINVAL; TODO:
goto oops;
}
}
return 0;
oops:
if (tcp->sck != STIO_SCKHND_INVALID)
{
stio_closeasyncsck (tcp->sck);
tcp->sck = STIO_SCKHND_INVALID;
}
return -1;
}
static int tcp_make_accepted (stio_dev_t* dev, void* ctx)
{
stio_dev_tcp_t* tcp = (stio_dev_tcp_t*)dev;
stio_syshnd_t* sck = (stio_syshnd_t*)ctx;
tcp->sck = *sck;
if (stio_makesckasync (tcp->sck) <= -1) return -1;
return 0;
}
static void tcp_kill (stio_dev_t* dev)
{
stio_dev_tcp_t* tcp = (stio_dev_tcp_t*)dev;
if (tcp->state | (STIO_DEV_TCP_ACCEPTED | STIO_DEV_TCP_CONNECTED))
{
if (tcp->on_disconnected) tcp->on_disconnected (tcp);
}
if (tcp->sck != STIO_SCKHND_INVALID)
{
stio_closeasyncsck (tcp->sck);
tcp->sck = STIO_SCKHND_INVALID;
}
}
static stio_syshnd_t tcp_getsyshnd (stio_dev_t* dev)
{
stio_dev_tcp_t* tcp = (stio_dev_tcp_t*)dev;
return (stio_syshnd_t)tcp->sck;
}
static int tcp_recv (stio_dev_t* dev, void* buf, stio_len_t* len)
{
stio_dev_tcp_t* tcp = (stio_dev_tcp_t*)dev;
int x;
printf ("TCP RECV...\n");
x = recv (tcp->sck, buf, *len, 0);
if (x <= -1)
{
if (errno == EINPROGRESS || errno == EWOULDBLOCK) return 0; /* no data available */
return -1;
}
*len = x;
return 1;
}
static int tcp_send (stio_dev_t* dev, const void* data, stio_len_t* len)
{
stio_dev_tcp_t* tcp = (stio_dev_tcp_t*)tcp;
ssize_t x;
#if 0
x = sendto (tcp->sck, data, *len, skad, stio_getskadlen(skad));
if (x <= -1)
{
if (errno == EINPROGRESS || errno == EWOULDBLOCK) return 0; /* no data can be written */
return -1;
}
/* for UDP, if the data chunk can't be written at one go, it's actually a failure */
if (x != *len) return -1; /* TODO: can i hava an indicator for this in stio? */
*len = x;
#endif
return 1;
}
static int tcp_ioctl (stio_dev_t* dev, int cmd, void* arg)
{
stio_dev_tcp_t* tcp = (stio_dev_tcp_t*)dev;
switch (cmd)
{
case STIO_DEV_TCP_BIND:
{
stio_dev_tcp_bind_t* bnd = (stio_dev_tcp_bind_t*)arg;
struct sockaddr* sa = (struct sockaddr*)&bnd->addr;
stio_scklen_t sl;
int x;
if (sa->sa_family == AF_INET) sl = STIO_SIZEOF(struct sockaddr_in);
else if (sa->sa_family == AF_INET6) sl = STIO_SIZEOF(struct sockaddr_in6);
else
{
dev->stio->errnum = STIO_EINVAL;
return -1;
}
#if defined(_WIN32)
/* TODO */
#else
/* the socket is already non-blocking */
x = bind (tcp->sck, sa, sl);
if (x == -1)
{
/* TODO: set dev->errnum from errno */
return -1;
}
return 0;
#endif
}
case STIO_DEV_TCP_CONNECT:
{
stio_dev_tcp_connect_t* conn = (stio_dev_tcp_connect_t*)arg;
struct sockaddr* sa = (struct sockaddr*)&conn->addr;
stio_scklen_t sl;
int x;
if (sa->sa_family == AF_INET) sl = STIO_SIZEOF(struct sockaddr_in);
else if (sa->sa_family == AF_INET6) sl = STIO_SIZEOF(struct sockaddr_in6);
else
{
dev->stio->errnum = STIO_EINVAL;
return -1;
}
#if defined(_WIN32)
/* TODO */
#else
/* the socket is already non-blocking */
x = connect (tcp->sck, sa, sl);
if (x == -1)
{
if (errno == EINPROGRESS || errno == EWOULDBLOCK)
{
if (stio_dev_event ((stio_dev_t*)tcp, STIO_DEV_EVENT_MOD, STIO_DEV_EVENT_IN | STIO_DEV_EVENT_OUT) >= 0)
{
tcp->state |= STIO_DEV_TCP_CONNECTING;
tcp->peer = conn->addr;
tcp->on_connected = conn->on_connected;
tcp->on_disconnected = conn->on_disconnected;
return 0;
}
}
/* TODO: set dev->errnum from errno */
return -1;
}
/* connected immediately */
tcp->state |= STIO_DEV_TCP_CONNECTED;
tcp->peer = conn->addr;
tcp->on_connected = conn->on_connected;
tcp->on_disconnected = conn->on_disconnected;
return 0;
#endif
}
case STIO_DEV_TCP_LISTEN:
{
stio_dev_tcp_listen_t* lstn = (stio_dev_tcp_listen_t*)arg;
int x;
#if defined(_WIN32)
/* TODO */
#else
x = listen (tcp->sck, lstn->backlogs);
if (x == -1)
{
/* TODO: set tcp->stio->errnum */
return -1;
}
tcp->state |= STIO_DEV_TCP_LISTENING;
tcp->on_accepted = lstn->on_accepted;
tcp->on_disconnected = lstn->on_disconnected;
return 0;
#endif
}
}
return 0;
}
int stio_dev_tcp_bind (stio_dev_tcp_t* tcp, stio_dev_tcp_bind_t* bind)
{
return stio_dev_ioctl ((stio_dev_t*)tcp, STIO_DEV_TCP_BIND, bind);
}
int stio_dev_tcp_connect (stio_dev_tcp_t* tcp, stio_dev_tcp_connect_t* conn)
{
return stio_dev_ioctl ((stio_dev_t*)tcp, STIO_DEV_TCP_CONNECT, conn);
}
int stio_dev_tcp_listen (stio_dev_tcp_t* tcp, stio_dev_tcp_listen_t* lstn)
{
return stio_dev_ioctl ((stio_dev_t*)tcp, STIO_DEV_TCP_LISTEN, lstn);
}
static stio_dev_mth_t tcp_mth =
{
tcp_make,
tcp_kill,
tcp_getsyshnd,
tcp_ioctl,
tcp_recv,
tcp_send
};
/* accepted tcp socket */
static stio_dev_mth_t tcp_acc_mth =
{
tcp_make_accepted,
tcp_kill,
tcp_getsyshnd,
tcp_ioctl,
tcp_recv,
tcp_send
};
/* ------------------------------------------------------------------------ */
static int tcp_ready (stio_dev_t* dev, int events)
{
stio_dev_tcp_t* tcp = (stio_dev_tcp_t*)dev;
printf ("TCP READY...%p\n", dev);
if (tcp->state & STIO_DEV_TCP_CONNECTING)
{
if (events & (STIO_DEV_EVENT_ERR | STIO_DEV_EVENT_HUP | STIO_DEV_EVENT_PRI | STIO_DEV_EVENT_IN))
{
int errcode;
stio_scklen_t len;
len = STIO_SIZEOF(errcode);
if (getsockopt (tcp->sck, SOL_SOCKET, SO_ERROR, (char*)&errcode, &len) == 0)
{
printf ("CANNOT CONNECT ERRORCODE - %s\n", strerror(errcode));
}
printf ("Cannot connect....\n");
return -1;
}
else if (events & STIO_DEV_EVENT_OUT)
{
int errcode;
stio_scklen_t len;
STIO_ASSERT (!(tcp->state & STIO_DEV_TCP_CONNECTED));
printf ("XXXXXXXXXXXXXXX CONNECTED...\n");
len = STIO_SIZEOF(errcode);
if (getsockopt (tcp->sck, SOL_SOCKET, SO_ERROR, (char*)&errcode, &len) == -1)
{
}
else if (errcode == 0)
{
tcp->state &= ~STIO_DEV_TCP_CONNECTING;
tcp->state |= STIO_DEV_TCP_CONNECTED;
if (stio_dev_event ((stio_dev_t*)tcp, STIO_DEV_EVENT_MOD, STIO_DEV_EVENT_IN) <= -1)
{
printf ("CAANOT MANIPULTE EVENT ... KILL DEVICE...\n");
return -1;
}
if (tcp->on_connected) tcp->on_connected (tcp);
}
else if (errcode == EINPROGRESS || errcode == EWOULDBLOCK)
{
/* still in progress */
}
else
{
printf ("failed to get SOCKET PROGRESS CODE...\n");
return -1;
}
}
return 0; /* success but don't invoke on_recv() */
}
else if (tcp->state & STIO_DEV_TCP_LISTENING)
{
stio_sckhnd_t clisck;
stio_sckadr_t peer;
stio_scklen_t addrlen;
/* this is a server(lisening) socket */
addrlen = STIO_SIZEOF(peer);
clisck = accept (tcp->sck, (struct sockaddr*)&peer, &addrlen);
if (clisck == -1)
{
if (errno == EINPROGRESS || errno == EWOULDBLOCK) return 0;
/* TODO: set tcp->stio->errnum from errno */
return -1;
}
if (tcp->on_accepted)
{
stio_dev_tcp_t* clitcp;
/* addr is the address of the peer */
/* local addresss is inherited from the server */
clitcp = (stio_dev_tcp_t*)stio_makedev (tcp->stio, STIO_SIZEOF(*tcp), &tcp_acc_mth, tcp->evcb, &clisck);
if (!clitcp)
{
close (clisck);
return -1;
}
clitcp->state |= STIO_DEV_TCP_ACCEPTED;
clitcp->peer = peer;
clitcp->parent = tcp;
/* inherit the parent's on_disconnected() handler.
* you can still change it inside the on_accepted handler */
clitcp->on_disconnected = tcp->on_disconnected;
tcp->on_accepted (tcp, clitcp);
}
else
{
/* no on_accepted callback is set. close the client socket
* without doing anything meaningful */
close (clisck);
}
return 0; /* success but don't invoke on_recv() */
}
printf ("READY WITH %d\n", events);
if (events & (STIO_DEV_EVENT_ERR | STIO_DEV_EVENT_HUP))
{
printf ("DISCONNECTED or ERROR \n");
stio_killdev (dev->stio, dev);
return 0;
}
return 1; /* the device is ok. carry on reading or writing */
}
static int tcp_on_recv (stio_dev_t* dev, const void* data, stio_len_t len)
{
stio_dev_tcp_t* tcp = (stio_dev_tcp_t*)dev;
printf ("TCP dATA received %d bytes\n", (int)len);
return 0;
}
static int tcp_on_sent (stio_dev_t* dev, const void* data, stio_len_t len)
{
stio_dev_tcp_t* tcp = (stio_dev_tcp_t*)dev;
/* TODO: do something */
printf ("TCP dATA sent %d bytes\n", (int)len);
return 0;
}
static stio_dev_evcb_t tcp_evcb =
{
tcp_ready,
tcp_on_recv,
tcp_on_sent
};
stio_dev_tcp_t* stio_dev_tcp_make (stio_t* stio, stio_sckadr_t* addr)
{
stio_dev_tcp_t* tcp;
tcp = (stio_dev_tcp_t*)stio_makedev (stio, STIO_SIZEOF(*tcp), &tcp_mth, &tcp_evcb, addr);
return tcp;
}
void stio_dev_tcp_kill (stio_dev_tcp_t* tcp)
{
stio_killdev (tcp->stio, (stio_dev_t*)tcp);
}