dol: initial dol commit
[jump.git] / dol / src / dol / visitor / hdsd / scd / scd_socket.cpp
diff --git a/dol/src/dol/visitor/hdsd/scd/scd_socket.cpp b/dol/src/dol/visitor/hdsd/scd/scd_socket.cpp
new file mode 100644 (file)
index 0000000..a5ca5de
--- /dev/null
@@ -0,0 +1,439 @@
+#include "scd_socket.h"
+
+#include <sys/socket.h>
+#include <netinet/tcp.h> // for TCP_NODELAY
+#include <cerrno>
+#include <fcntl.h>
+#include <cassert>
+
+#include "scd_logging.h"
+#include "scd_exception.h"
+
+scd_socket::scd_socket()
+{
+    _socket = -1;
+    _is_connecting = false;
+    _is_connected = false;
+}
+
+scd_socket::~scd_socket() {}
+
+bool scd_socket::is_valid() const
+{
+    return (_socket != -1);
+}
+
+bool scd_socket::create()
+{
+    // check if socket already created before
+    if ( is_valid() )
+    {
+        return false;
+    }
+
+    // create TCP socket
+    _socket = socket(PF_INET, SOCK_STREAM, 0);
+
+    assert(is_valid());
+    
+    _set_blocking(false);
+
+    _is_connecting = false;
+    _is_connected = false;
+
+    return true;
+
+} // create()
+
+void scd_socket::close()
+{
+    if ( is_valid() )
+    {
+        ::close(_socket);
+    }
+
+    _socket = -1;
+    _is_connecting = false;
+    _is_connected = false;
+}
+
+bool scd_socket::bind(const std::string &loc_name, const uint16_t loc_port)
+{
+    int ret;
+
+    if ( ! is_valid() )
+    {
+        return false;
+    }
+    
+    // prepare the local address
+    sockaddr_in loc_addr;
+    if ( !_get_sockaddr(loc_addr, loc_name, loc_port) )
+    {
+        return false;
+    }
+
+    // set socket reusable (to recreate listener without timeout)
+    int on = 1;
+    ret = setsockopt( _socket, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
+    if (ret == -1)
+    {
+        throw scd_exception("setsockopt()", errno);
+    }
+
+    ret = ::bind( _socket, reinterpret_cast<struct sockaddr*>(&loc_addr),
+                 sizeof( loc_addr ) );
+
+    if ( ret == -1 )
+    {
+        switch(errno)
+        {
+        case EACCES:
+            scd_error("must be root to bind to desired port");
+            break;
+        case EADDRINUSE:
+            scd_error("unable to bind as the address is already in use");
+            break;
+        default:
+            throw scd_exception("bind()", errno);
+        }
+        return false;
+    }
+
+    return true;
+
+} // bind()
+
+
+bool scd_socket::bind(const uint16_t loc_port)
+{
+    return bind("", loc_port);
+} // bind()
+
+
+bool scd_socket::listen()
+{
+    int ret;
+
+    if ( !is_valid() )
+    {
+        return false;
+    }
+
+    ret = ::listen(_socket, SCD_MAXCONN);
+
+    if (ret == -1)
+    {
+        throw scd_exception("listen()", errno);
+    }
+
+    return true;
+} // listen()
+
+bool scd_socket::accept(scd_socket &new_sock)
+{
+    int ret;
+
+    if ( !is_valid() )
+    {
+        return false;
+    }
+
+    ret = ::accept(_socket, NULL, NULL);
+
+    if (ret < 0)
+    {
+        switch (errno)
+        {
+        case EAGAIN:
+        case ECONNABORTED:
+        case EINTR:
+        case EPROTO:
+            return false;
+            break;
+        default:
+            throw scd_exception("accept()", errno);
+            break;
+        }
+    }
+    else
+    {
+        new_sock._is_connected = true;
+        new_sock._socket = ret;
+        new_sock._set_blocking(false);
+        #ifdef TCP_NODELAY
+        new_sock._set_nodelay();
+        #endif
+    }
+
+    return true;
+
+} // accept()
+
+
+bool scd_socket::connect(const std::string &rem_name, const uint16_t rem_port)
+{
+    int ret;
+
+    if ( !is_valid() || _is_connecting || _is_connected )
+        return false;
+
+    sockaddr_in rem_addr;
+    if ( !_get_sockaddr(rem_addr, rem_name, rem_port) )
+        return false;
+
+    ret = ::connect(_socket, reinterpret_cast<const sockaddr*>(&rem_addr),
+            sizeof(rem_addr));
+
+    if (ret == -1)
+    {
+        switch(errno)
+        {
+        case EINPROGRESS:
+            _is_connecting = true;
+            break;
+        case ECONNABORTED:
+        case ECONNREFUSED:
+        case EINTR:
+        case ENETUNREACH:
+        case ETIMEDOUT:
+            break;
+        case EALREADY: // should be prevented by _is_connecting
+        default:
+            throw scd_exception("connect()", errno);
+            break;
+        }
+    }
+    else // ret == 0
+    {
+        _is_connected = true;
+        _is_connecting = false;
+    }
+
+    return true;
+
+} // connect()
+
+bool scd_socket::is_connecting()
+{
+    if ( !is_valid() || !_is_connecting || _is_connected )
+        return false;
+
+    return true;
+
+} // is_connecting()
+
+
+bool scd_socket::is_connected()
+{
+    if ( !is_valid() || _is_connecting || !_is_connected)
+        return false;
+
+    return true;
+
+} // is_connecting()
+
+
+bool scd_socket::connected_event()
+{
+    if (_is_connected)
+        return true;
+
+    if ( !is_valid() )
+    {
+        scd_warn("write event occured on an invalid socket");
+        return false;
+    }
+    else if ( !_is_connecting )
+    {
+        scd_warn("checking connected event while not connecting");
+        return false;
+    }
+
+    // get possible errors from connection attempt
+    int err, ret;
+    socklen_t err_size = sizeof(err);
+    ret = getsockopt(_socket, SOL_SOCKET, SO_ERROR, &err, &err_size);
+
+    if (ret == -1)
+    {
+        throw scd_exception("getsockopt()", errno);
+    }
+
+    // handle errors
+    switch(err)
+    {
+    case 0:
+        // connection established
+        _is_connecting = false;
+        _is_connected = true;
+        #ifdef TCP_NODELAY
+        _set_nodelay();
+        #endif
+        return true;
+        break;
+    case ECONNABORTED:
+    case ECONNREFUSED:
+    case EINTR:
+    case ENETUNREACH:
+    case ETIMEDOUT:
+        // connection attempt failed
+        _is_connecting = false;
+        return false;
+        break;
+    case EINPROGRESS:
+        // should not be possible as we received a completion event
+        throw scd_exception("checking connected event while still connecting");
+        break;
+    case EALREADY: // should be prevented by _is_connecting
+    default:
+        throw scd_exception("connect()", errno);
+        break;
+    }
+    
+    // not reached
+} //connected_event()
+
+
+size_t scd_socket::send(const void* buf, size_t len)
+{
+    ssize_t ret;
+
+    if ( !is_valid() || len == 0 || !_is_connected )
+        return 0;
+
+    ret = ::send(_socket, buf, len, MSG_NOSIGNAL);
+
+    if (ret < 0)
+    {
+        switch(errno)
+        {
+        case ECONNRESET:
+        case EPIPE:
+        case ETIMEDOUT:
+            close();
+            break;
+        case EWOULDBLOCK:
+            break;
+        default:
+            throw scd_exception("send()", errno);
+            break;
+        }
+        return 0;
+    }
+    else
+        return ret;
+
+} // send()
+
+size_t scd_socket::recv(void* buf, size_t len)
+{
+    ssize_t ret;
+
+    if ( !is_valid() || len == 0 || !_is_connected)
+        return 0;
+
+    ret = ::recv(_socket, buf, len, MSG_NOSIGNAL);
+
+    if (ret < 0)
+    {
+        switch(errno)
+        {
+        case ETIMEDOUT:
+            close();
+            break;
+        case EAGAIN:
+        case EINTR:
+            break;
+        case ENOTCONN:
+        default:
+            throw scd_exception("recv()", errno);
+            break;
+        }
+        return 0;
+    }
+    else if (ret == 0)
+    {
+        close();
+        return 0;
+    }
+    else
+        return ret;
+
+} // recv()
+
+bool scd_socket::_get_sockaddr(sockaddr_in &addr, const std::string &name,
+            const uint16_t port) const
+{
+    // flush addr
+    memset( &addr, 0, sizeof(addr) );
+
+    /* set content */
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons( port );
+    if ( name.empty() )
+    {
+        // bind to all local addresses
+        addr.sin_addr.s_addr = INADDR_ANY;
+    }
+    else
+    {
+        // bint to specified address only
+        struct hostent* he;
+
+        // resolve name
+        he = gethostbyname2(&name[0], AF_INET);
+        if (he == NULL)
+            return false;
+
+        // copy address (network order)
+        addr.sin_addr.s_addr =
+               reinterpret_cast<struct in_addr*>(he->h_addr_list[0])->s_addr;
+    }
+
+    return true;
+
+} // _get_sockaddr()
+
+
+bool scd_socket::_set_blocking(const bool mode)
+{
+    if ( !is_valid() )
+    {
+        return false;
+    }
+
+    int opts = fcntl( _socket, F_GETFL );
+
+    if ( opts < 0 )
+    {
+        throw scd_exception("fcntl()", errno);
+    }
+
+    if ( !mode )
+        opts = ( opts | O_NONBLOCK );
+    else
+        opts = ( opts & ~O_NONBLOCK );
+
+    fcntl( _socket, F_SETFL,opts );
+
+    return true;
+
+} //_set_blocking()
+
+
+bool scd_socket::_set_nodelay()
+{
+    if ( !is_valid())
+        return false;
+
+    // disable Nagle algorithm
+    int on = 1;
+    int ret = setsockopt( _socket, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on) );
+    if (ret == -1)
+    {
+        throw scd_exception("setsockopt()", errno);
+    }
+
+    return true;
+}