451 lines
9.5 KiB
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);
|
||
|
}
|