From c787638696202173de5f46f841c8f3046172a84b Mon Sep 17 00:00:00 2001
From: Francois Berder <francois.berder@imgtec.com>
Date: Mon, 20 Mar 2017 14:54:31 +0000
Subject: [PATCH] posix: sockets: Implement SO_RCVTIMEO option in setsockopt

AwaLWM2M needs to be polled regularly to check for incoming data.
Since RIOT only supports timeout at the GNRC sock layer while
the network abstraction for RIOT in AwaLWM2M uses the posix layer,
this causes RIOT to be blocked waiting for data that never arrive.

This commit implements only the SO_RCVTIMEO option in setsockopt to
allow users to set a receive timeout for a socket at the posix layer.

Signed-off-by: Francois Berder <francois.berder@imgtec.com>
---
 sys/posix/include/sys/socket.h    | 12 +----
 sys/posix/sockets/posix_sockets.c | 88 ++++++++++++++++++++++++++++---
 2 files changed, 82 insertions(+), 18 deletions(-)

diff --git a/sys/posix/include/sys/socket.h b/sys/posix/include/sys/socket.h
index e9ba8cba96..1bd9ea8009 100644
--- a/sys/posix/include/sys/socket.h
+++ b/sys/posix/include/sys/socket.h
@@ -491,16 +491,8 @@ static inline int getsockopt(int socket, int level, int option_name, void *optio
     return -1;
 }
 
-static inline int setsockopt(int socket, int level, int option_name, const void *option_value,
-                             socklen_t option_len)
-{
-    (void)socket;
-    (void)level;
-    (void)option_name;
-    (void)option_value;
-    (void)option_len;
-    return -1;
-}
+int setsockopt(int socket, int level, int option_name, const void *option_value,
+               socklen_t option_len);
 
 /** @} */
 
diff --git a/sys/posix/sockets/posix_sockets.c b/sys/posix/sockets/posix_sockets.c
index 094da7df2e..2295522ba9 100644
--- a/sys/posix/sockets/posix_sockets.c
+++ b/sys/posix/sockets/posix_sockets.c
@@ -68,6 +68,9 @@ typedef struct {
     int type;
     int protocol;
     bool bound;
+#ifdef POSIX_SETSOCKOPT
+    uint32_t recv_timeout;
+#endif
     socket_sock_t *sock;
 #ifdef MODULE_SOCK_TCP
     sock_tcp_t *queue_array;
@@ -323,6 +326,9 @@ int socket(int domain, int type, int protocol)
             }
             s->bound = false;
             s->sock = NULL;
+#ifdef POSIX_SETSOCKOPT
+            s->recv_timeout = SOCK_NO_TIMEOUT;
+#endif
 #ifdef MODULE_SOCK_TCP
             if (type == SOCK_STREAM)  {
                 s->queue_array = NULL;
@@ -363,6 +369,13 @@ int accept(int socket, struct sockaddr *restrict address,
         errno = EINVAL;
         return -1;
     }
+
+#ifdef POSIX_SETSOCKOPT
+    const uint32_t recv_timeout = s->recv_timeout;
+#else
+    const uint32_t recv_timeout = SOCK_NO_TIMEOUT;
+#endif
+
     switch (s->type) {
         case SOCK_STREAM:
             new_s = _get_free_socket();
@@ -372,9 +385,8 @@ int accept(int socket, struct sockaddr *restrict address,
                 res = -1;
                 break;
             }
-            /* TODO: apply configured timeout */
             if ((res = sock_tcp_accept(&s->sock->tcp.queue, &sock,
-                                       SOCK_NO_TIMEOUT)) < 0) {
+                                       recv_timeout)) < 0) {
                 errno = -res;
                 res = -1;
                 break;
@@ -789,11 +801,17 @@ ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags,
             return res;
         }
     }
+
+#ifdef POSIX_SETSOCKOPT
+    const uint32_t recv_timeout = s->recv_timeout;
+#else
+    const uint32_t recv_timeout = SOCK_NO_TIMEOUT;
+#endif
+
     switch (s->type) {
 #ifdef MODULE_SOCK_IP
         case SOCK_RAW:
-            /* TODO: apply configured timeout */
-            if ((res = sock_ip_recv(&s->sock->raw, buffer, length, SOCK_NO_TIMEOUT,
+            if ((res = sock_ip_recv(&s->sock->raw, buffer, length, recv_timeout
                                (sock_ip_ep_t *)&ep)) < 0) {
                 errno = -res;
                 res = -1;
@@ -802,9 +820,8 @@ ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags,
 #endif
 #ifdef MODULE_SOCK_TCP
         case SOCK_STREAM:
-            /* TODO: apply configured timeout */
             if ((res = sock_tcp_read(&s->sock->tcp.sock, buffer, length,
-                                SOCK_NO_TIMEOUT)) < 0) {
+                                recv_timeout)) < 0) {
                 errno = -res;
                 res = -1;
             }
@@ -812,8 +829,7 @@ ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags,
 #endif
 #ifdef MODULE_SOCK_UDP
         case SOCK_DGRAM:
-            /* TODO: apply configured timeout */
-            if ((res = sock_udp_recv(&s->sock->udp, buffer, length, SOCK_NO_TIMEOUT,
+            if ((res = sock_udp_recv(&s->sock->udp, buffer, length, recv_timeout,
                                 &ep)) < 0) {
                 errno = -res;
                 res = -1;
@@ -920,6 +936,62 @@ ssize_t sendto(int socket, const void *buffer, size_t length, int flags,
     return res;
 }
 
+/*
+ * This is a partial implementation of setsockopt for changing the receive
+ * timeout value of a socket.
+ */
+int setsockopt(int socket, int level, int option_name, const void *option_value,
+               socklen_t option_len)
+{
+#ifdef POSIX_SETSOCKOPT
+    socket_t *s;
+    struct timeval *tv;
+    const uint32_t max_timeout_secs = UINT32_MAX / (1000 * 1000);
+
+    if (level != SOL_SOCKET
+    ||  option_name != SO_RCVTIMEO) {
+        errno = ENOTSUP;
+        return -1;
+    }
+    if (option_value == NULL
+    ||  option_len != sizeof(struct timeval)) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    mutex_lock(&_socket_pool_mutex);
+    s = _get_socket(socket);
+    mutex_unlock(&_socket_pool_mutex);
+    if (s == NULL) {
+        errno = ENOTSOCK;
+        return -1;
+    }
+
+    tv = (struct timeval *) option_value;
+
+    if (tv->tv_sec < 0 || tv->tv_usec < 0) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    if ((uint32_t)tv->tv_sec > max_timeout_secs
+    || ((uint32_t)tv->tv_sec == max_timeout_secs && (uint32_t)tv->tv_usec > UINT32_MAX - max_timeout_secs * 1000 * 1000)) {
+        errno = EDOM;
+        return -1;
+    }
+
+    s->recv_timeout = tv->tv_sec * 1000 * 1000 + tv->tv_usec;
+    return 0;
+#else
+    (void)socket;
+    (void)level;
+    (void)option_name;
+    (void)option_value;
+    (void)option_len;
+    return -1;
+#endif
+}
+
 /**
  * @}
  */
-- 
GitLab