[glib-networking/wip/pwithnall/dtls: 27/30] gnutls: Internally support per-operation timeouts



commit 091f52df908f5ae605f6b14562eccf08b0930fe5
Author: Philip Withnall <philip withnall collabora co uk>
Date:   Thu Jul 30 15:56:00 2015 +0100

    gnutls: Internally support per-operation timeouts
    
    For every operation except handshaking (which is complex), convert the
    per-operation blocking parameter to a timeout parameter, supporting
    blocking, non-blocking and timeout behaviour. This can be used by the
    GDatagramBased interface to support its per-operation timeouts.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=697908

 tls/gnutls/gtlsconnection-gnutls.c   |  130 +++++++++++++++++++++++----------
 tls/gnutls/gtlsconnection-gnutls.h   |    4 +-
 tls/gnutls/gtlsinputstream-gnutls.c  |    5 +-
 tls/gnutls/gtlsoutputstream-gnutls.c |    5 +-
 4 files changed, 98 insertions(+), 46 deletions(-)
---
diff --git a/tls/gnutls/gtlsconnection-gnutls.c b/tls/gnutls/gtlsconnection-gnutls.c
index c56b33b..4757a1e 100644
--- a/tls/gnutls/gtlsconnection-gnutls.c
+++ b/tls/gnutls/gtlsconnection-gnutls.c
@@ -116,7 +116,7 @@ static P11KitPin*    on_pin_prompt_callback  (const char     *pinfile,
 static void g_tls_connection_gnutls_init_priorities (void);
 
 static gboolean do_implicit_handshake (GTlsConnectionGnutls  *gnutls,
-                                      gboolean               blocking,
+                                      gint64                 timeout,
                                       GCancellable          *cancellable,
                                       GError               **error);
 static gboolean finish_handshake (GTlsConnectionGnutls  *gnutls,
@@ -224,12 +224,12 @@ struct _GTlsConnectionGnutlsPrivate
   GCancellable *waiting_for_op;
 
   gboolean      reading;
-  gboolean      read_blocking;
+  gint64        read_timeout;
   GError       *read_error;
   GCancellable *read_cancellable;
 
   gboolean      writing;
-  gboolean      write_blocking;
+  gint64        write_timeout;
   GError       *write_error;
   GCancellable *write_cancellable;
 
@@ -644,7 +644,7 @@ typedef enum {
 static gboolean
 claim_op (GTlsConnectionGnutls    *gnutls,
          GTlsConnectionGnutlsOp   op,
-         gboolean                 blocking,
+         gint64                   timeout,
          GCancellable            *cancellable,
          GError                 **error)
 {
@@ -687,7 +687,7 @@ claim_op (GTlsConnectionGnutls    *gnutls,
        {
          gnutls->priv->need_handshake = FALSE;
          gnutls->priv->handshaking = TRUE;
-         if (!do_implicit_handshake (gnutls, blocking, cancellable, error))
+         if (!do_implicit_handshake (gnutls, timeout, cancellable, error))
            {
              g_mutex_unlock (&gnutls->priv->op_mutex);
              return FALSE;
@@ -727,12 +727,14 @@ claim_op (GTlsConnectionGnutls    *gnutls,
     {
       GPollFD fds[2];
       int nfds;
+      gint64 start_time;
+      gint result;
 
       g_cancellable_reset (gnutls->priv->waiting_for_op);
 
       g_mutex_unlock (&gnutls->priv->op_mutex);
 
-      if (!blocking)
+      if (timeout == 0)
        {
          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK,
                               _("Operation would block"));
@@ -745,11 +747,39 @@ claim_op (GTlsConnectionGnutls    *gnutls,
       else
        nfds = 1;
 
-      g_poll (fds, nfds, -1);
+      /* Convert from microseconds to milliseconds. */
+      if (timeout != -1)
+        timeout = timeout / 1000;
+
+      /* Poll until cancellation or the timeout is reached. */
+      start_time = g_get_monotonic_time ();
+
+      while (!g_cancellable_is_cancelled (gnutls->priv->waiting_for_op) &&
+             !g_cancellable_is_cancelled (cancellable))
+        {
+          result = g_poll (fds, nfds, timeout);
+
+          if (result != -1 || errno != EINTR)
+            continue;
+
+          if (timeout != -1)
+            {
+              timeout -= (g_get_monotonic_time () - start_time) / 1000;
+              if (timeout < 0)
+                timeout = 0;
+            }
+        }
 
       if (nfds > 1)
         g_cancellable_release_fd (cancellable);
 
+      if (result == 0)
+        {
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+                               _("Socket I/O timed out"));
+          return FALSE;
+        }
+
       goto try_again;
     }
 
@@ -801,21 +831,21 @@ yield_op (GTlsConnectionGnutls   *gnutls,
 static void
 begin_gnutls_io (GTlsConnectionGnutls  *gnutls,
                 GIOCondition           direction,
-                gboolean               blocking,
+                gint64                 timeout,
                 GCancellable          *cancellable)
 {
   g_assert (direction & (G_IO_IN | G_IO_OUT));
 
   if (direction & G_IO_IN)
     {
-      gnutls->priv->read_blocking = blocking;
+      gnutls->priv->read_timeout = timeout;
       gnutls->priv->read_cancellable = cancellable;
       g_clear_error (&gnutls->priv->read_error);
     }
 
   if (direction & G_IO_OUT)
     {
-      gnutls->priv->write_blocking = blocking;
+      gnutls->priv->write_timeout = timeout;
       gnutls->priv->write_cancellable = cancellable;
       g_clear_error (&gnutls->priv->write_error);
     }
@@ -967,8 +997,8 @@ end_gnutls_io (GTlsConnectionGnutls  *gnutls,
   return status;
 }
 
-#define BEGIN_GNUTLS_IO(gnutls, direction, blocking, cancellable)      \
-  begin_gnutls_io (gnutls, direction, blocking, cancellable);          \
+#define BEGIN_GNUTLS_IO(gnutls, direction, timeout, cancellable)       \
+  begin_gnutls_io (gnutls, direction, timeout, cancellable);           \
   do {
 
 #define END_GNUTLS_IO(gnutls, direction, ret, errmsg, err)             \
@@ -1363,7 +1393,7 @@ g_tls_connection_gnutls_pull_func (gnutls_transport_ptr_t  transport_data,
 
       ret = g_datagram_based_receive_messages (gnutls->priv->base_socket,
                                                &message, 1, 0,
-                                               gnutls->priv->read_blocking ? -1 : 0,
+                                               gnutls->priv->read_timeout,
                                                gnutls->priv->read_cancellable,
                                                &gnutls->priv->read_error);
 
@@ -1374,7 +1404,7 @@ g_tls_connection_gnutls_pull_func (gnutls_transport_ptr_t  transport_data,
     {
       ret = g_pollable_stream_read (G_INPUT_STREAM (gnutls->priv->base_istream),
                                     buf, buflen,
-                                    gnutls->priv->read_blocking,
+                                    (gnutls->priv->read_timeout != 0),
                                     gnutls->priv->read_cancellable,
                                     &gnutls->priv->read_error);
     }
@@ -1407,7 +1437,7 @@ g_tls_connection_gnutls_push_func (gnutls_transport_ptr_t  transport_data,
 
       ret = g_datagram_based_send_messages (gnutls->priv->base_socket,
                                             &message, 1, 0,
-                                            gnutls->priv->write_blocking ? -1 : 0,
+                                            gnutls->priv->write_timeout,
                                             gnutls->priv->write_cancellable,
                                             &gnutls->priv->write_error);
 
@@ -1418,7 +1448,7 @@ g_tls_connection_gnutls_push_func (gnutls_transport_ptr_t  transport_data,
     {
       ret = g_pollable_stream_write (G_OUTPUT_STREAM (gnutls->priv->base_ostream),
                                      buf, buflen,
-                                     gnutls->priv->write_blocking,
+                                     (gnutls->priv->write_timeout != 0),
                                      gnutls->priv->write_cancellable,
                                      &gnutls->priv->write_error);
     }
@@ -1474,7 +1504,7 @@ g_tls_connection_gnutls_vec_push_func (gnutls_transport_ptr_t  transport_data,
 
   ret = g_datagram_based_send_messages (gnutls->priv->base_socket,
                                         &message, 1, 0,
-                                        gnutls->priv->write_blocking,
+                                        gnutls->priv->write_timeout,
                                         gnutls->priv->write_cancellable,
                                         &gnutls->priv->write_error);
 
@@ -1509,6 +1539,16 @@ read_datagram_based_cb (GDatagramBased  *datagram_based,
   return G_SOURCE_CONTINUE;
 }
 
+static gboolean
+read_timeout_cb (gpointer user_data)
+{
+  gboolean *timed_out = user_data;
+
+  *timed_out = TRUE;
+
+  return G_SOURCE_REMOVE;
+}
+
 static int
 g_tls_connection_gnutls_pull_timeout_func (gnutls_transport_ptr_t transport_data,
                                            unsigned int ms)
@@ -1525,11 +1565,19 @@ g_tls_connection_gnutls_pull_timeout_func (gnutls_transport_ptr_t transport_data
   if (ms > 0)
     {
       GMainContext *ctx = NULL;
-      GSource *read_source;
-      gboolean read_done = FALSE;
+      GSource *read_source = NULL, *timeout_source = NULL;
+      gboolean read_done = FALSE, timed_out = FALSE;
 
       ctx = g_main_context_new ();
 
+      /* Create a timeout source. */
+      timeout_source = g_timeout_source_new (ms);
+      g_source_set_callback (timeout_source, (GSourceFunc) read_timeout_cb,
+                             &timed_out, NULL);
+
+      /* Create a read source. We cannot use g_source_set_ready_time() on this
+       * to combine it with the @timeout_source, as that could mess with the
+       * internals of the #GDatagramBased’s #GSource implementation. */
       if (gnutls->priv->base_socket != NULL)
         {
           read_source = g_datagram_based_create_source (gnutls->priv->base_socket, G_IO_IN, NULL);
@@ -1543,16 +1591,18 @@ g_tls_connection_gnutls_pull_timeout_func (gnutls_transport_ptr_t transport_data
                                  &read_done, NULL);
         }
 
-      g_source_set_ready_time (read_source, g_get_monotonic_time () + ms * 1000);
       g_source_attach (read_source, ctx);
+      g_source_attach (timeout_source, ctx);
 
-      while (!read_done)
+      while (!read_done && !timed_out)
         g_main_context_iteration (ctx, TRUE);
 
       g_source_destroy (read_source);
+      g_source_destroy (timeout_source);
 
       g_main_context_unref (ctx);
       g_source_unref (read_source);
+      g_source_unref (timeout_source);
     }
 
   /* If @read_source was dispatched due to cancellation, the resulting error
@@ -1647,7 +1697,7 @@ handshake_thread (GTask        *task,
   gnutls->priv->started_handshake = FALSE;
 
   if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE,
-                TRUE, cancellable, &error))
+                -1  /* blocking */, cancellable, &error))
     {
       g_task_return_error (task, error);
       return;
@@ -1660,7 +1710,8 @@ handshake_thread (GTask        *task,
   if (!is_client && gnutls->priv->ever_handshaked &&
       !gnutls->priv->implicit_handshake)
     {
-      BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, TRUE, cancellable);
+      BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, -1  /* blocking */,
+                       cancellable);
       ret = gnutls_rehandshake (gnutls->priv->session);
       END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret,
                     _("Error performing TLS handshake: %s"), &error);
@@ -1679,7 +1730,7 @@ handshake_thread (GTask        *task,
 
   g_tls_connection_gnutls_set_handshake_priority (gnutls);
 
-  BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, TRUE, cancellable);
+  BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, -1  /* blocking */, cancellable);
   ret = gnutls_handshake (gnutls->priv->session);
   if (ret == GNUTLS_E_GOT_APPLICATION_DATA)
     {
@@ -1948,7 +1999,7 @@ g_tls_connection_gnutls_dtls_handshake_finish (GDtlsConnection       *conn,
 
 static gboolean
 do_implicit_handshake (GTlsConnectionGnutls  *gnutls,
-                      gboolean               blocking,
+                      gint64                 timeout,
                       GCancellable          *cancellable,
                       GError               **error)
 {
@@ -1960,7 +2011,8 @@ do_implicit_handshake (GTlsConnectionGnutls  *gnutls,
 
   begin_handshake (gnutls);
 
-  if (blocking)
+  /* FIXME: Support (timeout > 0). */
+  if (timeout != 0)
     {
       GError *my_error = NULL;
       gboolean success;
@@ -1995,7 +2047,7 @@ gssize
 g_tls_connection_gnutls_read (GTlsConnectionGnutls  *gnutls,
                              void                  *buffer,
                              gsize                  count,
-                             gboolean               blocking,
+                             gint64                 timeout,
                              GCancellable          *cancellable,
                              GError               **error)
 {
@@ -2014,10 +2066,10 @@ g_tls_connection_gnutls_read (GTlsConnectionGnutls  *gnutls,
 
  again:
   if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ,
-                blocking, cancellable, error))
+                timeout, cancellable, error))
     return -1;
 
-  BEGIN_GNUTLS_IO (gnutls, G_IO_IN, blocking, cancellable);
+  BEGIN_GNUTLS_IO (gnutls, G_IO_IN, timeout, cancellable);
   ret = gnutls_record_recv (gnutls->priv->session, buffer, count);
   END_GNUTLS_IO (gnutls, G_IO_IN, ret, _("Error reading data from TLS socket: %s"), error);
 
@@ -2093,10 +2145,10 @@ g_tls_connection_gnutls_read_message (GTlsConnectionGnutls  *gnutls,
 
  again:
   if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ,
-                timeout != 0, cancellable, error))
+                timeout, cancellable, error))
     return -1;
 
-  BEGIN_GNUTLS_IO (gnutls, G_IO_IN, timeout != 0, cancellable);
+  BEGIN_GNUTLS_IO (gnutls, G_IO_IN, timeout, cancellable);
 
   /* Receive the entire datagram (zero-copy). */
   ret = gnutls_record_recv_packet (gnutls->priv->session, &packet);
@@ -2203,7 +2255,7 @@ gssize
 g_tls_connection_gnutls_write (GTlsConnectionGnutls  *gnutls,
                               const void            *buffer,
                               gsize                  count,
-                              gboolean               blocking,
+                              gint64                 timeout,
                               GCancellable          *cancellable,
                               GError               **error)
 {
@@ -2211,10 +2263,10 @@ g_tls_connection_gnutls_write (GTlsConnectionGnutls  *gnutls,
 
  again:
   if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE,
-                blocking, cancellable, error))
+                timeout, cancellable, error))
     return -1;
 
-  BEGIN_GNUTLS_IO (gnutls, G_IO_OUT, blocking, cancellable);
+  BEGIN_GNUTLS_IO (gnutls, G_IO_OUT, timeout, cancellable);
   ret = gnutls_record_send (gnutls->priv->session, buffer, count);
   END_GNUTLS_IO (gnutls, G_IO_OUT, ret, _("Error writing data to TLS socket: %s"), error);
 
@@ -2242,7 +2294,7 @@ g_tls_connection_gnutls_write_message (GTlsConnectionGnutls  *gnutls,
 
  again:
   if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE,
-                 timeout != 0, cancellable, error))
+                 timeout, cancellable, error))
     return -1;
 
   /* Calculate the total message size and check it’s not too big. */
@@ -2276,7 +2328,7 @@ g_tls_connection_gnutls_write_message (GTlsConnectionGnutls  *gnutls,
         }
     }
 
-  BEGIN_GNUTLS_IO (gnutls, G_IO_OUT, timeout != 0, cancellable);
+  BEGIN_GNUTLS_IO (gnutls, G_IO_OUT, timeout, cancellable);
   ret = gnutls_record_uncork (gnutls->priv->session, 0  /* flags */);
   END_GNUTLS_IO (gnutls, G_IO_OUT, ret, _("Error writing data to TLS socket: %s"), error);
 
@@ -2391,8 +2443,6 @@ g_tls_connection_gnutls_close_internal (GIOStream     *stream,
    * this class and how the underlying stream is closed.
    */
 
-  /* FIXME: This does not properly support the @timeout parameter. */
-
   g_return_val_if_fail (direction != G_TLS_DIRECTION_NONE, FALSE);
 
   if (direction == G_TLS_DIRECTION_BOTH)
@@ -2402,13 +2452,13 @@ g_tls_connection_gnutls_close_internal (GIOStream     *stream,
   else
     op = G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE;
 
-  if (!claim_op (gnutls, op, TRUE, cancellable, error))
+  if (!claim_op (gnutls, op, timeout, cancellable, error))
     return FALSE;
 
   if (gnutls->priv->ever_handshaked && !gnutls->priv->write_closed &&
       direction & G_TLS_DIRECTION_WRITE)
     {
-      BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, TRUE, cancellable);
+      BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, timeout, cancellable);
       ret = gnutls_bye (gnutls->priv->session, GNUTLS_SHUT_WR);
       END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret,
                     _("Error performing TLS close: %s"), &gnutls_error);
diff --git a/tls/gnutls/gtlsconnection-gnutls.h b/tls/gnutls/gtlsconnection-gnutls.h
index 5165918..310acfb 100644
--- a/tls/gnutls/gtlsconnection-gnutls.h
+++ b/tls/gnutls/gtlsconnection-gnutls.h
@@ -70,13 +70,13 @@ gboolean g_tls_connection_gnutls_request_certificate (GTlsConnectionGnutls  *gnu
 gssize   g_tls_connection_gnutls_read          (GTlsConnectionGnutls  *gnutls,
                                                void                  *buffer,
                                                gsize                  size,
-                                               gboolean               blocking,
+                                               gint64                 timeout,
                                                GCancellable          *cancellable,
                                                GError               **error);
 gssize   g_tls_connection_gnutls_write         (GTlsConnectionGnutls  *gnutls,
                                                const void            *buffer,
                                                gsize                  size,
-                                               gboolean               blocking,
+                                               gint64                 timeout,
                                                GCancellable          *cancellable,
                                                GError               **error);
 
diff --git a/tls/gnutls/gtlsinputstream-gnutls.c b/tls/gnutls/gtlsinputstream-gnutls.c
index cb64aa3..37c7585 100644
--- a/tls/gnutls/gtlsinputstream-gnutls.c
+++ b/tls/gnutls/gtlsinputstream-gnutls.c
@@ -69,7 +69,7 @@ g_tls_input_stream_gnutls_read (GInputStream  *stream,
   g_return_val_if_fail (conn != NULL, -1);
 
   ret = g_tls_connection_gnutls_read (conn,
-                                      buffer, count, TRUE,
+                                      buffer, count, -1  /* blocking */,
                                       cancellable, error);
   g_object_unref (conn);
   return ret;
@@ -120,7 +120,8 @@ g_tls_input_stream_gnutls_pollable_read_nonblocking (GPollableInputStream  *poll
   conn = g_weak_ref_get (&tls_stream->priv->weak_conn);
   g_return_val_if_fail (conn != NULL, -1);
 
-  ret = g_tls_connection_gnutls_read (conn, buffer, size, FALSE, NULL, error);
+  ret = g_tls_connection_gnutls_read (conn, buffer, size,
+                                      0  /* non-blocking */, NULL, error);
 
   g_object_unref (conn);
   return ret;
diff --git a/tls/gnutls/gtlsoutputstream-gnutls.c b/tls/gnutls/gtlsoutputstream-gnutls.c
index bb26108..68ec482 100644
--- a/tls/gnutls/gtlsoutputstream-gnutls.c
+++ b/tls/gnutls/gtlsoutputstream-gnutls.c
@@ -68,7 +68,7 @@ g_tls_output_stream_gnutls_write (GOutputStream  *stream,
   conn = g_weak_ref_get (&tls_stream->priv->weak_conn);
   g_return_val_if_fail (conn != NULL, -1);
 
-  ret = g_tls_connection_gnutls_write (conn, buffer, count, TRUE,
+  ret = g_tls_connection_gnutls_write (conn, buffer, count, -1  /* blocking */,
                                        cancellable, error);
   g_object_unref (conn);
   return ret;
@@ -122,7 +122,8 @@ g_tls_output_stream_gnutls_pollable_write_nonblocking (GPollableOutputStream  *p
   conn = g_weak_ref_get (&tls_stream->priv->weak_conn);
   g_return_val_if_fail (conn != NULL, -1);
 
-  ret = g_tls_connection_gnutls_write (conn, buffer, size, FALSE, NULL, error);
+  ret = g_tls_connection_gnutls_write (conn, buffer, size,
+                                       0  /* non-blocking */, NULL, error);
 
   g_object_unref (conn);
   return ret;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]