[glib-networking/mcatanzaro/tls-thread: 1/2] Move TLS operations to separate op thread



commit 31f4516348896d8fa490cd70506487249c7a3111
Author: Michael Catanzaro <mcatanzaro gnome org>
Date:   Fri Nov 29 18:14:21 2019 -0600

    Move TLS operations to separate op thread
    
    The current OpenSSL backend does not maintain the threadsafety
    guarantees promised by GIOStream. We need to guarantee that
    GTlsConnection methods can be called on two threads simultaneously: a
    reader thread and a writer thread. Until 2017, the OpenSSL documentation
    incorrectly implied that it was safe to use the SSL object on multiple
    threads at the same time, and the previous code assumed this. But it was
    never true.
    
    This will also make it dramatically easier to fix four entirely
    unrelated issues in the future, as discussed in #89.
    
    To simplify the implementation, this commit removes support for TLS
    rehandshakes, which is desirable to do regardless because rehandshaking
    is no longer supported in TLS 1.3, and our API documentation already says
    the behavior is undefined if TLS 1.3 is in use, "except it is guaranteed
    to be reasonable and nondestructive." Doing nothing is best.
    
    Fixes #89

 meson.build                                |    2 +-
 tls/base/gtlsconnection-base.c             | 1115 ++++++------------
 tls/base/gtlsconnection-base.h             |  137 +--
 tls/base/gtlsoperationsthread-base.c       | 1726 ++++++++++++++++++++++++++++
 tls/base/gtlsoperationsthread-base.h       |  215 ++++
 tls/base/meson.build                       |   13 +-
 tls/gnutls/gtlscertificate-gnutls.c        |   20 +-
 tls/gnutls/gtlscertificate-gnutls.h        |    6 +-
 tls/gnutls/gtlsclientconnection-gnutls.c   |  377 +-----
 tls/gnutls/gtlsconnection-gnutls.c         | 1089 +-----------------
 tls/gnutls/gtlsconnection-gnutls.h         |    9 -
 tls/gnutls/gtlsoperationsthread-gnutls.c   | 1601 ++++++++++++++++++++++++++
 tls/gnutls/gtlsoperationsthread-gnutls.h   |   46 +
 tls/gnutls/gtlsserverconnection-gnutls.c   |   94 --
 tls/gnutls/meson.build                     |    1 +
 tls/openssl/gtlsbio.c                      |   52 -
 tls/openssl/gtlsclientconnection-openssl.c |   24 +-
 tls/openssl/gtlsconnection-openssl.c       |  391 +------
 tls/openssl/gtlsconnection-openssl.h       |    4 +-
 tls/openssl/gtlsoperationsthread-openssl.c |  327 ++++++
 tls/openssl/gtlsoperationsthread-openssl.h |   42 +
 tls/openssl/meson.build                    |    1 +
 tls/tests/connection.c                     |   87 +-
 23 files changed, 4420 insertions(+), 2959 deletions(-)
---
diff --git a/meson.build b/meson.build
index f04b60f..82333a3 100644
--- a/meson.build
+++ b/meson.build
@@ -76,7 +76,7 @@ gsettings_desktop_schemas_dep = dependency('gsettings-desktop-schemas', required
 backends = []
 
 # *** Checks for GnuTLS     ***
-gnutls_dep = dependency('gnutls', version: '>= 3.6.5', required: get_option('gnutls'))
+gnutls_dep = dependency('gnutls', version: '>= 3.6.7', required: get_option('gnutls'))
 
 if gnutls_dep.found()
   backends += ['gnutls']
diff --git a/tls/base/gtlsconnection-base.c b/tls/base/gtlsconnection-base.c
index 6a885d1..4980b13 100644
--- a/tls/base/gtlsconnection-base.c
+++ b/tls/base/gtlsconnection-base.c
@@ -3,6 +3,7 @@
  * GIO - GLib Input, Output and Streaming Library
  *
  * Copyright 2009-2011 Red Hat, Inc
+ * Copyright 2019 Igalia S.L.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -30,6 +31,7 @@
 #include "gtlsconnection-base.h"
 #include "gtlsinputstream.h"
 #include "gtlslog.h"
+#include "gtlsoperationsthread-base.h"
 #include "gtlsoutputstream.h"
 
 #include <glib/gi18n-lib.h>
@@ -52,7 +54,7 @@
  *    communications.
  *  • Implements GDtlsConnection and GDatagramBased, for DTLS and datagram
  *    communications.
- *  • Implements GInitable for failable initialisation.
+ *  • Implements GInitable for failable initialization.
  */
 
 typedef struct
@@ -88,16 +90,9 @@ typedef struct
   GTlsInteraction       *interaction;
 
   GTlsCertificate       *certificate;
-  gboolean               missing_requested_client_certificate;
-  GError                *interaction_error;
   GTlsCertificate       *peer_certificate;
   GTlsCertificateFlags   peer_certificate_errors;
 
-  GMutex                 verify_certificate_mutex;
-  GCond                  verify_certificate_condition;
-  gboolean               peer_certificate_accepted;
-  gboolean               peer_certificate_examined;
-
   gboolean               require_close_notify;
   GTlsRehandshakeMode    rehandshake_mode;
 
@@ -122,16 +117,15 @@ typedef struct
    * future operations). ever_handshaked indicates that TLS has been
    * successfully negotiated at some point.
    */
+  /* FIXME: remove a few of these */
   gboolean       need_handshake;
   gboolean       need_finish_handshake;
-  gboolean       sync_handshake_in_progress;
   gboolean       started_handshake;
   gboolean       handshaking;
   gboolean       ever_handshaked;
-  GMainContext  *handshake_context;
-  GTask         *implicit_handshake;
+  gboolean       peer_certificate_accepted;
+  GTask         *async_implicit_handshake;
   GError        *handshake_error;
-  GByteArray    *app_data_buf;
 
   /* read_closed means the read direction has closed; write_closed similarly.
    * If (and only if) both are set, the entire GTlsConnection is closed. */
@@ -139,16 +133,7 @@ typedef struct
   gboolean       write_closing, write_closed;
 
   gboolean       reading;
-  gint64         read_timeout;
-  GError        *read_error;
-  GCancellable  *read_cancellable;
-
   gboolean       writing;
-  gint64         write_timeout;
-  GError        *write_error;
-  GCancellable  *write_cancellable;
-
-  gboolean       successful_posthandshake_op;
 
   gboolean       is_system_certdb;
   gboolean       database_is_unset;
@@ -158,19 +143,23 @@ typedef struct
 
   gchar        **advertised_protocols;
   gchar         *negotiated_protocol;
+
+  GTlsOperationsThreadBase *thread;
 } GTlsConnectionBasePrivate;
 
 static void g_tls_connection_base_dtls_connection_iface_init (GDtlsConnectionInterface *iface);
 
 static void g_tls_connection_base_datagram_based_iface_init  (GDatagramBasedInterface  *iface);
 
+static void g_tls_connection_base_initable_iface_init (GInitableIface *iface);
+
 static gboolean do_implicit_handshake (GTlsConnectionBase  *tls,
                                        gint64               timeout,
                                        GCancellable        *cancellable,
                                        GError             **error);
 
 static gboolean finish_handshake (GTlsConnectionBase  *tls,
-                                  GTask               *task,
+                                  gboolean             success,
                                   GError             **error);
 
 static void g_tls_connection_base_handshake_async (GTlsConnection      *conn,
@@ -189,6 +178,8 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionBase, g_tls_connection_base, G_T
                                                          g_tls_connection_base_datagram_based_iface_init);
                                   G_IMPLEMENT_INTERFACE (G_TYPE_DTLS_CONNECTION,
                                                          g_tls_connection_base_dtls_connection_iface_init);
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                         g_tls_connection_base_initable_iface_init);
                                   );
 
 
@@ -219,6 +210,32 @@ g_tls_connection_base_is_dtls (GTlsConnectionBase *tls)
   return priv->base_socket != NULL;
 }
 
+static GTlsInteractionResult
+operations_thread_request_certificate_cb (GTlsOperationsThreadBase  *thread,
+                                          GTlsInteraction           *interaction,
+                                          GTlsCertificate          **own_certificate,
+                                          GCancellable              *cancellable,
+                                          GError                   **error,
+                                          GTlsConnectionBase        *tls)
+{
+  GTlsInteractionResult result = G_TLS_INTERACTION_UNHANDLED;
+
+  /* Careful! This is emitted on the op thread. */
+
+  if (interaction)
+    {
+      result = g_tls_interaction_invoke_request_certificate (interaction,
+                                                             G_TLS_CONNECTION (tls),
+                                                             0,
+                                                             cancellable,
+                                                             error);
+    }
+
+  *own_certificate = g_tls_connection_get_certificate (G_TLS_CONNECTION (tls));
+
+  return result;
+}
+
 static void
 g_tls_connection_base_init (GTlsConnectionBase *tls)
 {
@@ -228,20 +245,44 @@ g_tls_connection_base_init (GTlsConnectionBase *tls)
   priv->database_is_unset = TRUE;
   priv->is_system_certdb = TRUE;
 
-  g_mutex_init (&priv->verify_certificate_mutex);
-  g_cond_init (&priv->verify_certificate_condition);
-
   g_mutex_init (&priv->op_mutex);
 
   priv->waiting_for_op = g_cancellable_new ();
 }
 
+static gboolean
+g_tls_connection_base_initable_init (GInitable     *initable,
+                                     GCancellable  *cancellable,
+                                     GError       **error)
+{
+  GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (initable);
+  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
+
+  priv->thread = G_TLS_CONNECTION_BASE_GET_CLASS (tls)->create_op_thread (tls);
+  if (!priv->thread)
+    return FALSE;
+
+  if (priv->interaction)
+    g_tls_operations_thread_base_set_interaction (priv->thread, priv->interaction);
+
+  g_tls_operations_thread_base_set_close_notify_required (priv->thread,
+                                                          priv->require_close_notify);
+
+  g_signal_connect_object (priv->thread, "operations-thread-request-certificate",
+                           (GCallback)operations_thread_request_certificate_cb,
+                           tls, 0);
+
+  return TRUE;
+}
+
 static void
 g_tls_connection_base_finalize (GObject *object)
 {
   GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (object);
   GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
 
+  g_clear_object (&priv->thread);
+
   g_clear_object (&priv->base_io_stream);
   g_clear_object (&priv->base_socket);
 
@@ -250,32 +291,20 @@ g_tls_connection_base_finalize (GObject *object)
 
   g_clear_object (&priv->database);
   g_clear_object (&priv->certificate);
-  g_clear_error (&priv->interaction_error);
   g_clear_object (&priv->peer_certificate);
 
-  g_mutex_clear (&priv->verify_certificate_mutex);
-  g_cond_clear (&priv->verify_certificate_condition);
-
   g_clear_object (&priv->interaction);
 
-  g_clear_pointer (&priv->handshake_context, g_main_context_unref);
-
   /* This must always be NULL at this point, as it holds a reference to @tls as
    * its source object. However, we clear it anyway just in case this changes
    * in future. */
-  g_clear_object (&priv->implicit_handshake);
+  g_clear_object (&priv->async_implicit_handshake);
 
   g_clear_error (&priv->handshake_error);
-  g_clear_error (&priv->read_error);
-  g_clear_error (&priv->write_error);
-  g_clear_object (&priv->read_cancellable);
-  g_clear_object (&priv->write_cancellable);
 
   g_clear_object (&priv->waiting_for_op);
   g_mutex_clear (&priv->op_mutex);
 
-  g_clear_pointer (&priv->app_data_buf, g_byte_array_unref);
-
   g_clear_pointer (&priv->advertised_protocols, g_strfreev);
   g_clear_pointer (&priv->negotiated_protocol, g_free);
 
@@ -407,6 +436,10 @@ g_tls_connection_base_set_property (GObject      *object,
 
     case PROP_REQUIRE_CLOSE_NOTIFY:
       priv->require_close_notify = g_value_get_boolean (value);
+
+      if (priv->thread)
+        g_tls_operations_thread_base_set_close_notify_required (priv->thread,
+                                                                priv->require_close_notify);
       break;
 
     case PROP_REHANDSHAKE_MODE:
@@ -444,6 +477,10 @@ g_tls_connection_base_set_property (GObject      *object,
     case PROP_INTERACTION:
       g_clear_object (&priv->interaction);
       priv->interaction = g_value_dup_object (value);
+
+      if (priv->thread)
+        g_tls_operations_thread_base_set_interaction (priv->thread,
+                                                      priv->interaction);
       break;
 
     case PROP_ADVERTISED_PROTOCOLS:
@@ -487,21 +524,19 @@ op_to_string (GTlsConnectionBaseOp op)
 }
 
 static const gchar *
-status_to_string (GTlsConnectionBaseStatus st)
+status_to_string (GTlsOperationStatus status)
 {
-  switch (st)
+  switch (status)
     {
-    case G_TLS_CONNECTION_BASE_OK:
-      return "BASE_OK";
-    case G_TLS_CONNECTION_BASE_WOULD_BLOCK:
+    case G_TLS_OPERATION_SUCCESS:
+      return "SUCCESS";
+    case G_TLS_OPERATION_WOULD_BLOCK:
       return "WOULD_BLOCK";
-    case G_TLS_CONNECTION_BASE_TIMED_OUT:
+    case G_TLS_OPERATION_TIMED_OUT:
       return "TIMED_OUT";
-    case G_TLS_CONNECTION_BASE_REHANDSHAKE:
-      return "REHANDSHAKE";
-    case G_TLS_CONNECTION_BASE_TRY_AGAIN:
+    case G_TLS_OPERATION_TRY_AGAIN:
       return "TRY_AGAIN";
-    case G_TLS_CONNECTION_BASE_ERROR:
+    case G_TLS_OPERATION_ERROR:
       return "ERROR";
     }
   g_assert_not_reached ();
@@ -569,8 +604,9 @@ claim_op (GTlsConnectionBase    *tls,
             }
         }
 
+      /* Performed async implicit handshake? */
       if (priv->need_finish_handshake &&
-          priv->implicit_handshake)
+          priv->async_implicit_handshake)
         {
           GError *my_error = NULL;
           gboolean success;
@@ -578,9 +614,13 @@ claim_op (GTlsConnectionBase    *tls,
           priv->need_finish_handshake = FALSE;
 
           g_mutex_unlock (&priv->op_mutex);
-          success = finish_handshake (tls, priv->implicit_handshake, &my_error);
-          g_clear_object (&priv->implicit_handshake);
-          g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+
+          success = g_task_propagate_boolean (priv->async_implicit_handshake, &my_error);
+          g_clear_object (&priv->async_implicit_handshake);
+
+          /* If we already have an error, ignore further errors. */
+          success = finish_handshake (tls, success, my_error ? NULL : &my_error);
+
           g_mutex_lock (&priv->op_mutex);
 
           if (op != G_TLS_CONNECTION_BASE_OP_CLOSE_BOTH &&
@@ -590,7 +630,7 @@ claim_op (GTlsConnectionBase    *tls,
             {
               g_propagate_error (error, my_error);
               g_mutex_unlock (&priv->op_mutex);
-              g_tls_log_debug (tls, "claim_op failed: finish_handshake failed or operation has been 
cancelled");
+              g_tls_log_debug (tls, "claim_op failed: finish_handshake failed");
               return FALSE;
             }
 
@@ -598,7 +638,10 @@ claim_op (GTlsConnectionBase    *tls,
         }
     }
 
+  /* FIXME: store a GThread member to bring back this check */
+#if 0
   if (priv->handshaking &&
+      op != G_TLS_CONNECTION_BASE_OP_HANDSHAKE &&
       timeout != 0 &&
       g_main_context_is_owner (priv->handshake_context))
     {
@@ -609,11 +652,12 @@ claim_op (GTlsConnectionBase    *tls,
        * the handshake (forever, if there's no timeout). Even a close
        * op would deadlock here.
        */
-      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot perform blocking operation during 
TLS handshake"));
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, _("Cannot perform blocking operation 
during TLS handshake"));
       g_mutex_unlock (&priv->op_mutex);
       g_tls_log_debug (tls, "claim_op failed: %s", (*error)->message);
       return FALSE;
     }
+#endif
 
   if ((op != G_TLS_CONNECTION_BASE_OP_WRITE && priv->reading) ||
       (op != G_TLS_CONNECTION_BASE_OP_READ && priv->writing) ||
@@ -631,7 +675,8 @@ claim_op (GTlsConnectionBase    *tls,
       if (timeout == 0)
         {
           /* Intentionally not translated because this is not a fatal error to be
-           * presented to the user, and to avoid this showing up in profiling. */
+           * presented to the user, and to avoid this showing up in profiling.
+           */
           g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, "Operation would block");
           g_tls_log_debug (tls, "claim_op failed: %s", (*error)->message);
           return FALSE;
@@ -702,9 +747,9 @@ claim_op (GTlsConnectionBase    *tls,
 }
 
 static void
-yield_op (GTlsConnectionBase       *tls,
-          GTlsConnectionBaseOp      op,
-          GTlsConnectionBaseStatus  status)
+yield_op (GTlsConnectionBase   *tls,
+          GTlsConnectionBaseOp  op,
+          GTlsOperationStatus   status)
 {
   GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
 
@@ -714,8 +759,6 @@ yield_op (GTlsConnectionBase       *tls,
 
   if (op == G_TLS_CONNECTION_BASE_OP_HANDSHAKE)
     priv->handshaking = FALSE;
-  else if (status == G_TLS_CONNECTION_BASE_REHANDSHAKE && !priv->handshaking)
-    priv->need_handshake = TRUE;
 
   if (op == G_TLS_CONNECTION_BASE_OP_CLOSE_BOTH ||
       op == G_TLS_CONNECTION_BASE_OP_CLOSE_READ)
@@ -733,143 +776,9 @@ yield_op (GTlsConnectionBase       *tls,
   g_mutex_unlock (&priv->op_mutex);
 }
 
-static void
-g_tls_connection_base_real_push_io (GTlsConnectionBase *tls,
-                                    GIOCondition        direction,
-                                    gint64              timeout,
-                                    GCancellable       *cancellable)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
-  if (direction & G_IO_IN)
-    {
-      priv->read_timeout = timeout;;
-      priv->read_cancellable = cancellable;
-      g_clear_error (&priv->read_error);
-    }
-
-  if (direction & G_IO_OUT)
-    {
-      priv->write_timeout = timeout;
-      priv->write_cancellable = cancellable;
-      g_clear_error (&priv->write_error);
-    }
-}
-
-void
-g_tls_connection_base_push_io (GTlsConnectionBase *tls,
-                               GIOCondition        direction,
-                               gint64              timeout,
-                               GCancellable       *cancellable)
-{
-  g_assert (direction & (G_IO_IN | G_IO_OUT));
-  g_return_if_fail (G_IS_TLS_CONNECTION_BASE (tls));
-
-  G_TLS_CONNECTION_BASE_GET_CLASS (tls)->push_io (tls, direction,
-                                                  timeout, cancellable);
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_base_real_pop_io (GTlsConnectionBase  *tls,
-                                   GIOCondition         direction,
-                                   gboolean             success,
-                                   GError             **error)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  GError *my_error = NULL;
-
-  /* This function MAY or MAY NOT set error when it fails! */
-
-  if (direction & G_IO_IN)
-    {
-      priv->read_cancellable = NULL;
-      if (!success)
-        {
-          my_error = priv->read_error;
-          priv->read_error = NULL;
-        }
-      else
-        g_clear_error (&priv->read_error);
-    }
-
-  if (direction & G_IO_OUT)
-    {
-      priv->write_cancellable = NULL;
-      if (!success && !my_error)
-        {
-          my_error = priv->write_error;
-          priv->write_error = NULL;
-        }
-      else
-        g_clear_error (&priv->write_error);
-    }
-
-  if (success)
-    return G_TLS_CONNECTION_BASE_OK;
-
-  if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
-    {
-      g_propagate_error (error, my_error);
-      return G_TLS_CONNECTION_BASE_WOULD_BLOCK;
-    }
-
-  if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
-    {
-      g_propagate_error (error, my_error);
-      return G_TLS_CONNECTION_BASE_TIMED_OUT;
-    }
-
-  if (priv->missing_requested_client_certificate &&
-      !priv->successful_posthandshake_op)
-    {
-      g_assert (G_IS_TLS_CLIENT_CONNECTION (tls));
-
-      /* Probably the server requires a client certificate, but we failed to
-       * provide one. With TLS 1.3 the server is no longer able to tell us
-       * this, so we just have to guess. If there is an error from the TLS
-       * interaction (request for user certificate), we provide that. Otherwise,
-       * guess that G_TLS_ERROR_CERTIFICATE_REQUIRED is probably appropriate.
-       * This could be wrong, but only applies to the small minority of
-       * connections where a client cert is requested but not provided, and then
-       * then only if the client has never successfully read or written.
-       */
-      if (priv->interaction_error)
-        {
-          g_propagate_error (error, priv->interaction_error);
-          priv->interaction_error = NULL;
-        }
-      else
-        {
-          g_clear_error (error);
-          g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
-                               _("Server required TLS certificate"));
-        }
-      g_clear_error (&my_error);
-    }
-  else if (my_error)
-    {
-      g_propagate_error (error, my_error);
-    }
-
-  return G_TLS_CONNECTION_BASE_ERROR;
-}
-
-GTlsConnectionBaseStatus
-g_tls_connection_base_pop_io (GTlsConnectionBase  *tls,
-                              GIOCondition         direction,
-                              gboolean             success,
-                              GError             **error)
-{
-  g_assert (direction & (G_IO_IN | G_IO_OUT));
-  g_assert (!error || !*error);
-  g_return_val_if_fail (G_IS_TLS_CONNECTION_BASE (tls), G_TLS_CONNECTION_BASE_ERROR);
-
-  return G_TLS_CONNECTION_BASE_GET_CLASS (tls)->pop_io (tls, direction,
-                                                        success, error);
-}
-
 /* Checks whether the underlying base stream or GDatagramBased meets
- * @condition. */
+ * @condition.
+ */
 gboolean
 g_tls_connection_base_base_check (GTlsConnectionBase *tls,
                                   GIOCondition        condition)
@@ -890,7 +799,8 @@ g_tls_connection_base_base_check (GTlsConnectionBase *tls,
 }
 
 /* Checks whether the (D)TLS stream meets @condition; not the underlying base
- * stream or GDatagramBased. */
+ * stream or GDatagramBased.
+ */
 gboolean
 g_tls_connection_base_check (GTlsConnectionBase  *tls,
                              GIOCondition         condition)
@@ -934,7 +844,8 @@ typedef struct {
 
 /* Use a custom dummy callback instead of g_source_set_dummy_callback(), as that
  * uses a GClosure and is slow. (The GClosure is necessary to deal with any
- * function prototype.) */
+ * function prototype.)
+ */
 static gboolean
 dummy_callback (gpointer data)
 {
@@ -1076,7 +987,6 @@ g_tls_connection_tls_source_dtls_closure_callback (GDatagramBased *datagram_base
   g_value_unset (&param[1]);
 
   return result;
-
 }
 
 static GSourceFuncs tls_source_funcs =
@@ -1099,6 +1009,7 @@ static GSourceFuncs dtls_source_funcs =
   (GSourceDummyMarshal)g_cclosure_marshal_generic
 };
 
+/* FIXME: all needs to be threadsafe... */
 GSource *
 g_tls_connection_base_create_source (GTlsConnectionBase  *tls,
                                      GIOCondition         condition,
@@ -1158,7 +1069,7 @@ g_tls_connection_base_dtls_create_source (GDatagramBased  *datagram_based,
 
 static GIOCondition
 g_tls_connection_base_condition_check (GDatagramBased  *datagram_based,
-                                         GIOCondition     condition)
+                                       GIOCondition     condition)
 {
   GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (datagram_based);
 
@@ -1228,7 +1139,6 @@ static GTlsCertificateFlags
 verify_peer_certificate (GTlsConnectionBase *tls,
                          GTlsCertificate    *peer_certificate)
 {
-  GTlsConnectionBaseClass *tls_class = G_TLS_CONNECTION_BASE_GET_CLASS (tls);
   GSocketConnectable *peer_identity;
   GTlsDatabase *database;
   GTlsCertificateFlags errors;
@@ -1271,53 +1181,32 @@ verify_peer_certificate (GTlsConnectionBase *tls,
         }
     }
 
-  if (tls_class->verify_peer_certificate)
-    errors |= tls_class->verify_peer_certificate (tls, peer_certificate, errors);
-
   return errors;
 }
 
-static void
-update_peer_certificate_and_compute_errors (GTlsConnectionBase *tls)
+static gboolean
+verify_certificate_cb (GTlsOperationsThreadBase *thread,
+                       GTlsCertificate          *peer_certificate,
+                       GTlsConnectionBase       *tls)
 {
   GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  GTlsCertificate *peer_certificate = NULL;
-  GTlsCertificateFlags peer_certificate_errors = 0;
+  gboolean accepted = FALSE;
 
-  /* This function must be called from the handshake context thread
-   * (probably the main thread, NOT the handshake thread) because
-   * it emits notifies that are application-visible.
-   *
-   * verify_certificate_mutex should be locked.
+  /* FIXME: when doing async handshake as sync-on-a-thread, this function will
+   * be called from the handshake thread, which is unsafe. The code assumes
+   * it is used from the main thread.
+   * FIXME: eliminate handshake context.
    */
-  g_assert (priv->handshake_context);
-  g_assert (g_main_context_is_owner (priv->handshake_context));
-
-  peer_certificate = G_TLS_CONNECTION_BASE_GET_CLASS (tls)->retrieve_peer_certificate (tls);
-  if (peer_certificate)
-    peer_certificate_errors = verify_peer_certificate (tls, peer_certificate);
 
   g_set_object (&priv->peer_certificate, peer_certificate);
-  g_clear_object (&peer_certificate);
 
-  priv->peer_certificate_errors = peer_certificate_errors;
+  if (peer_certificate)
+    priv->peer_certificate_errors = verify_peer_certificate (tls, peer_certificate);
+  else
+    priv->peer_certificate_errors = 0;
 
   g_object_notify (G_OBJECT (tls), "peer-certificate");
   g_object_notify (G_OBJECT (tls), "peer-certificate-errors");
-}
-
-static gboolean
-accept_or_reject_peer_certificate (gpointer user_data)
-{
-  GTlsConnectionBase *tls = user_data;
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  gboolean accepted = FALSE;
-
-  g_assert (g_main_context_is_owner (priv->handshake_context));
-
-  g_mutex_lock (&priv->verify_certificate_mutex);
-
-  update_peer_certificate_and_compute_errors (tls);
 
   if (G_IS_TLS_CLIENT_CONNECTION (tls) && priv->peer_certificate)
     {
@@ -1336,264 +1225,157 @@ accept_or_reject_peer_certificate (gpointer user_data)
 
   if (!accepted)
     {
-      gboolean sync_handshake_in_progress;
-
-      g_mutex_lock (&priv->op_mutex);
-      sync_handshake_in_progress = priv->sync_handshake_in_progress;
-      g_mutex_unlock (&priv->op_mutex);
-
-      if (sync_handshake_in_progress)
-        g_main_context_pop_thread_default (priv->handshake_context);
-
       accepted = g_tls_connection_emit_accept_certificate (G_TLS_CONNECTION (tls),
                                                            priv->peer_certificate,
                                                            priv->peer_certificate_errors);
-
-      if (sync_handshake_in_progress)
-        g_main_context_push_thread_default (priv->handshake_context);
     }
 
   priv->peer_certificate_accepted = accepted;
 
-  /* This has to be the very last statement before signaling the
-   * condition variable because otherwise the code could spuriously
-   * wakeup and continue before we are done here.
-   */
-  priv->peer_certificate_examined = TRUE;
-
-  g_cond_signal (&priv->verify_certificate_condition);
-  g_mutex_unlock (&priv->verify_certificate_mutex);
-
-  return G_SOURCE_REMOVE;
+  return accepted;
 }
 
-gboolean
-g_tls_connection_base_handshake_thread_verify_certificate (GTlsConnectionBase *tls)
+static void
+session_resumed_cb (GTlsOperationsThreadBase *thread,
+                    GTlsCertificate          *peer_certificate,
+                    GTlsConnectionBase       *tls)
 {
   GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  gboolean accepted;
-
-  g_tls_log_debug (tls, "verifying peer certificate");
-
-  g_mutex_lock (&priv->verify_certificate_mutex);
-  priv->peer_certificate_examined = FALSE;
-  priv->peer_certificate_accepted = FALSE;
-  g_mutex_unlock (&priv->verify_certificate_mutex);
 
-  /* Invoke the callback on the handshake context's thread. This is
-   * necessary because we need to ensure the accept-certificate signal
-   * is emitted on the original thread.
+  /* FIXME: when doing async handshake as sync-on-a-thread, this function will
+   * be called from the handshake thread, which is unsafe. The code assumes
+   * it is used from the main thread.
+   * FIXME: eliminate handshake context.
    */
-  g_assert (priv->handshake_context);
-  g_main_context_invoke (priv->handshake_context, accept_or_reject_peer_certificate, tls);
 
-  /* We'll block the handshake thread until the original thread has
-   * decided whether to accept the certificate.
-   */
-  g_mutex_lock (&priv->verify_certificate_mutex);
-  while (!priv->peer_certificate_examined)
-    g_cond_wait (&priv->verify_certificate_condition, &priv->verify_certificate_mutex);
-  accepted = priv->peer_certificate_accepted;
-  g_mutex_unlock (&priv->verify_certificate_mutex);
+  g_set_object (&priv->peer_certificate, peer_certificate);
+  g_clear_object (&peer_certificate);
 
-  return accepted;
+  priv->peer_certificate_errors = 0;
+
+  g_object_notify (G_OBJECT (tls), "peer-certificate");
+  g_object_notify (G_OBJECT (tls), "peer-certificate-errors");
 }
 
-static void
-handshake_thread (GTask        *task,
-                  gpointer      object,
-                  gpointer      task_data,
-                  GCancellable *cancellable)
+static gboolean
+handshake (GTlsConnectionBase  *tls,
+           gint64               timeout,
+           GCancellable        *cancellable,
+           GError             **error)
 {
-  GTlsConnectionBase *tls = object;
   GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  GTlsConnectionBaseClass *tls_class = G_TLS_CONNECTION_BASE_GET_CLASS (tls);
-  GError *error = NULL;
-  gint64 start_time;
-  gint64 timeout;
+  GTlsAuthenticationMode auth_mode = G_TLS_AUTHENTICATION_NONE;
+  gchar *original_negotiated_protocol;
+  GList *accepted_cas;
 
-  g_tls_log_debug (tls, "TLS handshake thread starts");
+  /* FIXME: in async codepaths, this function is still called on a secondary
+   * handshake thread. That means use of priv members here needs to be guarded
+   * by a mutex. Additionally, it is SUPER UNSAFE to notify negotiated-protocol
+   * on this thread. We need to either eliminate this extra thread, or tighten
+   * up the threadsafety of this function.
+   */
 
-  /* A timeout, in microseconds, must be provided as a gint64* task_data. */
-  g_assert (task_data);
-  start_time = g_get_monotonic_time ();
-  timeout = *((gint64 *)task_data);
+  g_tls_log_debug (tls, "TLS handshake starts");
 
   priv->started_handshake = FALSE;
-  priv->missing_requested_client_certificate = FALSE;
 
+  /* FIXME: I don't like this mismatch. If we claim here, let's yield at the
+   * bottom. Otherwise, move claim to the caller.
+   */
   if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE,
-                 timeout, cancellable, &error))
+                 timeout, cancellable, error))
     {
-      g_task_return_error (task, error);
-      g_tls_log_debug (tls, "TLS handshake thread failed: claiming op failed");
-      return;
+      g_tls_log_debug (tls, "TLS handshake failed: claiming op failed");
+      return FALSE;
     }
 
   g_clear_error (&priv->handshake_error);
 
   if (priv->ever_handshaked && !priv->need_handshake)
     {
-      GTlsConnectionBaseStatus status;
-
-      if (tls_class->handshake_thread_safe_renegotiation_status (tls) != 
G_TLS_SAFE_RENEGOTIATION_SUPPORTED_BY_PEER)
-        {
-          g_task_return_new_error (task, G_TLS_ERROR, G_TLS_ERROR_MISC,
-                                   _("Peer does not support safe renegotiation"));
-          g_tls_log_debug (tls, "TLS handshake thread failed: peer does not support safe renegotiation");
-          return;
-        }
-
-      /* Adjust the timeout for the next operation in the sequence. */
-      if (timeout > 0)
-        {
-          timeout -= (g_get_monotonic_time () - start_time);
-          if (timeout <= 0)
-            timeout = 1;
-        }
-
-      status = tls_class->handshake_thread_request_rehandshake (tls, timeout, cancellable, &error);
-      if (status != G_TLS_CONNECTION_BASE_OK)
-        {
-          g_task_return_error (task, error);
-          g_tls_log_debug (tls, "TLS handshake thread failed: %s", error ? error->message : "no error");
-          return;
-        }
+      /* Once upon a time, we allowed calling g_tls_connection_handshake()
+       * twice in order to request a rehandshake. Now that rehandshaking has
+       * been removed from TLS 1.3, we'll instead just ignore the request. We
+       * can't throw an error here because this used to be allowed.
+       */
+      g_tls_log_debug (tls, "Ignoring duplicate TLS handshake request");
+      return TRUE;
     }
 
-  /* Adjust the timeout for the next operation in the sequence. */
-  if (timeout > 0)
+  if (G_IS_TLS_SERVER_CONNECTION (tls))
     {
-      timeout -= (g_get_monotonic_time () - start_time);
-      if (timeout <= 0)
-        timeout = 1;
+      g_object_get (tls,
+                    "authentication-mode", &auth_mode,
+                    NULL);
     }
 
-  priv->started_handshake = TRUE;
-  tls_class->handshake_thread_handshake (tls, timeout, cancellable, &error);
-  priv->need_handshake = FALSE;
-
-  if (error)
-    {
-      g_task_return_error (task, error);
-      g_tls_log_debug (tls, "TLS handshake thread failed: %s", error->message);
-    }
-  else
-    {
-      priv->ever_handshaked = TRUE;
-      g_task_return_boolean (task, TRUE);
-      g_tls_log_debug (tls, "TLS handshake thread succeeded");
-    }
-}
-
-static void
-sync_handshake_thread_completed (GObject      *object,
-                                 GAsyncResult *result,
-                                 gpointer      user_data)
-{
-  GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (object);
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  gpointer source_tag;
-
-  g_tls_log_debug (tls, "synchronous TLS handshake thread completed");
+  original_negotiated_protocol = g_steal_pointer (&priv->negotiated_protocol);
 
-  source_tag = g_task_get_source_tag (G_TASK (result));
-  g_assert (source_tag == do_implicit_handshake || source_tag == g_tls_connection_base_handshake);
-  g_assert (g_task_is_valid (result, object));
+  priv->started_handshake = TRUE;
 
-  g_assert (g_main_context_is_owner (priv->handshake_context));
+  g_tls_operations_thread_base_handshake (priv->thread,
+                                          priv->certificate,
+                                          (const gchar **)priv->advertised_protocols,
+                                          auth_mode,
+                                          timeout,
+                                          (GTlsVerifyCertificateFunc)verify_certificate_cb,
+                                          (GTlsSessionResumedFunc)session_resumed_cb,
+                                          &priv->negotiated_protocol,
+                                          &accepted_cas,
+                                          cancellable,
+                                          tls,
+                                          error);
 
-  g_mutex_lock (&priv->op_mutex);
-  priv->sync_handshake_in_progress = FALSE;
-  g_mutex_unlock (&priv->op_mutex);
+  priv->need_handshake = FALSE;
 
-  g_main_context_wakeup (priv->handshake_context);
-}
+  /* FIXME: notify should be next to accepted-cas? */
+  if (g_strcmp0 (original_negotiated_protocol, priv->negotiated_protocol) != 0)
+    g_object_notify (G_OBJECT (tls), "negotiated-protocol");
+  g_free (original_negotiated_protocol);
 
-static void
-crank_sync_handshake_context (GTlsConnectionBase *tls,
-                              GCancellable       *cancellable)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
+  if (G_IS_TLS_CLIENT_CONNECTION (tls))
+    G_TLS_CONNECTION_BASE_GET_CLASS (tls)->set_accepted_cas (tls, accepted_cas);
+  else
+    g_assert (!accepted_cas);
 
-  /* need_finish_handshake will be set inside sync_handshake_thread_completed(),
-   * which should only ever be invoked while iterating the handshake context
-   * here. So need_finish_handshake should only change on this thread.
-   *
-   * FIXME: This function is not cancellable. We should figure out how to
-   * support cancellation. We must not return from this function before it is
-   * safe to destroy handshake_context, but it's not safe to destroy
-   * handshake_context until after the handshake has completed. And the
-   * handshake operation is not cancellable, so we have a problem.
-   */
-  g_mutex_lock (&priv->op_mutex);
-  priv->sync_handshake_in_progress = TRUE;
-  while (priv->sync_handshake_in_progress)
+  if (error && *error)
     {
-      g_mutex_unlock (&priv->op_mutex);
-      g_main_context_iteration (priv->handshake_context, TRUE);
-      g_mutex_lock (&priv->op_mutex);
+      g_tls_log_debug (tls, "TLS handshake failed: %s", (*error)->message);
+      return FALSE;
     }
-  g_mutex_unlock (&priv->op_mutex);
+
+  priv->ever_handshaked = TRUE;
+  g_tls_log_debug (tls, "TLS handshake succeeded");
+  return TRUE;
 }
 
 static gboolean
 finish_handshake (GTlsConnectionBase  *tls,
-                  GTask               *task,
+                  gboolean             success,
                   GError             **error)
 {
   GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  GTlsConnectionBaseClass *tls_class = G_TLS_CONNECTION_BASE_GET_CLASS (tls);
-  gchar *original_negotiated_protocol;
   GError *my_error = NULL;
 
   g_tls_log_debug (tls, "finishing TLS handshake");
 
-  original_negotiated_protocol = g_steal_pointer (&priv->negotiated_protocol);
-
-  if (g_task_propagate_boolean (task, &my_error))
+  /* FIXME: Return an error from the handshake instead? */
+  if (success && priv->peer_certificate && !priv->peer_certificate_accepted)
     {
-      if (tls_class->is_session_resumed && tls_class->is_session_resumed (tls))
-        {
-          /* Because this session was resumed, we skipped certificate
-           * verification on this handshake, so we missed our earlier
-           * chance to set peer_certificate and peer_certificate_errors.
-           * Do so here instead.
-           *
-           * The certificate has already been accepted, so we don't do
-           * anything with the result here.
-           */
-          g_mutex_lock (&priv->verify_certificate_mutex);
-          update_peer_certificate_and_compute_errors (tls);
-          priv->peer_certificate_examined = TRUE;
-          priv->peer_certificate_accepted = TRUE;
-          g_mutex_unlock (&priv->verify_certificate_mutex);
-        }
-
-      /* FIXME: Return an error from the handshake thread instead. */
-      if (priv->peer_certificate && !priv->peer_certificate_accepted)
-        {
-          g_set_error_literal (&my_error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
-                               _("Unacceptable TLS certificate"));
-        }
+      g_set_error_literal (&my_error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+                           _("Unacceptable TLS certificate"));
     }
 
-  if (tls_class->complete_handshake)
-    {
-      /* If we already have an error, ignore further errors. */
-      tls_class->complete_handshake (tls, &priv->negotiated_protocol, my_error ? NULL : &my_error);
-
-      if (g_strcmp0 (original_negotiated_protocol, priv->negotiated_protocol) != 0)
-        g_object_notify (G_OBJECT (tls), "negotiated-protocol");
-    }
-  g_free (original_negotiated_protocol);
+  /* FIXME: notify only when accepted-cas has actually changed. */
+  if (G_IS_TLS_CLIENT_CONNECTION (tls))
+    g_object_notify (G_OBJECT (tls), "accepted-cas");
 
   if (my_error && priv->started_handshake)
     priv->handshake_error = g_error_copy (my_error);
 
   if (!my_error) {
     g_tls_log_debug (tls, "TLS handshake has finished successfully");
-    return TRUE;
+    return success;
   }
 
   g_tls_log_debug (tls, "TLS handshake has finished with error: %s", my_error->message);
@@ -1607,43 +1389,17 @@ g_tls_connection_base_handshake (GTlsConnection   *conn,
                                  GError          **error)
 {
   GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (conn);
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  GTlsConnectionBaseClass *tls_class = G_TLS_CONNECTION_BASE_GET_CLASS (tls);
-  GTask *task;
   gboolean success;
-  gint64 *timeout = NULL;
   GError *my_error = NULL;
 
   g_tls_log_debug (tls, "Starting synchronous TLS handshake");
 
-  g_assert (!priv->handshake_context);
-  priv->handshake_context = g_main_context_new ();
-
-  g_main_context_push_thread_default (priv->handshake_context);
-
-  if (tls_class->prepare_handshake)
-    tls_class->prepare_handshake (tls, priv->advertised_protocols);
-
-  task = g_task_new (conn, cancellable, sync_handshake_thread_completed, NULL);
-  g_task_set_source_tag (task, g_tls_connection_base_handshake);
-  g_task_set_name (task, "[glib-networking] g_tls_connection_base_handshake");
-  g_task_set_return_on_cancel (task, TRUE);
+  success = handshake (tls, -1 /* blocking */, cancellable, error);
 
-  timeout = g_new0 (gint64, 1);
-  *timeout = -1; /* blocking */
-  g_task_set_task_data (task, timeout, g_free);
+  /* If we already have an error, ignore further errors. */
+  success = finish_handshake (tls, success, my_error ? NULL : &my_error);
 
-  g_task_run_in_thread (task, handshake_thread);
-  crank_sync_handshake_context (tls, cancellable);
-
-  success = finish_handshake (tls, task, &my_error);
-  g_object_unref (task);
-
-  g_main_context_pop_thread_default (priv->handshake_context);
-  g_clear_pointer (&priv->handshake_context, g_main_context_unref);
-
-  yield_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE,
-            G_TLS_CONNECTION_BASE_OK);
+  yield_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE, G_TLS_OPERATION_SUCCESS);
 
   if (my_error)
     g_propagate_error (error, my_error);
@@ -1663,6 +1419,9 @@ g_tls_connection_base_dtls_handshake (GDtlsConnection  *conn,
  * handshake_thread() and then call async_handshake_thread_completed(),
  * and a second to call the caller's original callback after we call
  * finish_handshake().
+ *
+ * Note: async_handshake_thread_completed() is called only for explicit
+ * async handshakes, not for implicit async handshakes.
  */
 
 static void
@@ -1691,17 +1450,13 @@ async_handshake_thread_completed (GObject      *object,
     need_finish_handshake = FALSE;
   g_mutex_unlock (&priv->op_mutex);
 
-  /* We have to clear handshake_context before g_task_return_* because it can
-   * return immediately to application code inside g_task_return_*,
-   * and the application code could then start a new TLS operation.
-   *
-   * But we can't clear until after finish_handshake().
-   */
+  /* FIXME: this looks weird, why do we ignore the result of the GTask in the !need_finish_handshake case? */
   if (need_finish_handshake)
     {
-      success = finish_handshake (tls, G_TASK (result), &error);
+      success = g_task_propagate_boolean (G_TASK (result), &error);
 
-      g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+      /* If we already have an error, ignore further errors. */
+      success = finish_handshake (tls, success, error ? NULL : &error);
 
       if (success)
         g_task_return_boolean (caller_task, TRUE);
@@ -1710,8 +1465,6 @@ async_handshake_thread_completed (GObject      *object,
     }
   else
     {
-      g_clear_pointer (&priv->handshake_context, g_main_context_unref);
-
       if (priv->handshake_error)
         g_task_return_error (caller_task, g_error_copy (priv->handshake_error));
       else
@@ -1727,12 +1480,13 @@ async_handshake_thread (GTask        *task,
                         gpointer      task_data,
                         GCancellable *cancellable)
 {
-  GTlsConnectionBase *tls = object;
+  GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (object);
   GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
+  GError *error = NULL;
 
   g_tls_log_debug (tls, "Asynchronous TLS handshake thread starts");
 
-  handshake_thread (task, object, task_data, cancellable);
+  handshake (tls, -1 /* blocking */, cancellable, &error);
 
   g_mutex_lock (&priv->op_mutex);
   priv->need_finish_handshake = TRUE;
@@ -1743,8 +1497,12 @@ async_handshake_thread (GTask        *task,
   priv->handshaking = FALSE;
   g_mutex_unlock (&priv->op_mutex);
 
-  yield_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE,
-            G_TLS_CONNECTION_BASE_OK);
+  yield_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE, G_TLS_OPERATION_SUCCESS);
+
+  if (error)
+    g_task_return_error (task, error);
+  else
+    g_task_return_boolean (task, TRUE);
 }
 
 static void
@@ -1755,19 +1513,10 @@ g_tls_connection_base_handshake_async (GTlsConnection      *conn,
                                        gpointer             user_data)
 {
   GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (conn);
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  GTlsConnectionBaseClass *tls_class = G_TLS_CONNECTION_BASE_GET_CLASS (tls);
   GTask *thread_task, *caller_task;
-  gint64 *timeout = NULL;
 
   g_tls_log_debug (tls, "Starting asynchronous TLS handshake");
 
-  g_assert (!priv->handshake_context);
-  priv->handshake_context = g_main_context_ref_thread_default ();
-
-  if (tls_class->prepare_handshake)
-    tls_class->prepare_handshake (tls, priv->advertised_protocols);
-
   caller_task = g_task_new (conn, cancellable, callback, user_data);
   g_task_set_source_tag (caller_task, g_tls_connection_base_handshake_async);
   g_task_set_name (caller_task, "[glib-networking] g_tls_connection_base_handshake_async (caller task)");
@@ -1778,10 +1527,6 @@ g_tls_connection_base_handshake_async (GTlsConnection      *conn,
   g_task_set_name (caller_task, "[glib-networking] g_tls_connection_base_handshake_async (thread task)");
   g_task_set_priority (thread_task, io_priority);
 
-  timeout = g_new0 (gint64, 1);
-  *timeout = -1; /* blocking */
-  g_task_set_task_data (thread_task, timeout, g_free);
-
   g_task_run_in_thread (thread_task, async_handshake_thread);
   g_object_unref (thread_task);
 }
@@ -1818,141 +1563,104 @@ g_tls_connection_base_dtls_handshake_finish (GDtlsConnection  *conn,
 }
 
 static gboolean
-do_implicit_handshake (GTlsConnectionBase  *tls,
-                       gint64               timeout,
-                       GCancellable        *cancellable,
-                       GError             **error)
+start_async_implicit_handshake (GTlsConnectionBase  *tls,
+                                GCancellable        *cancellable,
+                                GError             **error)
 {
   GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  GTlsConnectionBaseClass *tls_class = G_TLS_CONNECTION_BASE_GET_CLASS (tls);
-  gint64 *thread_timeout = NULL;
 
-  g_tls_log_debug (tls, "Implcit TLS handshaking starts");
+  g_tls_log_debug (tls, "Starting async implicit handshake");
 
   /* We have op_mutex */
 
-  g_assert (!priv->handshake_context);
-  if (timeout != 0)
-    {
-      priv->handshake_context = g_main_context_new ();
-      g_main_context_push_thread_default (priv->handshake_context);
-    }
-  else
-    {
-      priv->handshake_context = g_main_context_ref_thread_default ();
-    }
+  g_assert (!priv->async_implicit_handshake);
+  priv->async_implicit_handshake = g_task_new (tls, cancellable,
+                                               NULL, NULL);
+  g_task_set_source_tag (priv->async_implicit_handshake, do_implicit_handshake);
+  g_task_set_name (priv->async_implicit_handshake, "[glib-networking] do_implicit_handshake");
 
-  g_assert (!priv->implicit_handshake);
-  priv->implicit_handshake = g_task_new (tls, cancellable,
-                                        timeout ? sync_handshake_thread_completed : NULL,
-                                        NULL);
-  g_task_set_source_tag (priv->implicit_handshake, do_implicit_handshake);
-  g_task_set_name (priv->implicit_handshake, "[glib-networking] do_implicit_handshake");
+  /* In the non-blocking case, start the asynchronous handshake operation
+   * and return EWOULDBLOCK to the caller, who will handle polling for
+   * completion of the handshake and whatever operation they actually cared
+   * about. Run the actual operation as blocking in its thread.
+   */
 
-  thread_timeout = g_new0 (gint64, 1);
-  g_task_set_task_data (priv->implicit_handshake,
-                        thread_timeout, g_free);
+  g_task_run_in_thread (priv->async_implicit_handshake, async_handshake_thread);
 
-  if (tls_class->prepare_handshake)
-    tls_class->prepare_handshake (tls, priv->advertised_protocols);
+  /* Intentionally not translated because this is not a fatal error to be
+   * presented to the user, and to avoid this showing up in profiling.
+   */
+  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, "Operation would block");
+  return FALSE;
+}
 
-  if (timeout != 0)
-    {
-      GError *my_error = NULL;
-      gboolean success;
+static gboolean
+do_sync_implicit_handshake (GTlsConnectionBase  *tls,
+                            gint64               timeout,
+                            GCancellable        *cancellable,
+                            GError             **error)
+{
+  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
+  GError *my_error = NULL;
+  gboolean success;
 
-      /* In the blocking case, run the handshake operation synchronously in
-       * another thread, and delegate handling the timeout to that thread; it
-       * should return G_IO_ERROR_TIMED_OUT iff (timeout > 0) and the operation
-       * times out. If (timeout < 0) it should block indefinitely until the
-       * operation is complete or errors. */
-      *thread_timeout = timeout;
+  g_tls_log_debug (tls, "Starting sync implicit handshake");
 
-      g_mutex_unlock (&priv->op_mutex);
+  /* We have op_mutex */
 
-      g_task_set_return_on_cancel (priv->implicit_handshake, TRUE);
-      g_task_run_in_thread (priv->implicit_handshake, handshake_thread);
+  g_mutex_unlock (&priv->op_mutex);
 
-      crank_sync_handshake_context (tls, cancellable);
+  success = handshake (tls, timeout, cancellable, &my_error);
 
-      success = finish_handshake (tls,
-                                  priv->implicit_handshake,
-                                  &my_error);
+  /* If we already have an error, ignore further errors. */
+  success = finish_handshake (tls, success,
+                              my_error ? NULL : &my_error);
 
-      g_main_context_pop_thread_default (priv->handshake_context);
-      g_clear_pointer (&priv->handshake_context, g_main_context_unref);
-      g_clear_object (&priv->implicit_handshake);
+  yield_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE, G_TLS_OPERATION_SUCCESS);
 
-      yield_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE,
-                G_TLS_CONNECTION_BASE_OK);
+  g_mutex_lock (&priv->op_mutex);
 
-      g_mutex_lock (&priv->op_mutex);
+  if (my_error)
+    g_propagate_error (error, my_error);
+  return success;
+}
 
-      if (my_error)
-        g_propagate_error (error, my_error);
-      return success;
-    }
-  else
-    {
-      /* In the non-blocking case, start the asynchronous handshake operation
-       * and return EWOULDBLOCK to the caller, who will handle polling for
-       * completion of the handshake and whatever operation they actually cared
-       * about. Run the actual operation as blocking in its thread. */
-      *thread_timeout = -1; /* blocking */
-
-      g_task_run_in_thread (priv->implicit_handshake,
-                            async_handshake_thread);
-
-      /* Intentionally not translated because this is not a fatal error to be
-       * presented to the user, and to avoid this showing up in profiling. */
-      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, "Operation would block");
-      return FALSE;
-    }
+static gboolean
+do_implicit_handshake (GTlsConnectionBase  *tls,
+                       gint64               timeout,
+                       GCancellable        *cancellable,
+                       GError             **error)
+{
+  if (timeout == 0)
+    return start_async_implicit_handshake (tls, cancellable, error);
+
+  return do_sync_implicit_handshake (tls, timeout, cancellable, error);
 }
 
 gssize
 g_tls_connection_base_read (GTlsConnectionBase  *tls,
                             void                *buffer,
-                            gsize                count,
+                            gsize                size,
                             gint64               timeout,
                             GCancellable        *cancellable,
                             GError             **error)
 {
   GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  GTlsConnectionBaseStatus status;
+  GTlsOperationStatus status;
   gssize nread;
 
   g_tls_log_debug (tls, "starting to read data from TLS connection");
 
-  do
-    {
-      if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_READ,
-                     timeout, cancellable, error))
-        return -1;
+  if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_READ,
+                 timeout, cancellable, error))
+    return -1;
 
-      if (priv->app_data_buf && !priv->handshaking)
-        {
-          nread = MIN (count, priv->app_data_buf->len);
-          memcpy (buffer, priv->app_data_buf->data, nread);
-          if (nread == priv->app_data_buf->len)
-            g_clear_pointer (&priv->app_data_buf, g_byte_array_unref);
-          else
-            g_byte_array_remove_range (priv->app_data_buf, 0, nread);
-          status = G_TLS_CONNECTION_BASE_OK;
-        }
-      else
-        {
-          status = G_TLS_CONNECTION_BASE_GET_CLASS (tls)->
-            read_fn (tls, buffer, count, timeout, &nread, cancellable, error);
-        }
+  status = g_tls_operations_thread_base_read (priv->thread, buffer, size, timeout, &nread, cancellable, 
error);
 
-      yield_op (tls, G_TLS_CONNECTION_BASE_OP_READ, status);
-    }
-  while (status == G_TLS_CONNECTION_BASE_REHANDSHAKE);
+  yield_op (tls, G_TLS_CONNECTION_BASE_OP_READ, status);
 
-  if (status == G_TLS_CONNECTION_BASE_OK)
+  if (status == G_TLS_OPERATION_SUCCESS)
     {
-      priv->successful_posthandshake_op = TRUE;
       g_tls_log_debug (tls, "successfully read %" G_GSSIZE_FORMAT " bytes from TLS connection", nread);
       return nread;
     }
@@ -1970,50 +1678,20 @@ g_tls_connection_base_read_message (GTlsConnectionBase  *tls,
                                     GError             **error)
 {
   GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  GTlsConnectionBaseStatus status;
+  GTlsOperationStatus status;
   gssize nread;
 
   g_tls_log_debug (tls, "starting to read messages from TLS connection");
 
-  do {
-    if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_READ,
-                   timeout, cancellable, error))
-      return -1;
+  if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_READ,
+                 timeout, cancellable, error))
+    return -1;
+
+  status = g_tls_operations_thread_base_read_message (priv->thread, vectors, num_vectors, timeout, &nread, 
cancellable, error);
+  yield_op (tls, G_TLS_CONNECTION_BASE_OP_READ, status);
 
-    /* Copy data out of the app data buffer first. */
-    if (priv->app_data_buf && !priv->handshaking)
-      {
-        nread = 0;
-
-        for (guint i = 0; i < num_vectors; i++)
-          {
-            gsize count;
-            GInputVector *vec = &vectors[i];
-
-            count = MIN (vec->size, priv->app_data_buf->len);
-            nread += count;
-
-            memcpy (vec->buffer, priv->app_data_buf->data, count);
-            if (count == priv->app_data_buf->len)
-              g_clear_pointer (&priv->app_data_buf, g_byte_array_unref);
-            else
-              g_byte_array_remove_range (priv->app_data_buf, 0, count);
-            status = G_TLS_CONNECTION_BASE_OK;
-          }
-      }
-    else
-      {
-        g_assert (G_TLS_CONNECTION_BASE_GET_CLASS (tls)->read_message_fn);
-        status = G_TLS_CONNECTION_BASE_GET_CLASS (tls)->
-          read_message_fn (tls, vectors, num_vectors, timeout, &nread, cancellable, error);
-      }
-
-    yield_op (tls, G_TLS_CONNECTION_BASE_OP_READ, status);
-  } while (status == G_TLS_CONNECTION_BASE_REHANDSHAKE);
-
-  if (status == G_TLS_CONNECTION_BASE_OK)
+  if (status == G_TLS_OPERATION_SUCCESS)
     {
-      priv->successful_posthandshake_op = TRUE;
       g_tls_log_debug (tls, "successfully read %" G_GSSIZE_FORMAT " bytes from TLS connection", nread);
       return nread;
     }
@@ -2032,7 +1710,6 @@ g_tls_connection_base_receive_messages (GDatagramBased  *datagram_based,
                                         GError         **error)
 {
   GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (datagram_based);
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
   guint i;
   GError *child_error = NULL;
 
@@ -2095,40 +1772,32 @@ g_tls_connection_base_receive_messages (GDatagramBased  *datagram_based,
       return -1;
     }
 
-  priv->successful_posthandshake_op = TRUE;
   return i;
 }
 
 gssize
 g_tls_connection_base_write (GTlsConnectionBase  *tls,
                              const void          *buffer,
-                             gsize                count,
+                             gsize                size,
                              gint64               timeout,
                              GCancellable        *cancellable,
                              GError             **error)
 {
   GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  GTlsConnectionBaseStatus status;
+  GTlsOperationStatus status;
   gssize nwrote;
 
-  g_tls_log_debug (tls, "starting to write %" G_GSIZE_FORMAT " bytes to TLS connection", count);
+  g_tls_log_debug (tls, "starting to write %" G_GSIZE_FORMAT " bytes to TLS connection", size);
 
-  do
-    {
-      if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE,
-                     timeout, cancellable, error))
-        return -1;
+  if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE,
+                 timeout, cancellable, error))
+    return -1;
 
-      status = G_TLS_CONNECTION_BASE_GET_CLASS (tls)->
-        write_fn (tls, buffer, count, timeout, &nwrote, cancellable, error);
+  status = g_tls_operations_thread_base_write (priv->thread, buffer, size, timeout, &nwrote, cancellable, 
error);
+  yield_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE, status);
 
-      yield_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE, status);
-    }
-  while (status == G_TLS_CONNECTION_BASE_REHANDSHAKE);
-
-  if (status == G_TLS_CONNECTION_BASE_OK)
+  if (status == G_TLS_OPERATION_SUCCESS)
     {
-      priv->successful_posthandshake_op = TRUE;
       g_tls_log_debug (tls, "successfully write %" G_GSSIZE_FORMAT " bytes to TLS connection", nwrote);
       return nwrote;
     }
@@ -2146,26 +1815,21 @@ g_tls_connection_base_write_message (GTlsConnectionBase  *tls,
                                      GError             **error)
 {
   GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  GTlsConnectionBaseStatus status;
+  GTlsOperationStatus status;
   gssize nwrote;
 
   g_tls_log_debug (tls, "starting to write messages to TLS connection");
 
-  do {
-    if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE,
-                   timeout, cancellable, error))
-      return -1;
+  if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE,
+                 timeout, cancellable, error))
+    return -1;
 
-    g_assert (G_TLS_CONNECTION_BASE_GET_CLASS (tls)->read_message_fn);
-    status = G_TLS_CONNECTION_BASE_GET_CLASS (tls)->
-      write_message_fn (tls, vectors, num_vectors, timeout, &nwrote, cancellable, error);
+  status = g_tls_operations_thread_base_write_message (priv->thread, vectors, num_vectors, timeout, &nwrote, 
cancellable, error);
 
-    yield_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE, status);
-  } while (status == G_TLS_CONNECTION_BASE_REHANDSHAKE);
+  yield_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE, status);
 
-  if (status == G_TLS_CONNECTION_BASE_OK)
+  if (status == G_TLS_OPERATION_SUCCESS)
     {
-      priv->successful_posthandshake_op = TRUE;
       g_tls_log_debug (tls, "successfully write %" G_GSSIZE_FORMAT " bytes to TLS connection", nwrote);
       return nwrote;
     }
@@ -2184,7 +1848,6 @@ g_tls_connection_base_send_messages (GDatagramBased  *datagram_based,
                                      GError         **error)
 {
   GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (datagram_based);
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
   guint i;
   GError *child_error = NULL;
 
@@ -2235,7 +1898,6 @@ g_tls_connection_base_send_messages (GDatagramBased  *datagram_based,
       return -1;
     }
 
-  priv->successful_posthandshake_op = TRUE;
   return i;
 }
 
@@ -2267,7 +1929,7 @@ g_tls_connection_base_close_internal (GIOStream      *stream,
   GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (stream);
   GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
   GTlsConnectionBaseOp op;
-  GTlsConnectionBaseStatus status;
+  GTlsOperationStatus status;
   gboolean success = TRUE;
   GError *close_error = NULL, *stream_error = NULL;
 
@@ -2294,13 +1956,12 @@ g_tls_connection_base_close_internal (GIOStream      *stream,
   if (priv->ever_handshaked && !priv->write_closed &&
       direction & G_TLS_DIRECTION_WRITE)
     {
-      status = G_TLS_CONNECTION_BASE_GET_CLASS (tls)->
-        close_fn (tls, timeout, cancellable, &close_error);
+      status = g_tls_operations_thread_base_close (priv->thread, cancellable, &close_error);
 
       priv->write_closed = TRUE;
     }
   else
-    status = G_TLS_CONNECTION_BASE_OK;
+    status = G_TLS_OPERATION_SUCCESS;
 
   if (!priv->read_closed && direction & G_TLS_DIRECTION_READ)
     priv->read_closed = TRUE;
@@ -2335,7 +1996,7 @@ g_tls_connection_base_close_internal (GIOStream      *stream,
   yield_op (tls, op, status);
 
   /* Propagate errors. */
-  if (status != G_TLS_CONNECTION_BASE_OK)
+  if (status != G_TLS_OPERATION_SUCCESS)
     {
       g_tls_log_debug (tls, "error closing TLS connection: %s", close_error->message);
       g_propagate_error (error, close_error);
@@ -2352,7 +2013,7 @@ g_tls_connection_base_close_internal (GIOStream      *stream,
       g_tls_log_debug (tls, "the TLS connection has been closed successfully");
     }
 
-  return success && status == G_TLS_CONNECTION_BASE_OK;
+  return success && status == G_TLS_OPERATION_SUCCESS;
 }
 
 static gboolean
@@ -2520,133 +2181,12 @@ g_tls_connection_base_get_base_iostream (GTlsConnectionBase *tls)
   return priv->base_io_stream;
 }
 
-GPollableInputStream *
-g_tls_connection_base_get_base_istream (GTlsConnectionBase *tls)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
-  g_assert (!g_tls_connection_base_is_dtls (tls));
-
-  return priv->base_istream;
-}
-
-GPollableOutputStream *
-g_tls_connection_base_get_base_ostream (GTlsConnectionBase *tls)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
-  g_assert (!g_tls_connection_base_is_dtls (tls));
-
-  return priv->base_ostream;
-}
-
-void
-g_tls_connection_base_handshake_thread_set_missing_requested_client_certificate (GTlsConnectionBase *tls)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
-  priv->missing_requested_client_certificate = TRUE;
-}
-
-GError **
-g_tls_connection_base_get_read_error (GTlsConnectionBase *tls)
+GTlsOperationsThreadBase *
+g_tls_connection_base_get_op_thread (GTlsConnectionBase *tls)
 {
   GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
 
-  return &priv->read_error;
-}
-
-GError **
-g_tls_connection_base_get_write_error (GTlsConnectionBase *tls)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
-  return &priv->write_error;
-}
-
-gint64
-g_tls_connection_base_get_read_timeout (GTlsConnectionBase *tls)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
-  return priv->read_timeout;
-}
-
-gint64
-g_tls_connection_base_get_write_timeout (GTlsConnectionBase *tls)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
-  return priv->write_timeout;
-}
-
-GCancellable *
-g_tls_connection_base_get_read_cancellable (GTlsConnectionBase *tls)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
-  return priv->read_cancellable;
-}
-
-GCancellable *
-g_tls_connection_base_get_write_cancellable (GTlsConnectionBase *tls)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
-  return priv->write_cancellable;
-}
-
-gboolean
-g_tls_connection_base_is_handshaking (GTlsConnectionBase *tls)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
-  return priv->handshaking;
-}
-
-gboolean
-g_tls_connection_base_ever_handshaked (GTlsConnectionBase *tls)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
-  return priv->ever_handshaked;
-}
-
-gboolean
-g_tls_connection_base_handshake_thread_request_certificate (GTlsConnectionBase *tls)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-  GTlsInteractionResult res = G_TLS_INTERACTION_UNHANDLED;
-  GTlsInteraction *interaction;
-  GTlsConnection *conn;
-
-  g_return_val_if_fail (G_IS_TLS_CONNECTION_BASE (tls), FALSE);
-
-  conn = G_TLS_CONNECTION (tls);
-
-  g_clear_error (&priv->interaction_error);
-
-  interaction = g_tls_connection_get_interaction (conn);
-  if (!interaction)
-    return FALSE;
-
-  res = g_tls_interaction_invoke_request_certificate (interaction, conn, 0,
-                                                      priv->read_cancellable,
-                                                      &priv->interaction_error);
-  return res != G_TLS_INTERACTION_FAILED;
-}
-
-void
-g_tls_connection_base_handshake_thread_buffer_application_data (GTlsConnectionBase *tls,
-                                                                guint8             *data,
-                                                                gsize               length)
-{
-  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
-  if (!priv->app_data_buf)
-    priv->app_data_buf = g_byte_array_new ();
-
-  g_byte_array_append (priv->app_data_buf, data, length);
+  return priv->thread;
 }
 
 static void
@@ -2670,9 +2210,6 @@ g_tls_connection_base_class_init (GTlsConnectionBaseClass *klass)
   iostream_class->close_async       = g_tls_connection_base_close_async;
   iostream_class->close_finish      = g_tls_connection_base_close_finish;
 
-  klass->push_io = g_tls_connection_base_real_push_io;
-  klass->pop_io = g_tls_connection_base_real_pop_io;
-
   /* For GTlsConnection and GDtlsConnection: */
   g_object_class_override_property (gobject_class, PROP_BASE_IO_STREAM, "base-io-stream");
   g_object_class_override_property (gobject_class, PROP_BASE_SOCKET, "base-socket");
@@ -2710,3 +2247,9 @@ g_tls_connection_base_datagram_based_iface_init (GDatagramBasedInterface *iface)
   iface->condition_check = g_tls_connection_base_condition_check;
   iface->condition_wait = g_tls_connection_base_condition_wait;
 }
+
+static void
+g_tls_connection_base_initable_iface_init (GInitableIface *iface)
+{
+  iface->init = g_tls_connection_base_initable_init;
+}
diff --git a/tls/base/gtlsconnection-base.h b/tls/base/gtlsconnection-base.h
index bacefab..2db056f 100644
--- a/tls/base/gtlsconnection-base.h
+++ b/tls/base/gtlsconnection-base.h
@@ -3,6 +3,7 @@
  * GIO - GLib Input, Output and Streaming Library
  *
  * Copyright 2009-2011 Red Hat, Inc
+ * Copyright 2019 Igalia S.L.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -32,111 +33,25 @@ G_BEGIN_DECLS
 
 G_DECLARE_DERIVABLE_TYPE (GTlsConnectionBase, g_tls_connection_base, G, TLS_CONNECTION_BASE, GTlsConnection)
 
-typedef enum {
-  G_TLS_CONNECTION_BASE_OK,
-  G_TLS_CONNECTION_BASE_WOULD_BLOCK,
-  G_TLS_CONNECTION_BASE_TIMED_OUT,
-  G_TLS_CONNECTION_BASE_REHANDSHAKE,
-  G_TLS_CONNECTION_BASE_TRY_AGAIN,
-  G_TLS_CONNECTION_BASE_ERROR,
-} GTlsConnectionBaseStatus;
-
 typedef enum {
   G_TLS_DIRECTION_NONE = 0,
   G_TLS_DIRECTION_READ = 1 << 0,
   G_TLS_DIRECTION_WRITE = 1 << 1,
 } GTlsDirection;
 
-typedef enum {
-  G_TLS_SAFE_RENEGOTIATION_SUPPORTED_BY_PEER,
-  G_TLS_SAFE_RENEGOTIATION_UNSUPPORTED
-} GTlsSafeRenegotiationStatus;
-
 #define G_TLS_DIRECTION_BOTH (G_TLS_DIRECTION_READ | G_TLS_DIRECTION_WRITE)
 
+typedef struct _GTlsOperationsThreadBase GTlsOperationsThreadBase;
+
 struct _GTlsConnectionBaseClass
 {
   GTlsConnectionClass parent_class;
 
-  void                        (*prepare_handshake)          (GTlsConnectionBase   *tls,
-                                                             gchar               **advertised_protocols);
-  GTlsSafeRenegotiationStatus (*handshake_thread_safe_renegotiation_status)
-                                                            (GTlsConnectionBase    *tls);
-  GTlsConnectionBaseStatus    (*handshake_thread_request_rehandshake)
-                                                            (GTlsConnectionBase   *tls,
-                                                             gint64                timeout,
-                                                             GCancellable         *cancellable,
-                                                             GError              **error);
-  GTlsConnectionBaseStatus    (*handshake_thread_handshake) (GTlsConnectionBase   *tls,
-                                                             gint64                timeout,
-                                                             GCancellable         *cancellable,
-                                                             GError              **error);
-  GTlsCertificate            *(*retrieve_peer_certificate)  (GTlsConnectionBase   *tls);
-  GTlsCertificateFlags        (*verify_peer_certificate)    (GTlsConnectionBase   *tls,
-                                                             GTlsCertificate      *certificate,
-                                                             GTlsCertificateFlags  flags);
-  void                        (*complete_handshake)         (GTlsConnectionBase   *tls,
-                                                             gchar               **negotiated_protocol,
-                                                             GError              **error);
-
-  gboolean                    (*is_session_resumed)         (GTlsConnectionBase   *tls);
-
-  void                        (*push_io)                    (GTlsConnectionBase   *tls,
-                                                             GIOCondition          direction,
-                                                             gint64                timeout,
-                                                             GCancellable         *cancellable);
-  GTlsConnectionBaseStatus    (*pop_io)                     (GTlsConnectionBase   *tls,
-                                                             GIOCondition          direction,
-                                                             gboolean              success,
-                                                             GError              **error);
-
-  GTlsConnectionBaseStatus    (*read_fn)                    (GTlsConnectionBase   *tls,
-                                                             void                 *buffer,
-                                                             gsize                 count,
-                                                             gint64                timeout,
-                                                             gssize               *nread,
-                                                             GCancellable         *cancellable,
-                                                             GError              **error);
-  GTlsConnectionBaseStatus    (*read_message_fn)            (GTlsConnectionBase   *tls,
-                                                             GInputVector         *vectors,
-                                                             guint                 num_vectors,
-                                                             gint64                timeout,
-                                                             gssize               *nread,
-                                                             GCancellable         *cancellable,
-                                                             GError              **error);
-
-  GTlsConnectionBaseStatus    (*write_fn)                   (GTlsConnectionBase   *tls,
-                                                             const void           *buffer,
-                                                             gsize                 count,
-                                                             gint64                timeout,
-                                                             gssize               *nwrote,
-                                                             GCancellable         *cancellable,
-                                                             GError              **error);
-  GTlsConnectionBaseStatus    (*write_message_fn)           (GTlsConnectionBase   *tls,
-                                                             GOutputVector        *vectors,
-                                                             guint                 num_vectors,
-                                                             gint64                timeout,
-                                                             gssize               *nwrote,
-                                                             GCancellable         *cancellable,
-                                                             GError              **error);
-
-  GTlsConnectionBaseStatus    (*close_fn)                   (GTlsConnectionBase   *tls,
-                                                             gint64                timeout,
-                                                             GCancellable         *cancellable,
-                                                             GError              **error);
-};
-
-gboolean                  g_tls_connection_base_handshake_thread_verify_certificate
-                                                                        (GTlsConnectionBase *tls);
+  GTlsOperationsThreadBase   *(*create_op_thread)           (GTlsConnectionBase   *tls);
 
-void                      g_tls_connection_base_push_io                 (GTlsConnectionBase *tls,
-                                                                         GIOCondition        direction,
-                                                                         gint64              timeout,
-                                                                         GCancellable       *cancellable);
-GTlsConnectionBaseStatus  g_tls_connection_base_pop_io                  (GTlsConnectionBase  *tls,
-                                                                         GIOCondition         direction,
-                                                                         gboolean             success,
-                                                                         GError             **error);
+  void                        (*set_accepted_cas)           (GTlsConnectionBase    *tls,
+                                                             GList                 *accepted_cas);
+};
 
 gssize                    g_tls_connection_base_read                    (GTlsConnectionBase  *tls,
                                                                          void                *buffer,
@@ -158,43 +73,21 @@ gboolean                  g_tls_connection_base_base_check              (GTlsCon
 GSource                  *g_tls_connection_base_create_source           (GTlsConnectionBase  *tls,
                                                                          GIOCondition         condition,
                                                                          GCancellable        *cancellable);
+gboolean                  g_tls_connection_base_close_internal          (GIOStream           *stream,
+                                                                         GTlsDirection        direction,
+                                                                         gint64               timeout,
+                                                                         GCancellable        *cancellable,
+                                                                         GError             **error);
 
-gboolean                  g_tls_connection_base_close_internal          (GIOStream      *stream,
-                                                                         GTlsDirection   direction,
-                                                                         gint64          timeout,
-                                                                         GCancellable   *cancellable,
-                                                                         GError        **error);
+/* FIXME: audit, which are still needed? in public header? */
 
 gboolean                  g_tls_connection_base_is_dtls                 (GTlsConnectionBase *tls);
 
 GDatagramBased           *g_tls_connection_base_get_base_socket         (GTlsConnectionBase *tls);
 
 GIOStream                *g_tls_connection_base_get_base_iostream       (GTlsConnectionBase *tls);
-GPollableInputStream     *g_tls_connection_base_get_base_istream        (GTlsConnectionBase *tls);
-GPollableOutputStream    *g_tls_connection_base_get_base_ostream        (GTlsConnectionBase *tls);
-
-void                      g_tls_connection_base_handshake_thread_set_missing_requested_client_certificate
-                                                                        (GTlsConnectionBase *tls);
-
-GError                  **g_tls_connection_base_get_read_error          (GTlsConnectionBase *tls);
-GError                  **g_tls_connection_base_get_write_error         (GTlsConnectionBase *tls);
-
-gint64                    g_tls_connection_base_get_read_timeout        (GTlsConnectionBase *tls);
-gint64                    g_tls_connection_base_get_write_timeout       (GTlsConnectionBase *tls);
-
-GCancellable             *g_tls_connection_base_get_read_cancellable    (GTlsConnectionBase *tls);
-GCancellable             *g_tls_connection_base_get_write_cancellable   (GTlsConnectionBase *tls);
-
-gboolean                  g_tls_connection_base_is_handshaking          (GTlsConnectionBase *tls);
-
-gboolean                  g_tls_connection_base_ever_handshaked         (GTlsConnectionBase *tls);
-
-gboolean                  g_tls_connection_base_handshake_thread_request_certificate
-                                                                        (GTlsConnectionBase  *tls);
 
-void                      g_tls_connection_base_handshake_thread_buffer_application_data
-                                                                        (GTlsConnectionBase *tls,
-                                                                         guint8             *data,
-                                                                         gsize               length);
+/* FIXME: needed? */
+GTlsOperationsThreadBase *g_tls_connection_base_get_op_thread           (GTlsConnectionBase *tls);
 
 G_END_DECLS
diff --git a/tls/base/gtlsoperationsthread-base.c b/tls/base/gtlsoperationsthread-base.c
new file mode 100644
index 0000000..81e4656
--- /dev/null
+++ b/tls/base/gtlsoperationsthread-base.c
@@ -0,0 +1,1726 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * In addition, when the library is used with OpenSSL, a special
+ * exception applies. Refer to the LICENSE_EXCEPTION file for details.
+ */
+
+#include "config.h"
+#include "gtlsoperationsthread-base.h"
+
+#include "tls-base-builtins.h"
+
+#include <glib/gi18n-lib.h>
+
+/* The purpose of this class is to ensure the underlying TLS library is only
+ * ever used on a single thread. There are multiple benefits of this:
+ *
+ * - OpenSSL objects like the SSL* are not threadsafe and must only be accessed
+ *   from a single thread.
+ *
+ * - With GnuTLS, this dramatically simplifies implementation of post-handshake
+ *   authentication and alerts, which are hard to handle when the
+ *   gnutls_session_t may be used on multiple threads at once. Moving
+ *   gnutls_session_t use to a single thread should also make it easier to
+ *   implement support for downloading missing certificates using the
+ *   Authority Information Access extension.
+ *
+ * - GTlsConnectionBase and its subclasses are very complicated, and it has
+ *   become difficult to ensure the correctness of the code considering that the
+ *   threadsafety semantics of its parent class, GIOStream, allow it to be used
+ *   from separate reader and writer threads simultaneously.
+ *
+ * While the TLS thread class is intended to simplify our code, it has one major
+ * disadvantage: the TLS thread *must never block* during read or write
+ * operations, because GIOStream users are allowed to do a sync read and a sync
+ * write simultaneously in separate threads. Consider a hypothetical scenario:
+ *
+ * (1) Application starts a read on thread A
+ * (2) Application starts a write on thread B
+ * (3) Application's peer waits for the write to complete before sending data.
+ *
+ * In this scenario, the read on thread A is stalled until the write on thread B
+ * is completed. The application is allowed to do this and expect it to work,
+ * because GIOStream says it will work. If our TLS thread were to block on the
+ * read, then the write would never start, and the read could never complete.
+ *
+ * This means that underlying TLS operations must use entirely nonblocking I/O.
+ * We specify a timeout of 0 for every operation to ensure it returns
+ * immediately with an error if I/O cannot be performed immediately. If so, we
+ * create a GSource that will trigger later on, when possibly ready to perform
+ * I/O. In this way, we can simultaneously handle separate synchronous read and
+ * write operations on one thread without either one blocking the other.
+ */
+typedef struct {
+  /* Objects explicitly designed for unlocked multithreaded use. */
+  GThread *op_thread;
+  GMainContext *op_thread_context;
+  GAsyncQueue *queue;
+
+  /* Never mutated after construction. */
+  GTlsOperationsThreadType thread_type;
+
+  /* GIOStream is only partially threadsafe, and GDatagramBased is not
+   * threadsafe at all. Although they are shared across threads, we try to
+   * ensure that we only use them on one thread at any given time.
+   */
+  GIOStream *base_iostream;
+  GDatagramBased *base_socket;
+
+  /* This mutex guards everything below. It's a bit of a failure of design.
+   * Ideally we wouldn't need to share this data between threads and would
+   * instead pass data to the op thread and return data from the op thread
+   * using the op struct. But this is not always easy.
+   *
+   * FIXME: what of this can move into the op struct? The booleans are needed
+   * for more than handshakes, so they'd need to be part of every op.
+   */
+  GMutex mutex;
+
+  GTlsInteraction *interaction;
+  GError *interaction_error;
+  gboolean missing_requested_client_certificate;
+  gboolean performed_successful_posthandshake_op;
+  gboolean require_close_notify;
+} GTlsOperationsThreadBasePrivate;
+
+typedef enum {
+  G_TLS_THREAD_OP_COPY_CLIENT_SESSION_STATE,
+  G_TLS_THREAD_OP_SET_SERVER_IDENTITY,
+  G_TLS_THREAD_OP_HANDSHAKE,
+  G_TLS_THREAD_OP_READ,
+  G_TLS_THREAD_OP_READ_MESSAGE,
+  G_TLS_THREAD_OP_WRITE,
+  G_TLS_THREAD_OP_WRITE_MESSAGE,
+  G_TLS_THREAD_OP_CLOSE,
+  G_TLS_THREAD_OP_SHUTDOWN_THREAD
+} GTlsThreadOperationType;
+
+struct _HandshakeContext
+{
+  GMainContext *caller_context;
+  GTlsVerifyCertificateFunc verify_callback;
+  gboolean certificate_verified; /* FIXME: remove and track is_session_resumed instead */
+  gpointer user_data;
+};
+
+typedef struct {
+  HandshakeContext *context;
+  GTlsCertificate *own_certificate;
+  gchar **advertised_protocols;
+  GTlsAuthenticationMode auth_mode;
+  gboolean require_close_notify;
+  gchar *negotiated_protocol;
+  GList *accepted_cas;
+  GTlsCertificate *peer_certificate;
+} HandshakeData;
+
+typedef struct {
+  GTlsThreadOperationType type;
+  GIOCondition io_condition;
+
+  GTlsOperationsThreadBase *thread;
+
+  /* Op input */
+  union {
+    GTlsOperationsThreadBase *source; /* for copy client session state */
+    gchar *server_identity;           /* for set server identity */
+    HandshakeData *handshake_data;    /* for handshake */
+    void *data;                       /* for read/write */
+    GInputVector *input_vectors;      /* for read message */
+    GOutputVector *output_vectors;    /* for write message */
+  };
+  union {
+    gsize size;        /* for read/write */
+    guint num_vectors; /* for read/write message */
+  };
+  gint64 timeout;
+  gint64 start_time;
+
+  GCancellable *cancellable;
+
+  /* Op output */
+  GTlsOperationStatus result;
+  gssize count; /* Bytes read or written */
+  GError *error;
+
+  GMutex finished_mutex;
+  GCond finished_condition;
+  gboolean finished;
+} GTlsThreadOperation;
+
+static gboolean process_op (GAsyncQueue         *queue,
+                            GTlsThreadOperation *delayed_op,
+                            GMainLoop           *main_loop);
+
+enum
+{
+  REQUEST_CERTIFICATE,
+
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+enum
+{
+  PROP_0,
+  PROP_BASE_IO_STREAM,
+  PROP_BASE_SOCKET,
+  PROP_THREAD_TYPE,
+  LAST_PROP
+};
+
+static GParamSpec *obj_properties[LAST_PROP];
+
+static void g_tls_operations_thread_base_initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsOperationsThreadBase, g_tls_operations_thread_base, G_TYPE_OBJECT,
+                                  G_ADD_PRIVATE (GTlsOperationsThreadBase);
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                         g_tls_operations_thread_base_initable_iface_init);)
+
+static inline gboolean
+is_dtls (GTlsOperationsThreadBase *self)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+  return !!priv->base_socket;
+}
+
+static inline gboolean
+is_client (GTlsOperationsThreadBase *self)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+  return priv->thread_type == G_TLS_OPERATIONS_THREAD_CLIENT;
+}
+
+static inline gboolean
+is_server (GTlsOperationsThreadBase *self)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+  return priv->thread_type == G_TLS_OPERATIONS_THREAD_SERVER;
+}
+
+void
+g_tls_operations_thread_base_set_interaction (GTlsOperationsThreadBase *self,
+                                              GTlsInteraction          *interaction)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+  g_mutex_lock (&priv->mutex);
+  g_clear_object (&priv->interaction);
+  priv->interaction = interaction? g_object_ref (interaction) : NULL;
+  g_mutex_unlock (&priv->mutex);
+}
+
+GTlsInteraction *
+g_tls_operations_thread_base_ref_interaction (GTlsOperationsThreadBase *self)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  GTlsInteraction *ref = NULL;
+
+  g_mutex_lock (&priv->mutex);
+  if (priv->interaction)
+    ref = g_object_ref (priv->interaction);
+  g_mutex_unlock (&priv->mutex);
+
+  return ref;
+}
+
+static GError *
+take_interaction_error (GTlsOperationsThreadBase *self)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  GError *error;
+
+  g_mutex_lock (&priv->mutex);
+  error = g_steal_pointer (&priv->interaction_error);
+  g_mutex_unlock (&priv->mutex);
+
+  return error;
+}
+
+gboolean
+g_tls_operations_thread_base_request_certificate (GTlsOperationsThreadBase  *self,
+                                                  GCancellable              *cancellable,
+                                                  GTlsCertificate          **own_certificate)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  GTlsInteractionResult result = G_TLS_INTERACTION_UNHANDLED;
+  GTlsCertificate *cert = NULL;
+
+  g_mutex_lock (&priv->mutex);
+
+  g_clear_error (&priv->interaction_error);
+  g_signal_emit (self, signals[REQUEST_CERTIFICATE], 0,
+                 priv->interaction,
+                 &cert,
+                 cancellable,
+                 &priv->interaction_error,
+                 &result);
+
+  if (cert)
+    *own_certificate = G_TLS_OPERATIONS_THREAD_BASE_GET_CLASS (self)->copy_certificate (self, cert);
+  else
+    *own_certificate = NULL;
+
+  g_mutex_unlock (&priv->mutex);
+
+  return result != G_TLS_INTERACTION_FAILED;
+}
+
+void
+g_tls_operations_thread_base_set_missing_requested_client_certificate (GTlsOperationsThreadBase *self)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+  /* For client connections only */
+
+  g_mutex_lock (&priv->mutex);
+  priv->missing_requested_client_certificate = TRUE;
+  g_mutex_unlock (&priv->mutex);
+}
+
+static gboolean
+get_is_missing_requested_client_certificate (GTlsOperationsThreadBase *self)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  gboolean ret;
+
+  g_mutex_lock (&priv->mutex);
+  ret = priv->missing_requested_client_certificate;
+  g_mutex_unlock (&priv->mutex);
+
+  return ret;
+}
+
+void
+g_tls_operations_thread_base_set_close_notify_required (GTlsOperationsThreadBase *self,
+                                                        gboolean                  required)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+  g_mutex_lock (&priv->mutex);
+  priv->require_close_notify = required;
+  g_mutex_unlock (&priv->mutex);
+}
+
+gboolean
+g_tls_operations_thread_base_get_close_notify_required (GTlsOperationsThreadBase *self)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  gboolean ret;
+
+  g_mutex_lock (&priv->mutex);
+  ret = priv->require_close_notify;
+  g_mutex_unlock (&priv->mutex);
+
+  return ret;
+}
+
+static void
+set_performed_successful_posthandshake_op (GTlsOperationsThreadBase *self)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+  g_mutex_lock (&priv->mutex);
+  priv->performed_successful_posthandshake_op = TRUE;
+  g_mutex_unlock (&priv->mutex);
+}
+
+static gboolean
+has_performed_successful_posthandshake_op (GTlsOperationsThreadBase *self)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  gboolean ret;
+
+  g_mutex_lock (&priv->mutex);
+  ret = priv->performed_successful_posthandshake_op;
+  g_mutex_unlock (&priv->mutex);
+
+  return ret;
+}
+
+void
+g_tls_operations_thread_base_push_io (GTlsOperationsThreadBase *self,
+                                      GIOCondition              direction,
+                                      GCancellable             *cancellable)
+{
+  /* FIXME: this is weird, can't we get rid of it on OpenSSL side? */
+  if (G_TLS_OPERATIONS_THREAD_BASE_GET_CLASS (self)->push_io)
+    {
+      G_TLS_OPERATIONS_THREAD_BASE_GET_CLASS (self)->push_io (self, direction, cancellable);
+    }
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_base_real_pop_io (GTlsOperationsThreadBase  *self,
+                                          GIOCondition               direction,
+                                          gboolean                   success,
+                                          GError                    *op_error /* owned */,
+                                          GError                   **error)
+{
+  /* This function MAY or MAY NOT set error when it fails! */
+
+  if (success)
+    {
+      g_assert (!op_error);
+      return G_TLS_OPERATION_SUCCESS;
+    }
+
+  if (g_error_matches (op_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+    {
+      g_propagate_error (error, op_error);
+      return G_TLS_OPERATION_WOULD_BLOCK;
+    }
+
+  if (g_error_matches (op_error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
+    {
+      g_propagate_error (error, op_error);
+      return G_TLS_OPERATION_TIMED_OUT;
+    }
+
+  if (get_is_missing_requested_client_certificate (self) &&
+      !has_performed_successful_posthandshake_op (self))
+    {
+      GError *interaction_error;
+
+      interaction_error = take_interaction_error (self);
+
+      /* We are a client connection.
+       *
+       * Probably the server requires a client certificate, but we failed to
+       * provide one. With TLS 1.3 the server is no longer able to tell us
+       * this, so we just have to guess. If there is an error from the TLS
+       * interaction (request for user certificate), we provide that. Otherwise,
+       * guess that G_TLS_ERROR_CERTIFICATE_REQUIRED is probably appropriate.
+       * This could be wrong, but only applies to the small minority of
+       * connections where a client cert is requested but not provided, and then
+       * then only if the client has never successfully read or written.
+       */
+      if (interaction_error)
+        {
+          g_propagate_error (error, interaction_error);
+        }
+      else
+        {
+          g_clear_error (error);
+          g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
+                               _("Server required TLS certificate"));
+        }
+
+      if (op_error)
+        g_error_free (op_error);
+    }
+  else if (op_error)
+    {
+      g_propagate_error (error, op_error);
+    }
+
+  return G_TLS_OPERATION_ERROR;
+}
+
+GTlsOperationStatus
+g_tls_operations_thread_base_pop_io (GTlsOperationsThreadBase  *self,
+                                     GIOCondition               direction,
+                                     gboolean                   success,
+                                     GError                    *op_error,
+                                     GError                   **error)
+{
+  return G_TLS_OPERATIONS_THREAD_BASE_GET_CLASS (self)->pop_io (self, direction,
+                                                                success, op_error, error);
+}
+
+static HandshakeContext *
+handshake_context_new (GTlsVerifyCertificateFunc  verify_callback,
+                       gpointer                   user_data)
+{
+  HandshakeContext *context;
+
+  context = g_new0 (HandshakeContext, 1);
+  context->caller_context = g_main_context_ref_thread_default ();
+  context->verify_callback = verify_callback;
+  context->user_data = user_data;
+
+  return context;
+}
+
+static void
+handshake_context_free (HandshakeContext *context)
+{
+  g_main_context_unref (context->caller_context);
+
+  g_free (context);
+}
+
+static HandshakeData *
+handshake_data_new (HandshakeContext        *context,
+                    GTlsCertificate         *own_certificate,
+                    const gchar            **advertised_protocols,
+                    GTlsAuthenticationMode   mode)
+{
+  HandshakeData *data;
+
+  data = g_new0 (HandshakeData, 1);
+  data->context = context;
+  data->own_certificate = own_certificate ? g_object_ref (own_certificate) : NULL;
+  data->advertised_protocols = g_strdupv ((gchar **)advertised_protocols);
+  data->auth_mode = mode;
+
+  return data;
+}
+
+static void
+handshake_data_free (HandshakeData *data)
+{
+  g_strfreev (data->advertised_protocols);
+
+  g_clear_object (&data->own_certificate);
+  g_clear_object (&data->peer_certificate);
+
+  g_assert (!data->accepted_cas);
+  g_assert (!data->negotiated_protocol);
+
+  g_free (data);
+}
+
+static GTlsThreadOperation *
+g_tls_thread_copy_client_session_state_operation_new (GTlsOperationsThreadBase *thread,
+                                                      GTlsOperationsThreadBase *source)
+{
+  GTlsThreadOperation *op;
+
+  op = g_new0 (GTlsThreadOperation, 1);
+  op->type = G_TLS_THREAD_OP_COPY_CLIENT_SESSION_STATE;
+  op->thread = thread;
+  op->source = source;
+
+  g_mutex_init (&op->finished_mutex);
+  g_cond_init (&op->finished_condition);
+
+  return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_set_server_identity_operation_new (GTlsOperationsThreadBase *thread,
+                                                const gchar              *server_identity)
+{
+  GTlsThreadOperation *op;
+
+  op = g_new0 (GTlsThreadOperation, 1);
+  op->type = G_TLS_THREAD_OP_SET_SERVER_IDENTITY;
+  op->thread = thread;
+  op->server_identity = g_strdup (server_identity);
+
+  g_mutex_init (&op->finished_mutex);
+  g_cond_init (&op->finished_condition);
+
+  return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_handshake_operation_new (GTlsOperationsThreadBase  *thread,
+                                      HandshakeContext          *context,
+                                      GTlsCertificate           *own_certificate,
+                                      const gchar              **advertised_protocols,
+                                      GTlsAuthenticationMode     auth_mode,
+                                      gint64                     timeout,
+                                      GCancellable              *cancellable)
+{
+  GTlsThreadOperation *op;
+
+  op = g_new0 (GTlsThreadOperation, 1);
+  op->type = G_TLS_THREAD_OP_HANDSHAKE;
+  op->io_condition = G_IO_IN | G_IO_OUT;
+  op->thread = thread;
+  op->timeout = timeout;
+  op->cancellable = cancellable;
+
+  op->handshake_data = handshake_data_new (context,
+                                           own_certificate,
+                                           advertised_protocols,
+                                           auth_mode);
+
+  g_mutex_init (&op->finished_mutex);
+  g_cond_init (&op->finished_condition);
+
+  return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_read_operation_new (GTlsOperationsThreadBase *thread,
+                                 void                     *data,
+                                 gsize                     size,
+                                 gint64                    timeout,
+                                 GCancellable             *cancellable)
+{
+  GTlsThreadOperation *op;
+
+  op = g_new0 (GTlsThreadOperation, 1);
+  op->type = G_TLS_THREAD_OP_READ;
+  op->io_condition = G_IO_IN;
+  op->thread = thread;
+  op->data = data;
+  op->size = size;
+  op->timeout = timeout;
+  op->cancellable = cancellable;
+
+  g_mutex_init (&op->finished_mutex);
+  g_cond_init (&op->finished_condition);
+
+  return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_read_message_operation_new (GTlsOperationsThreadBase *thread,
+                                         GInputVector             *vectors,
+                                         guint                     num_vectors,
+                                         gint64                    timeout,
+                                         GCancellable             *cancellable)
+{
+  GTlsThreadOperation *op;
+
+  op = g_new0 (GTlsThreadOperation, 1);
+  op->type = G_TLS_THREAD_OP_READ_MESSAGE;
+  op->io_condition = G_IO_IN;
+  op->thread = thread;
+  op->input_vectors = vectors;
+  op->num_vectors = num_vectors;
+  op->timeout = timeout;
+  op->cancellable = cancellable;
+
+  g_mutex_init (&op->finished_mutex);
+  g_cond_init (&op->finished_condition);
+
+  return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_write_operation_new (GTlsOperationsThreadBase *thread,
+                                  const void               *data,
+                                  gsize                     size,
+                                  gint64                    timeout,
+                                  GCancellable             *cancellable)
+{
+  GTlsThreadOperation *op;
+
+  op = g_new0 (GTlsThreadOperation, 1);
+  op->type = G_TLS_THREAD_OP_WRITE;
+  op->io_condition = G_IO_OUT;
+  op->thread = thread;
+  op->data = (void *)data;
+  op->size = size;
+  op->timeout = timeout;
+  op->cancellable = cancellable;
+
+  g_mutex_init (&op->finished_mutex);
+  g_cond_init (&op->finished_condition);
+
+  return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_write_message_operation_new (GTlsOperationsThreadBase *thread,
+                                          GOutputVector            *vectors,
+                                          guint                     num_vectors,
+                                          gint64                    timeout,
+                                          GCancellable             *cancellable)
+{
+  GTlsThreadOperation *op;
+
+  op = g_new0 (GTlsThreadOperation, 1);
+  op->type = G_TLS_THREAD_OP_WRITE_MESSAGE;
+  op->io_condition = G_IO_OUT;
+  op->thread = thread;
+  op->output_vectors = vectors;
+  op->num_vectors = num_vectors;
+  op->timeout = timeout;
+  op->cancellable = cancellable;
+
+  g_mutex_init (&op->finished_mutex);
+  g_cond_init (&op->finished_condition);
+
+  return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_close_operation_new (GTlsOperationsThreadBase *thread,
+                                  GCancellable             *cancellable)
+{
+  GTlsThreadOperation *op;
+
+  op = g_new0 (GTlsThreadOperation, 1);
+  op->type = G_TLS_THREAD_OP_CLOSE;
+  op->io_condition = G_IO_IN | G_IO_OUT;
+  op->thread = thread;
+  op->timeout = -1;
+  op->cancellable = cancellable;
+
+  g_mutex_init (&op->finished_mutex);
+  g_cond_init (&op->finished_condition);
+
+  return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_shutdown_operation_new (void)
+{
+  GTlsThreadOperation *op;
+
+  op = g_new0 (GTlsThreadOperation, 1);
+  op->type = G_TLS_THREAD_OP_SHUTDOWN_THREAD;
+
+  return op;
+}
+
+static void
+g_tls_thread_operation_free (GTlsThreadOperation *op)
+{
+  if (op->type == G_TLS_THREAD_OP_SET_SERVER_IDENTITY)
+    g_free (op->server_identity);
+
+  if (op->type == G_TLS_THREAD_OP_HANDSHAKE)
+    handshake_data_free (op->handshake_data);
+
+  if (op->type != G_TLS_THREAD_OP_SHUTDOWN_THREAD)
+    {
+      g_mutex_clear (&op->finished_mutex);
+      g_cond_clear (&op->finished_condition);
+    }
+
+  g_free (op);
+}
+
+static void
+wait_for_op_completion (GTlsThreadOperation *op)
+{
+  g_mutex_lock (&op->finished_mutex);
+  while (!op->finished)
+    g_cond_wait (&op->finished_condition, &op->finished_mutex);
+  g_mutex_unlock (&op->finished_mutex);
+}
+
+static GTlsOperationStatus
+execute_op (GTlsOperationsThreadBase *self,
+            GTlsThreadOperation      *op,
+            gssize                   *count,
+            GError                  **error)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  GTlsOperationStatus result;
+
+  g_async_queue_push (priv->queue, op);
+  g_main_context_wakeup (priv->op_thread_context);
+
+  wait_for_op_completion (op);
+
+  if (count)
+    *count = op->count;
+
+  result = op->result;
+
+  if (op->error)
+    {
+      g_propagate_error (error, op->error);
+      op->error = NULL;
+    }
+
+  return result;
+}
+
+void
+g_tls_operations_thread_base_copy_client_session_state (GTlsOperationsThreadBase *self,
+                                                        GTlsOperationsThreadBase *source)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  GTlsThreadOperation *op;
+
+  g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+  op = g_tls_thread_copy_client_session_state_operation_new (self, source);
+  execute_op (self, op, NULL, NULL);
+  g_tls_thread_operation_free (op);
+}
+
+void
+g_tls_operations_thread_base_set_server_identity (GTlsOperationsThreadBase *self,
+                                                  const gchar              *server_identity)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  GTlsThreadOperation *op;
+
+  g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+  op = g_tls_thread_set_server_identity_operation_new (self, server_identity);
+  execute_op (self, op, NULL, NULL);
+  g_tls_thread_operation_free (op);
+}
+
+typedef struct {
+  GTlsOperationsThreadBase *thread;
+  GTlsCertificate *peer_certificate;
+  HandshakeContext *context;
+
+  gboolean result;
+  gboolean complete;
+  GMutex mutex;
+  GCond condition;
+} VerifyCertificateData;
+
+static VerifyCertificateData *
+verify_certificate_data_new (GTlsOperationsThreadBase *thread,
+                             GTlsCertificate          *peer_certificate,
+                             HandshakeContext         *context)
+{
+  VerifyCertificateData *data;
+
+  data = g_new0 (VerifyCertificateData, 1);
+  data->thread = g_object_ref (thread);
+  data->peer_certificate = g_object_ref (peer_certificate);
+  data->context = context;
+
+  g_mutex_init (&data->mutex);
+  g_cond_init (&data->condition);
+
+  return data;
+}
+
+static void
+verify_certificate_data_free (VerifyCertificateData *data)
+{
+  g_object_unref (data->thread);
+  g_object_unref (data->peer_certificate);
+
+  g_mutex_clear (&data->mutex);
+  g_cond_clear (&data->condition);
+
+  g_free (data);
+}
+
+static gboolean
+execute_verify_certificate_callback_cb (VerifyCertificateData *data)
+{
+  data->result = data->context->verify_callback (data->thread,
+                                                 data->peer_certificate,
+                                                 data->context->user_data);
+
+  g_mutex_lock (&data->mutex);
+  data->complete = TRUE;
+  g_cond_signal (&data->condition);
+  g_mutex_unlock (&data->mutex);
+
+  return G_SOURCE_REMOVE;
+}
+
+gboolean
+g_tls_operations_thread_base_verify_certificate (GTlsOperationsThreadBase *self,
+                                                 GTlsCertificate          *peer_certificate,
+                                                 HandshakeContext         *context)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  VerifyCertificateData *data;
+  gboolean accepted;
+
+  g_assert (g_main_context_is_owner (priv->op_thread_context));
+  g_assert (G_IS_TLS_CERTIFICATE (peer_certificate));
+  g_assert (context);
+
+  data = verify_certificate_data_new (self, peer_certificate, context);
+
+  /* Invoke the caller's callback on the calling thread, not the op thread. */
+  g_main_context_invoke (context->caller_context,
+                         (GSourceFunc)execute_verify_certificate_callback_cb,
+                         data);
+
+  /* Block the op thread until the calling thread's callback finishes. */
+  g_mutex_lock (&data->mutex);
+  while (!data->complete)
+    g_cond_wait (&data->condition, &data->mutex);
+  g_mutex_unlock (&data->mutex);
+
+  context->certificate_verified = TRUE; /* FIXME: not good, not accurate */
+  accepted = data->result;
+
+  verify_certificate_data_free (data);
+
+  return accepted;
+}
+
+GTlsOperationStatus
+g_tls_operations_thread_base_handshake (GTlsOperationsThreadBase   *self,
+                                        GTlsCertificate            *own_certificate,
+                                        const gchar               **advertised_protocols,
+                                        GTlsAuthenticationMode      auth_mode,
+                                        gint64                      timeout,
+                                        GTlsVerifyCertificateFunc   verify_callback,
+                                        GTlsSessionResumedFunc      resumed_callback,
+                                        gchar                     **negotiated_protocol,
+                                        GList                     **accepted_cas,
+                                        GCancellable               *cancellable,
+                                        gpointer                    user_data,
+                                        GError                    **error)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  GTlsOperationStatus status;
+  GTlsThreadOperation *op;
+  GTlsCertificate *copied_cert;
+  HandshakeContext *context;
+
+  g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+  g_mutex_lock (&priv->mutex);
+  priv->missing_requested_client_certificate = FALSE;
+  g_mutex_unlock (&priv->mutex);
+
+  context = handshake_context_new (verify_callback,
+                                   user_data);
+
+  copied_cert = G_TLS_OPERATIONS_THREAD_BASE_GET_CLASS (self)->copy_certificate (self,
+                                                                                 own_certificate);
+
+  op = g_tls_thread_handshake_operation_new (self,
+                                             context,
+                                             copied_cert,
+                                             advertised_protocols,
+                                             auth_mode,
+                                             timeout,
+                                             cancellable);
+  status = execute_op (self, op, NULL, error);
+
+  /* FIXME: is this right? Probably we should really check for session resumption? is_session_resumed? */
+  if (!context->certificate_verified)
+    resumed_callback (self, op->handshake_data->peer_certificate, user_data);
+
+  *negotiated_protocol = g_steal_pointer (&op->handshake_data->negotiated_protocol);
+  *accepted_cas = g_steal_pointer (&op->handshake_data->accepted_cas);
+
+  handshake_context_free (context);
+  g_tls_thread_operation_free (op);
+  g_clear_object (&copied_cert);
+
+  return status;
+}
+
+GTlsOperationStatus
+g_tls_operations_thread_base_read (GTlsOperationsThreadBase  *self,
+                                   void                      *buffer,
+                                   gsize                      size,
+                                   gint64                     timeout,
+                                   gssize                    *nread,
+                                   GCancellable              *cancellable,
+                                   GError                   **error)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  GTlsOperationStatus status;
+  GTlsThreadOperation *op;
+
+  g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+  op = g_tls_thread_read_operation_new (self,
+                                        buffer, size,
+                                        timeout,
+                                        cancellable);
+  status = execute_op (self, op, nread, error);
+  g_tls_thread_operation_free (op);
+
+  return status;
+}
+
+GTlsOperationStatus
+g_tls_operations_thread_base_read_message (GTlsOperationsThreadBase  *self,
+                                           GInputVector              *vectors,
+                                           guint                      num_vectors,
+                                           gint64                     timeout,
+                                           gssize                    *nread,
+                                           GCancellable              *cancellable,
+                                           GError                   **error)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  GTlsOperationStatus status;
+  GTlsThreadOperation *op;
+
+  g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+  op = g_tls_thread_read_message_operation_new (self,
+                                                vectors, num_vectors,
+                                                timeout,
+                                                cancellable);
+  status = execute_op (self, op, nread, error);
+  g_tls_thread_operation_free (op);
+
+  return status;
+}
+
+GTlsOperationStatus
+g_tls_operations_thread_base_write (GTlsOperationsThreadBase  *self,
+                                    const void                *buffer,
+                                    gsize                      size,
+                                    gint64                     timeout,
+                                    gssize                    *nwrote,
+                                    GCancellable              *cancellable,
+                                    GError                   **error)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  GTlsOperationStatus status;
+  GTlsThreadOperation *op;
+
+  g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+  op = g_tls_thread_write_operation_new (self,
+                                         buffer, size,
+                                         timeout,
+                                         cancellable);
+  status = execute_op (self, op, nwrote, error);
+  g_tls_thread_operation_free (op);
+
+  return status;
+}
+
+GTlsOperationStatus
+g_tls_operations_thread_base_write_message (GTlsOperationsThreadBase  *self,
+                                            GOutputVector             *vectors,
+                                            guint                      num_vectors,
+                                            gint64                     timeout,
+                                            gssize                    *nwrote,
+                                            GCancellable              *cancellable,
+                                            GError                   **error)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  GTlsOperationStatus status;
+  GTlsThreadOperation *op;
+
+  g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+  op = g_tls_thread_write_message_operation_new (self,
+                                                 vectors, num_vectors,
+                                                 timeout,
+                                                 cancellable);
+  status = execute_op (self, op, nwrote, error);
+  g_tls_thread_operation_free (op);
+
+  return status;
+}
+
+GTlsOperationStatus
+g_tls_operations_thread_base_close (GTlsOperationsThreadBase  *self,
+                                    GCancellable              *cancellable,
+                                    GError                   **error)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  GTlsOperationStatus status;
+  GTlsThreadOperation *op;
+
+  g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+  op = g_tls_thread_close_operation_new (self, cancellable);
+  status = execute_op (self, op, NULL, error);
+  g_tls_thread_operation_free (op);
+
+  return status;
+}
+
+GIOStream *
+g_tls_operations_thread_base_get_base_iostream (GTlsOperationsThreadBase *self)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+  return priv->base_iostream;
+}
+
+GDatagramBased *
+g_tls_operations_thread_base_get_base_socket (GTlsOperationsThreadBase *self)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+  return priv->base_socket;
+}
+
+gboolean
+g_tls_operations_thread_base_check (GTlsOperationsThreadBase *self,
+                                    GIOCondition              condition)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+  if (is_dtls (self))
+    return g_datagram_based_condition_check (priv->base_socket, condition);
+
+  if (condition & G_IO_IN)
+    {
+      GInputStream *istream = g_io_stream_get_input_stream (priv->base_iostream);
+      return g_pollable_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (istream));
+    }
+
+  if (condition & G_IO_OUT)
+    {
+      GOutputStream *ostream = g_io_stream_get_output_stream (priv->base_iostream);
+      return g_pollable_output_stream_is_writable (G_POLLABLE_OUTPUT_STREAM (ostream));
+    }
+
+  g_assert_not_reached ();
+  return FALSE;
+}
+
+static GSource *
+create_base_source (GTlsOperationsThreadBase *self,
+                    GIOCondition              condition,
+                    GCancellable             *cancellable)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+  if (is_dtls (self))
+    return g_datagram_based_create_source (priv->base_socket, condition, cancellable);
+
+  if (condition & G_IO_IN)
+    {
+      GInputStream *istream = g_io_stream_get_input_stream (priv->base_iostream);
+      return g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (istream), cancellable);
+    }
+
+  if (condition & G_IO_OUT)
+    {
+      GOutputStream *ostream = g_io_stream_get_output_stream (priv->base_iostream);
+      return g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (ostream), cancellable);
+    }
+
+  g_assert_not_reached ();
+}
+
+typedef struct {
+  GSource source;
+
+  GAsyncQueue *queue;
+} GTlsOpQueueSource;
+
+typedef gboolean (*GTlsOpQueueSourceFunc) (GAsyncQueue         *queue,
+                                           GTlsThreadOperation *op,
+                                           GMainLoop           *main_loop);
+
+static gboolean
+queue_has_pending_op (GAsyncQueue *queue)
+{
+  GTlsThreadOperation *op;
+  gboolean ready = FALSE;
+
+  g_async_queue_lock (queue);
+
+  op = g_async_queue_try_pop_unlocked (queue);
+  if (op)
+    {
+      g_async_queue_push_front_unlocked (queue, op);
+      ready = TRUE;
+    }
+
+  g_async_queue_unlock (queue);
+
+  return ready;
+}
+
+static gboolean
+tls_op_queue_source_prepare (GSource *source,
+                             gint    *timeout)
+{
+  GTlsOpQueueSource *op_source = (GTlsOpQueueSource *)source;
+  gboolean ready;
+
+  ready = queue_has_pending_op (op_source->queue);
+
+  /* If we are ready to dispatch, timeout should be 0 to ensure poll() returns
+   * immediately. Otherwise, we are in no hurry and can wait "forever." If
+   * a new op is pushed onto the queue, the code performing the push is
+   * responsible for calling g_main_context_wakeup() to end the wait.
+   */
+  *timeout = ready ? 0 : -1;
+
+  return ready;
+}
+
+static gboolean
+tls_op_queue_source_check (GSource *source)
+{
+  GTlsOpQueueSource *op_source = (GTlsOpQueueSource *)source;
+
+  return queue_has_pending_op (op_source->queue);
+}
+
+static gboolean
+tls_op_queue_source_dispatch (GSource     *source,
+                              GSourceFunc  callback,
+                              gpointer     user_data)
+{
+  GTlsOpQueueSource *op_source = (GTlsOpQueueSource *)source;
+
+  return ((GTlsOpQueueSourceFunc)callback) (op_source->queue,
+                                            NULL, /* no delayed source */
+                                            user_data);
+}
+
+static void
+tls_op_queue_source_finalize (GSource *source)
+{
+  GTlsOpQueueSource *op_source = (GTlsOpQueueSource *)source;
+
+  g_async_queue_unref (op_source->queue);
+}
+
+static gboolean
+tls_op_queue_source_closure_callback (GAsyncQueue *queue,
+                                      GMainLoop   *main_loop,
+                                      gpointer     data)
+{
+  GClosure *closure = data;
+
+  GValue param[3] = { G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT };
+  GValue result_value = G_VALUE_INIT;
+  gboolean result;
+
+  g_value_init (&result_value, G_TYPE_BOOLEAN);
+
+  g_value_init (&param[0], G_TYPE_POINTER);
+  g_value_set_pointer (&param[0], queue);
+  g_value_init (&param[1], G_TYPE_POINTER);
+  g_value_set_pointer (&param[1], NULL);
+  g_value_init (&param[2], G_TYPE_MAIN_LOOP);
+  g_value_set_pointer (&param[2], main_loop);
+
+  g_closure_invoke (closure, &result_value, 3, param, NULL);
+
+  result = g_value_get_boolean (&result_value);
+  g_value_unset (&result_value);
+  g_value_unset (&param[0]);
+  g_value_unset (&param[1]);
+  g_value_unset (&param[2]);
+
+  return result;
+}
+
+static GSourceFuncs tls_op_queue_source_funcs =
+{
+  tls_op_queue_source_prepare,
+  tls_op_queue_source_check,
+  tls_op_queue_source_dispatch,
+  tls_op_queue_source_finalize,
+  (GSourceFunc)tls_op_queue_source_closure_callback,
+  (GSourceDummyMarshal)g_cclosure_marshal_generic
+};
+
+/* TODO: Move this into GLib so we don't need a custom source. glib#94 */
+static GSource *
+tls_op_queue_source_new (GAsyncQueue *queue)
+{
+  GTlsOpQueueSource *source;
+
+  source = (GTlsOpQueueSource *)g_source_new (&tls_op_queue_source_funcs, sizeof (GTlsOpQueueSource));
+  source->queue = g_async_queue_ref (queue);
+
+  return (GSource *)source;
+}
+
+typedef struct
+{
+  GAsyncQueue *queue;
+  GTlsThreadOperation *op;
+  GMainLoop *main_loop;
+} DelayedOpAsyncData;
+
+static DelayedOpAsyncData *
+delayed_op_async_data_new (GAsyncQueue         *queue,
+                           GTlsThreadOperation *op,
+                           GMainLoop           *main_loop)
+{
+  DelayedOpAsyncData *data;
+
+  data = g_new (DelayedOpAsyncData, 1);
+
+  /* No refs because these are guaranteed to outlive data. */
+  data->queue = queue;
+  data->op = op;
+  data->main_loop = main_loop;
+
+  return data;
+}
+
+static void
+delayed_op_async_data_free (DelayedOpAsyncData *data)
+{
+  g_free (data);
+}
+
+static gboolean
+resume_tls_op (GObject  *pollable_stream,
+               gpointer  user_data)
+{
+  DelayedOpAsyncData *data = (DelayedOpAsyncData *)user_data;
+  gboolean ret;
+
+  ret = process_op (data->queue, data->op, data->main_loop);
+  g_assert (ret == G_SOURCE_CONTINUE);
+
+  delayed_op_async_data_free (data);
+
+  return G_SOURCE_REMOVE;
+}
+
+static gboolean
+resume_dtls_op (GDatagramBased *datagram_based,
+                GIOCondition    condition,
+                gpointer        user_data)
+{
+  DelayedOpAsyncData *data = (DelayedOpAsyncData *)user_data;
+  gboolean ret;
+
+  ret = process_op (data->queue, data->op, data->main_loop);
+  g_assert (ret == G_SOURCE_CONTINUE);
+
+  delayed_op_async_data_free (data);
+
+  return G_SOURCE_REMOVE;
+}
+
+/* Use a custom dummy callback instead of g_source_set_dummy_callback(), as that
+ * uses a GClosure and is slow. (The GClosure is necessary to deal with any
+ * function prototype.)
+ */
+static gboolean
+dummy_callback (gpointer data)
+{
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+adjust_op_timeout (GTlsThreadOperation *op)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (op->thread);
+  GSocket *socket = NULL;
+
+  /* Nonblocking? */
+  if (op->timeout == 0)
+    return;
+
+  if (is_dtls (op->thread))
+    {
+      if (G_IS_SOCKET (priv->base_socket))
+        socket = (GSocket *)priv->base_socket;
+    }
+  else
+    {
+      if (G_IS_SOCKET_CONNECTION (priv->base_iostream))
+        socket = g_socket_connection_get_socket ((GSocketConnection *)priv->base_iostream);
+    }
+
+  /* We have to "massage" the timeout here because we are using only nonblocking
+   * I/O, so the underlying socket will never time out even if a timeout has
+   * been set. But if we are emulating a blocking operation, we need to make
+   * sure we don't block for longer than the underyling timeout.
+   */
+  if (socket)
+    {
+      gint64 socket_timeout = g_socket_get_timeout (socket);
+
+      if (socket_timeout > 0)
+        {
+          if (op->timeout == -1)
+            op->timeout = socket_timeout;
+
+          g_assert (op->timeout > 0);
+          op->timeout = MIN (op->timeout, socket_timeout);
+        }
+    }
+}
+
+static gboolean
+process_op (GAsyncQueue         *queue,
+            GTlsThreadOperation *delayed_op,
+            GMainLoop           *main_loop)
+{
+  GTlsThreadOperation *op;
+  GTlsOperationsThreadBaseClass *base_class;
+  gboolean performed_posthandshake_op = FALSE;
+
+  if (delayed_op)
+    {
+      op = delayed_op;
+      g_clear_error (&op->error);
+
+      if (op->timeout != -1)
+        {
+          op->timeout -= g_get_monotonic_time () - op->start_time;
+          op->timeout = MAX (op->timeout, 0);
+        }
+
+      g_assert (op->io_condition != 0);
+      if (!g_tls_operations_thread_base_check (op->thread, op->io_condition))
+        {
+          /* Not ready for I/O. Either we timed out, or were cancelled, or we
+           * could have a spurious wakeup caused by GTlsConnectionBase yield_op.
+           */
+          /* FIXME: very fragile, assumes op->cancellable is the GTlsConnectionBase's cancellable */
+          if (g_cancellable_is_cancelled (op->cancellable))
+            {
+              op->count = 0;
+              g_set_error (&op->error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                           _("Operation cancelled"));
+              goto finished;
+            }
+
+          if (op->timeout == 0)
+            {
+              op->count = 0;
+              g_set_error (&op->error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+                           _("Socket I/O timed out"));
+              goto finished;
+            }
+
+          /* Spurious wakeup. Try again later. */
+          op->result = G_TLS_OPERATION_WOULD_BLOCK;
+          goto wait;
+        }
+    }
+  else
+    {
+      op = g_async_queue_try_pop (queue);
+      g_assert (op);
+
+      if (op->type == G_TLS_THREAD_OP_SHUTDOWN_THREAD)
+        {
+          g_main_loop_quit (main_loop);
+          return G_SOURCE_REMOVE;
+        }
+
+      adjust_op_timeout (op);
+    }
+
+  if (op->type != G_TLS_THREAD_OP_SHUTDOWN_THREAD)
+    {
+      g_assert (op->thread);
+      base_class = G_TLS_OPERATIONS_THREAD_BASE_GET_CLASS (op->thread);
+    }
+
+  switch (op->type)
+    {
+    case G_TLS_THREAD_OP_COPY_CLIENT_SESSION_STATE:
+      if (base_class->copy_client_session_state)
+        base_class->copy_client_session_state (op->thread, op->source);
+      break;
+    case G_TLS_THREAD_OP_SET_SERVER_IDENTITY:
+      g_assert (base_class->set_server_identity);
+      base_class->set_server_identity (op->thread,
+                                       op->server_identity);
+      break;
+    case G_TLS_THREAD_OP_HANDSHAKE:
+      op->result = base_class->handshake_fn (op->thread,
+                                             op->handshake_data->context,
+                                             op->handshake_data->own_certificate,
+                                             (const gchar **)op->handshake_data->advertised_protocols,
+                                             op->handshake_data->auth_mode,
+                                             op->timeout,
+                                             &op->handshake_data->negotiated_protocol,
+                                             &op->handshake_data->accepted_cas,
+                                             &op->handshake_data->peer_certificate,
+                                             op->cancellable,
+                                             &op->error);
+      break;
+    case G_TLS_THREAD_OP_READ:
+      op->result = base_class->read_fn (op->thread,
+                                        op->data, op->size,
+                                        &op->count,
+                                        op->cancellable,
+                                        &op->error);
+      performed_posthandshake_op = TRUE;
+      break;
+    case G_TLS_THREAD_OP_READ_MESSAGE:
+      g_assert (base_class->read_message_fn);
+      op->result = base_class->read_message_fn (op->thread,
+                                                op->input_vectors, op->num_vectors,
+                                                &op->count,
+                                                op->cancellable,
+                                                &op->error);
+      performed_posthandshake_op = TRUE;
+      break;
+    case G_TLS_THREAD_OP_WRITE:
+      op->result = base_class->write_fn (op->thread,
+                                         op->data, op->size,
+                                         &op->count,
+                                         op->cancellable,
+                                         &op->error);
+      performed_posthandshake_op = TRUE;
+      break;
+    case G_TLS_THREAD_OP_WRITE_MESSAGE:
+      g_assert (base_class->write_message_fn);
+      op->result = base_class->write_message_fn (op->thread,
+                                                 op->output_vectors, op->num_vectors,
+                                                 &op->count,
+                                                 op->cancellable,
+                                                 &op->error);
+      performed_posthandshake_op = TRUE;
+      break;
+    case G_TLS_THREAD_OP_CLOSE:
+      op->result = base_class->close_fn (op->thread,
+                                         op->cancellable,
+                                         &op->error);
+      performed_posthandshake_op = TRUE;
+      break;
+    case G_TLS_THREAD_OP_SHUTDOWN_THREAD:
+      g_assert_not_reached ();
+    }
+
+  if (op->result == G_TLS_OPERATION_SUCCESS && performed_posthandshake_op)
+    set_performed_successful_posthandshake_op (op->thread);
+
+wait:
+  if (op->result == G_TLS_OPERATION_WOULD_BLOCK &&
+      op->timeout != 0)
+    {
+      GSource *tls_source;
+      GSource *timeout_source;
+      GMainContext *main_context;
+      DelayedOpAsyncData *data;
+
+      tls_source = create_base_source (op->thread,
+                                       op->io_condition,
+                                       op->cancellable);
+      if (op->timeout > 0)
+        {
+          op->start_time = g_get_monotonic_time ();
+
+          /* tls_source should fire if (a) we're ready to ready/write without
+           * blocking, or (b) the timeout has elasped.
+           */
+          timeout_source = g_timeout_source_new (op->timeout);
+          g_source_set_callback (timeout_source, dummy_callback, NULL, NULL);
+          g_source_add_child_source (tls_source, timeout_source);
+          g_source_unref (timeout_source);
+        }
+
+      data = delayed_op_async_data_new (queue, op, main_loop);
+      if (is_dtls (op->thread))
+        g_source_set_callback (tls_source, G_SOURCE_FUNC (resume_dtls_op), data, NULL);
+      else
+        g_source_set_callback (tls_source, G_SOURCE_FUNC (resume_tls_op), data, NULL);
+
+      main_context = g_main_loop_get_context (main_loop);
+      g_source_attach (tls_source, main_context);
+      g_source_unref (tls_source);
+
+      return G_SOURCE_CONTINUE;
+    }
+
+finished:
+  g_mutex_lock (&op->finished_mutex);
+  op->finished = TRUE;
+  g_cond_signal (&op->finished_condition);
+  g_mutex_unlock (&op->finished_mutex);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static gpointer
+tls_op_thread (gpointer data)
+{
+  GTlsOperationsThreadBase *self = G_TLS_OPERATIONS_THREAD_BASE (data);
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  GMainLoop *main_loop;
+  GSource *source;
+
+  main_loop = g_main_loop_new (priv->op_thread_context, FALSE);
+
+  g_main_context_push_thread_default (priv->op_thread_context);
+
+  source = tls_op_queue_source_new (priv->queue);
+  g_source_set_callback (source, G_SOURCE_FUNC (process_op), main_loop, NULL);
+  g_source_attach (source, priv->op_thread_context);
+  g_source_unref (source);
+
+  g_main_loop_run (main_loop);
+
+  g_assert (!queue_has_pending_op (priv->queue));
+
+  g_main_context_pop_thread_default (priv->op_thread_context);
+
+  g_main_loop_unref (main_loop);
+
+  return NULL;
+}
+
+static void
+g_tls_operations_thread_base_get_property (GObject    *object,
+                                           guint       prop_id,
+                                           GValue     *value,
+                                           GParamSpec *pspec)
+{
+  GTlsOperationsThreadBase *self = G_TLS_OPERATIONS_THREAD_BASE (object);
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_BASE_IO_STREAM:
+      g_value_set_object (value, priv->base_iostream);
+      break;
+
+    case PROP_BASE_SOCKET:
+      g_value_set_object (value, priv->base_socket);
+      break;
+
+    case PROP_THREAD_TYPE:
+      g_value_set_enum (value, priv->thread_type);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+g_tls_operations_thread_base_set_property (GObject      *object,
+                                           guint         prop_id,
+                                           const GValue *value,
+                                           GParamSpec   *pspec)
+{
+  GTlsOperationsThreadBase *self = G_TLS_OPERATIONS_THREAD_BASE (object);
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_BASE_IO_STREAM:
+      priv->base_iostream = g_value_dup_object (value);
+      if (priv->base_iostream)
+        g_assert (!priv->base_socket);
+      break;
+
+    case PROP_BASE_SOCKET:
+      priv->base_socket = g_value_dup_object (value);
+      if (priv->base_socket)
+        g_assert (!priv->base_iostream);
+      break;
+
+    case PROP_THREAD_TYPE:
+      priv->thread_type = g_value_get_enum (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static gboolean
+g_tls_operations_thread_base_initable_init (GInitable     *initable,
+                                            GCancellable  *cancellable,
+                                            GError       **error)
+{
+  return TRUE;
+}
+
+static void
+g_tls_operations_thread_base_init (GTlsOperationsThreadBase *self)
+{
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+  priv->queue = g_async_queue_new ();
+  priv->op_thread_context = g_main_context_new ();
+  priv->op_thread = g_thread_new ("[glib-networking] GTlsOperationsThreadBase TLS operations thread",
+                                  tls_op_thread,
+                                  self);
+
+  g_mutex_init (&priv->mutex);
+}
+
+static void
+g_tls_operations_thread_base_finalize (GObject *object)
+{
+  GTlsOperationsThreadBase *self = G_TLS_OPERATIONS_THREAD_BASE (object);
+  GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+  GTlsThreadOperation *op;
+
+  op = g_tls_thread_shutdown_operation_new ();
+  g_async_queue_push (priv->queue, op);
+  g_main_context_wakeup (priv->op_thread_context);
+
+  g_clear_pointer (&priv->op_thread, g_thread_join);
+  g_clear_pointer (&priv->op_thread_context, g_main_context_unref);
+  g_clear_pointer (&priv->queue, g_async_queue_unref);
+  g_tls_thread_operation_free (op);
+
+  g_clear_object (&priv->base_iostream);
+  g_clear_object (&priv->base_socket);
+
+  g_mutex_clear (&priv->mutex);
+
+  g_clear_object (&priv->interaction);
+  g_clear_error (&priv->interaction_error);
+
+  G_OBJECT_CLASS (g_tls_operations_thread_base_parent_class)->finalize (object);
+}
+
+static void
+g_tls_operations_thread_base_class_init (GTlsOperationsThreadBaseClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize     = g_tls_operations_thread_base_finalize;
+  gobject_class->get_property = g_tls_operations_thread_base_get_property;
+  gobject_class->set_property = g_tls_operations_thread_base_set_property;
+
+  klass->pop_io = g_tls_operations_thread_base_real_pop_io;
+
+  signals[REQUEST_CERTIFICATE] =
+    g_signal_new ("operations-thread-request-certificate",
+                             G_TYPE_TLS_OPERATIONS_THREAD_BASE,
+                             G_SIGNAL_RUN_LAST, 0,
+                             g_signal_accumulator_first_wins,
+                             NULL, NULL,
+                             G_TYPE_TLS_INTERACTION_RESULT, 4,
+                             G_TYPE_TLS_INTERACTION,
+                             G_TYPE_POINTER,
+                             G_TYPE_CANCELLABLE,
+                             G_TYPE_POINTER);
+
+  obj_properties[PROP_BASE_IO_STREAM] =
+    g_param_spec_object ("base-io-stream",
+                         "Base IOStream",
+                         "The underlying GIOStream, for TLS connections",
+                         G_TYPE_IO_STREAM,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+  obj_properties[PROP_BASE_SOCKET] =
+    g_param_spec_object ("base-socket",
+                         "Base socket",
+                         "The underlying GDatagramBased, for DTLS connections",
+                         G_TYPE_DATAGRAM_BASED,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+  obj_properties[PROP_THREAD_TYPE] =
+    g_param_spec_enum ("thread-type",
+                       "Thread type",
+                       "Whether this thread runs a TLS client or server",
+                       G_TYPE_TLS_OPERATIONS_THREAD_TYPE,
+                       0,
+                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, LAST_PROP, obj_properties);
+}
+
+static void
+g_tls_operations_thread_base_initable_iface_init (GInitableIface *iface)
+{
+  iface->init = g_tls_operations_thread_base_initable_init;
+}
diff --git a/tls/base/gtlsoperationsthread-base.h b/tls/base/gtlsoperationsthread-base.h
new file mode 100644
index 0000000..5d463cf
--- /dev/null
+++ b/tls/base/gtlsoperationsthread-base.h
@@ -0,0 +1,215 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2019 Igalia S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * In addition, when the library is used with OpenSSL, a special
+ * exception applies. Refer to the LICENSE_EXCEPTION file for details.
+ */
+
+#pragma once
+
+#include "gtlsconnection-base.h"
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_TLS_OPERATIONS_THREAD_BASE (g_tls_operations_thread_base_get_type ())
+
+G_DECLARE_DERIVABLE_TYPE (GTlsOperationsThreadBase, g_tls_operations_thread_base, G, 
TLS_OPERATIONS_THREAD_BASE, GObject)
+
+typedef enum {
+  G_TLS_OPERATION_SUCCESS,
+  G_TLS_OPERATION_WOULD_BLOCK,
+  G_TLS_OPERATION_TIMED_OUT,
+  G_TLS_OPERATION_TRY_AGAIN,
+  G_TLS_OPERATION_ERROR,
+} GTlsOperationStatus;
+
+typedef enum {
+  G_TLS_OPERATIONS_THREAD_CLIENT,
+  G_TLS_OPERATIONS_THREAD_SERVER
+} GTlsOperationsThreadType;
+
+typedef struct _HandshakeContext HandshakeContext;
+
+struct _GTlsOperationsThreadBaseClass
+{
+  GObjectClass parent_class;
+
+  GTlsCertificate       *(*copy_certificate)           (GTlsOperationsThreadBase  *self,
+                                                        GTlsCertificate           *cert);
+
+  void                   (*copy_client_session_state)  (GTlsOperationsThreadBase  *self,
+                                                        GTlsOperationsThreadBase  *source);
+
+  void                   (*set_server_identity)        (GTlsOperationsThreadBase  *self,
+                                                        const gchar               *server_identity);
+
+  void                   (*push_io)                    (GTlsOperationsThreadBase  *self,
+                                                        GIOCondition               direction,
+                                                        GCancellable              *cancellable);
+  GTlsOperationStatus    (*pop_io)                     (GTlsOperationsThreadBase  *self,
+                                                        GIOCondition               direction,
+                                                        gboolean                   success,
+                                                        GError                    *op_error,
+                                                        GError                   **error);
+
+  GTlsOperationStatus    (*handshake_fn)               (GTlsOperationsThreadBase  *self,
+                                                        HandshakeContext          *context,
+                                                        GTlsCertificate           *own_certificate,
+                                                        const gchar              **advertised_protocols,
+                                                        GTlsAuthenticationMode     auth_mode,
+                                                        gint64                     timeout,
+                                                        gchar                    **negotiated_protocol,
+                                                        GList                    **accepted_cas,
+                                                        GTlsCertificate          **peer_certificate,
+                                                        GCancellable              *cancellable,
+                                                        GError                   **error);
+
+  GTlsOperationStatus    (*read_fn)                    (GTlsOperationsThreadBase  *self,
+                                                        void                      *buffer,
+                                                        gsize                      size,
+                                                        gssize                    *nread,
+                                                        GCancellable              *cancellable,
+                                                        GError                   **error);
+  GTlsOperationStatus    (*read_message_fn)            (GTlsOperationsThreadBase  *self,
+                                                        GInputVector              *vectors,
+                                                        guint                      num_vectors,
+                                                        gssize                    *nread,
+                                                        GCancellable              *cancellable,
+                                                        GError                   **error);
+
+  GTlsOperationStatus    (*write_fn)                   (GTlsOperationsThreadBase  *self,
+                                                        const void                *buffer,
+                                                        gsize                      size,
+                                                        gssize                    *nwrote,
+                                                        GCancellable              *cancellable,
+                                                        GError                   **error);
+  GTlsOperationStatus    (*write_message_fn)           (GTlsOperationsThreadBase  *self,
+                                                        GOutputVector             *vectors,
+                                                        guint                      num_vectors,
+                                                        gssize                    *nwrote,
+                                                        GCancellable              *cancellable,
+                                                        GError                   **error);
+
+  GTlsOperationStatus    (*close_fn)                   (GTlsOperationsThreadBase  *self,
+                                                        GCancellable              *cancellable,
+                                                        GError                   **error);
+};
+
+typedef gboolean (*GTlsVerifyCertificateFunc) (GTlsOperationsThreadBase *thread,
+                                               GTlsCertificate          *peer_certificate,
+                                               gpointer                  user_data);
+typedef void     (*GTlsSessionResumedFunc)    (GTlsOperationsThreadBase *thread,
+                                               GTlsCertificate          *peer_certificate,
+                                               gpointer                  user_data);
+
+void                 g_tls_operations_thread_base_set_interaction           (GTlsOperationsThreadBase   
*self,
+                                                                             GTlsInteraction            
*interaction);
+GTlsInteraction     *g_tls_operations_thread_base_ref_interaction           (GTlsOperationsThreadBase   
*self);
+GError              *g_tls_operations_thread_base_take_interaction_error    (GTlsOperationsThreadBase   
*self);
+
+gboolean             g_tls_operations_thread_base_request_certificate       (GTlsOperationsThreadBase   
*self,
+                                                                             GCancellable               
*cancellable,
+                                                                             GTlsCertificate           
**own_certificate);
+
+void                 g_tls_operations_thread_base_set_missing_requested_client_certificate
+                                                                            (GTlsOperationsThreadBase  
*self);
+
+void                 g_tls_operations_thread_base_set_close_notify_required (GTlsOperationsThreadBase  *self,
+                                                                             gboolean                   
required);
+gboolean             g_tls_operations_thread_base_get_close_notify_required (GTlsOperationsThreadBase  
*self);
+
+gboolean             g_tls_operations_thread_base_verify_certificate        (GTlsOperationsThreadBase  *self,
+                                                                             GTlsCertificate           
*peer_certificate,
+                                                                             HandshakeContext          
*context);
+
+void                 g_tls_operations_thread_base_copy_client_session_state (GTlsOperationsThreadBase   
*self,
+                                                                             GTlsOperationsThreadBase   
*source);
+
+void                 g_tls_operations_thread_base_set_server_identity       (GTlsOperationsThreadBase   
*self,
+                                                                             const gchar                
*server_identity);
+
+void                 g_tls_operations_thread_base_push_io                   (GTlsOperationsThreadBase   
*self,
+                                                                             GIOCondition                
direction,
+                                                                             GCancellable               
*cancellable);
+GTlsOperationStatus  g_tls_operations_thread_base_pop_io                    (GTlsOperationsThreadBase   
*self,
+                                                                             GIOCondition                
direction,
+                                                                             gboolean                    
success,
+                                                                             GError                     
*op_error,
+                                                                             GError                    
**error);
+
+GTlsOperationStatus  g_tls_operations_thread_base_handshake                 (GTlsOperationsThreadBase   
*self,
+                                                                             GTlsCertificate            
*own_certificate,
+                                                                             const gchar               
**advertised_protocols,
+                                                                             GTlsAuthenticationMode      
auth_mode,
+                                                                             gint64                      
timeout,
+                                                                             GTlsVerifyCertificateFunc   
verify_callback,
+                                                                             GTlsSessionResumedFunc      
resumed_callback,
+                                                                             gchar                     
**negotiated_protocol,
+                                                                             GList                     
**accepted_cas,
+                                                                             GCancellable               
*cancellable,
+                                                                             gpointer                    
user_data,
+                                                                             GError                    
**error);
+
+GTlsOperationStatus  g_tls_operations_thread_base_read                      (GTlsOperationsThreadBase   
*self,
+                                                                             void                       
*buffer,
+                                                                             gsize                       
size,
+                                                                             gint64                      
timeout,
+                                                                             gssize                     
*nread,
+                                                                             GCancellable               
*cancellable,
+                                                                             GError                    
**error);
+
+GTlsOperationStatus  g_tls_operations_thread_base_read_message              (GTlsOperationsThreadBase   
*self,
+                                                                             GInputVector               
*vectors,
+                                                                             guint                       
num_vectors,
+                                                                             gint64                      
timeout,
+                                                                             gssize                     
*nread,
+                                                                             GCancellable               
*cancellable,
+                                                                             GError                    
**error);
+
+GTlsOperationStatus  g_tls_operations_thread_base_write                     (GTlsOperationsThreadBase   
*self,
+                                                                             const void                 
*buffer,
+                                                                             gsize                       
size,
+                                                                             gint64                      
timeout,
+                                                                             gssize                     
*nwrote,
+                                                                             GCancellable               
*cancellable,
+                                                                             GError                    
**error);
+
+GTlsOperationStatus  g_tls_operations_thread_base_write_message             (GTlsOperationsThreadBase   
*self,
+                                                                             GOutputVector              
*vectors,
+                                                                             guint                       
num_vectors,
+                                                                             gint64                      
timeout,
+                                                                             gssize                     
*nwrote,
+                                                                             GCancellable               
*cancellable,
+                                                                             GError                    
**error);
+
+GTlsOperationStatus  g_tls_operations_thread_base_close                     (GTlsOperationsThreadBase   
*self,
+                                                                             GCancellable               
*cancellable,
+                                                                             GError                    
**error);
+
+GIOStream           *g_tls_operations_thread_base_get_base_iostream         (GTlsOperationsThreadBase   
*self);
+GDatagramBased      *g_tls_operations_thread_base_get_base_socket           (GTlsOperationsThreadBase   
*self);
+
+gboolean             g_tls_operations_thread_base_check                     (GTlsOperationsThreadBase   
*self,
+                                                                             GIOCondition                
condition);
+
+
+G_END_DECLS
diff --git a/tls/base/meson.build b/tls/base/meson.build
index ca7d5a3..70a897a 100644
--- a/tls/base/meson.build
+++ b/tls/base/meson.build
@@ -1,9 +1,18 @@
-tlsbase_sources = files(
+enums_headers = [
+  'gtlsoperationsthread-base.h'
+]
+
+enums = gnome.mkenums_simple('tls-base-builtins',
+                             sources: enums_headers)
+
+tlsbase_sources = [
   'gtlsconnection-base.c',
   'gtlsinputstream.c',
   'gtlslog.c',
+  'gtlsoperationsthread-base.c',
   'gtlsoutputstream.c',
-)
+  enums
+]
 
 tlsbase = static_library('tlsbase',
                          tlsbase_sources,
diff --git a/tls/gnutls/gtlscertificate-gnutls.c b/tls/gnutls/gtlscertificate-gnutls.c
index 3def092..1fcc771 100644
--- a/tls/gnutls/gtlscertificate-gnutls.c
+++ b/tls/gnutls/gtlscertificate-gnutls.c
@@ -470,11 +470,11 @@ g_tls_certificate_gnutls_has_key (GTlsCertificateGnutls *gnutls)
 }
 
 void
-g_tls_certificate_gnutls_copy  (GTlsCertificateGnutls  *gnutls,
-                                const gchar            *interaction_id,
-                                gnutls_pcert_st       **pcert,
-                                unsigned int           *pcert_length,
-                                gnutls_privkey_t       *pkey)
+g_tls_certificate_gnutls_copy_internals (GTlsCertificateGnutls  *gnutls,
+                                         const gchar            *interaction_id,
+                                         gnutls_pcert_st       **pcert,
+                                         unsigned int           *pcert_length,
+                                         gnutls_privkey_t       *pkey)
 {
   GTlsCertificateGnutls *chain;
   guint num_certs = 0;
@@ -544,9 +544,9 @@ g_tls_certificate_gnutls_copy  (GTlsCertificateGnutls  *gnutls,
 }
 
 void
-g_tls_certificate_gnutls_copy_free (gnutls_pcert_st  *pcert,
-                                    unsigned int      pcert_length,
-                                    gnutls_privkey_t  pkey)
+g_tls_certificate_gnutls_internals_free (gnutls_pcert_st  *pcert,
+                                         unsigned int      pcert_length,
+                                         gnutls_privkey_t  pkey)
 {
   if (pcert)
     {
@@ -749,7 +749,7 @@ error:
   return NULL;
 }
 
-GTlsCertificateGnutls *
+GTlsCertificate *
 g_tls_certificate_gnutls_build_chain (const gnutls_datum_t  *certs,
                                       guint                  num_certs,
                                       gnutls_x509_crt_fmt_t  format)
@@ -811,5 +811,5 @@ g_tls_certificate_gnutls_build_chain (const gnutls_datum_t  *certs,
     gnutls_x509_crt_deinit (gnutls_certs[i]);
   g_free (gnutls_certs);
 
-  return result;
+  return G_TLS_CERTIFICATE (result);
 }
diff --git a/tls/gnutls/gtlscertificate-gnutls.h b/tls/gnutls/gtlscertificate-gnutls.h
index 838f7db..c74584b 100644
--- a/tls/gnutls/gtlscertificate-gnutls.h
+++ b/tls/gnutls/gtlscertificate-gnutls.h
@@ -46,13 +46,13 @@ const gnutls_x509_crt_t      g_tls_certificate_gnutls_get_cert        (GTlsCerti
 gboolean                     g_tls_certificate_gnutls_has_key         (GTlsCertificateGnutls *gnutls);
 gboolean                     g_tls_certificate_gnutls_is_pkcs11_backed (GTlsCertificateGnutls *gnutls);
 
-void                         g_tls_certificate_gnutls_copy            (GTlsCertificateGnutls  *gnutls,
+void                         g_tls_certificate_gnutls_copy_internals  (GTlsCertificateGnutls  *gnutls,
                                                                        const gchar            
*interaction_id,
                                                                        gnutls_pcert_st       **pcert,
                                                                        unsigned int           *pcert_length,
                                                                        gnutls_privkey_t       *pkey);
 
-void                         g_tls_certificate_gnutls_copy_free       (gnutls_pcert_st        *pcert,
+void                         g_tls_certificate_gnutls_internals_free  (gnutls_pcert_st        *pcert,
                                                                        unsigned int            pcert_length,
                                                                        gnutls_privkey_t        pkey);
 
@@ -66,7 +66,7 @@ void                         g_tls_certificate_gnutls_set_issuer      (GTlsCerti
 
 GTlsCertificateGnutls*       g_tls_certificate_gnutls_steal_issuer    (GTlsCertificateGnutls *gnutls);
 
-GTlsCertificateGnutls*       g_tls_certificate_gnutls_build_chain     (const gnutls_datum_t  *certs,
+GTlsCertificate *            g_tls_certificate_gnutls_build_chain     (const gnutls_datum_t  *certs,
                                                                        guint                  num_certs,
                                                                        gnutls_x509_crt_fmt_t  format);
 
diff --git a/tls/gnutls/gtlsclientconnection-gnutls.c b/tls/gnutls/gtlsclientconnection-gnutls.c
index 734ad75..a30f169 100644
--- a/tls/gnutls/gtlsclientconnection-gnutls.c
+++ b/tls/gnutls/gtlsclientconnection-gnutls.c
@@ -23,19 +23,20 @@
  */
 
 #include "config.h"
-#include "glib.h"
+#include "gtlsclientconnection-gnutls.h"
+
+#include "gtlsbackend-gnutls.h"
+#include "gtlsconnection-base.h"
+#include "gtlscertificate-gnutls.h"
+#include "gtlsoperationsthread-base.h"
 
 #include <errno.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
 #include <string.h>
 
-#include "gtlsconnection-base.h"
-#include "gtlsclientconnection-gnutls.h"
-#include "gtlsbackend-gnutls.h"
-#include "gtlscertificate-gnutls.h"
-#include <glib/gi18n-lib.h>
-
 enum
 {
   PROP_0,
@@ -53,21 +54,7 @@ struct _GTlsClientConnectionGnutls
   GSocketConnectable *server_identity;
   gboolean use_ssl3;
 
-  /* session_data is either the session ticket that was used to resume this
-   * connection, or the most recent session ticket received from the server.
-   * Because session ticket reuse is generally undesirable, it should only be
-   * accessed if session_data_override is set.
-   */
-  GBytes *session_id;
-  GBytes *session_data;
-  gboolean session_data_override;
-
-  GPtrArray *accepted_cas;
-  gboolean accepted_cas_changed;
-
-  gnutls_pcert_st *pcert;
-  unsigned int pcert_length;
-  gnutls_privkey_t pkey;
+  GList *accepted_cas;
 };
 
 static void g_tls_client_connection_gnutls_initable_interface_init (GInitableIface  *iface);
@@ -75,15 +62,6 @@ static void g_tls_client_connection_gnutls_initable_interface_init (GInitableIfa
 static void g_tls_client_connection_gnutls_client_connection_interface_init (GTlsClientConnectionInterface 
*iface);
 static void g_tls_client_connection_gnutls_dtls_client_connection_interface_init 
(GDtlsClientConnectionInterface *iface);
 
-static int g_tls_client_connection_gnutls_handshake_thread_retrieve_function (gnutls_session_t              
session,
-                                                                              const gnutls_datum_t         
*req_ca_rdn,
-                                                                              int                           
nreqs,
-                                                                              const gnutls_pk_algorithm_t  
*pk_algos,
-                                                                              int                           
pk_algos_length,
-                                                                              gnutls_pcert_st             
**pcert,
-                                                                              unsigned int                 
*pcert_length,
-                                                                              gnutls_privkey_t             
*pkey);
-
 static GInitableIface *g_tls_client_connection_gnutls_parent_initable_iface;
 
 G_DEFINE_TYPE_WITH_CODE (GTlsClientConnectionGnutls, g_tls_client_connection_gnutls, 
G_TYPE_TLS_CONNECTION_GNUTLS,
@@ -94,16 +72,6 @@ G_DEFINE_TYPE_WITH_CODE (GTlsClientConnectionGnutls, g_tls_client_connection_gnu
                          G_IMPLEMENT_INTERFACE (G_TYPE_DTLS_CLIENT_CONNECTION,
                                                 
g_tls_client_connection_gnutls_dtls_client_connection_interface_init));
 
-static void
-clear_gnutls_certificate_copy (GTlsClientConnectionGnutls *gnutls)
-{
-  g_tls_certificate_gnutls_copy_free (gnutls->pcert, gnutls->pcert_length, gnutls->pkey);
-
-  gnutls->pcert = NULL;
-  gnutls->pcert_length = 0;
-  gnutls->pkey = NULL;
-}
-
 static void
 g_tls_client_connection_gnutls_init (GTlsClientConnectionGnutls *gnutls)
 {
@@ -120,126 +88,18 @@ get_server_identity (GTlsClientConnectionGnutls *gnutls)
     return NULL;
 }
 
-static void
-g_tls_client_connection_gnutls_compute_session_id (GTlsClientConnectionGnutls *gnutls)
-{
-  GSocketConnection *base_conn;
-  GSocketAddress *remote_addr;
-  GInetAddress *iaddr;
-  guint port;
-
-  /* The testsuite expects handshakes to actually happen. E.g. a test might
-   * check to see that a handshake succeeds and then later check that a new
-   * handshake fails. If we get really unlucky and the same port number is
-   * reused for the server socket between connections, then we'll accidentally
-   * resume the old session and skip certificate verification. Such failures
-   * are difficult to debug because they require running the tests hundreds of
-   * times simultaneously to reproduce (the port number does not get reused
-   * quickly enough if the tests are run sequentially).
-   *
-   * So session resumption will just need to be tested manually.
-   */
-  if (g_test_initialized ())
-    return;
-
-  /* Create a TLS "session ID." We base it on the IP address since
-   * different hosts serving the same hostname/service will probably
-   * not share the same session cache. We base it on the
-   * server-identity because at least some servers will fail (rather
-   * than just failing to resume the session) if we don't.
-   * (https://bugs.launchpad.net/bugs/823325)
-   *
-   * Note that our session IDs have no relation to TLS protocol
-   * session IDs, e.g. as provided by gnutls_session_get_id2(). Unlike
-   * our session IDs, actual TLS session IDs can no longer be used for
-   * session resumption.
-   */
-  g_object_get (G_OBJECT (gnutls), "base-io-stream", &base_conn, NULL);
-  if (G_IS_SOCKET_CONNECTION (base_conn))
-    {
-      remote_addr = g_socket_connection_get_remote_address (base_conn, NULL);
-      if (G_IS_INET_SOCKET_ADDRESS (remote_addr))
-        {
-          GInetSocketAddress *isaddr = G_INET_SOCKET_ADDRESS (remote_addr);
-          const gchar *server_hostname;
-          gchar *addrstr, *session_id;
-          GTlsCertificate *cert = NULL;
-          gchar *cert_hash = NULL;
-
-          iaddr = g_inet_socket_address_get_address (isaddr);
-          port = g_inet_socket_address_get_port (isaddr);
-
-          addrstr = g_inet_address_to_string (iaddr);
-          server_hostname = get_server_identity (gnutls);
-
-          /* If we have a certificate, make its hash part of the session ID, so
-           * that different connections to the same server can use different
-           * certificates.
-           */
-          g_object_get (G_OBJECT (gnutls), "certificate", &cert, NULL);
-          if (cert)
-            {
-              GByteArray *der = NULL;
-              g_object_get (G_OBJECT (cert), "certificate", &der, NULL);
-              if (der)
-                {
-                  cert_hash = g_compute_checksum_for_data (G_CHECKSUM_SHA256, der->data, der->len);
-                  g_byte_array_unref (der);
-                }
-              g_object_unref (cert);
-            }
-          session_id = g_strdup_printf ("%s/%s/%d/%s", addrstr,
-                                        server_hostname ? server_hostname : "",
-                                        port,
-                                        cert_hash ? cert_hash : "");
-          gnutls->session_id = g_bytes_new_take (session_id, strlen (session_id));
-          g_free (addrstr);
-          g_free (cert_hash);
-        }
-      g_object_unref (remote_addr);
-    }
-  g_clear_object (&base_conn);
-}
-
-static int
-handshake_thread_session_ticket_received_cb (gnutls_session_t      session,
-                                             guint                 htype,
-                                             guint                 when,
-                                             guint                 incoming,
-                                             const gnutls_datum_t *msg)
-{
-  GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (gnutls_session_get_ptr (session));
-  gnutls_datum_t session_datum;
-
-  if (gnutls_session_get_data2 (session, &session_datum) == GNUTLS_E_SUCCESS)
-    {
-      g_clear_pointer (&gnutls->session_data, g_bytes_unref);
-      gnutls->session_data = g_bytes_new_with_free_func (session_datum.data,
-                                                         session_datum.size,
-                                                         (GDestroyNotify)gnutls_free,
-                                                         session_datum.data);
-
-      if (gnutls->session_id)
-        {
-          g_tls_backend_gnutls_store_session_data (gnutls->session_id,
-                                                   gnutls->session_data);
-        }
-    }
-
-  return 0;
-}
-
 static void
 g_tls_client_connection_gnutls_finalize (GObject *object)
 {
   GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
 
   g_clear_object (&gnutls->server_identity);
-  g_clear_pointer (&gnutls->accepted_cas, g_ptr_array_unref);
-  g_clear_pointer (&gnutls->session_id, g_bytes_unref);
-  g_clear_pointer (&gnutls->session_data, g_bytes_unref);
 
-  clear_gnutls_certificate_copy (gnutls);
+  if (gnutls->accepted_cas)
+    {
+      g_list_free_full (gnutls->accepted_cas, (GDestroyNotify)g_byte_array_unref);
+      gnutls->accepted_cas = NULL;
+    }
 
   G_OBJECT_CLASS (g_tls_client_connection_gnutls_parent_class)->finalize (object);
 }
@@ -250,33 +110,17 @@ g_tls_client_connection_gnutls_initable_init (GInitable       *initable,
                                               GError         **error)
 {
   GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (initable);
-  gnutls_session_t session;
+  GTlsOperationsThreadBase *thread;
   const gchar *hostname;
-  gnutls_certificate_credentials_t creds;
 
   if (!g_tls_client_connection_gnutls_parent_initable_iface->init (initable, cancellable, error))
     return FALSE;
 
-  creds = g_tls_connection_gnutls_get_credentials (G_TLS_CONNECTION_GNUTLS (gnutls));
-  gnutls_certificate_set_retrieve_function2 (creds, 
g_tls_client_connection_gnutls_handshake_thread_retrieve_function);
+  thread = g_tls_connection_base_get_op_thread (G_TLS_CONNECTION_BASE (gnutls));
 
-  session = g_tls_connection_gnutls_get_session (gnutls);
   hostname = get_server_identity (G_TLS_CLIENT_CONNECTION_GNUTLS (gnutls));
   if (hostname)
-    {
-      gchar *normalized_hostname = g_strdup (hostname);
-
-      if (hostname[strlen (hostname) - 1] == '.')
-        normalized_hostname[strlen (hostname) - 1] = '\0';
-
-      gnutls_server_name_set (session, GNUTLS_NAME_DNS,
-                              normalized_hostname, strlen (normalized_hostname));
-
-      g_free (normalized_hostname);
-    }
-
-  gnutls_handshake_set_hook_function (session, GNUTLS_HANDSHAKE_NEW_SESSION_TICKET,
-                                      GNUTLS_HOOK_POST, handshake_thread_session_ticket_received_cb);
+    g_tls_operations_thread_base_set_server_identity (thread, hostname);
 
   return TRUE;
 }
@@ -288,8 +132,6 @@ g_tls_client_connection_gnutls_get_property (GObject    *object,
                                              GParamSpec *pspec)
 {
   GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
-  GList *accepted_cas;
-  gint i;
 
   switch (prop_id)
     {
@@ -306,17 +148,7 @@ g_tls_client_connection_gnutls_get_property (GObject    *object,
       break;
 
     case PROP_ACCEPTED_CAS:
-      accepted_cas = NULL;
-      if (gnutls->accepted_cas)
-        {
-          for (i = 0; i < gnutls->accepted_cas->len; ++i)
-            {
-              accepted_cas = g_list_prepend (accepted_cas, g_byte_array_ref (
-                                             gnutls->accepted_cas->pdata[i]));
-            }
-          accepted_cas = g_list_reverse (accepted_cas);
-        }
-      g_value_set_pointer (value, accepted_cas);
+      g_value_set_pointer (value, g_list_copy (gnutls->accepted_cas));
       break;
 
     default:
@@ -347,22 +179,11 @@ g_tls_client_connection_gnutls_set_property (GObject      *object,
       hostname = get_server_identity (gnutls);
       if (hostname)
         {
-          gnutls_session_t session = g_tls_connection_gnutls_get_session (G_TLS_CONNECTION_GNUTLS (gnutls));
-
-          /* This will only be triggered if the identity is set after
-           * initialization */
-          if (session)
-            {
-              gchar *normalized_hostname = g_strdup (hostname);
-
-              if (hostname[strlen (hostname) - 1] == '.')
-                normalized_hostname[strlen (hostname) - 1] = '\0';
+          GTlsOperationsThreadBase *thread;
 
-              gnutls_server_name_set (session, GNUTLS_NAME_DNS,
-                                      normalized_hostname, strlen (normalized_hostname));
-
-              g_free (normalized_hostname);
-            }
+          thread = g_tls_connection_base_get_op_thread (G_TLS_CONNECTION_BASE (gnutls));
+          if (thread)
+            g_tls_operations_thread_base_set_server_identity (thread, hostname);
         }
       break;
 
@@ -375,161 +196,26 @@ g_tls_client_connection_gnutls_set_property (GObject      *object,
     }
 }
 
-static int
-g_tls_client_connection_gnutls_handshake_thread_retrieve_function (gnutls_session_t              session,
-                                                                   const gnutls_datum_t         *req_ca_rdn,
-                                                                   int                           nreqs,
-                                                                   const gnutls_pk_algorithm_t  *pk_algos,
-                                                                   int                           
pk_algos_length,
-                                                                   gnutls_pcert_st             **pcert,
-                                                                   unsigned int                 
*pcert_length,
-                                                                   gnutls_privkey_t             *pkey)
-{
-  GTlsConnectionBase *tls = gnutls_transport_get_ptr (session);
-  GTlsClientConnectionGnutls *gnutls = gnutls_transport_get_ptr (session);
-  GTlsConnectionGnutls *conn = G_TLS_CONNECTION_GNUTLS (gnutls);
-  GPtrArray *accepted_cas;
-  gboolean had_accepted_cas;
-  GByteArray *dn;
-  int i;
-
-  /* FIXME: Here we are supposed to ensure that the certificate supports one of
-   * the algorithms given in pk_algos.
-   */
-
-  had_accepted_cas = gnutls->accepted_cas != NULL;
-
-  accepted_cas = g_ptr_array_new_with_free_func ((GDestroyNotify)g_byte_array_unref);
-  for (i = 0; i < nreqs; i++)
-    {
-      dn = g_byte_array_new ();
-      g_byte_array_append (dn, req_ca_rdn[i].data, req_ca_rdn[i].size);
-      g_ptr_array_add (accepted_cas, dn);
-    }
-
-  if (gnutls->accepted_cas)
-    g_ptr_array_unref (gnutls->accepted_cas);
-  gnutls->accepted_cas = accepted_cas;
-
-  gnutls->accepted_cas_changed = gnutls->accepted_cas || had_accepted_cas;
-
-  clear_gnutls_certificate_copy (gnutls);
-  g_tls_connection_gnutls_handshake_thread_get_certificate (conn, pcert, pcert_length, pkey);
-
-  if (*pcert_length == 0)
-    {
-      g_tls_certificate_gnutls_copy_free (*pcert, *pcert_length, *pkey);
-
-      if (g_tls_connection_base_handshake_thread_request_certificate (tls))
-        g_tls_connection_gnutls_handshake_thread_get_certificate (conn, pcert, pcert_length, pkey);
-
-      if (*pcert_length == 0)
-        {
-          g_tls_certificate_gnutls_copy_free (*pcert, *pcert_length, *pkey);
-
-          /* If there is still no client certificate, this connection will
-           * probably fail, but we must not give up yet. The certificate might
-           * be optional, e.g. if the server is using
-           * G_TLS_AUTHENTICATION_REQUESTED, not G_TLS_AUTHENTICATION_REQUIRED.
-           */
-          g_tls_connection_base_handshake_thread_set_missing_requested_client_certificate (tls);
-          return 0;
-        }
-    }
-
-  if (!*pkey)
-    {
-      g_tls_certificate_gnutls_copy_free (*pcert, *pcert_length, *pkey);
-
-      /* No private key. GnuTLS expects it to be non-null if pcert_length is
-       * nonzero, so we have to abort now.
-       */
-      g_tls_connection_base_handshake_thread_set_missing_requested_client_certificate (tls);
-      return -1;
-    }
-
-  gnutls->pcert = *pcert;
-  gnutls->pcert_length = *pcert_length;
-  gnutls->pkey = *pkey;
-
-  return 0;
-}
-
 static void
-g_tls_client_connection_gnutls_prepare_handshake (GTlsConnectionBase  *tls,
-                                                  gchar              **advertised_protocols)
+g_tls_client_connection_gnutls_set_accepted_cas (GTlsConnectionBase *tls,
+                                                 GList              *accepted_cas)
 {
   GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (tls);
 
-  g_tls_client_connection_gnutls_compute_session_id (gnutls);
-
-  if (gnutls->session_data_override)
-    {
-      g_assert (gnutls->session_data);
-      gnutls_session_set_data (g_tls_connection_gnutls_get_session (G_TLS_CONNECTION_GNUTLS (tls)),
-                               g_bytes_get_data (gnutls->session_data, NULL),
-                               g_bytes_get_size (gnutls->session_data));
-    }
-  else if (gnutls->session_id)
-    {
-      GBytes *session_data;
-
-      session_data = g_tls_backend_gnutls_lookup_session_data (gnutls->session_id);
-      if (session_data)
-        {
-          gnutls_session_set_data (g_tls_connection_gnutls_get_session (G_TLS_CONNECTION_GNUTLS (tls)),
-                                   g_bytes_get_data (session_data, NULL),
-                                   g_bytes_get_size (session_data));
-          g_clear_pointer (&gnutls->session_data, g_bytes_unref);
-          gnutls->session_data = g_steal_pointer (&session_data);
-        }
-    }
-
-  G_TLS_CONNECTION_BASE_CLASS (g_tls_client_connection_gnutls_parent_class)->
-    prepare_handshake (tls, advertised_protocols);
-}
-
-static void
-g_tls_client_connection_gnutls_complete_handshake (GTlsConnectionBase  *tls,
-                                                   gchar              **negotiated_protocol,
-                                                   GError             **error)
-{
-  GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (tls);
-
-  G_TLS_CONNECTION_BASE_CLASS (g_tls_client_connection_gnutls_parent_class)->complete_handshake (tls, 
negotiated_protocol, error);
+  if (gnutls->accepted_cas)
+    g_list_free_full (gnutls->accepted_cas, (GDestroyNotify)g_byte_array_unref);
 
-  /* It may have changed during the handshake, but we have to wait until here
-   * because we can't emit notifies on the handshake thread.
-   */
-  if (gnutls->accepted_cas_changed)
-    g_object_notify (G_OBJECT (gnutls), "accepted-cas");
+  gnutls->accepted_cas = g_steal_pointer (&accepted_cas);
 }
 
 static void
 g_tls_client_connection_gnutls_copy_session_state (GTlsClientConnection *conn,
                                                    GTlsClientConnection *source)
 {
-  GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (conn);
-  GTlsClientConnectionGnutls *gnutls_source = G_TLS_CLIENT_CONNECTION_GNUTLS (source);
-
-  /* Precondition: source has handshaked, conn has not. */
-  g_return_if_fail (!gnutls->session_id);
-  g_return_if_fail (gnutls_source->session_id);
-
-  /* Prefer to use a new session ticket, if possible. */
-  gnutls->session_data = g_tls_backend_gnutls_lookup_session_data (gnutls_source->session_id);
-
-  if (!gnutls->session_data && gnutls_source->session_data)
-    {
-      /* If it's not possible, we'll try to reuse the old ticket, even though
-       * this is a privacy risk since TLS 1.3. Applications should not use this
-       * function unless they need us to try as hard as possible to resume a
-       * session, even at the cost of privacy.
-       */
-      gnutls->session_data = g_bytes_ref (gnutls_source->session_data);
-    }
+  GTlsOperationsThreadBase *thread = g_tls_connection_base_get_op_thread (G_TLS_CONNECTION_BASE (conn));
+  GTlsOperationsThreadBase *source_thread = g_tls_connection_base_get_op_thread (G_TLS_CONNECTION_BASE 
(source));
 
-  gnutls->session_data_override = !!gnutls->session_data;
+  g_tls_operations_thread_base_copy_client_session_state (thread, source_thread);
 }
 
 static void
@@ -542,8 +228,7 @@ g_tls_client_connection_gnutls_class_init (GTlsClientConnectionGnutlsClass *klas
   gobject_class->set_property = g_tls_client_connection_gnutls_set_property;
   gobject_class->finalize     = g_tls_client_connection_gnutls_finalize;
 
-  base_class->prepare_handshake  = g_tls_client_connection_gnutls_prepare_handshake;
-  base_class->complete_handshake = g_tls_client_connection_gnutls_complete_handshake;
+  base_class->set_accepted_cas = g_tls_client_connection_gnutls_set_accepted_cas;
 
   g_object_class_override_property (gobject_class, PROP_VALIDATION_FLAGS, "validation-flags");
   g_object_class_override_property (gobject_class, PROP_SERVER_IDENTITY, "server-identity");
diff --git a/tls/gnutls/gtlsconnection-gnutls.c b/tls/gnutls/gtlsconnection-gnutls.c
index 39cc7c5..f7aa928 100644
--- a/tls/gnutls/gtlsconnection-gnutls.c
+++ b/tls/gnutls/gtlsconnection-gnutls.c
@@ -4,6 +4,7 @@
  *
  * Copyright 2009 Red Hat, Inc
  * Copyright 2015, 2016 Collabora, Ltd.
+ * Copyright 2019 Igalia S.L.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -26,6 +27,8 @@
 #include "config.h"
 #include "glib.h"
 
+/* FIXME: audit includes to remove */
+
 #include <errno.h>
 #include <stdarg.h>
 #include <gnutls/dtls.h>
@@ -36,6 +39,7 @@
 #include "gtlsbackend-gnutls.h"
 #include "gtlscertificate-gnutls.h"
 #include "gtlsclientconnection-gnutls.h"
+#include "gtlsoperationsthread-gnutls.h"
 
 #ifdef G_OS_WIN32
 #include <winsock2.h>
@@ -50,31 +54,14 @@
 #include <glib/gi18n-lib.h>
 #include <glib/gprintf.h>
 
-static ssize_t g_tls_connection_gnutls_push_func (gnutls_transport_ptr_t  transport_data,
-                                                  const void             *buf,
-                                                  size_t                  buflen);
-static ssize_t g_tls_connection_gnutls_vec_push_func (gnutls_transport_ptr_t  transport_data,
-                                                      const giovec_t         *iov,
-                                                      int                     iovcnt);
-static ssize_t g_tls_connection_gnutls_pull_func (gnutls_transport_ptr_t  transport_data,
-                                                  void                   *buf,
-                                                  size_t                  buflen);
-
-static int     g_tls_connection_gnutls_pull_timeout_func (gnutls_transport_ptr_t transport_data,
-                                                          unsigned int           ms);
+static GInitableIface *g_tls_connection_gnutls_parent_initable_iface;
 
 static void g_tls_connection_gnutls_initable_iface_init (GInitableIface *iface);
 
-static int verify_certificate_cb (gnutls_session_t session);
-
-static gnutls_priority_t priority;
-
 typedef struct
 {
-  gnutls_certificate_credentials_t creds;
-  gnutls_session_t session;
-  gchar *interaction_id;
-  GCancellable *cancellable;
+  gnutls_session_t session; /* FIXME: should be used only by GTlsOperationsThreadGnutls */
+
 } GTlsConnectionGnutlsPrivate;
 
 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionGnutls, g_tls_connection_gnutls, G_TYPE_TLS_CONNECTION_BASE,
@@ -83,31 +70,9 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionGnutls, g_tls_connection_gnutls,
                                                          g_tls_connection_gnutls_initable_iface_init);
                                   );
 
-static gint unique_interaction_id = 0;
-
 static void
 g_tls_connection_gnutls_init (GTlsConnectionGnutls *gnutls)
 {
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-  int unique_id;
-
-  unique_id = g_atomic_int_add (&unique_interaction_id, 1);
-  priv->interaction_id = g_strdup_printf ("gtls:%d", unique_id);
-
-  priv->cancellable = g_cancellable_new ();
-}
-
-static void
-g_tls_connection_gnutls_set_handshake_priority (GTlsConnectionGnutls *gnutls)
-{
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-  int ret;
-
-  g_assert (priority);
-
-  ret = gnutls_priority_set (priv->session, priority);
-  if (ret != GNUTLS_E_SUCCESS)
-    g_warning ("Failed to set GnuTLS session priority: %s", gnutls_strerror (ret));
 }
 
 static gboolean
@@ -117,1049 +82,59 @@ g_tls_connection_gnutls_initable_init (GInitable     *initable,
 {
   GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (initable);
   GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+  if (!g_tls_connection_gnutls_parent_initable_iface->init (initable, cancellable, error))
+    return FALSE;
+
+  /* FIXME bad */
+  priv->session = g_tls_operations_thread_gnutls_get_session (G_TLS_OPERATIONS_THREAD_GNUTLS 
(g_tls_connection_base_get_op_thread (G_TLS_CONNECTION_BASE (gnutls))));
+
+  return TRUE;
+}
+
+static GTlsOperationsThreadBase *
+g_tls_connection_gnutls_create_op_thread (GTlsConnectionBase *tls)
+{
   GIOStream *base_io_stream = NULL;
   GDatagramBased *base_socket = NULL;
-  gboolean client = G_IS_TLS_CLIENT_CONNECTION (gnutls);
+  gboolean client = G_IS_TLS_CLIENT_CONNECTION (tls);
   guint flags = client ? GNUTLS_CLIENT : GNUTLS_SERVER;
-  int status;
-  int ret;
+  GTlsOperationsThreadBase *thread;
 
-  g_object_get (gnutls,
+  g_object_get (tls,
                 "base-io-stream", &base_io_stream,
                 "base-socket", &base_socket,
                 NULL);
 
   /* Ensure we are in TLS mode or DTLS mode. */
-  g_return_val_if_fail (!!base_io_stream != !!base_socket, FALSE);
+  g_assert (!!base_io_stream != !!base_socket);
 
   if (base_socket)
     flags |= GNUTLS_DATAGRAM;
 
-  ret = gnutls_certificate_allocate_credentials (&priv->creds);
-  if (ret != GNUTLS_E_SUCCESS)
-    {
-      g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
-                   _("Could not create TLS connection: %s"),
-                   gnutls_strerror (ret));
-      g_clear_object (&base_io_stream);
-      g_clear_object (&base_socket);
-      return FALSE;
-    }
-
-  gnutls_init (&priv->session, flags);
-
-  gnutls_session_set_ptr (priv->session, gnutls);
-  gnutls_session_set_verify_function (priv->session, verify_certificate_cb);
-
-  status = gnutls_credentials_set (priv->session,
-                                   GNUTLS_CRD_CERTIFICATE,
-                                   priv->creds);
-  if (status != 0)
-    {
-      g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
-                   _("Could not create TLS connection: %s"),
-                   gnutls_strerror (status));
-      g_clear_object (&base_io_stream);
-      g_clear_object (&base_socket);
-      return FALSE;
-    }
-
-  gnutls_transport_set_push_function (priv->session,
-                                      g_tls_connection_gnutls_push_func);
-  gnutls_transport_set_pull_function (priv->session,
-                                      g_tls_connection_gnutls_pull_func);
-  gnutls_transport_set_pull_timeout_function (priv->session,
-                                              g_tls_connection_gnutls_pull_timeout_func);
-  gnutls_transport_set_ptr (priv->session, gnutls);
-
-  /* GDatagramBased supports vectored I/O; GPollableOutputStream does not. */
-  if (base_socket)
-    {
-      gnutls_transport_set_vec_push_function (priv->session,
-                                              g_tls_connection_gnutls_vec_push_func);
-    }
-
-  /* Set reasonable MTU */
-  if (flags & GNUTLS_DATAGRAM)
-    gnutls_dtls_set_mtu (priv->session, 1400);
+  thread = g_tls_operations_thread_gnutls_new (G_TLS_CONNECTION_GNUTLS (tls),
+                                               base_io_stream,
+                                               base_socket,
+                                               flags);
 
   g_clear_object (&base_io_stream);
   g_clear_object (&base_socket);
 
-  return TRUE;
-}
-
-static void
-g_tls_connection_gnutls_finalize (GObject *object)
-{
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-
-  if (priv->session)
-    gnutls_deinit (priv->session);
-  if (priv->creds)
-    gnutls_certificate_free_credentials (priv->creds);
-
-  if (priv->cancellable)
-    {
-      g_cancellable_cancel (priv->cancellable);
-      g_clear_object (&priv->cancellable);
-    }
-
-  g_free (priv->interaction_id);
-
-  G_OBJECT_CLASS (g_tls_connection_gnutls_parent_class)->finalize (object);
-}
-
-gnutls_certificate_credentials_t
-g_tls_connection_gnutls_get_credentials (GTlsConnectionGnutls *gnutls)
-{
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-
-  return priv->creds;
-}
-
-gnutls_session_t
-g_tls_connection_gnutls_get_session (GTlsConnectionGnutls *gnutls)
-{
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-
-  return priv->session;
-}
-
-static int
-on_pin_request (void         *userdata,
-                int           attempt,
-                const char   *token_url,
-                const char   *token_label,
-                unsigned int  callback_flags,
-                char         *pin,
-                size_t        pin_max)
-{
-  GTlsConnection *connection = G_TLS_CONNECTION (userdata);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (G_TLS_CONNECTION_GNUTLS 
(connection));
-  GTlsInteraction *interaction = g_tls_connection_get_interaction (connection);
-  GTlsInteractionResult result;
-  GTlsPassword *password;
-  GTlsPasswordFlags password_flags = 0;
-  GError *error = NULL;
-  gchar *description;
-  int ret = -1;
-
-  if (!interaction)
-    return -1;
-
-  if (callback_flags & GNUTLS_PIN_WRONG)
-    password_flags |= G_TLS_PASSWORD_RETRY;
-  if (callback_flags & GNUTLS_PIN_COUNT_LOW)
-    password_flags |= G_TLS_PASSWORD_MANY_TRIES;
-  if (callback_flags & GNUTLS_PIN_FINAL_TRY || attempt > 5) /* Give up at some point */
-    password_flags |= G_TLS_PASSWORD_FINAL_TRY;
-
-  description = g_strdup_printf (" %s (%s)", token_label, token_url);
-  password = g_tls_password_new (password_flags, description);
-  result = g_tls_interaction_invoke_ask_password (interaction, password,
-                                                  priv->cancellable,
-                                                  &error);
-  g_free (description);
-
-  switch (result)
-    {
-    case G_TLS_INTERACTION_FAILED:
-      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-        g_warning ("Error getting PIN: %s", error->message);
-      g_error_free (error);
-      break;
-    case G_TLS_INTERACTION_UNHANDLED:
-      break;
-    case G_TLS_INTERACTION_HANDLED:
-      {
-        gsize password_size;
-        const guchar *password_data = g_tls_password_get_value (password, &password_size);
-        if (password_size > pin_max)
-          g_warning ("PIN is larger than max PIN size");
-
-        memcpy (pin, password_data, MIN (password_size, pin_max));
-        ret = GNUTLS_E_SUCCESS;
-        break;
-      }
-    default:
-      g_assert_not_reached ();
-    }
-
-  g_object_unref (password);
-
-  return ret;
-}
-
-void
-g_tls_connection_gnutls_handshake_thread_get_certificate (GTlsConnectionGnutls  *gnutls,
-                                                          gnutls_pcert_st      **pcert,
-                                                          unsigned int          *pcert_length,
-                                                          gnutls_privkey_t      *pkey)
-{
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-  GTlsCertificate *cert;
-
-  cert = g_tls_connection_get_certificate (G_TLS_CONNECTION (gnutls));
-
-  if (cert)
-    {
-      /* Send along a pre-initialized privkey so we can handle the callback here. */
-      gnutls_privkey_t privkey;
-      gnutls_privkey_init (&privkey);
-      gnutls_privkey_set_pin_function (privkey, on_pin_request, gnutls);
-
-      g_tls_certificate_gnutls_copy (G_TLS_CERTIFICATE_GNUTLS (cert),
-                                     priv->interaction_id,
-                                     pcert, pcert_length, &privkey);
-      *pkey = privkey;
-    }
-  else
-    {
-      *pcert = NULL;
-      *pcert_length = 0;
-      *pkey = NULL;
-    }
-}
-
-static GTlsConnectionBaseStatus
-end_gnutls_io (GTlsConnectionGnutls  *gnutls,
-               GIOCondition           direction,
-               int                    ret,
-               GError               **error,
-               const char            *err_prefix)
-{
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-  GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (gnutls);
-  GTlsConnectionBaseStatus status;
-  gboolean handshaking;
-  gboolean ever_handshaked;
-  GError *my_error = NULL;
-
-  /* We intentionally do not check for GNUTLS_E_INTERRUPTED here
-   * Instead, the caller may poll for the source to become ready again.
-   * (Note that GTlsOutputStreamGnutls and GTlsInputStreamGnutls inherit
-   * from GPollableOutputStream and GPollableInputStream, respectively.)
-   * See also the comment in set_gnutls_error().
-   */
-  if (ret == GNUTLS_E_AGAIN ||
-      ret == GNUTLS_E_WARNING_ALERT_RECEIVED)
-    return G_TLS_CONNECTION_BASE_TRY_AGAIN;
-
-  status = g_tls_connection_base_pop_io (tls, direction, ret >= 0, &my_error);
-  if (status == G_TLS_CONNECTION_BASE_OK ||
-      status == G_TLS_CONNECTION_BASE_WOULD_BLOCK ||
-      status == G_TLS_CONNECTION_BASE_TIMED_OUT)
-    {
-      if (my_error)
-        g_propagate_error (error, my_error);
-      return status;
-    }
-
-  g_assert (status == G_TLS_CONNECTION_BASE_ERROR);
-
-  handshaking = g_tls_connection_base_is_handshaking (tls);
-  ever_handshaked = g_tls_connection_base_ever_handshaked (tls);
-
-  if (handshaking && !ever_handshaked)
-    {
-      if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_FAILED) ||
-          g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE))
-        {
-          g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
-                       _("Peer failed to perform TLS handshake: %s"), my_error->message);
-          g_clear_error (&my_error);
-          return G_TLS_CONNECTION_BASE_ERROR;
-        }
-
-      if (status == GNUTLS_E_UNEXPECTED_PACKET_LENGTH ||
-          status == GNUTLS_E_DECRYPTION_FAILED ||
-          status == GNUTLS_E_UNSUPPORTED_VERSION_PACKET)
-        {
-          g_clear_error (&my_error);
-          g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
-                       _("Peer failed to perform TLS handshake: %s"), gnutls_strerror (ret));
-          return G_TLS_CONNECTION_BASE_ERROR;
-        }
-    }
-
-  if (ret == GNUTLS_E_REHANDSHAKE)
-    return G_TLS_CONNECTION_BASE_REHANDSHAKE;
-
-  if (ret == GNUTLS_E_PREMATURE_TERMINATION)
-    {
-      if (handshaking && !ever_handshaked)
-        {
-          g_clear_error (&my_error);
-          g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
-                       _("Peer failed to perform TLS handshake: %s"), gnutls_strerror (ret));
-          return G_TLS_CONNECTION_BASE_ERROR;
-        }
-
-      if (g_tls_connection_get_require_close_notify (G_TLS_CONNECTION (gnutls)))
-        {
-          g_clear_error (&my_error);
-          g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_EOF,
-                               _("TLS connection closed unexpectedly"));
-          return G_TLS_CONNECTION_BASE_ERROR;
-        }
-
-      return G_TLS_CONNECTION_BASE_OK;
-    }
-
-  if (ret == GNUTLS_E_NO_CERTIFICATE_FOUND
-#ifdef GNUTLS_E_CERTIFICATE_REQUIRED
-           || ret == GNUTLS_E_CERTIFICATE_REQUIRED /* Added in GnuTLS 3.6.7 */
-#endif
-          )
-    {
-      g_clear_error (&my_error);
-      g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
-                           _("TLS connection peer did not send a certificate"));
-      return G_TLS_CONNECTION_BASE_ERROR;
-    }
-
-  if (ret == GNUTLS_E_CERTIFICATE_ERROR)
-    {
-      g_clear_error (&my_error);
-      g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
-                           _("Unacceptable TLS certificate"));
-      return G_TLS_CONNECTION_BASE_ERROR;
-    }
-
-  if (ret == GNUTLS_E_FATAL_ALERT_RECEIVED)
-    {
-      g_clear_error (&my_error);
-      g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
-                   _("Peer sent fatal TLS alert: %s"),
-                   gnutls_alert_get_name (gnutls_alert_get (priv->session)));
-      return G_TLS_CONNECTION_BASE_ERROR;
-    }
-
-  if (ret == GNUTLS_E_INAPPROPRIATE_FALLBACK)
-    {
-      g_clear_error (&my_error);
-      g_set_error_literal (error, G_TLS_ERROR,
-                           G_TLS_ERROR_INAPPROPRIATE_FALLBACK,
-                           _("Protocol version downgrade attack detected"));
-      return G_TLS_CONNECTION_BASE_ERROR;
-    }
-
-  if (ret == GNUTLS_E_LARGE_PACKET)
-    {
-      guint mtu = gnutls_dtls_get_data_mtu (priv->session);
-      g_clear_error (&my_error);
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
-                   ngettext ("Message is too large for DTLS connection; maximum is %u byte",
-                             "Message is too large for DTLS connection; maximum is %u bytes", mtu), mtu);
-      return G_TLS_CONNECTION_BASE_ERROR;
-    }
-
-  if (ret == GNUTLS_E_TIMEDOUT)
-    {
-      g_clear_error (&my_error);
-      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
-                           _("The operation timed out"));
-      return G_TLS_CONNECTION_BASE_ERROR;
-    }
-
-  if (error && my_error)
-    g_propagate_error (error, my_error);
-
-  if (error && !*error)
-    {
-      *error = g_error_new (G_TLS_ERROR, G_TLS_ERROR_MISC, "%s: %s",
-                            err_prefix, gnutls_strerror (ret));
-    }
-
-  return G_TLS_CONNECTION_BASE_ERROR;
-}
-
-#define BEGIN_GNUTLS_IO(gnutls, direction, timeout, cancellable)        \
-  g_tls_connection_base_push_io (G_TLS_CONNECTION_BASE (gnutls),        \
-                                 direction, timeout, cancellable);      \
-  do {
-
-#define END_GNUTLS_IO(gnutls, direction, ret, status, errmsg, err)      \
-    status = end_gnutls_io (gnutls, direction, ret, err, errmsg);       \
-  } while (status == G_TLS_CONNECTION_BASE_TRY_AGAIN);
-
-static void
-set_gnutls_error (GTlsConnectionGnutls *gnutls,
-                  GError               *error)
-{
-  GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (gnutls);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-
-  /* We set EINTR rather than EAGAIN for G_IO_ERROR_WOULD_BLOCK so
-   * that GNUTLS_E_AGAIN only gets returned for gnutls-internal
-   * reasons, not for actual socket EAGAINs (and we have access
-   * to @error at the higher levels, so we can distinguish them
-   * that way later).
-   */
-
-  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-    gnutls_transport_set_errno (priv->session, EINTR);
-  else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
-    {
-      /* Return EAGAIN while handshaking so that GnuTLS handles retries for us
-       * internally in its handshaking code. */
-      if (g_tls_connection_base_is_dtls (tls) && g_tls_connection_base_is_handshaking (tls))
-        gnutls_transport_set_errno (priv->session, EAGAIN);
-      else
-        gnutls_transport_set_errno (priv->session, EINTR);
-    }
-  else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
-    gnutls_transport_set_errno (priv->session, EINTR);
-  else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE))
-    gnutls_transport_set_errno (priv->session, EMSGSIZE);
-  else
-    gnutls_transport_set_errno (priv->session, EIO);
-}
-
-static ssize_t
-g_tls_connection_gnutls_pull_func (gnutls_transport_ptr_t  transport_data,
-                                   void                   *buf,
-                                   size_t                  buflen)
-{
-  GTlsConnectionBase *tls = transport_data;
-  GTlsConnectionGnutls *gnutls = transport_data;
-  ssize_t ret;
-
-  /* If read_error is nonnull when we're called, it means
-   * that an error previously occurred, but GnuTLS decided not to
-   * propagate it. So it's correct for us to just clear it. (Usually
-   * this means it ignored an EAGAIN after a short read, and now
-   * we'll return EAGAIN again, which it will obey this time.)
-   */
-  g_clear_error (g_tls_connection_base_get_read_error (tls));
-
-  if (g_tls_connection_base_is_dtls (tls))
-    {
-      GInputVector vector = { buf, buflen };
-      GInputMessage message = { NULL, &vector, 1, 0, 0, NULL, NULL };
-
-      ret = g_datagram_based_receive_messages (g_tls_connection_base_get_base_socket (tls),
-                                               &message, 1, 0,
-                                               g_tls_connection_base_is_handshaking (tls) ? 0 : 
g_tls_connection_base_get_read_timeout (tls),
-                                               g_tls_connection_base_get_read_cancellable (tls),
-                                               g_tls_connection_base_get_read_error (tls));
-
-      if (ret > 0)
-        ret = message.bytes_received;
-    }
-  else
-    {
-      ret = g_pollable_stream_read (G_INPUT_STREAM (g_tls_connection_base_get_base_istream (tls)),
-                                    buf, buflen,
-                                    g_tls_connection_base_get_read_timeout (tls) != 0,
-                                    g_tls_connection_base_get_read_cancellable (tls),
-                                    g_tls_connection_base_get_read_error (tls));
-    }
-
-  if (ret < 0)
-    set_gnutls_error (gnutls, *g_tls_connection_base_get_read_error (tls));
-
-  return ret;
-}
-
-static ssize_t
-g_tls_connection_gnutls_push_func (gnutls_transport_ptr_t  transport_data,
-                                   const void             *buf,
-                                   size_t                  buflen)
-{
-  GTlsConnectionBase *tls = transport_data;
-  GTlsConnectionGnutls *gnutls = transport_data;
-  ssize_t ret;
-
-  /* See comment in pull_func. */
-  g_clear_error (g_tls_connection_base_get_write_error (tls));
-
-  if (g_tls_connection_base_is_dtls (tls))
-    {
-      GOutputVector vector = { buf, buflen };
-      GOutputMessage message = { NULL, &vector, 1, 0, NULL, 0 };
-
-      ret = g_datagram_based_send_messages (g_tls_connection_base_get_base_socket (tls),
-                                            &message, 1, 0,
-                                            g_tls_connection_base_get_write_timeout (tls),
-                                            g_tls_connection_base_get_write_cancellable (tls),
-                                            g_tls_connection_base_get_write_error (tls));
-
-      if (ret > 0)
-        ret = message.bytes_sent;
-    }
-  else
-    {
-      ret = g_pollable_stream_write (G_OUTPUT_STREAM (g_tls_connection_base_get_base_ostream (tls)),
-                                     buf, buflen,
-                                     g_tls_connection_base_get_write_timeout (tls) != 0,
-                                     g_tls_connection_base_get_write_cancellable (tls),
-                                     g_tls_connection_base_get_write_error (tls));
-    }
-
-  if (ret < 0)
-    set_gnutls_error (gnutls, *g_tls_connection_base_get_write_error (tls));
-
-  return ret;
-}
-
-static ssize_t
-g_tls_connection_gnutls_vec_push_func (gnutls_transport_ptr_t  transport_data,
-                                       const giovec_t         *iov,
-                                       int                     iovcnt)
-{
-  GTlsConnectionBase *tls = transport_data;
-  GTlsConnectionGnutls *gnutls = transport_data;
-  ssize_t ret;
-  GOutputMessage message = { NULL, };
-  GOutputVector *vectors;
-
-  g_assert (g_tls_connection_base_is_dtls (tls));
-
-  /* See comment in pull_func. */
-  g_clear_error (g_tls_connection_base_get_write_error (tls));
-
-  /* this entire expression will be evaluated at compile time */
-  if (sizeof *iov == sizeof *vectors &&
-      sizeof iov->iov_base == sizeof vectors->buffer &&
-      G_STRUCT_OFFSET (giovec_t, iov_base) ==
-      G_STRUCT_OFFSET (GOutputVector, buffer) &&
-      sizeof iov->iov_len == sizeof vectors->size &&
-      G_STRUCT_OFFSET (giovec_t, iov_len) ==
-      G_STRUCT_OFFSET (GOutputVector, size))
-    /* ABI is compatible */
-    {
-      message.vectors = (GOutputVector *)iov;
-      message.num_vectors = iovcnt;
-    }
-  else
-    /* ABI is incompatible */
-    {
-      gint i;
-
-      message.vectors = g_newa (GOutputVector, iovcnt);
-      for (i = 0; i < iovcnt; i++)
-        {
-          message.vectors[i].buffer = (void *)iov[i].iov_base;
-          message.vectors[i].size = iov[i].iov_len;
-        }
-      message.num_vectors = iovcnt;
-    }
-
-  ret = g_datagram_based_send_messages (g_tls_connection_base_get_base_socket (tls),
-                                        &message, 1, 0,
-                                        g_tls_connection_base_get_write_timeout (tls),
-                                        g_tls_connection_base_get_write_cancellable (tls),
-                                        g_tls_connection_base_get_write_error (tls));
-
-  if (ret > 0)
-    ret = message.bytes_sent;
-  else if (ret < 0)
-    set_gnutls_error (gnutls, *g_tls_connection_base_get_write_error (tls));
-
-  return ret;
-}
-
-static gboolean
-read_pollable_cb (GPollableInputStream *istream,
-                  gpointer              user_data)
-{
-  gboolean *read_done = user_data;
-
-  *read_done = TRUE;
-
-  return G_SOURCE_CONTINUE;
-}
-
-static gboolean
-read_datagram_based_cb (GDatagramBased *datagram_based,
-                        GIOCondition    condition,
-                        gpointer        user_data)
-{
-  gboolean *read_done = user_data;
-
-  *read_done = TRUE;
-
-  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)
-{
-  GTlsConnectionBase *tls = transport_data;
-
-  /* Fast path. */
-  if (g_tls_connection_base_base_check (tls, G_IO_IN) ||
-      g_cancellable_is_cancelled (g_tls_connection_base_get_read_cancellable (tls)))
-    return 1;
-
-  /* If @ms is 0, GnuTLS wants an instant response, so there’s no need to
-   * construct and query a #GSource. */
-  if (ms > 0)
-    {
-      GMainContext *ctx = NULL;
-      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 (g_tls_connection_base_is_dtls (tls))
-        {
-          read_source = g_datagram_based_create_source (g_tls_connection_base_get_base_socket (tls),
-                                                        G_IO_IN, NULL);
-          g_source_set_callback (read_source, (GSourceFunc)read_datagram_based_cb,
-                                 &read_done, NULL);
-        }
-      else
-        {
-          read_source = g_pollable_input_stream_create_source (g_tls_connection_base_get_base_istream (tls),
-                                                               NULL);
-          g_source_set_callback (read_source, (GSourceFunc)read_pollable_cb,
-                                 &read_done, NULL);
-        }
-
-      g_source_attach (read_source, ctx);
-      g_source_attach (timeout_source, ctx);
-
-      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
-       * will be handled in g_tls_connection_gnutls_pull_func(). */
-      if (g_tls_connection_base_base_check (tls, G_IO_IN) ||
-          g_cancellable_is_cancelled (g_tls_connection_base_get_read_cancellable (tls)))
-        return 1;
-    }
-
-  return 0;
-}
-
-static GTlsSafeRenegotiationStatus
-g_tls_connection_gnutls_handshake_thread_safe_renegotiation_status (GTlsConnectionBase *tls)
-{
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-
-  return gnutls_safe_renegotiation_status (priv->session) ? G_TLS_SAFE_RENEGOTIATION_SUPPORTED_BY_PEER
-                                                          : G_TLS_SAFE_RENEGOTIATION_UNSUPPORTED;
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_gnutls_handshake_thread_request_rehandshake (GTlsConnectionBase  *tls,
-                                                              gint64               timeout,
-                                                              GCancellable        *cancellable,
-                                                              GError             **error)
-{
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-  GTlsConnectionBaseStatus status;
-  int ret;
-
-  /* On a client-side connection, gnutls_handshake() itself will start
-   * a rehandshake, so we only need to do something special here for
-   * server-side connections.
-   */
-  if (!G_IS_TLS_SERVER_CONNECTION (tls))
-    return G_TLS_CONNECTION_BASE_OK;
-
-  BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, timeout, cancellable);
-  ret = gnutls_rehandshake (priv->session);
-  END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret, status, _("Error performing TLS handshake: %s"), error);
-
-  return status;
-}
-
-static GTlsCertificate *
-g_tls_connection_gnutls_retrieve_peer_certificate (GTlsConnectionBase *tls)
-{
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-  const gnutls_datum_t *certs;
-  GTlsCertificateGnutls *chain;
-  unsigned int num_certs;
-
-  if (gnutls_certificate_type_get (priv->session) != GNUTLS_CRT_X509)
-    return NULL;
-
-  certs = gnutls_certificate_get_peers (priv->session, &num_certs);
-  if (!certs || !num_certs)
-    return NULL;
-
-  chain = g_tls_certificate_gnutls_build_chain (certs, num_certs, GNUTLS_X509_FMT_DER);
-  if (!chain)
-    return NULL;
-
-  return G_TLS_CERTIFICATE (chain);
-}
-
-static int
-verify_certificate_cb (gnutls_session_t session)
-{
-  GTlsConnectionBase *tls = gnutls_session_get_ptr (session);
-
-  /* Return 0 for the handshake to continue, non-zero to terminate.
-   * Complete opposite of what OpenSSL does. */
-  return !g_tls_connection_base_handshake_thread_verify_certificate (tls);
-}
-
-static void
-g_tls_connection_gnutls_prepare_handshake (GTlsConnectionBase  *tls,
-                                           gchar              **advertised_protocols)
-{
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-
-  if (advertised_protocols)
-    {
-      gnutls_datum_t *protocols;
-      int n_protos, i;
-
-      n_protos = g_strv_length (advertised_protocols);
-      protocols = g_new (gnutls_datum_t, n_protos);
-      for (i = 0; advertised_protocols[i]; i++)
-        {
-          protocols[i].size = strlen (advertised_protocols[i]);
-          protocols[i].data = (guchar *)advertised_protocols[i];
-        }
-      gnutls_alpn_set_protocols (priv->session, protocols, n_protos, 0);
-      g_free (protocols);
-    }
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_gnutls_handshake_thread_handshake (GTlsConnectionBase  *tls,
-                                                    gint64               timeout,
-                                                    GCancellable        *cancellable,
-                                                    GError             **error)
-{
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-  GTlsConnectionBaseStatus status;
-  int ret;
-
-  if (!g_tls_connection_base_ever_handshaked (tls))
-    g_tls_connection_gnutls_set_handshake_priority (gnutls);
-
-  if (timeout > 0)
-    {
-      unsigned int timeout_ms;
-
-      /* Convert from microseconds to milliseconds, but ensure the timeout
-       * remains positive. */
-      timeout_ms = (timeout + 999) / 1000;
-
-      gnutls_handshake_set_timeout (priv->session, timeout_ms);
-      gnutls_dtls_set_timeouts (priv->session, 1000 /* default */, timeout_ms);
-    }
-
-  BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, timeout, cancellable);
-  ret = gnutls_handshake (priv->session);
-  if (ret == GNUTLS_E_GOT_APPLICATION_DATA)
-    {
-      guint8 buf[1024];
-
-      /* Got app data while waiting for rehandshake; buffer it and try again */
-      ret = gnutls_record_recv (priv->session, buf, sizeof (buf));
-      if (ret > -1)
-        {
-          g_tls_connection_base_handshake_thread_buffer_application_data (tls, buf, ret);
-          ret = GNUTLS_E_AGAIN;
-        }
-    }
-  END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret, status,
-                 _("Error performing TLS handshake"), error);
-
-  return status;
-}
-
-static void
-g_tls_connection_gnutls_complete_handshake (GTlsConnectionBase  *tls,
-                                            gchar              **negotiated_protocol,
-                                            GError             **error)
-{
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-  gnutls_datum_t protocol;
-
-  if (gnutls_alpn_get_selected_protocol (priv->session, &protocol) == 0 && protocol.size > 0)
-    {
-      g_assert (!*negotiated_protocol);
-      *negotiated_protocol = g_strndup ((gchar *)protocol.data, protocol.size);
-    }
-}
-
-static gboolean
-g_tls_connection_gnutls_is_session_resumed (GTlsConnectionBase *tls)
-{
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-
-  return gnutls_session_is_resumed (priv->session);
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_gnutls_read (GTlsConnectionBase  *tls,
-                              void                *buffer,
-                              gsize                count,
-                              gint64               timeout,
-                              gssize              *nread,
-                              GCancellable        *cancellable,
-                              GError             **error)
-{
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-  GTlsConnectionBaseStatus status;
-  gssize ret;
-
-  BEGIN_GNUTLS_IO (gnutls, G_IO_IN, timeout, cancellable);
-  ret = gnutls_record_recv (priv->session, buffer, count);
-  END_GNUTLS_IO (gnutls, G_IO_IN, ret, status, _("Error reading data from TLS socket"), error);
-
-  *nread = MAX (ret, 0);
-  return status;
-}
-
-static gsize
-input_vectors_from_gnutls_datum_t (GInputVector         *vectors,
-                                   guint                 num_vectors,
-                                   const gnutls_datum_t *datum)
-{
-  guint i;
-  gsize total = 0;
-
-  /* Copy into the receive vectors. */
-  for (i = 0; i < num_vectors && total < datum->size; i++)
-    {
-      gsize count;
-      GInputVector *vec = &vectors[i];
-
-      count = MIN (vec->size, datum->size - total);
-
-      memcpy (vec->buffer, datum->data + total, count);
-      total += count;
-    }
-
-  g_assert (total <= datum->size);
-
-  return total;
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_gnutls_read_message (GTlsConnectionBase  *tls,
-                                      GInputVector        *vectors,
-                                      guint                num_vectors,
-                                      gint64               timeout,
-                                      gssize              *nread,
-                                      GCancellable        *cancellable,
-                                      GError             **error)
-{
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-  GTlsConnectionBaseStatus status;
-  gssize ret;
-  gnutls_packet_t packet = { 0, };
-
-  BEGIN_GNUTLS_IO (gnutls, G_IO_IN, timeout, cancellable);
-
-  /* Receive the entire datagram (zero-copy). */
-  ret = gnutls_record_recv_packet (priv->session, &packet);
-
-  if (ret > 0)
-    {
-      gnutls_datum_t data = { 0, };
-
-      gnutls_packet_get (packet, &data, NULL);
-      ret = input_vectors_from_gnutls_datum_t (vectors, num_vectors, &data);
-      gnutls_packet_deinit (packet);
-    }
-
-  END_GNUTLS_IO (gnutls, G_IO_IN, ret, status, _("Error reading data from TLS socket"), error);
-
-  *nread = MAX (ret, 0);
-  return status;
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_gnutls_write (GTlsConnectionBase  *tls,
-                               const void          *buffer,
-                               gsize                count,
-                               gint64               timeout,
-                               gssize              *nwrote,
-                               GCancellable        *cancellable,
-                               GError             **error)
-{
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-  GTlsConnectionBaseStatus status;
-  gssize ret;
-
-  BEGIN_GNUTLS_IO (gnutls, G_IO_OUT, timeout, cancellable);
-  ret = gnutls_record_send (priv->session, buffer, count);
-  END_GNUTLS_IO (gnutls, G_IO_OUT, ret, status, _("Error writing data to TLS socket"), error);
-
-  *nwrote = MAX (ret, 0);
-  return status;
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_gnutls_write_message (GTlsConnectionBase  *tls,
-                                       GOutputVector       *vectors,
-                                       guint                num_vectors,
-                                       gint64               timeout,
-                                       gssize              *nwrote,
-                                       GCancellable        *cancellable,
-                                       GError             **error)
-{
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-  GTlsConnectionBaseStatus status;
-  gssize ret;
-  guint i;
-  gsize total_message_size;
-
-  /* Calculate the total message size and check it’s not too big. */
-  for (i = 0, total_message_size = 0; i < num_vectors; i++)
-    total_message_size += vectors[i].size;
-
-  if (g_tls_connection_base_is_dtls (tls) &&
-      gnutls_dtls_get_data_mtu (priv->session) < total_message_size)
-    {
-      char *message;
-      guint mtu = gnutls_dtls_get_data_mtu (priv->session);
-
-      ret = GNUTLS_E_LARGE_PACKET;
-      message = g_strdup_printf("%s %s",
-                                ngettext ("Message of size %lu byte is too large for DTLS connection",
-                                          "Message of size %lu bytes is too large for DTLS connection", 
total_message_size),
-                                ngettext ("(maximum is %u byte)", "(maximum is %u bytes)", mtu));
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
-                   message,
-                   total_message_size,
-                   mtu);
-      g_free (message);
-
-      return G_TLS_CONNECTION_BASE_ERROR;
-    }
-
-  /* Queue up the data from all the vectors. */
-  gnutls_record_cork (priv->session);
-
-  for (i = 0; i < num_vectors; i++)
-    {
-      ret = gnutls_record_send (priv->session,
-                                vectors[i].buffer, vectors[i].size);
-
-      if (ret < 0 || ret < vectors[i].size)
-        {
-          /* Uncork to restore state, then bail. The peer will receive a
-           * truncated datagram. */
-          break;
-        }
-    }
-
-  BEGIN_GNUTLS_IO (gnutls, G_IO_OUT, timeout, cancellable);
-  ret = gnutls_record_uncork (priv->session, 0  /* flags */);
-  END_GNUTLS_IO (gnutls, G_IO_OUT, ret, status, _("Error writing data to TLS socket"), error);
-
-  *nwrote = MAX (ret, 0);
-  return status;
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_gnutls_close (GTlsConnectionBase  *tls,
-                               gint64               timeout,
-                               GCancellable        *cancellable,
-                               GError             **error)
-{
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
-  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-  GTlsConnectionBaseStatus status;
-  int ret;
-
-  BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, timeout, cancellable);
-  ret = gnutls_bye (priv->session, GNUTLS_SHUT_WR);
-  END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret, status, _("Error performing TLS close: %s"), error);
-
-  return status;
-}
-
-static void
-initialize_gnutls_priority (void)
-{
-  const gchar *priority_override;
-  const gchar *error_pos = NULL;
-  int ret;
-
-  g_assert (!priority);
-
-  priority_override = g_getenv ("G_TLS_GNUTLS_PRIORITY");
-  if (priority_override)
-    {
-      ret = gnutls_priority_init2 (&priority, priority_override, &error_pos, 0);
-      if (ret != GNUTLS_E_SUCCESS)
-        g_warning ("Failed to set GnuTLS session priority with beginning at %s: %s", error_pos, 
gnutls_strerror (ret));
-      return;
-    }
-
-  ret = gnutls_priority_init2 (&priority, "%COMPAT:-VERS-TLS1.1:-VERS-TLS1.0", &error_pos, 
GNUTLS_PRIORITY_INIT_DEF_APPEND);
-  if (ret != GNUTLS_E_SUCCESS)
-    g_warning ("Failed to set GnuTLS session priority with error beginning at %s: %s", error_pos, 
gnutls_strerror (ret));
+  return thread;
 }
 
 static void
 g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass)
 {
-  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
   GTlsConnectionBaseClass *base_class = G_TLS_CONNECTION_BASE_CLASS (klass);
 
-  gobject_class->finalize                                = g_tls_connection_gnutls_finalize;
-
-  base_class->prepare_handshake                          = g_tls_connection_gnutls_prepare_handshake;
-  base_class->handshake_thread_safe_renegotiation_status = 
g_tls_connection_gnutls_handshake_thread_safe_renegotiation_status;
-  base_class->handshake_thread_request_rehandshake       = 
g_tls_connection_gnutls_handshake_thread_request_rehandshake;
-  base_class->handshake_thread_handshake                 = 
g_tls_connection_gnutls_handshake_thread_handshake;
-  base_class->retrieve_peer_certificate                  = g_tls_connection_gnutls_retrieve_peer_certificate;
-  base_class->complete_handshake                         = g_tls_connection_gnutls_complete_handshake;
-  base_class->is_session_resumed                         = g_tls_connection_gnutls_is_session_resumed;
-  base_class->read_fn                                    = g_tls_connection_gnutls_read;
-  base_class->read_message_fn                            = g_tls_connection_gnutls_read_message;
-  base_class->write_fn                                   = g_tls_connection_gnutls_write;
-  base_class->write_message_fn                           = g_tls_connection_gnutls_write_message;
-  base_class->close_fn                                   = g_tls_connection_gnutls_close;
-
-  initialize_gnutls_priority ();
+  base_class->create_op_thread = g_tls_connection_gnutls_create_op_thread;
 }
 
 static void
 g_tls_connection_gnutls_initable_iface_init (GInitableIface *iface)
 {
+  g_tls_connection_gnutls_parent_initable_iface = g_type_interface_peek_parent (iface);
+
   iface->init = g_tls_connection_gnutls_initable_init;
 }
diff --git a/tls/gnutls/gtlsconnection-gnutls.h b/tls/gnutls/gtlsconnection-gnutls.h
index 55ae5ee..d7747d9 100644
--- a/tls/gnutls/gtlsconnection-gnutls.h
+++ b/tls/gnutls/gtlsconnection-gnutls.h
@@ -41,13 +41,4 @@ struct _GTlsConnectionGnutlsClass
   GTlsConnectionBaseClass parent_class;
 };
 
-gnutls_certificate_credentials_t g_tls_connection_gnutls_get_credentials (GTlsConnectionGnutls *connection);
-
-gnutls_session_t                 g_tls_connection_gnutls_get_session     (GTlsConnectionGnutls *connection);
-
-void     g_tls_connection_gnutls_handshake_thread_get_certificate     (GTlsConnectionGnutls  *gnutls,
-                                                                       gnutls_pcert_st      **pcert,
-                                                                       unsigned int          *pcert_length,
-                                                                       gnutls_privkey_t      *pkey);
-
 G_END_DECLS
diff --git a/tls/gnutls/gtlsoperationsthread-gnutls.c b/tls/gnutls/gtlsoperationsthread-gnutls.c
new file mode 100644
index 0000000..5cccdd7
--- /dev/null
+++ b/tls/gnutls/gtlsoperationsthread-gnutls.c
@@ -0,0 +1,1601 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2009 Red Hat, Inc
+ * Copyright 2015, 2016 Collabora, Ltd.
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * In addition, when the library is used with OpenSSL, a special
+ * exception applies. Refer to the LICENSE_EXCEPTION file for details.
+ */
+
+#include "config.h"
+#include "gtlsoperationsthread-gnutls.h"
+
+#include "gtlsbackend-gnutls.h"
+#include "gtlscertificate-gnutls.h"
+#include "gtlsconnection-gnutls.h"
+
+#include <errno.h>
+#include <glib/gi18n-lib.h>
+#include <gnutls/dtls.h>
+#include <limits.h>
+
+struct _GTlsOperationsThreadGnutls {
+  GTlsOperationsThreadBase parent_instance;
+
+  guint                            init_flags;
+  gnutls_certificate_credentials_t creds;
+
+  /* session_data is either the session ticket that was used to resume this
+   * connection, or the most recent session ticket received from the server.
+   * Because session ticket reuse is generally undesirable, it should only be
+   * accessed if session_data_override is set.
+   */
+  GBytes                  *session_id;
+  GBytes                  *session_data;
+  gboolean                 session_data_override; /* FIXME: sort this all out */
+
+  gnutls_session_t         session;
+
+  GIOStream               *base_iostream;
+  GInputStream            *base_istream;
+  GOutputStream           *base_ostream;
+  GDatagramBased          *base_socket;
+
+  HandshakeContext        *handshake_context;
+  gboolean                 handshaking;
+  gboolean                 ever_handshaked;
+
+  /* This data is valid only during current operation */
+  GTlsAuthenticationMode   op_auth_mode;
+  GTlsCertificate         *op_own_certificate;
+  GTlsCertificate         *op_peer_certificate;
+  GCancellable            *op_cancellable;
+  GError                  *op_error;
+
+  /* Certificate internals, must be kept alive here. */
+  gnutls_pcert_st         *pcert;
+  unsigned int             pcert_length;
+  gnutls_privkey_t         pkey;
+
+  GList                   *accepted_cas;
+
+  gchar                   *server_identity;
+
+  gchar                   *interaction_id;
+
+  GByteArray              *application_data_buffer;
+};
+
+enum
+{
+  PROP_0,
+  PROP_GNUTLS_FLAGS,
+  LAST_PROP
+};
+
+static GParamSpec *obj_properties[LAST_PROP];
+
+static gnutls_priority_t priority;
+
+static GInitableIface *g_tls_operations_thread_gnutls_parent_initable_iface;
+
+static void g_tls_operations_thread_gnutls_initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GTlsOperationsThreadGnutls, g_tls_operations_thread_gnutls, 
G_TYPE_TLS_OPERATIONS_THREAD_BASE,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                g_tls_operations_thread_gnutls_initable_iface_init);
+                         )
+
+static inline gboolean
+is_dtls (GTlsOperationsThreadGnutls *self)
+{
+  return self->init_flags & GNUTLS_DATAGRAM;
+}
+
+static inline gboolean
+is_client (GTlsOperationsThreadGnutls *self)
+{
+  return self->init_flags & GNUTLS_CLIENT;
+}
+
+static inline gboolean
+is_server (GTlsOperationsThreadGnutls *self)
+{
+  return self->init_flags & GNUTLS_SERVER;
+}
+
+static void
+begin_gnutls_io (GTlsOperationsThreadGnutls *self,
+                 GIOCondition                direction,
+                 GCancellable               *cancellable)
+{
+  g_assert (!self->op_error);
+  g_assert (!self->op_cancellable);
+
+  self->op_cancellable = cancellable;
+
+  g_tls_operations_thread_base_push_io (G_TLS_OPERATIONS_THREAD_BASE (self),
+                                        direction, cancellable);
+}
+
+static GTlsOperationStatus
+end_gnutls_io (GTlsOperationsThreadGnutls  *self,
+               GIOCondition                 direction,
+               int                          ret,
+               GError                     **error,
+               const char                  *err_prefix)
+{
+  GTlsOperationStatus status;
+  GError *my_error = NULL;
+
+  /* We intentionally do not check for GNUTLS_E_INTERRUPTED here
+   * Instead, the caller may poll for the source to become ready again.
+   * (Note that GTlsOutputStreamGnutls and GTlsInputStreamGnutls inherit
+   * from GPollableOutputStream and GPollableInputStream, respectively.)
+   * See also the comment in set_gnutls_error().
+   */
+  if (ret == GNUTLS_E_AGAIN ||
+      ret == GNUTLS_E_WARNING_ALERT_RECEIVED)
+    return G_TLS_OPERATION_TRY_AGAIN;
+
+  self->op_cancellable = NULL;
+
+  status = g_tls_operations_thread_base_pop_io (G_TLS_OPERATIONS_THREAD_BASE (self),
+                                                direction,
+                                                ret >= 0,
+                                                g_steal_pointer (&self->op_error),
+                                                &my_error);
+
+  if (status == G_TLS_OPERATION_SUCCESS ||
+      status == G_TLS_OPERATION_WOULD_BLOCK ||
+      status == G_TLS_OPERATION_TIMED_OUT)
+    {
+      if (my_error)
+        g_propagate_error (error, my_error);
+      return status;
+    }
+
+  g_assert (status == G_TLS_OPERATION_ERROR);
+
+  if (self->handshaking && !self->ever_handshaked)
+    {
+      if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_FAILED) ||
+          g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE))
+        {
+          g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
+                       _("Peer failed to perform TLS handshake: %s"), my_error->message);
+          g_clear_error (&my_error);
+          return G_TLS_OPERATION_ERROR;
+        }
+
+      if (status == GNUTLS_E_UNEXPECTED_PACKET_LENGTH ||
+          status == GNUTLS_E_DECRYPTION_FAILED ||
+          status == GNUTLS_E_UNSUPPORTED_VERSION_PACKET)
+        {
+          g_clear_error (&my_error);
+          g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
+                       _("Peer failed to perform TLS handshake: %s"), gnutls_strerror (ret));
+          return G_TLS_OPERATION_ERROR;
+        }
+    }
+
+  if (ret == GNUTLS_E_REHANDSHAKE)
+    {
+      if (is_client (self))
+        {
+          /* Ignore server's request for rehandshake, because we no longer
+           * support obsolete TLS rehandshakes.
+           *
+           * TODO: Send GNUTLS_A_NO_RENEGOTIATION here once we support alerts.
+           */
+          return G_TLS_OPERATION_SUCCESS;
+        }
+      else
+        {
+          /* Are you hitting this error? If so, we may need to restore support
+           * for obsolete TLS rehandshakes. Hopefully not, because not many
+           * applications use GTlsServerConnection, and presumably not many
+           * clients request rehandshakes.
+           *
+           * The server cannot simply ignore a rehandshake request like clients
+           * can, so this is fatal.
+           */
+          g_clear_error (&my_error);
+          g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+                               _("Client requested TLS rehandshake, which is no longer supported"));
+          return G_TLS_OPERATION_ERROR;
+        }
+    }
+
+  if (ret == GNUTLS_E_PREMATURE_TERMINATION)
+    {
+      if (self->handshaking && !self->ever_handshaked)
+        {
+          g_clear_error (&my_error);
+          g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
+                       _("Peer failed to perform TLS handshake: %s"), gnutls_strerror (ret));
+          return G_TLS_OPERATION_ERROR;
+        }
+
+      if (g_tls_operations_thread_base_get_close_notify_required (G_TLS_OPERATIONS_THREAD_BASE (self)))
+        {
+          g_clear_error (&my_error);
+          g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_EOF,
+                               _("TLS connection closed unexpectedly"));
+          return G_TLS_OPERATION_ERROR;
+        }
+
+      return G_TLS_OPERATION_SUCCESS;
+    }
+
+  if (ret == GNUTLS_E_NO_CERTIFICATE_FOUND || ret == GNUTLS_E_CERTIFICATE_REQUIRED)
+    {
+      g_clear_error (&my_error);
+      g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
+                           _("TLS connection peer did not send a certificate"));
+      return G_TLS_OPERATION_ERROR;
+    }
+
+  if (ret == GNUTLS_E_CERTIFICATE_ERROR)
+    {
+      g_clear_error (&my_error);
+      g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+                           _("Unacceptable TLS certificate"));
+      return G_TLS_OPERATION_ERROR;
+    }
+
+  if (ret == GNUTLS_E_FATAL_ALERT_RECEIVED)
+    {
+      g_clear_error (&my_error);
+      g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+                   _("Peer sent fatal TLS alert: %s"),
+                   gnutls_alert_get_name (gnutls_alert_get (self->session)));
+      return G_TLS_OPERATION_ERROR;
+    }
+
+  if (ret == GNUTLS_E_INAPPROPRIATE_FALLBACK)
+    {
+      g_clear_error (&my_error);
+      g_set_error_literal (error, G_TLS_ERROR,
+                           G_TLS_ERROR_INAPPROPRIATE_FALLBACK,
+                           _("Protocol version downgrade attack detected"));
+      return G_TLS_OPERATION_ERROR;
+    }
+
+  if (ret == GNUTLS_E_LARGE_PACKET)
+    {
+      guint mtu = gnutls_dtls_get_data_mtu (self->session);
+      g_clear_error (&my_error);
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
+                   ngettext ("Message is too large for DTLS connection; maximum is %u byte",
+                             "Message is too large for DTLS connection; maximum is %u bytes", mtu), mtu);
+      return G_TLS_OPERATION_ERROR;
+    }
+
+  if (ret == GNUTLS_E_TIMEDOUT)
+    {
+      g_clear_error (&my_error);
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+                           _("The operation timed out"));
+      return G_TLS_OPERATION_ERROR;
+    }
+
+  if (error && my_error)
+    g_propagate_error (error, my_error);
+
+  if (error && !*error)
+    {
+      *error = g_error_new (G_TLS_ERROR, G_TLS_ERROR_MISC, "%s: %s",
+                            err_prefix, gnutls_strerror (ret));
+    }
+
+  return G_TLS_OPERATION_ERROR;
+}
+
+/* FIXME: do not use GTlsConnectionBase at all. */
+
+#define BEGIN_GNUTLS_IO(self, direction, cancellable)          \
+  begin_gnutls_io (self, direction, cancellable);              \
+  do {
+
+#define END_GNUTLS_IO(self, direction, ret, status, errmsg, err)      \
+    status = end_gnutls_io (self, direction, ret, err, errmsg);       \
+  } while (status == G_TLS_OPERATION_TRY_AGAIN);
+
+static void
+initialize_gnutls_priority (void)
+{
+  const gchar *priority_override;
+  const gchar *error_pos = NULL;
+  int ret;
+
+  g_assert (!priority);
+
+  priority_override = g_getenv ("G_TLS_GNUTLS_PRIORITY");
+  if (priority_override)
+    {
+      ret = gnutls_priority_init2 (&priority, priority_override, &error_pos, 0);
+      if (ret != GNUTLS_E_SUCCESS)
+        g_warning ("Failed to set GnuTLS session priority with beginning at %s: %s", error_pos, 
gnutls_strerror (ret));
+      return;
+    }
+
+  ret = gnutls_priority_init2 (&priority, "%COMPAT:-VERS-TLS1.1:-VERS-TLS1.0", &error_pos, 
GNUTLS_PRIORITY_INIT_DEF_APPEND);
+  if (ret != GNUTLS_E_SUCCESS)
+    g_warning ("Failed to set GnuTLS session priority with error beginning at %s: %s", error_pos, 
gnutls_strerror (ret));
+}
+
+static GTlsCertificate *
+g_tls_operations_thread_gnutls_copy_certificate (GTlsOperationsThreadBase *base,
+                                                 GTlsCertificate          *cert)
+{
+  /* FIXME: need a real copy to avoid sharing the certificate across threads.
+   * Copy must copy private key. Must copy ENTIRE CHAIN including issuers.
+   */
+
+  return cert ? g_object_ref (cert) : NULL;
+}
+
+static void
+g_tls_operations_thread_gnutls_copy_client_session_state (GTlsOperationsThreadBase *base,
+                                                          GTlsOperationsThreadBase *base_source)
+{
+  GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+  GTlsOperationsThreadGnutls *source = G_TLS_OPERATIONS_THREAD_GNUTLS (base_source);
+
+  g_assert (is_client (self));
+
+  /* Precondition: source has handshaked, conn has not. */
+  g_return_if_fail (!self->session_id);
+  g_return_if_fail (source->session_id);
+
+  /* Prefer to use a new session ticket, if possible. */
+  self->session_data = g_tls_backend_gnutls_lookup_session_data (source->session_id);
+
+  if (!self->session_data && source->session_data)
+    {
+      /* If it's not possible, we'll try to reuse the old ticket, even though
+       * this is a privacy risk since TLS 1.3. Applications should not use this
+       * function unless they need us to try as hard as possible to resume a
+       * session, even at the cost of privacy.
+       */
+      self->session_data = g_bytes_ref (source->session_data);
+    }
+
+  self->session_data_override = !!self->session_data;
+}
+
+static void
+g_tls_operations_thread_gnutls_set_server_identity (GTlsOperationsThreadBase *base,
+                                                    const gchar              *server_identity)
+{
+  GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+  gchar *normalized_hostname;
+  size_t len;
+
+  g_assert (is_client (self));
+
+  normalized_hostname = g_strdup (server_identity);
+  len = strlen (server_identity);
+
+  if (server_identity[len - 1] == '.')
+    {
+      normalized_hostname[len - 1] = '\0';
+      len--;
+    }
+
+  gnutls_server_name_set (self->session, GNUTLS_NAME_DNS,
+                          normalized_hostname, len);
+
+  g_clear_pointer (&self->server_identity, g_free);
+  self->server_identity = g_steal_pointer (&normalized_hostname);
+}
+
+static void
+set_handshake_priority (GTlsOperationsThreadGnutls *self)
+{
+  int ret;
+
+  g_assert (priority);
+
+  ret = gnutls_priority_set (self->session, priority);
+  if (ret != GNUTLS_E_SUCCESS)
+    g_warning ("Failed to set GnuTLS session priority: %s", gnutls_strerror (ret));
+}
+
+static void
+set_handshake_timeout (GTlsOperationsThreadGnutls *self,
+                       gint64                      timeout)
+{
+  unsigned int timeout_ms;
+
+  /* Convert from microseconds to milliseconds, but ensure the timeout
+   * remains positive.
+   */
+  timeout_ms = (timeout + 999) / 1000;
+
+  if (is_dtls (self))
+    gnutls_dtls_set_timeouts (self->session, 1000 /* default */, timeout_ms);
+  else
+    gnutls_handshake_set_timeout (self->session, timeout_ms);
+}
+
+static void
+set_advertised_protocols (GTlsOperationsThreadGnutls  *self,
+                          const gchar                **advertised_protocols)
+{
+  gnutls_datum_t *protocols;
+  int n_protos, i;
+
+  n_protos = g_strv_length ((gchar **)advertised_protocols);
+  protocols = g_new (gnutls_datum_t, n_protos);
+  for (i = 0; advertised_protocols[i]; i++)
+    {
+      protocols[i].size = strlen (advertised_protocols[i]);
+      protocols[i].data = (guchar *)advertised_protocols[i];
+    }
+  gnutls_alpn_set_protocols (self->session, protocols, n_protos, 0);
+  g_free (protocols);
+}
+
+static void
+compute_session_id (GTlsOperationsThreadGnutls *self)
+{
+  GSocketAddress *remote_addr;
+  GInetAddress *iaddr;
+  guint port;
+
+  g_assert (is_client (self));
+
+  /* The testsuite expects handshakes to actually happen. E.g. a test might
+   * check to see that a handshake succeeds and then later check that a new
+   * handshake fails. If we get really unlucky and the same port number is
+   * reused for the server socket between connections, then we'll accidentally
+   * resume the old session and skip certificate verification. Such failures
+   * are difficult to debug because they require running the tests hundreds of
+   * times simultaneously to reproduce (the port number does not get reused
+   * quickly enough if the tests are run sequentially).
+   *
+   * So session resumption will just need to be tested manually.
+   */
+  if (g_test_initialized ())
+    return;
+
+  /* Create a TLS "session ID." We base it on the IP address since
+   * different hosts serving the same hostname/service will probably
+   * not share the same session cache. We base it on the
+   * server-identity because at least some servers will fail (rather
+   * than just failing to resume the session) if we don't.
+   * (https://bugs.launchpad.net/bugs/823325)
+   *
+   * Note that our session IDs have no relation to TLS protocol
+   * session IDs, e.g. as provided by gnutls_session_get_id2(). Unlike
+   * our session IDs, actual TLS session IDs can no longer be used for
+   * session resumption.
+   */
+  if (G_IS_SOCKET_CONNECTION (self->base_iostream))
+    {
+      remote_addr = g_socket_connection_get_remote_address (G_SOCKET_CONNECTION (self->base_iostream), NULL);
+      if (G_IS_INET_SOCKET_ADDRESS (remote_addr))
+        {
+          GInetSocketAddress *isaddr = G_INET_SOCKET_ADDRESS (remote_addr);
+          const gchar *server_hostname;
+          gchar *addrstr;
+          gchar *session_id;
+          gchar *cert_hash = NULL;
+
+          iaddr = g_inet_socket_address_get_address (isaddr);
+          port = g_inet_socket_address_get_port (isaddr);
+
+          addrstr = g_inet_address_to_string (iaddr);
+          server_hostname = self->server_identity;
+
+          /* If we have a certificate, make its hash part of the session ID, so
+           * that different connections to the same server can use different
+           * certificates.
+           */
+          if (self->op_own_certificate)
+            {
+              GByteArray *der = NULL;
+              g_object_get (self->op_own_certificate,
+                            "certificate", &der,
+                            NULL);
+              if (der)
+                {
+                  cert_hash = g_compute_checksum_for_data (G_CHECKSUM_SHA256, der->data, der->len);
+                  g_byte_array_unref (der);
+                }
+            }
+
+          session_id = g_strdup_printf ("%s/%s/%d/%s", addrstr,
+                                        server_hostname ? server_hostname : "",
+                                        port,
+                                        cert_hash ? cert_hash : "");
+          self->session_id = g_bytes_new_take (session_id, strlen (session_id));
+          g_free (addrstr);
+          g_free (cert_hash);
+        }
+      g_object_unref (remote_addr);
+    }
+}
+
+static void
+set_session_data (GTlsOperationsThreadGnutls *self)
+{
+  g_assert (is_client (self));
+
+  compute_session_id (self);
+
+  if (self->session_data_override)
+    {
+      g_assert (self->session_data);
+      gnutls_session_set_data (self->session,
+                               g_bytes_get_data (self->session_data, NULL),
+                               g_bytes_get_size (self->session_data));
+    }
+  else if (self->session_id)
+    {
+      GBytes *session_data;
+
+      session_data = g_tls_backend_gnutls_lookup_session_data (self->session_id);
+      if (session_data)
+        {
+          gnutls_session_set_data (self->session,
+                                   g_bytes_get_data (session_data, NULL),
+                                   g_bytes_get_size (session_data));
+          g_clear_pointer (&self->session_data, g_bytes_unref);
+          self->session_data = g_steal_pointer (&session_data);
+        }
+    }
+}
+
+static void
+set_authentication_mode (GTlsOperationsThreadGnutls *self,
+                         GTlsAuthenticationMode      auth_mode)
+{
+  gnutls_certificate_request_t req = GNUTLS_CERT_IGNORE;
+
+  g_assert (is_server (self));
+
+  switch (auth_mode)
+    {
+    case G_TLS_AUTHENTICATION_REQUESTED:
+      req = GNUTLS_CERT_REQUEST;
+      break;
+    case G_TLS_AUTHENTICATION_REQUIRED:
+      req = GNUTLS_CERT_REQUIRE;
+      break;
+    default:
+      break;
+    }
+
+  gnutls_certificate_server_set_request (self->session, req);
+}
+
+static GTlsCertificate *
+get_peer_certificate (GTlsOperationsThreadGnutls *self)
+{
+  const gnutls_datum_t *certs;
+  unsigned int num_certs;
+
+  if (gnutls_certificate_type_get (self->session) == GNUTLS_CRT_X509)
+    {
+      certs = gnutls_certificate_get_peers (self->session, &num_certs);
+      if (certs && num_certs > 0)
+        return g_tls_certificate_gnutls_build_chain (certs, num_certs, GNUTLS_X509_FMT_DER);
+    }
+
+  return NULL;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_gnutls_handshake (GTlsOperationsThreadBase  *base,
+                                          HandshakeContext          *context,
+                                          GTlsCertificate           *own_certificate,
+                                          const gchar              **advertised_protocols,
+                                          GTlsAuthenticationMode     auth_mode,
+                                          gint64                     timeout,
+                                          gchar                    **negotiated_protocol,
+                                          GList                    **accepted_cas,
+                                          GTlsCertificate          **peer_certificate,
+                                          GCancellable              *cancellable,
+                                          GError                   **error)
+{
+  GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+  GTlsOperationStatus status;
+  gnutls_datum_t protocol;
+  int ret;
+
+  self->op_own_certificate = own_certificate;
+  self->op_auth_mode = auth_mode;
+
+  if (!self->ever_handshaked)
+    set_handshake_priority (self);
+
+  if (timeout > 0)
+    set_handshake_timeout (self, timeout);
+
+  if (advertised_protocols)
+    set_advertised_protocols (self, advertised_protocols);
+
+  if (is_client (self))
+    set_session_data (self);
+
+  if (is_server (self))
+    set_authentication_mode (self, auth_mode);
+
+  self->handshaking = TRUE;
+  self->handshake_context = context;
+
+  BEGIN_GNUTLS_IO (self, G_IO_IN | G_IO_OUT, cancellable);
+  ret = gnutls_handshake (self->session);
+  if (ret == GNUTLS_E_GOT_APPLICATION_DATA)
+    {
+      guint8 buf[1024];
+
+      /* FIXME: no longer supports rehandshake, but what about reauth? */
+      /* Got app data while waiting for rehandshake; buffer it and try again */
+      ret = gnutls_record_recv (self->session, buf, sizeof (buf));
+      if (ret > -1)
+        {
+          if (!self->application_data_buffer)
+            self->application_data_buffer = g_byte_array_new ();
+          g_byte_array_append (self->application_data_buffer, buf, ret);
+          ret = GNUTLS_E_AGAIN;
+        }
+    }
+  END_GNUTLS_IO (self, G_IO_IN | G_IO_OUT, ret, status,
+                 _("Error performing TLS handshake"), error);
+
+  self->op_own_certificate = NULL;
+  self->op_auth_mode = G_TLS_AUTHENTICATION_NONE;
+  self->handshake_context = NULL;
+  self->handshaking = FALSE;
+
+  if (status == G_TLS_OPERATION_SUCCESS)
+    self->ever_handshaked = TRUE;
+
+  if (gnutls_alpn_get_selected_protocol (self->session, &protocol) == 0 && protocol.size > 0)
+    *negotiated_protocol = g_strndup ((gchar *)protocol.data, protocol.size);
+  else
+    *negotiated_protocol = NULL;
+
+  *accepted_cas = g_list_copy (self->accepted_cas);
+
+  if (!self->op_peer_certificate)
+    self->op_peer_certificate = get_peer_certificate (self);
+  *peer_certificate = g_steal_pointer (&self->op_peer_certificate);
+
+  return status;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_gnutls_read (GTlsOperationsThreadBase  *base,
+                                     void                      *buffer,
+                                     gsize                      size,
+                                     gssize                    *nread,
+                                     GCancellable              *cancellable,
+                                     GError                   **error)
+{
+  GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+  GTlsOperationStatus status;
+  gssize ret;
+
+  if (self->application_data_buffer)
+    {
+      *nread = MIN (size, self->application_data_buffer->len);
+      memcpy (buffer, self->application_data_buffer->data, *nread);
+      if (*nread == self->application_data_buffer->len)
+        g_clear_pointer (&self->application_data_buffer, g_byte_array_unref);
+      else
+        g_byte_array_remove_range (self->application_data_buffer, 0, *nread);
+      return G_TLS_OPERATION_SUCCESS;
+    }
+
+  BEGIN_GNUTLS_IO (self, G_IO_IN, cancellable);
+  ret = gnutls_record_recv (self->session, buffer, size);
+  END_GNUTLS_IO (self, G_IO_IN, ret, status, _("Error reading data from TLS socket"), error);
+
+  *nread = MAX (ret, 0);
+  return status;
+}
+
+static gsize
+input_vectors_from_gnutls_datum_t (GInputVector         *vectors,
+                                   guint                 num_vectors,
+                                   const gnutls_datum_t *datum)
+{
+  guint i;
+  gsize total = 0;
+
+  /* Copy into the receive vectors. */
+  for (i = 0; i < num_vectors && total < datum->size; i++)
+    {
+      gsize count;
+      GInputVector *vec = &vectors[i];
+
+      count = MIN (vec->size, datum->size - total);
+
+      memcpy (vec->buffer, datum->data + total, count);
+      total += count;
+    }
+
+  g_assert (total <= datum->size);
+
+  return total;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_gnutls_read_message (GTlsOperationsThreadBase  *base,
+                                             GInputVector              *vectors,
+                                             guint                      num_vectors,
+                                             gssize                    *nread,
+                                             GCancellable              *cancellable,
+                                             GError                   **error)
+{
+  GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+  GTlsOperationStatus status;
+  gssize ret;
+  gnutls_packet_t packet = { 0, };
+
+    /* Copy data out of the app data buffer first. */
+    if (self->application_data_buffer)
+      {
+        *nread = 0;
+
+        for (guint i = 0; i < num_vectors; i++)
+          {
+            gsize count;
+            GInputVector *vec = &vectors[i];
+
+            count = MIN (vec->size, self->application_data_buffer->len);
+            *nread += count;
+
+            memcpy (vec->buffer, self->application_data_buffer->data, count);
+            if (count == self->application_data_buffer->len)
+              g_clear_pointer (&self->application_data_buffer, g_byte_array_unref);
+            else
+              g_byte_array_remove_range (self->application_data_buffer, 0, count);
+            return G_TLS_OPERATION_SUCCESS;
+          }
+      }
+
+  BEGIN_GNUTLS_IO (self, G_IO_IN, cancellable);
+
+  /* Receive the entire datagram (zero-copy). */
+  ret = gnutls_record_recv_packet (self->session, &packet);
+
+  if (ret > 0)
+    {
+      gnutls_datum_t data = { 0, };
+
+      gnutls_packet_get (packet, &data, NULL);
+      ret = input_vectors_from_gnutls_datum_t (vectors, num_vectors, &data);
+      gnutls_packet_deinit (packet);
+    }
+
+  END_GNUTLS_IO (self, G_IO_IN, ret, status, _("Error reading data from TLS socket"), error);
+
+  *nread = MAX (ret, 0);
+  return status;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_gnutls_write (GTlsOperationsThreadBase  *base,
+                                      const void                *buffer,
+                                      gsize                      size,
+                                      gssize                    *nwrote,
+                                      GCancellable              *cancellable,
+                                      GError                   **error)
+{
+  GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+  GTlsOperationStatus status;
+  gssize ret;
+
+  BEGIN_GNUTLS_IO (self, G_IO_OUT, cancellable);
+  ret = gnutls_record_send (self->session, buffer, size);
+  END_GNUTLS_IO (self, G_IO_OUT, ret, status, _("Error writing data to TLS socket"), error);
+
+  *nwrote = MAX (ret, 0);
+  return status;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_gnutls_write_message (GTlsOperationsThreadBase  *base,
+                                              GOutputVector             *vectors,
+                                              guint                      num_vectors,
+                                              gssize                    *nwrote,
+                                              GCancellable              *cancellable,
+                                              GError                   **error)
+{
+  GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+  GTlsOperationStatus status;
+  gssize ret;
+  guint i;
+  gsize total_message_size;
+
+  /* Calculate the total message size and check it’s not too big. */
+  for (i = 0, total_message_size = 0; i < num_vectors; i++)
+    total_message_size += vectors[i].size;
+
+  if (is_dtls (self) &&
+      gnutls_dtls_get_data_mtu (self->session) < total_message_size)
+    {
+      char *message;
+      guint mtu = gnutls_dtls_get_data_mtu (self->session);
+
+      ret = GNUTLS_E_LARGE_PACKET;
+      message = g_strdup_printf("%s %s",
+                                ngettext ("Message of size %lu byte is too large for DTLS connection",
+                                          "Message of size %lu bytes is too large for DTLS connection", 
total_message_size),
+                                ngettext ("(maximum is %u byte)", "(maximum is %u bytes)", mtu));
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
+                   message,
+                   total_message_size,
+                   mtu);
+      g_free (message);
+
+      return G_TLS_OPERATION_ERROR;
+    }
+
+  /* Queue up the data from all the vectors. */
+  gnutls_record_cork (self->session);
+
+  for (i = 0; i < num_vectors; i++)
+    {
+      ret = gnutls_record_send (self->session,
+                                vectors[i].buffer, vectors[i].size);
+
+      if (ret < 0 || ret < vectors[i].size)
+        {
+          /* Uncork to restore state, then bail. The peer will receive a
+           * truncated datagram.
+           */
+          break;
+        }
+    }
+
+  BEGIN_GNUTLS_IO (self, G_IO_OUT, cancellable);
+  ret = gnutls_record_uncork (self->session, 0  /* flags */);
+  END_GNUTLS_IO (self, G_IO_OUT, ret, status, _("Error writing data to TLS socket"), error);
+
+  *nwrote = MAX (ret, 0);
+  return status;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_gnutls_close (GTlsOperationsThreadBase  *base,
+                                      GCancellable              *cancellable,
+                                      GError                   **error)
+{
+  GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+  GTlsOperationStatus status;
+  int ret;
+
+  BEGIN_GNUTLS_IO (self, G_IO_IN | G_IO_OUT, cancellable);
+  ret = gnutls_bye (self->session, GNUTLS_SHUT_WR);
+  END_GNUTLS_IO (self, G_IO_IN | G_IO_OUT, ret, status, _("Error performing TLS close: %s"), error);
+
+  return status;
+}
+
+static void
+set_gnutls_error (GTlsOperationsThreadGnutls *self,
+                  GError                     *error)
+{
+  /* We set EINTR rather than EAGAIN for G_IO_ERROR_WOULD_BLOCK so
+   * that GNUTLS_E_AGAIN only gets returned for gnutls-internal
+   * reasons, not for actual socket EAGAINs (and we have access
+   * to @error at the higher levels, so we can distinguish them
+   * that way later).
+   */
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    gnutls_transport_set_errno (self->session, EINTR);
+  else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+    {
+      /* Return EAGAIN while handshaking so that GnuTLS handles retries for us
+       * internally in its handshaking code.
+       */
+      if (is_dtls (self) && self->handshaking)
+        gnutls_transport_set_errno (self->session, EAGAIN);
+      else
+        gnutls_transport_set_errno (self->session, EINTR);
+    }
+  else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
+    gnutls_transport_set_errno (self->session, EINTR);
+  else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE))
+    gnutls_transport_set_errno (self->session, EMSGSIZE);
+  else
+    gnutls_transport_set_errno (self->session, EIO);
+}
+
+static ssize_t
+g_tls_operations_thread_gnutls_pull_func (gnutls_transport_ptr_t  transport_data,
+                                          void                   *buf,
+                                          size_t                  buflen)
+{
+  GTlsOperationsThreadGnutls *self = transport_data;
+  ssize_t ret;
+
+  /* If op_error is nonnull when we're called, it means
+   * that an error previously occurred, but GnuTLS decided not to
+   * propagate it. So it's correct for us to just clear it. (Usually
+   * this means it ignored an EAGAIN after a short read, and now
+   * we'll return EAGAIN again, which it will obey this time.)
+   */
+  g_clear_error (&self->op_error);
+
+  if (is_dtls (self))
+    {
+      GInputVector vector = { buf, buflen };
+      GInputMessage message = { NULL, &vector, 1, 0, 0, NULL, NULL };
+
+      ret = g_datagram_based_receive_messages (self->base_socket,
+                                               &message, 1,
+                                               0, 0,
+                                               self->op_cancellable,
+                                               &self->op_error);
+
+      if (ret > 0)
+        ret = message.bytes_received;
+    }
+  else
+    {
+      ret = g_pollable_stream_read (self->base_istream,
+                                    buf, buflen,
+                                    FALSE,
+                                    self->op_cancellable,
+                                    &self->op_error);
+    }
+
+  if (ret < 0)
+    set_gnutls_error (self, self->op_error);
+
+  return ret;
+}
+
+static ssize_t
+g_tls_operations_thread_gnutls_push_func (gnutls_transport_ptr_t  transport_data,
+                                          const void             *buf,
+                                          size_t                  buflen)
+{
+  GTlsOperationsThreadGnutls *self = transport_data;
+  ssize_t ret;
+
+  /* See comment in pull_func. */
+  g_clear_error (&self->op_error);
+
+  if (is_dtls (self))
+    {
+      GOutputVector vector = { buf, buflen };
+      GOutputMessage message = { NULL, &vector, 1, 0, NULL, 0 };
+
+      ret = g_datagram_based_send_messages (self->base_socket,
+                                            &message, 1,
+                                            0, 0,
+                                            self->op_cancellable,
+                                            &self->op_error);
+
+      if (ret > 0)
+        ret = message.bytes_sent;
+    }
+  else
+    {
+      ret = g_pollable_stream_write (self->base_ostream,
+                                     buf, buflen,
+                                     FALSE,
+                                     self->op_cancellable,
+                                     &self->op_error);
+    }
+
+  if (ret < 0)
+    set_gnutls_error (self, self->op_error);
+
+  return ret;
+}
+
+static ssize_t
+g_tls_operations_thread_gnutls_vec_push_func (gnutls_transport_ptr_t  transport_data,
+                                              const giovec_t         *iov,
+                                              int                     iovcnt)
+{
+  GTlsOperationsThreadGnutls *self = transport_data;
+  ssize_t ret;
+  GOutputMessage message = { NULL, };
+  GOutputVector *vectors;
+
+  g_assert (is_dtls (self));
+
+  /* See comment in pull_func. */
+  g_clear_error (&self->op_error);
+
+  /* this entire expression will be evaluated at compile time */
+  if (sizeof *iov == sizeof *vectors &&
+      sizeof iov->iov_base == sizeof vectors->buffer &&
+      G_STRUCT_OFFSET (giovec_t, iov_base) == G_STRUCT_OFFSET (GOutputVector, buffer) &&
+      sizeof iov->iov_len == sizeof vectors->size &&
+      G_STRUCT_OFFSET (giovec_t, iov_len) == G_STRUCT_OFFSET (GOutputVector, size))
+    /* ABI is compatible */
+    {
+      message.vectors = (GOutputVector *)iov;
+      message.num_vectors = iovcnt;
+    }
+  else
+    /* ABI is incompatible */
+    {
+      gint i;
+
+      message.vectors = g_newa (GOutputVector, iovcnt);
+      for (i = 0; i < iovcnt; i++)
+        {
+          message.vectors[i].buffer = (void *)iov[i].iov_base;
+          message.vectors[i].size = iov[i].iov_len;
+        }
+      message.num_vectors = iovcnt;
+    }
+
+  ret = g_datagram_based_send_messages (self->base_socket,
+                                        &message, 1,
+                                        0, 0,
+                                        self->op_cancellable,
+                                        &self->op_error);
+
+  if (ret > 0)
+    ret = message.bytes_sent;
+  else if (ret < 0)
+    set_gnutls_error (self, self->op_error);
+
+  return ret;
+}
+
+static gboolean
+read_pollable_cb (GPollableInputStream *istream,
+                  gpointer              user_data)
+{
+  gboolean *read_done = user_data;
+
+  *read_done = TRUE;
+
+  return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+read_datagram_based_cb (GDatagramBased *datagram_based,
+                        GIOCondition    condition,
+                        gpointer        user_data)
+{
+  gboolean *read_done = user_data;
+
+  *read_done = TRUE;
+
+  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 /* FIXME: can this be removed? switch to GNUTLS_NONBLOCK, right? */
+g_tls_operations_thread_gnutls_pull_timeout_func (gnutls_transport_ptr_t transport_data,
+                                                  unsigned int           ms)
+{
+  GTlsOperationsThreadGnutls *self = transport_data;
+
+  /* Fast path. */
+  if (g_tls_operations_thread_base_check (G_TLS_OPERATIONS_THREAD_BASE (self), G_IO_IN) ||
+      g_cancellable_is_cancelled (self->op_cancellable))
+    return 1;
+
+  /* If @ms is 0, GnuTLS wants an instant response, so there’s no need to
+   * construct and query a #GSource. */
+  if (ms > 0)
+    {
+      GMainContext *ctx = NULL;
+      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 (is_dtls (self))
+        {
+          read_source = g_datagram_based_create_source (self->base_socket,
+                                                        G_IO_IN, NULL);
+          g_source_set_callback (read_source, (GSourceFunc)read_datagram_based_cb,
+                                 &read_done, NULL);
+        }
+      else
+        {
+          read_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (self->base_istream),
+                                                               NULL);
+          g_source_set_callback (read_source, (GSourceFunc)read_pollable_cb,
+                                 &read_done, NULL);
+        }
+
+      g_source_attach (read_source, ctx);
+      g_source_attach (timeout_source, ctx);
+
+      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
+       * will be handled in pull_func.
+       */
+      if (g_tls_operations_thread_base_check (G_TLS_OPERATIONS_THREAD_BASE (self), G_IO_IN) ||
+          g_cancellable_is_cancelled (self->op_cancellable))
+        return 1;
+    }
+
+  return 0;
+}
+
+static int
+verify_certificate_cb (gnutls_session_t session)
+{
+  GTlsOperationsThreadGnutls *self = gnutls_session_get_ptr (session);
+  gboolean accepted;
+
+  g_assert (!self->op_peer_certificate);
+  self->op_peer_certificate = get_peer_certificate (self);
+
+  if (self->op_peer_certificate)
+    {
+      accepted = g_tls_operations_thread_base_verify_certificate (G_TLS_OPERATIONS_THREAD_BASE (self),
+                                                                  self->op_peer_certificate,
+                                                                  self->handshake_context);
+    }
+  else
+    {
+      accepted = is_server (self) && self->op_auth_mode != G_TLS_AUTHENTICATION_REQUIRED;
+    }
+
+  /* Return 0 for the handshake to continue, non-zero to terminate.
+   * Complete opposite of what OpenSSL does.
+   */
+  return !accepted;
+}
+
+static int
+pin_request_cb (void         *userdata,
+                int           attempt,
+                const char   *token_url,
+                const char   *token_label,
+                unsigned int  callback_flags,
+                char         *pin,
+                size_t        pin_max)
+{
+  GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (userdata);
+  GTlsInteraction *interaction = g_tls_operations_thread_base_ref_interaction (G_TLS_OPERATIONS_THREAD_BASE 
(self));
+  GTlsInteractionResult result;
+  GTlsPassword *password;
+  GTlsPasswordFlags password_flags = 0;
+  GError *error = NULL;
+  gchar *description;
+  int ret = -1;
+
+  if (!interaction)
+    return -1;
+
+  if (callback_flags & GNUTLS_PIN_WRONG)
+    password_flags |= G_TLS_PASSWORD_RETRY;
+  if (callback_flags & GNUTLS_PIN_COUNT_LOW)
+    password_flags |= G_TLS_PASSWORD_MANY_TRIES;
+  if (callback_flags & GNUTLS_PIN_FINAL_TRY || attempt > 5) /* Give up at some point */
+    password_flags |= G_TLS_PASSWORD_FINAL_TRY;
+
+  description = g_strdup_printf (" %s (%s)", token_label, token_url);
+  password = g_tls_password_new (password_flags, description);
+  result = g_tls_interaction_invoke_ask_password (interaction, password,
+                                                  self->op_cancellable,
+                                                  &error);
+  g_free (description);
+  g_object_unref (interaction);
+
+  switch (result)
+    {
+    case G_TLS_INTERACTION_FAILED:
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        g_warning ("Error getting PIN: %s", error->message);
+      g_error_free (error);
+      break;
+    case G_TLS_INTERACTION_UNHANDLED:
+      break;
+    case G_TLS_INTERACTION_HANDLED:
+      {
+        gsize password_size;
+        const guchar *password_data = g_tls_password_get_value (password, &password_size);
+        if (password_size > pin_max)
+          g_warning ("PIN is larger than max PIN size");
+
+        memcpy (pin, password_data, MIN (password_size, pin_max));
+        ret = GNUTLS_E_SUCCESS;
+        break;
+      }
+    default:
+      g_assert_not_reached ();
+    }
+
+  g_object_unref (password);
+
+  return ret;
+}
+
+static void
+clear_own_certificate_internals (GTlsOperationsThreadGnutls *self)
+{
+  g_tls_certificate_gnutls_internals_free (self->pcert, self->pcert_length, self->pkey);
+
+  self->pcert = NULL;
+  self->pcert_length = 0;
+  self->pkey = NULL;
+}
+
+static void
+get_own_certificate_internals (GTlsOperationsThreadGnutls  *self,
+                               gnutls_pcert_st            **pcert,
+                               unsigned int                *pcert_length,
+                               gnutls_privkey_t            *pkey)
+{
+  clear_own_certificate_internals (self);
+
+  if (self->op_own_certificate)
+    {
+      gnutls_privkey_t privkey;
+      gnutls_privkey_init (&privkey);
+      gnutls_privkey_set_pin_function (privkey, pin_request_cb, self);
+
+      g_tls_certificate_gnutls_copy_internals (G_TLS_CERTIFICATE_GNUTLS (self->op_own_certificate),
+                                               self->interaction_id,
+                                               pcert, pcert_length, &privkey);
+      *pkey = privkey;
+    }
+  else
+    {
+      *pcert = NULL;
+      *pcert_length = 0;
+      *pkey = NULL;
+    }
+}
+
+static int
+retrieve_certificate_cb (gnutls_session_t              session,
+                         const gnutls_datum_t         *req_ca_rdn,
+                         int                           nreqs,
+                         const gnutls_pk_algorithm_t  *pk_algos,
+                         int                           pk_algos_length,
+                         gnutls_pcert_st             **pcert,
+                         unsigned int                 *pcert_length,
+                         gnutls_privkey_t             *pkey)
+{
+  GTlsOperationsThreadGnutls *self = gnutls_transport_get_ptr (session);
+  GByteArray *dn;
+  int i;
+
+  if (is_client (self))
+    {
+      /* FIXME: Here we are supposed to ensure that the certificate supports one
+       * of the algorithms given in pk_algos.
+       */
+
+      if (self->accepted_cas)
+        {
+          g_list_free_full (self->accepted_cas, (GDestroyNotify)g_byte_array_unref);
+          self->accepted_cas = NULL;
+        }
+
+      for (i = 0; i < nreqs; i++)
+        {
+          dn = g_byte_array_new ();
+          g_byte_array_append (dn, req_ca_rdn[i].data, req_ca_rdn[i].size);
+          self->accepted_cas = g_list_prepend (self->accepted_cas, dn);
+        }
+
+      self->accepted_cas = g_list_reverse (self->accepted_cas);
+    }
+
+  get_own_certificate_internals (self, pcert, pcert_length, pkey);
+
+  if (is_client (self))
+    {
+      if (*pcert_length == 0)
+        {
+          g_tls_certificate_gnutls_internals_free (*pcert, *pcert_length, *pkey);
+
+          if (g_tls_operations_thread_base_request_certificate (G_TLS_OPERATIONS_THREAD_BASE (self),
+                                                                self->op_cancellable,
+                                                                &self->op_own_certificate))
+            get_own_certificate_internals (self, pcert, pcert_length, pkey);
+
+          if (*pcert_length == 0)
+            {
+              g_tls_certificate_gnutls_internals_free (*pcert, *pcert_length, *pkey);
+
+              /* If there is still no client certificate, this connection will
+               * probably fail, but we must not give up yet. The certificate might
+               * be optional, e.g. if the server is using
+               * G_TLS_AUTHENTICATION_REQUESTED, not G_TLS_AUTHENTICATION_REQUIRED.
+               */
+              g_tls_operations_thread_base_set_missing_requested_client_certificate 
(G_TLS_OPERATIONS_THREAD_BASE (self));
+              return 0;
+            }
+        }
+
+      if (!*pkey)
+        {
+          g_tls_certificate_gnutls_internals_free (*pcert, *pcert_length, *pkey);
+
+          /* No private key. GnuTLS expects it to be non-null if pcert_length is
+           * nonzero, so we have to abort now.
+           */
+          g_tls_operations_thread_base_set_missing_requested_client_certificate 
(G_TLS_OPERATIONS_THREAD_BASE (self));
+          return -1;
+        }
+    }
+
+  self->pcert = *pcert;
+  self->pcert_length = *pcert_length;
+  self->pkey = *pkey;
+
+  return 0;
+}
+
+static int
+session_ticket_received_cb (gnutls_session_t      session,
+                            guint                 htype,
+                            guint                 when,
+                            guint                 incoming,
+                            const gnutls_datum_t *msg)
+{
+  GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (gnutls_session_get_ptr (session));
+  gnutls_datum_t session_datum;
+
+  if (gnutls_session_get_data2 (session, &session_datum) == GNUTLS_E_SUCCESS)
+    {
+      g_clear_pointer (&self->session_data, g_bytes_unref);
+      self->session_data = g_bytes_new_with_free_func (session_datum.data,
+                                                       session_datum.size,
+                                                       (GDestroyNotify)gnutls_free,
+                                                       session_datum.data);
+
+      if (self->session_id)
+        {
+          g_tls_backend_gnutls_store_session_data (self->session_id,
+                                                   self->session_data);
+        }
+    }
+
+  return 0;
+}
+
+static void
+g_tls_operations_thread_gnutls_set_property (GObject      *object,
+                                             guint         prop_id,
+                                             const GValue *value,
+                                             GParamSpec   *pspec)
+{
+  GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (object);
+
+  switch (prop_id)
+    {
+    case PROP_GNUTLS_FLAGS:
+      self->init_flags = g_value_get_uint (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+g_tls_operations_thread_gnutls_finalize (GObject *object)
+{
+  GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (object);
+
+  g_clear_pointer (&self->session, gnutls_deinit);
+  g_clear_pointer (&self->creds, gnutls_certificate_free_credentials);
+  g_clear_pointer (&self->session_id, g_bytes_unref);
+  g_clear_pointer (&self->session_data, g_bytes_unref);
+  g_clear_pointer (&self->application_data_buffer, g_byte_array_unref);
+  g_clear_pointer (&self->server_identity, g_free);
+  g_clear_pointer (&self->interaction_id, g_free);
+
+  clear_own_certificate_internals (self);
+
+  if (self->accepted_cas)
+    {
+      g_list_free_full (self->accepted_cas, (GDestroyNotify)g_byte_array_unref);
+      self->accepted_cas = NULL;
+    }
+
+  g_assert (!self->op_peer_certificate);
+  g_assert (!self->op_own_certificate);
+
+  g_assert (!self->op_cancellable);
+  g_assert (!self->op_error);
+
+  G_OBJECT_CLASS (g_tls_operations_thread_gnutls_parent_class)->finalize (object);
+}
+
+static gboolean
+g_tls_operations_thread_gnutls_initable_init (GInitable     *initable,
+                                              GCancellable  *cancellable,
+                                              GError       **error)
+{
+  GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (initable);
+  int ret;
+
+  if (!g_tls_operations_thread_gnutls_parent_initable_iface->init (initable, cancellable, error))
+    return FALSE;
+
+  self->base_iostream = g_tls_operations_thread_base_get_base_iostream (G_TLS_OPERATIONS_THREAD_BASE (self));
+  if (self->base_iostream)
+    {
+      self->base_istream = g_io_stream_get_input_stream (self->base_iostream);
+      self->base_ostream = g_io_stream_get_output_stream (self->base_iostream);
+    }
+  else
+    self->base_socket = g_tls_operations_thread_base_get_base_socket (G_TLS_OPERATIONS_THREAD_BASE (self));
+
+  ret = gnutls_certificate_allocate_credentials (&self->creds);
+  if (ret != 0)
+    {
+      g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+                   _("Failed to allocate credentials: %s"),
+                   gnutls_strerror (ret));
+      return FALSE;
+    }
+  gnutls_certificate_set_retrieve_function2 (self->creds, retrieve_certificate_cb);
+
+  gnutls_init (&self->session, self->init_flags);
+
+  gnutls_session_set_ptr (self->session, self);
+  gnutls_session_set_verify_function (self->session, verify_certificate_cb);
+
+  ret = gnutls_credentials_set (self->session,
+                                GNUTLS_CRD_CERTIFICATE,
+                                self->creds);
+  if (ret != 0)
+    {
+      g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+                   _("Could not create TLS connection: %s"),
+                   gnutls_strerror (ret));
+      return FALSE;
+    }
+
+  gnutls_transport_set_push_function (self->session,
+                                      g_tls_operations_thread_gnutls_push_func);
+  gnutls_transport_set_pull_function (self->session,
+                                      g_tls_operations_thread_gnutls_pull_func);
+  /* FIXME: remove timeout func and switch to GNUTLS_NONBLOCK */
+  gnutls_transport_set_pull_timeout_function (self->session,
+                                              g_tls_operations_thread_gnutls_pull_timeout_func);
+  gnutls_transport_set_ptr (self->session, self);
+
+  if (is_dtls (self))
+    {
+      /* GDatagramBased supports vectored I/O; GPollableOutputStream does not. */
+      gnutls_transport_set_vec_push_function (self->session,
+                                              g_tls_operations_thread_gnutls_vec_push_func);
+
+      /* Set reasonable MTU */
+      gnutls_dtls_set_mtu (self->session, 1400);
+    }
+
+  if (is_client (self))
+    {
+      gnutls_handshake_set_hook_function (self->session,
+                                          GNUTLS_HANDSHAKE_NEW_SESSION_TICKET,
+                                          GNUTLS_HOOK_POST,
+                                          session_ticket_received_cb);
+    }
+
+  return TRUE;
+}
+
+static void
+g_tls_operations_thread_gnutls_init (GTlsOperationsThreadGnutls *self)
+{
+  static int unique_interaction_id = 0;
+
+  self->interaction_id = g_strdup_printf ("gtls:%d", unique_interaction_id++);
+}
+
+static void
+g_tls_operations_thread_gnutls_class_init (GTlsOperationsThreadGnutlsClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GTlsOperationsThreadBaseClass *base_class = G_TLS_OPERATIONS_THREAD_BASE_CLASS (klass);
+
+  gobject_class->finalize      = g_tls_operations_thread_gnutls_finalize;
+  gobject_class->set_property  = g_tls_operations_thread_gnutls_set_property;
+
+  base_class->copy_certificate          = g_tls_operations_thread_gnutls_copy_certificate;
+  base_class->copy_client_session_state = g_tls_operations_thread_gnutls_copy_client_session_state;
+  base_class->set_server_identity       = g_tls_operations_thread_gnutls_set_server_identity;
+  base_class->handshake_fn              = g_tls_operations_thread_gnutls_handshake;
+  base_class->read_fn                   = g_tls_operations_thread_gnutls_read;
+  base_class->read_message_fn           = g_tls_operations_thread_gnutls_read_message;
+  base_class->write_fn                  = g_tls_operations_thread_gnutls_write;
+  base_class->write_message_fn          = g_tls_operations_thread_gnutls_write_message;
+  base_class->close_fn                  = g_tls_operations_thread_gnutls_close;
+
+  obj_properties[PROP_GNUTLS_FLAGS] =
+    g_param_spec_uint ("gnutls-flags",
+                       "GnuTLS flags",
+                       "Flags for initializing GnuTLS session",
+                       0, UINT_MAX, 0,
+                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, LAST_PROP, obj_properties);
+
+  initialize_gnutls_priority ();
+}
+
+GTlsOperationsThreadBase *
+g_tls_operations_thread_gnutls_new (GTlsConnectionGnutls *connection,
+                                    GIOStream            *base_iostream,
+                                    GDatagramBased       *base_socket,
+                                    guint                 flags)
+{
+  return g_initable_new (G_TYPE_TLS_OPERATIONS_THREAD_GNUTLS,
+                         NULL, NULL,
+                         "base-io-stream", base_iostream,
+                         "base-socket", base_socket,
+                         "gnutls-flags", flags,
+                         "thread-type", (flags & GNUTLS_CLIENT) ? G_TLS_OPERATIONS_THREAD_CLIENT : 
G_TLS_OPERATIONS_THREAD_SERVER,
+                         NULL);
+}
+
+static void
+g_tls_operations_thread_gnutls_initable_iface_init (GInitableIface *iface)
+{
+  g_tls_operations_thread_gnutls_parent_initable_iface = g_type_interface_peek_parent (iface);
+
+  iface->init = g_tls_operations_thread_gnutls_initable_init;
+}
+
+/* FIXME: must remove this! */
+gnutls_session_t
+g_tls_operations_thread_gnutls_get_session (GTlsOperationsThreadGnutls *self)
+{
+  return self->session;
+}
diff --git a/tls/gnutls/gtlsoperationsthread-gnutls.h b/tls/gnutls/gtlsoperationsthread-gnutls.h
new file mode 100644
index 0000000..7637a42
--- /dev/null
+++ b/tls/gnutls/gtlsoperationsthread-gnutls.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2019 Igalia S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * In addition, when the library is used with OpenSSL, a special
+ * exception applies. Refer to the LICENSE_EXCEPTION file for details.
+ */
+
+#pragma once
+
+#include "gtlsconnection-gnutls.h"
+#include "gtlsoperationsthread-base.h"
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_TLS_OPERATIONS_THREAD_GNUTLS (g_tls_operations_thread_gnutls_get_type ())
+
+G_DECLARE_FINAL_TYPE (GTlsOperationsThreadGnutls, g_tls_operations_thread_gnutls, G, 
TLS_OPERATIONS_THREAD_GNUTLS, GTlsOperationsThreadBase)
+
+GTlsOperationsThreadBase *g_tls_operations_thread_gnutls_new (GTlsConnectionGnutls *connection,
+                                                              GIOStream            *base_iostream,
+                                                              GDatagramBased       *base_socket,
+                                                              guint                 flags);
+
+/* FIXME: must remove this!!! */
+gnutls_session_t g_tls_operations_thread_gnutls_get_session (GTlsOperationsThreadGnutls *self);
+
+G_END_DECLS
diff --git a/tls/gnutls/gtlsserverconnection-gnutls.c b/tls/gnutls/gtlsserverconnection-gnutls.c
index 090b57d..1d834ca 100644
--- a/tls/gnutls/gtlsserverconnection-gnutls.c
+++ b/tls/gnutls/gtlsserverconnection-gnutls.c
@@ -45,25 +45,12 @@ struct _GTlsServerConnectionGnutls
   GTlsConnectionGnutls parent_instance;
 
   GTlsAuthenticationMode authentication_mode;
-
-  gnutls_pcert_st *pcert;
-  unsigned int pcert_length;
-  gnutls_privkey_t pkey;
 };
 
 static void     g_tls_server_connection_gnutls_initable_interface_init (GInitableIface  *iface);
 
 static void g_tls_server_connection_gnutls_server_connection_interface_init (GTlsServerConnectionInterface 
*iface);
 
-static int g_tls_server_connection_gnutls_handshake_thread_retrieve_function (gnutls_session_t              
session,
-                                                                              const gnutls_datum_t         
*req_ca_rdn,
-                                                                              int                           
nreqs,
-                                                                              const gnutls_pk_algorithm_t  
*pk_algos,
-                                                                              int                           
pk_algos_length,
-                                                                              gnutls_pcert_st             
**pcert,
-                                                                              unsigned int                 
*pcert_length,
-                                                                              gnutls_privkey_t             
*pkey);
-
 static GInitableIface *g_tls_server_connection_gnutls_parent_initable_iface;
 
 G_DEFINE_TYPE_WITH_CODE (GTlsServerConnectionGnutls, g_tls_server_connection_gnutls, 
G_TYPE_TLS_CONNECTION_GNUTLS,
@@ -75,46 +62,21 @@ G_DEFINE_TYPE_WITH_CODE (GTlsServerConnectionGnutls, g_tls_server_connection_gnu
                                                 NULL)
 )
 
-static void
-clear_gnutls_certificate_copy (GTlsServerConnectionGnutls *gnutls)
-{
-  g_tls_certificate_gnutls_copy_free (gnutls->pcert, gnutls->pcert_length, gnutls->pkey);
-
-  gnutls->pcert = NULL;
-  gnutls->pcert_length = 0;
-  gnutls->pkey = NULL;
-}
-
 static void
 g_tls_server_connection_gnutls_init (GTlsServerConnectionGnutls *gnutls)
 {
 }
 
-static void
-g_tls_server_connection_gnutls_finalize (GObject *object)
-{
-  GTlsServerConnectionGnutls *gnutls = G_TLS_SERVER_CONNECTION_GNUTLS (object);
-
-  clear_gnutls_certificate_copy (gnutls);
-
-  G_OBJECT_CLASS (g_tls_server_connection_gnutls_parent_class)->finalize (object);
-}
-
 static gboolean
 g_tls_server_connection_gnutls_initable_init (GInitable       *initable,
                                               GCancellable    *cancellable,
                                               GError         **error)
 {
-  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (initable);
   GTlsCertificate *cert;
-  gnutls_certificate_credentials_t creds;
 
   if (!g_tls_server_connection_gnutls_parent_initable_iface->init (initable, cancellable, error))
     return FALSE;
 
-  creds = g_tls_connection_gnutls_get_credentials (G_TLS_CONNECTION_GNUTLS (gnutls));
-  gnutls_certificate_set_retrieve_function2 (creds, 
g_tls_server_connection_gnutls_handshake_thread_retrieve_function);
-
   /* Currently we don't know ahead of time if a PKCS #11 backed certificate has a private key. */
   cert = g_tls_connection_get_certificate (G_TLS_CONNECTION (initable));
   if (cert && !g_tls_certificate_gnutls_has_key (G_TLS_CERTIFICATE_GNUTLS (cert)) &&
@@ -166,70 +128,14 @@ g_tls_server_connection_gnutls_set_property (GObject      *object,
     }
 }
 
-static int
-g_tls_server_connection_gnutls_handshake_thread_retrieve_function (gnutls_session_t              session,
-                                                                   const gnutls_datum_t         *req_ca_rdn,
-                                                                   int                           nreqs,
-                                                                   const gnutls_pk_algorithm_t  *pk_algos,
-                                                                   int                           
pk_algos_length,
-                                                                   gnutls_pcert_st             **pcert,
-                                                                   unsigned int                 
*pcert_length,
-                                                                   gnutls_privkey_t             *pkey)
-{
-  GTlsServerConnectionGnutls *gnutls = G_TLS_SERVER_CONNECTION_GNUTLS (gnutls_transport_get_ptr (session));
-
-  clear_gnutls_certificate_copy (gnutls);
-
-  g_tls_connection_gnutls_handshake_thread_get_certificate (G_TLS_CONNECTION_GNUTLS (gnutls),
-                                                            pcert, pcert_length, pkey);
-
-  gnutls->pcert = *pcert;
-  gnutls->pcert_length = *pcert_length;
-  gnutls->pkey = *pkey;
-
-  return 0;
-}
-
-static void
-g_tls_server_connection_gnutls_prepare_handshake (GTlsConnectionBase  *tls,
-                                                  gchar              **advertised_protocols)
-{
-  GTlsServerConnectionGnutls *gnutls = G_TLS_SERVER_CONNECTION_GNUTLS (tls);
-  gnutls_session_t session;
-  gnutls_certificate_request_t req_mode;
-
-  switch (gnutls->authentication_mode)
-    {
-    case G_TLS_AUTHENTICATION_REQUESTED:
-      req_mode = GNUTLS_CERT_REQUEST;
-      break;
-    case G_TLS_AUTHENTICATION_REQUIRED:
-      req_mode = GNUTLS_CERT_REQUIRE;
-      break;
-    case G_TLS_AUTHENTICATION_NONE:
-    default:
-      req_mode = GNUTLS_CERT_IGNORE;
-      break;
-    }
-
-  session = g_tls_connection_gnutls_get_session (G_TLS_CONNECTION_GNUTLS (tls));
-  gnutls_certificate_server_set_request (session, req_mode);
-
-  G_TLS_CONNECTION_BASE_CLASS (g_tls_server_connection_gnutls_parent_class)->prepare_handshake (tls, 
advertised_protocols);
-}
-
 static void
 g_tls_server_connection_gnutls_class_init (GTlsServerConnectionGnutlsClass *klass)
 {
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-  GTlsConnectionBaseClass *base_class = G_TLS_CONNECTION_BASE_CLASS (klass);
 
-  gobject_class->finalize     = g_tls_server_connection_gnutls_finalize;
   gobject_class->get_property = g_tls_server_connection_gnutls_get_property;
   gobject_class->set_property = g_tls_server_connection_gnutls_set_property;
 
-  base_class->prepare_handshake  = g_tls_server_connection_gnutls_prepare_handshake;
-
   g_object_class_override_property (gobject_class, PROP_AUTHENTICATION_MODE, "authentication-mode");
 }
 
diff --git a/tls/gnutls/meson.build b/tls/gnutls/meson.build
index ac46981..e993301 100644
--- a/tls/gnutls/meson.build
+++ b/tls/gnutls/meson.build
@@ -6,6 +6,7 @@ sources = files(
   'gtlsconnection-gnutls.c',
   'gtlsdatabase-gnutls.c',
   'gtlsfiledatabase-gnutls.c',
+  'gtlsoperationsthread-gnutls.c',
   'gtlsserverconnection-gnutls.c'
 )
 
diff --git a/tls/openssl/gtlsbio.c b/tls/openssl/gtlsbio.c
index b138432..e19edd2 100644
--- a/tls/openssl/gtlsbio.c
+++ b/tls/openssl/gtlsbio.c
@@ -35,8 +35,6 @@ typedef struct {
   gboolean write_blocking;
   GError **read_error;
   GError **write_error;
-  GMainContext *context;
-  GMainLoop *loop;
 } GTlsBio;
 
 static void
@@ -45,8 +43,6 @@ free_gbio (gpointer user_data)
   GTlsBio *bio = (GTlsBio *)user_data;
 
   g_object_unref (bio->io_stream);
-  g_main_context_unref (bio->context);
-  g_main_loop_unref (bio->loop);
   g_free (bio);
 }
 
@@ -294,8 +290,6 @@ g_tls_bio_new (GIOStream *io_stream)
 
   gbio = g_new0 (GTlsBio, 1);
   gbio->io_stream = g_object_ref (io_stream);
-  gbio->context = g_main_context_new ();
-  gbio->loop = g_main_loop_new (gbio->context, FALSE);
 
 #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
   ret->ptr = gbio;
@@ -403,49 +397,3 @@ g_tls_bio_set_write_error (BIO     *bio,
 #endif
   gbio->write_error = error;
 }
-
-static gboolean
-on_source_ready (GObject *pollable_stream,
-                 gpointer user_data)
-{
-  GMainLoop *loop = user_data;
-
-  g_main_loop_quit (loop);
-
-  return G_SOURCE_REMOVE;
-}
-
-void
-g_tls_bio_wait_available (BIO          *bio,
-                          GIOCondition  condition,
-                          GCancellable *cancellable)
-{
-  GTlsBio *gbio;
-  GSource *source;
-
-  g_return_if_fail (bio);
-
-#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
-  gbio = (GTlsBio *)bio->ptr;
-#else
-  gbio = BIO_get_data (bio);
-#endif
-
-  g_main_context_push_thread_default (gbio->context);
-
-  if (condition & G_IO_IN)
-    source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (g_io_stream_get_input_stream 
(gbio->io_stream)),
-                                                    cancellable);
-  else
-    source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (g_io_stream_get_output_stream 
(gbio->io_stream)),
-                                                     cancellable);
-
-  g_source_set_callback (source, (GSourceFunc)on_source_ready, gbio->loop, NULL);
-  g_source_attach (source, gbio->context);
-
-  g_main_loop_run (gbio->loop);
-  g_main_context_pop_thread_default (gbio->context);
-
-  g_source_destroy (source);
-  g_source_unref (source);
-}
diff --git a/tls/openssl/gtlsclientconnection-openssl.c b/tls/openssl/gtlsclientconnection-openssl.c
index f14401a..94feb62 100644
--- a/tls/openssl/gtlsclientconnection-openssl.c
+++ b/tls/openssl/gtlsclientconnection-openssl.c
@@ -207,6 +207,10 @@ g_tls_client_connection_openssl_constructed (GObject *object)
    * server-identity because at least some servers will fail (rather
    * than just failing to resume the session) if we don't.
    * (https://bugs.launchpad.net/bugs/823325)
+   *
+   * FIXME: this logic is broken because it doesn't consider the client
+   * certificate when computing the session ID. The GnuTLS version of this
+   * code has this problem fixed. Eliminate this code duplication.
    */
   g_object_get (G_OBJECT (openssl), "base-io-stream", &base_conn, NULL);
   if (G_IS_SOCKET_CONNECTION (base_conn))
@@ -236,22 +240,6 @@ g_tls_client_connection_openssl_constructed (GObject *object)
   G_OBJECT_CLASS (g_tls_client_connection_openssl_parent_class)->constructed (object);
 }
 
-static void
-g_tls_client_connection_openssl_complete_handshake (GTlsConnectionBase  *tls,
-                                                    gchar              **negotiated_protocol,
-                                                    GError             **error)
-{
-  GTlsClientConnectionOpenssl *client = G_TLS_CLIENT_CONNECTION_OPENSSL (tls);
-
-  G_TLS_CONNECTION_BASE_CLASS (g_tls_client_connection_openssl_parent_class)->complete_handshake (tls, 
negotiated_protocol, error);
-
-  /* It may have changed during the handshake, but we have to wait until here
-   * because we can't emit notifies on the handshake thread.
-   */
-  if (client->ca_list_changed)
-    g_object_notify (G_OBJECT (client), "accepted-cas");
-}
-
 static GTlsCertificateFlags
 verify_ocsp_response (GTlsClientConnectionOpenssl *openssl,
                       GTlsCertificate             *peer_certificate)
@@ -320,7 +308,6 @@ g_tls_client_connection_openssl_class_init (GTlsClientConnectionOpensslClass *kl
   gobject_class->set_property         = g_tls_client_connection_openssl_set_property;
   gobject_class->constructed          = g_tls_client_connection_openssl_constructed;
 
-  base_class->complete_handshake      = g_tls_client_connection_openssl_complete_handshake;
   base_class->verify_peer_certificate = g_tls_client_connection_openssl_verify_peer_certificate;
 
   openssl_class->get_ssl              = g_tls_client_connection_openssl_get_ssl;
@@ -560,8 +547,7 @@ g_tls_client_connection_openssl_initable_init (GInitable       *initable,
     SSL_set_tlsext_status_type (client->ssl, TLSEXT_STATUSTYPE_ocsp);
 #endif
 
-  if (!g_tls_client_connection_openssl_parent_initable_iface->
-      init (initable, cancellable, error))
+  if (!g_tls_client_connection_openssl_parent_initable_iface->init (initable, cancellable, error))
     return FALSE;
 
   return TRUE;
diff --git a/tls/openssl/gtlsconnection-openssl.c b/tls/openssl/gtlsconnection-openssl.c
index 2e728f9..43c6f16 100644
--- a/tls/openssl/gtlsconnection-openssl.c
+++ b/tls/openssl/gtlsconnection-openssl.c
@@ -34,6 +34,7 @@
 #include "gtlsbackend-openssl.h"
 #include "gtlscertificate-openssl.h"
 #include "gtlsdatabase-openssl.h"
+#include "gtlsoperationsthread-openssl.h"
 #include "gtlsbio.h"
 
 #include <glib/gi18n-lib.h>
@@ -41,11 +42,10 @@
 typedef struct _GTlsConnectionOpensslPrivate
 {
   BIO *bio;
-  GMutex ssl_mutex;
-
-  gboolean shutting_down;
 } GTlsConnectionOpensslPrivate;
 
+static GInitableIface *g_tls_connection_openssl_parent_initable_iface;
+
 static void g_tls_connection_openssl_initable_iface_init (GInitableIface *iface);
 
 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionOpenssl, g_tls_connection_openssl, 
G_TYPE_TLS_CONNECTION_BASE,
@@ -53,205 +53,10 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionOpenssl, g_tls_connection_openss
                                   G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
                                                          g_tls_connection_openssl_initable_iface_init))
 
-static void
-g_tls_connection_openssl_finalize (GObject *object)
+static GTlsOperationsThreadBase *
+g_tls_connection_openssl_create_op_thread (GTlsConnectionBase *tls)
 {
-  GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (object);
-  GTlsConnectionOpensslPrivate *priv;
-
-  priv = g_tls_connection_openssl_get_instance_private (openssl);
-
-  g_mutex_clear (&priv->ssl_mutex);
-
-  G_OBJECT_CLASS (g_tls_connection_openssl_parent_class)->finalize (object);
-}
-
-static GTlsSafeRenegotiationStatus
-g_tls_connection_openssl_handshake_thread_safe_renegotiation_status (GTlsConnectionBase *tls)
-{
-  GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
-  SSL *ssl;
-
-  ssl = g_tls_connection_openssl_get_ssl (openssl);
-
-  return SSL_get_secure_renegotiation_support (ssl) ? G_TLS_SAFE_RENEGOTIATION_SUPPORTED_BY_PEER
-                                                    : G_TLS_SAFE_RENEGOTIATION_UNSUPPORTED;
-}
-
-static GTlsConnectionBaseStatus
-end_openssl_io (GTlsConnectionOpenssl  *openssl,
-                GIOCondition            direction,
-                int                     ret,
-                gboolean                blocking,
-                GError                **error,
-                const char             *err_prefix,
-                const char             *err_str)
-{
-  GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (openssl);
-  GTlsConnectionOpensslPrivate *priv;
-  int err_code, err, err_lib, reason;
-  GError *my_error = NULL;
-  GTlsConnectionBaseStatus status;
-  SSL *ssl;
-
-  priv = g_tls_connection_openssl_get_instance_private (openssl);
-
-  ssl = g_tls_connection_openssl_get_ssl (openssl);
-
-  err_code = SSL_get_error (ssl, ret);
-
-  status = g_tls_connection_base_pop_io (tls, direction, ret > 0, &my_error);
-
-  if ((err_code == SSL_ERROR_WANT_READ ||
-       err_code == SSL_ERROR_WANT_WRITE) &&
-      blocking)
-    {
-      if (my_error)
-        g_error_free (my_error);
-      return G_TLS_CONNECTION_BASE_TRY_AGAIN;
-    }
-
-  if (err_code == SSL_ERROR_ZERO_RETURN)
-    return G_TLS_CONNECTION_BASE_OK;
-
-  if (status == G_TLS_CONNECTION_BASE_OK ||
-      status == G_TLS_CONNECTION_BASE_WOULD_BLOCK ||
-      status == G_TLS_CONNECTION_BASE_TIMED_OUT)
-    {
-      if (my_error)
-        g_propagate_error (error, my_error);
-      return status;
-    }
-
-  /* This case is documented that it may happen and that is perfectly fine */
-  if (err_code == SSL_ERROR_SYSCALL &&
-      ((priv->shutting_down && !my_error) ||
-       g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE)))
-    {
-      g_clear_error (&my_error);
-      return G_TLS_CONNECTION_BASE_OK;
-    }
-
-  err = ERR_get_error ();
-  err_lib = ERR_GET_LIB (err);
-  reason = ERR_GET_REASON (err);
-
-  if (g_tls_connection_base_is_handshaking (tls) && !g_tls_connection_base_ever_handshaked (tls))
-    {
-      if (reason == SSL_R_BAD_PACKET_LENGTH ||
-          reason == SSL_R_UNKNOWN_ALERT_TYPE ||
-          reason == SSL_R_DECRYPTION_FAILED ||
-          reason == SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC ||
-          reason == SSL_R_BAD_PROTOCOL_VERSION_NUMBER ||
-          reason == SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE ||
-          reason == SSL_R_UNKNOWN_PROTOCOL)
-        {
-          g_clear_error (&my_error);
-          g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
-                       _("Peer failed to perform TLS handshake: %s"), ERR_reason_error_string (err));
-          return G_TLS_CONNECTION_BASE_ERROR;
-        }
-    }
-
-#ifdef SSL_R_SHUTDOWN_WHILE_IN_INIT
-  /* XXX: this error happens on ubuntu when shutting down the connection, it
-   * seems to be a bug in a specific version of openssl, so let's handle it
-   * gracefully
-   */
-  if (reason == SSL_R_SHUTDOWN_WHILE_IN_INIT)
-    {
-      g_clear_error (&my_error);
-      return G_TLS_CONNECTION_BASE_OK;
-    }
-#endif
-
-  if (reason == SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE
-#ifdef SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED
-      || reason == SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED
-#endif
-     )
-    {
-      g_clear_error (&my_error);
-      g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
-                           _("TLS connection peer did not send a certificate"));
-      return status;
-    }
-
-  if (reason == SSL_R_CERTIFICATE_VERIFY_FAILED)
-    {
-      g_clear_error (&my_error);
-      g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
-                   _("Unacceptable TLS certificate"));
-      return G_TLS_CONNECTION_BASE_ERROR;
-    }
-
-  if (reason == SSL_R_TLSV1_ALERT_UNKNOWN_CA)
-    {
-      g_clear_error (&my_error);
-      g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
-                   _("Unacceptable TLS certificate authority"));
-      return G_TLS_CONNECTION_BASE_ERROR;
-    }
-
-  if (err_lib == ERR_LIB_RSA && reason == RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY)
-    {
-      g_clear_error (&my_error);
-      g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
-                           _("Digest too big for RSA key"));
-      return G_TLS_CONNECTION_BASE_ERROR;
-    }
-
-  if (my_error)
-    g_propagate_error (error, my_error);
-  else
-    /* FIXME: this is just for debug */
-    g_message ("end_openssl_io %s: %d, %d, %d", G_IS_TLS_CLIENT_CONNECTION (openssl) ? "client" : "server", 
err_code, err_lib, reason);
-
-  if (error && !*error)
-    *error = g_error_new (G_TLS_ERROR, G_TLS_ERROR_MISC, "%s: %s", err_prefix, err_str);
-
-  return G_TLS_CONNECTION_BASE_ERROR;
-}
-
-#define BEGIN_OPENSSL_IO(openssl, direction, timeout, cancellable)          \
-  do {                                                                      \
-    char error_str[256];                                                    \
-    g_tls_connection_base_push_io (G_TLS_CONNECTION_BASE (openssl),         \
-                                   direction, timeout, cancellable);
-
-#define END_OPENSSL_IO(openssl, direction, ret, timeout, status, errmsg, err) \
-    ERR_error_string_n (SSL_get_error (ssl, ret), error_str, sizeof(error_str)); \
-    status = end_openssl_io (openssl, direction, ret, timeout == -1, err, errmsg, error_str); \
-  } while (status == G_TLS_CONNECTION_BASE_TRY_AGAIN);
-
-static GTlsConnectionBaseStatus
-g_tls_connection_openssl_handshake_thread_request_rehandshake (GTlsConnectionBase  *tls,
-                                                               gint64               timeout,
-                                                               GCancellable        *cancellable,
-                                                               GError             **error)
-{
-  GTlsConnectionOpenssl *openssl;
-  GTlsConnectionBaseStatus status;
-  SSL *ssl;
-  int ret;
-
-  /* On a client-side connection, SSL_renegotiate() itself will start
-   * a rehandshake, so we only need to do something special here for
-   * server-side connections.
-   */
-  if (!G_IS_TLS_SERVER_CONNECTION (tls))
-    return G_TLS_CONNECTION_BASE_OK;
-
-  openssl = G_TLS_CONNECTION_OPENSSL (tls);
-
-  ssl = g_tls_connection_openssl_get_ssl (openssl);
-
-  BEGIN_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, timeout, cancellable);
-  ret = SSL_renegotiate (ssl);
-  END_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, ret, timeout, status,
-                  _("Error performing TLS handshake"), error);
-
-  return status;
+  return g_tls_operations_thread_openssl_new (G_TLS_CONNECTION_OPENSSL (tls));
 }
 
 static GTlsCertificate *
@@ -283,33 +88,7 @@ g_tls_connection_openssl_retrieve_peer_certificate (GTlsConnectionBase *tls)
   return G_TLS_CERTIFICATE (chain);
 }
 
-static GTlsConnectionBaseStatus
-g_tls_connection_openssl_handshake_thread_handshake (GTlsConnectionBase  *tls,
-                                                     gint64               timeout,
-                                                     GCancellable        *cancellable,
-                                                     GError             **error)
-{
-  GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
-  GTlsConnectionBaseStatus status;
-  SSL *ssl;
-  int ret;
-
-  ssl = g_tls_connection_openssl_get_ssl (openssl);
-
-  BEGIN_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, timeout, cancellable);
-  ret = SSL_do_handshake (ssl);
-  END_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, ret, timeout, status,
-                  _("Error performing TLS handshake"), error);
-
-  if (ret > 0)
-    {
-      if (!g_tls_connection_base_handshake_thread_verify_certificate (G_TLS_CONNECTION_BASE (openssl)))
-        return G_TLS_CONNECTION_BASE_ERROR;
-    }
-
-  return status;
-}
-
+/* FIXME: move to op thread? */
 static void
 g_tls_connection_openssl_push_io (GTlsConnectionBase *tls,
                                   GIOCondition        direction,
@@ -320,13 +99,11 @@ g_tls_connection_openssl_push_io (GTlsConnectionBase *tls,
   GTlsConnectionOpensslPrivate *priv;
   GError **error;
 
-  priv = g_tls_connection_openssl_get_instance_private (openssl);
-
-  G_TLS_CONNECTION_BASE_CLASS (g_tls_connection_openssl_parent_class)->push_io (tls, direction,
-                                                                                timeout, cancellable);
+  priv = g_tls_connection_openssl_get_instance_private (openssl);;
 
   /* FIXME: need to support timeout > 0
-   * This will require changes in GTlsBio */
+   * This will require changes in GTlsBio
+   */
 
   if (direction & G_IO_IN)
     {
@@ -345,11 +122,9 @@ g_tls_connection_openssl_push_io (GTlsConnectionBase *tls,
       g_clear_error (error);
       g_tls_bio_set_write_error (priv->bio, error);
     }
-
-  g_mutex_lock (&priv->ssl_mutex);
 }
 
-static GTlsConnectionBaseStatus
+static GTlsOperationStatus
 g_tls_connection_openssl_pop_io (GTlsConnectionBase  *tls,
                                  GIOCondition         direction,
                                  gboolean             success,
@@ -360,8 +135,6 @@ g_tls_connection_openssl_pop_io (GTlsConnectionBase  *tls,
 
   priv = g_tls_connection_openssl_get_instance_private (openssl);
 
-  g_mutex_unlock (&priv->ssl_mutex);
-
   if (direction & G_IO_IN)
     g_tls_bio_set_read_cancellable (priv->bio, NULL);
 
@@ -372,143 +145,16 @@ g_tls_connection_openssl_pop_io (GTlsConnectionBase  *tls,
                                                                                       success, error);
 }
 
-static GTlsConnectionBaseStatus
-g_tls_connection_openssl_read (GTlsConnectionBase    *tls,
-                               void                  *buffer,
-                               gsize                  count,
-                               gint64                 timeout,
-                               gssize                *nread,
-                               GCancellable          *cancellable,
-                               GError               **error)
-{
-  GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
-  GTlsConnectionOpensslPrivate *priv;
-  GTlsConnectionBaseStatus status;
-  SSL *ssl;
-  gssize ret;
-
-  priv = g_tls_connection_openssl_get_instance_private (openssl);
-
-  ssl = g_tls_connection_openssl_get_ssl (openssl);
-
-  /* FIXME: revert back to use BEGIN/END_OPENSSL_IO once we move all the ssl
-   * operations into a worker thread
-   */
-  while (TRUE)
-    {
-      char error_str[256];
-
-      /* We want to always be non blocking here to avoid deadlocks */
-      g_tls_connection_base_push_io (G_TLS_CONNECTION_BASE (openssl),
-                                     G_IO_IN, 0, cancellable);
-
-      ret = SSL_read (ssl, buffer, count);
-
-      ERR_error_string_n (SSL_get_error (ssl, ret), error_str, sizeof (error_str));
-      status = end_openssl_io (openssl, G_IO_IN, ret, timeout == -1, error,
-                               _("Error reading data from TLS socket"), error_str);
-
-      if (status != G_TLS_CONNECTION_BASE_TRY_AGAIN)
-        break;
-
-      /* Wait for the socket to be available again to avoid an infinite loop */
-      g_tls_bio_wait_available (priv->bio, G_IO_IN, cancellable);
-    }
-
-  *nread = MAX (ret, 0);
-  return status;
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_openssl_write (GTlsConnectionBase    *tls,
-                                const void            *buffer,
-                                gsize                  count,
-                                gint64                 timeout,
-                                gssize                *nwrote,
-                                GCancellable          *cancellable,
-                                GError               **error)
-{
-  GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
-  GTlsConnectionOpensslPrivate *priv;
-  GTlsConnectionBaseStatus status;
-  SSL *ssl;
-  gssize ret;
-
-  priv = g_tls_connection_openssl_get_instance_private (openssl);
-
-  ssl = g_tls_connection_openssl_get_ssl (openssl);
-
-  while (TRUE)
-    {
-      char error_str[256];
-
-      /* We want to always be non blocking here to avoid deadlocks */
-      g_tls_connection_base_push_io (G_TLS_CONNECTION_BASE (openssl),
-                                     G_IO_OUT, 0, cancellable);
-
-      ret = SSL_write (ssl, buffer, count);
-
-      ERR_error_string_n (SSL_get_error (ssl, ret), error_str, sizeof (error_str));
-      status = end_openssl_io (openssl, G_IO_OUT, ret, timeout == -1, error,
-                               _("Error writing data to TLS socket"), error_str);
-
-      if (status != G_TLS_CONNECTION_BASE_TRY_AGAIN)
-        break;
-
-      /* Wait for the socket to be available again to avoid an infinite loop */
-      g_tls_bio_wait_available (priv->bio, G_IO_OUT, cancellable);
-    }
-
-  *nwrote = MAX (ret, 0);
-  return status;
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_openssl_close (GTlsConnectionBase  *tls,
-                                gint64               timeout,
-                                GCancellable        *cancellable,
-                                GError             **error)
-{
-  GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
-  GTlsConnectionOpensslPrivate *priv;
-  GTlsConnectionBaseStatus status;
-  SSL *ssl;
-  int ret;
-
-  ssl = g_tls_connection_openssl_get_ssl (openssl);
-  priv = g_tls_connection_openssl_get_instance_private (openssl);
-
-  priv->shutting_down = TRUE;
-
-  BEGIN_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, timeout, cancellable);
-  ret = SSL_shutdown (ssl);
-  /* Note it is documented that getting 0 is correct when shutting down since
-   * it means it will close the write direction
-   */
-  ret = ret == 0 ? 1 : ret;
-  END_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, ret, timeout, status,
-                  _("Error performing TLS close"), error);
-
-  return status;
-}
-
 static void
 g_tls_connection_openssl_class_init (GTlsConnectionOpensslClass *klass)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GTlsConnectionBaseClass *base_class = G_TLS_CONNECTION_BASE_CLASS (klass);
 
-  object_class->finalize                                 = g_tls_connection_openssl_finalize;
-
-  base_class->handshake_thread_safe_renegotiation_status = 
g_tls_connection_openssl_handshake_thread_safe_renegotiation_status;
-  base_class->handshake_thread_request_rehandshake       = 
g_tls_connection_openssl_handshake_thread_request_rehandshake;
-  base_class->handshake_thread_handshake                 = 
g_tls_connection_openssl_handshake_thread_handshake;
+  base_class->create_op_thread                           = g_tls_connection_openssl_create_op_thread;
+  /* FIXME: remove */
   base_class->retrieve_peer_certificate                  = 
g_tls_connection_openssl_retrieve_peer_certificate;
-  base_class->push_io                                    = g_tls_connection_openssl_push_io;
+  base_class->push_io                                    = g_tls_connection_openssl_push_io; /* FIXME: move 
to op thread */
   base_class->pop_io                                     = g_tls_connection_openssl_pop_io;
-  base_class->read_fn                                    = g_tls_connection_openssl_read;
-  base_class->write_fn                                   = g_tls_connection_openssl_write;
-  base_class->close_fn                                   = g_tls_connection_openssl_close;
 }
 
 static int data_index = -1;
@@ -543,23 +189,20 @@ g_tls_connection_openssl_initable_init (GInitable     *initable,
 
   SSL_set_bio (ssl, priv->bio, priv->bio);
 
-  return TRUE;
+  return g_tls_connection_openssl_parent_initable_iface->init (initable, cancellable, error);
 }
 
 static void
 g_tls_connection_openssl_initable_iface_init (GInitableIface *iface)
 {
+  g_tls_connection_openssl_parent_initable_iface = g_type_interface_peek_parent (iface);
+
   iface->init = g_tls_connection_openssl_initable_init;
 }
 
 static void
 g_tls_connection_openssl_init (GTlsConnectionOpenssl *openssl)
 {
-  GTlsConnectionOpensslPrivate *priv;
-
-  priv = g_tls_connection_openssl_get_instance_private (openssl);
-
-  g_mutex_init (&priv->ssl_mutex);
 }
 
 SSL *
diff --git a/tls/openssl/gtlsconnection-openssl.h b/tls/openssl/gtlsconnection-openssl.h
index 7b85fdc..ba5a6bc 100644
--- a/tls/openssl/gtlsconnection-openssl.h
+++ b/tls/openssl/gtlsconnection-openssl.h
@@ -40,9 +40,11 @@ struct _GTlsConnectionOpensslClass
 {
   GTlsConnectionBaseClass parent_class;
 
-  SSL *(*get_ssl) (GTlsConnectionOpenssl *connection);
+  /* FIXME: remove this, or entire refactor is a failure */
+  SSL      *(*get_ssl)          (GTlsConnectionOpenssl *connection);
 };
 
+/* FIXME: remove this, or entire refactor is a failure */
 SSL *g_tls_connection_openssl_get_ssl (GTlsConnectionOpenssl *connection);
 
 GTlsConnectionOpenssl *g_tls_connection_openssl_get_connection_from_ssl (SSL *ssl);
diff --git a/tls/openssl/gtlsoperationsthread-openssl.c b/tls/openssl/gtlsoperationsthread-openssl.c
new file mode 100644
index 0000000..e67bbee
--- /dev/null
+++ b/tls/openssl/gtlsoperationsthread-openssl.c
@@ -0,0 +1,327 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2015 NICE s.r.l.
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * In addition, when the library is used with OpenSSL, a special
+ * exception applies. Refer to the LICENSE_EXCEPTION file for details.
+ */
+
+#include "config.h"
+#include "gtlsoperationsthread-openssl.h"
+
+#include "gtlsconnection-openssl.h"
+
+#include <glib/gi18n-lib.h>
+
+struct _GTlsOperationsThreadOpenssl {
+  GTlsOperationsThreadBase parent_instance;
+
+  SSL *ssl;
+
+  gboolean shutting_down;
+};
+
+static GInitableIface *g_tls_operations_thread_openssl_parent_initable_iface;
+
+static void g_tls_operations_thread_openssl_initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GTlsOperationsThreadOpenssl, g_tls_operations_thread_openssl, 
G_TYPE_TLS_OPERATIONS_THREAD_BASE,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                g_tls_operations_thread_openssl_initable_iface_init);
+                         )
+
+static GTlsOperationStatus
+end_openssl_io (GTlsOperationsThreadOpenssl  *self,
+                GIOCondition                  direction,
+                int                           ret,
+                GError                      **error,
+                const char                   *err_prefix,
+                const char                   *err_str)
+{
+  GTlsConnectionBase *tls;
+  int err_code, err, err_lib, reason;
+  GError *my_error = NULL;
+  GTlsOperationStatus status;
+
+  tls = g_tls_operations_thread_base_get_connection (G_TLS_OPERATIONS_THREAD_BASE (self));
+
+  err_code = SSL_get_error (self->ssl, ret);
+
+  status = g_tls_connection_base_pop_io (tls, direction, ret > 0, &my_error);
+
+  if (err_code == SSL_ERROR_ZERO_RETURN)
+    return G_TLS_OPERATION_SUCCESS;
+
+  if (status == G_TLS_OPERATION_SUCCESS ||
+      status == G_TLS_OPERATION_WOULD_BLOCK ||
+      status == G_TLS_OPERATION_TIMED_OUT)
+    {
+      if (my_error)
+        g_propagate_error (error, my_error);
+      return status;
+    }
+
+  /* This case is documented that it may happen and that is perfectly fine */
+  if (err_code == SSL_ERROR_SYSCALL &&
+      ((self->shutting_down && !my_error) || g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE)))
+    {
+      g_clear_error (&my_error);
+      return G_TLS_OPERATION_SUCCESS;
+    }
+
+  err = ERR_get_error ();
+  err_lib = ERR_GET_LIB (err);
+  reason = ERR_GET_REASON (err);
+
+  if (g_tls_connection_base_is_handshaking (tls) && !g_tls_connection_base_ever_handshaked (tls))
+    {
+      if (reason == SSL_R_BAD_PACKET_LENGTH ||
+          reason == SSL_R_UNKNOWN_ALERT_TYPE ||
+          reason == SSL_R_DECRYPTION_FAILED ||
+          reason == SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC ||
+          reason == SSL_R_BAD_PROTOCOL_VERSION_NUMBER ||
+          reason == SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE ||
+          reason == SSL_R_UNKNOWN_PROTOCOL)
+        {
+          g_clear_error (&my_error);
+          g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
+                       _("Peer failed to perform TLS handshake: %s"), ERR_reason_error_string (err));
+          return G_TLS_OPERATION_ERROR;
+        }
+    }
+
+#ifdef SSL_R_SHUTDOWN_WHILE_IN_INIT
+  /* XXX: this error happens on ubuntu when shutting down the connection, it
+   * seems to be a bug in a specific version of openssl, so let's handle it
+   * gracefully
+   */
+  if (reason == SSL_R_SHUTDOWN_WHILE_IN_INIT)
+    {
+      g_clear_error (&my_error);
+      return G_TLS_OPERATION_SUCCESS;
+    }
+#endif
+
+  if (reason == SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE
+#ifdef SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED
+      || reason == SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED
+#endif
+     )
+    {
+      g_clear_error (&my_error);
+      g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
+                           _("TLS connection peer did not send a certificate"));
+      return status;
+    }
+
+  if (reason == SSL_R_CERTIFICATE_VERIFY_FAILED)
+    {
+      g_clear_error (&my_error);
+      g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+                   _("Unacceptable TLS certificate"));
+      return G_TLS_OPERATION_ERROR;
+    }
+
+  if (reason == SSL_R_TLSV1_ALERT_UNKNOWN_CA)
+    {
+      g_clear_error (&my_error);
+      g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+                   _("Unacceptable TLS certificate authority"));
+      return G_TLS_OPERATION_ERROR;
+    }
+
+  if (err_lib == ERR_LIB_RSA && reason == RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY)
+    {
+      g_clear_error (&my_error);
+      g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+                           _("Digest too big for RSA key"));
+      return G_TLS_OPERATION_ERROR;
+    }
+
+  if (my_error)
+    g_propagate_error (error, my_error);
+  else
+    /* FIXME: this is just for debug */
+    g_message ("end_openssl_io %s: %d, %d, %d", G_IS_TLS_CLIENT_CONNECTION (tls) ? "client" : "server", 
err_code, err_lib, reason);
+
+  if (error && !*error)
+    *error = g_error_new (G_TLS_ERROR, G_TLS_ERROR_MISC, "%s: %s", err_prefix, err_str);
+
+  return G_TLS_OPERATION_ERROR;
+}
+
+#define BEGIN_OPENSSL_IO(self, direction, cancellable)          \
+  do {                                                          \
+    char error_str[256];                                        \
+    g_tls_connection_base_push_io (g_tls_operations_thread_base_get_connection (G_TLS_OPERATIONS_THREAD_BASE 
(self)), \
+                                   direction, 0, cancellable);
+
+#define END_OPENSSL_IO(self, direction, ret, status, errmsg, err) \
+    ERR_error_string_n (SSL_get_error (self->ssl, ret), error_str, sizeof (error_str)); \
+    status = end_openssl_io (self, direction, ret, err, errmsg, error_str); \
+  } while (status == G_TLS_OPERATION_TRY_AGAIN);
+
+static GTlsOperationStatus
+g_tls_operations_thread_openssl_handshake (GTlsOperationsThreadBase  *base,
+                                           gint64                     timeout,
+                                           GCancellable              *cancellable,
+                                           GError                   **error)
+{
+  GTlsOperationsThreadOpenssl *self = G_TLS_OPERATIONS_THREAD_OPENSSL (base);
+  GTlsOperationStatus status;
+  int ret;
+
+  /* FIXME: doesn't respect timeout */
+
+  BEGIN_OPENSSL_IO (self, G_IO_IN | G_IO_OUT, cancellable);
+  ret = SSL_do_handshake (ssl);
+  END_OPENSSL_IO (self, G_IO_IN | G_IO_OUT, ret, status,
+                  _("Error performing TLS handshake"), error);
+
+  /* FIXME: sabotage */
+#if 0
+  if (ret > 0)
+    {
+      if (!g_tls_connection_base_handshake_thread_verify_certificate (G_TLS_CONNECTION_BASE (openssl)))
+        return G_TLS_OPERATION_ERROR;
+    }
+#endif
+
+  return status;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_openssl_read (GTlsOperationsThreadBase   *base,
+                                      void                       *buffer,
+                                      gsize                       size,
+                                      gssize                     *nread,
+                                      GCancellable               *cancellable,
+                                      GError                    **error)
+{
+  GTlsOperationsThreadOpenssl *self = G_TLS_OPERATIONS_THREAD_OPENSSL (base);
+  GTlsOperationStatus status;
+  gssize ret;
+
+  BEGIN_OPENSSL_IO (self, G_IO_OUT, cancellable);
+  ret = SSL_read (self->ssl, buffer, size);
+  END_OPENSSL_IO (self, G_IO_OUT, ret, status,
+                  _("Error reading data from TLS socket"), error);
+
+
+  *nread = MAX (ret, 0);
+  return status;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_openssl_write (GTlsOperationsThreadBase  *base,
+                                       const void                *buffer,
+                                       gsize                      size,
+                                       gssize                    *nwrote,
+                                       GCancellable              *cancellable,
+                                       GError                   **error)
+{
+  GTlsOperationsThreadOpenssl *self = G_TLS_OPERATIONS_THREAD_OPENSSL (base);
+  GTlsOperationStatus status;
+  gssize ret;
+
+  BEGIN_OPENSSL_IO (self, G_IO_OUT, cancellable);
+  ret = SSL_write (self->ssl, buffer, size);
+  END_OPENSSL_IO (self, G_IO_OUT, ret, status,
+                  _("Error writing data to TLS socket"), error);
+  *nwrote = MAX (ret, 0);
+  return status;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_openssl_close (GTlsOperationsThreadBase  *base,
+                                       GCancellable              *cancellable,
+                                       GError                   **error)
+{
+  GTlsOperationsThreadOpenssl *self = G_TLS_OPERATIONS_THREAD_OPENSSL (base);
+  GTlsOperationStatus status;
+  int ret;
+
+  self->shutting_down = TRUE;
+
+  BEGIN_OPENSSL_IO (self, G_IO_IN | G_IO_OUT, cancellable);
+  ret = SSL_shutdown (self->ssl);
+  /* Note it is documented that getting 0 is correct when shutting down since
+   * it means it will close the write direction
+   */
+  ret = ret == 0 ? 1 : ret;
+  END_OPENSSL_IO (self, G_IO_IN | G_IO_OUT, ret, status,
+                  _("Error performing TLS close"), error);
+
+  return status;
+}
+
+static gboolean
+g_tls_operations_thread_openssl_initable_init (GInitable     *initable,
+                                               GCancellable  *cancellable,
+                                               GError       **error)
+{
+  GTlsOperationsThreadOpenssl *self = G_TLS_OPERATIONS_THREAD_OPENSSL (initable);
+  GTlsConnectionBase *openssl;
+
+  if (!g_tls_operations_thread_openssl_parent_initable_iface->init (initable, cancellable, error))
+    return FALSE;
+
+  openssl = g_tls_operations_thread_base_get_connection (G_TLS_OPERATIONS_THREAD_BASE (self));
+  self->ssl = g_tls_connection_openssl_get_ssl (G_TLS_CONNECTION_OPENSSL (openssl));
+
+  return TRUE;
+}
+
+static void
+g_tls_operations_thread_openssl_init (GTlsOperationsThreadOpenssl *self)
+{
+}
+
+static void
+g_tls_operations_thread_openssl_class_init (GTlsOperationsThreadOpensslClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GTlsOperationsThreadBaseClass *base_class = G_TLS_OPERATIONS_THREAD_BASE_CLASS (klass);
+
+  base_class->handshake_fn   = g_tls_operations_thread_openssl_handshake;
+  base_class->read_fn        = g_tls_operations_thread_openssl_read;
+  base_class->write_fn       = g_tls_operations_thread_openssl_write;
+  base_class->close_fn       = g_tls_operations_thread_openssl_close;
+}
+
+static void
+g_tls_operations_thread_openssl_initable_iface_init (GInitableIface *iface)
+{
+  g_tls_operations_thread_openssl_parent_initable_iface = g_type_interface_peek_parent (iface);
+
+  iface->init = g_tls_operations_thread_openssl_initable_init;
+}
+
+GTlsOperationsThreadBase *
+g_tls_operations_thread_openssl_new (GTlsConnectionOpenssl *tls,
+                                     GIOStream             *base_iostream)
+{
+  return g_initable_new (G_TYPE_TLS_OPERATIONS_THREAD_OPENSSL,
+                         NULL, NULL,
+                         "base-iostream", base_iostream,
+                         "tls-connection", tls,
+                         NULL);
+}
diff --git a/tls/openssl/gtlsoperationsthread-openssl.h b/tls/openssl/gtlsoperationsthread-openssl.h
new file mode 100644
index 0000000..7441f3c
--- /dev/null
+++ b/tls/openssl/gtlsoperationsthread-openssl.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * In addition, when the library is used with OpenSSL, a special
+ * exception applies. Refer to the LICENSE_EXCEPTION file for details.
+ */
+
+#pragma once
+
+#include "gtlsconnection-openssl.h"
+#include "gtlsoperationsthread-base.h"
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_TLS_OPERATIONS_THREAD_OPENSSL (g_tls_operations_thread_openssl_get_type ())
+
+G_DECLARE_FINAL_TYPE (GTlsOperationsThreadOpenssl, g_tls_operations_thread_openssl, G, 
TLS_OPERATIONS_THREAD_OPENSSL, GTlsOperationsThreadBase)
+
+GTlsOperationsThreadBase *g_tls_operations_thread_openssl_new (GTlsConnectionOpenssl *tls,
+                                                               GIOStream             *base_iostream);
+
+G_END_DECLS
diff --git a/tls/openssl/meson.build b/tls/openssl/meson.build
index 0ac25c8..9b799ec 100644
--- a/tls/openssl/meson.build
+++ b/tls/openssl/meson.build
@@ -7,6 +7,7 @@ sources = files(
   'gtlsclientconnection-openssl.c',
   'gtlsdatabase-openssl.c',
   'gtlsfiledatabase-openssl.c',
+  'gtlsoperationsthread-openssl.c',
   'gtlsbio.c',
   'openssl-util.c',
 )
diff --git a/tls/tests/connection.c b/tls/tests/connection.c
index 6bb4585..8a4f182 100644
--- a/tls/tests/connection.c
+++ b/tls/tests/connection.c
@@ -82,7 +82,6 @@ typedef struct {
   GSocketConnectable *identity;
   GSocketAddress *address;
   GTlsAuthenticationMode auth_mode;
-  gboolean rehandshake;
   GTlsCertificateFlags accept_flags;
   GError *read_error;
   GError *server_error;
@@ -211,6 +210,9 @@ on_accept_certificate (GTlsConnection       *conn,
                        gpointer              user_data)
 {
   TestConnection *test = user_data;
+
+  g_assert_nonnull (cert);
+
   return errors == test->accept_flags;
 }
 
@@ -218,25 +220,6 @@ static void on_output_write_finish (GObject        *object,
                                     GAsyncResult   *res,
                                     gpointer        user_data);
 
-static void
-on_rehandshake_finish (GObject        *object,
-                       GAsyncResult   *res,
-                       gpointer        user_data)
-{
-  TestConnection *test = user_data;
-  GError *error = NULL;
-  GOutputStream *stream;
-
-  g_tls_connection_handshake_finish (G_TLS_CONNECTION (object), res, &error);
-  g_assert_no_error (error);
-
-  stream = g_io_stream_get_output_stream (test->server_connection);
-  g_output_stream_write_async (stream, TEST_DATA + TEST_DATA_LENGTH / 2,
-                               TEST_DATA_LENGTH / 2,
-                               G_PRIORITY_DEFAULT, NULL,
-                               on_output_write_finish, test);
-}
-
 static void
 on_server_close_finish (GObject        *object,
                         GAsyncResult   *res,
@@ -269,15 +252,6 @@ on_output_write_finish (GObject        *object,
   g_assert_no_error (test->server_error);
   g_output_stream_write_finish (G_OUTPUT_STREAM (object), res, &test->server_error);
 
-  if (!test->server_error && test->rehandshake)
-    {
-      test->rehandshake = FALSE;
-      g_tls_connection_handshake_async (G_TLS_CONNECTION (test->server_connection),
-                                        G_PRIORITY_DEFAULT, NULL,
-                                        on_rehandshake_finish, test);
-      return;
-    }
-
   if (test->connection_received_strategy == WRITE_THEN_CLOSE)
     close_server_connection (test);
 }
@@ -339,7 +313,7 @@ on_incoming_connection (GSocketService     *service,
       test->connection_received_strategy == WRITE_THEN_WAIT)
     {
       g_output_stream_write_async (stream, TEST_DATA,
-                                   test->rehandshake ? TEST_DATA_LENGTH / 2 : TEST_DATA_LENGTH,
+                                   TEST_DATA_LENGTH,
                                    G_PRIORITY_DEFAULT, NULL,
                                    on_output_write_finish, test);
     }
@@ -437,13 +411,6 @@ run_echo_server (GThreadedSocketService *service,
           nwrote = g_output_stream_write (ostream, buf + total, nread - total, NULL, &error);
           g_assert_no_error (error);
         }
-
-      if (test->rehandshake)
-        {
-          test->rehandshake = FALSE;
-          g_tls_connection_handshake (tlsconn, NULL, &error);
-          g_assert_no_error (error);
-        }
     }
 
   g_io_stream_close (test->server_connection, NULL, &error);
@@ -1168,20 +1135,6 @@ test_client_auth_pkcs11_connection (TestConnection *test,
 }
 #endif
 
-static void
-test_client_auth_rehandshake (TestConnection *test,
-                              gconstpointer   data)
-{
-#ifdef BACKEND_IS_OPENSSL
-  /* FIXME: this doesn't make sense, we should support safe renegotation */
-  g_test_skip ("the server avoids rehandshake to avoid the security problem CVE-2009-3555");
-  return;
-#endif
-
-  test->rehandshake = TRUE;
-  test_client_auth_connection (test, data);
-}
-
 static void
 test_client_auth_failure (TestConnection *test,
                           gconstpointer   data)
@@ -1878,19 +1831,6 @@ test_simultaneous_async (TestConnection *test,
   g_assert_cmpstr (test->buf, ==, TEST_DATA);
 }
 
-static void
-test_simultaneous_async_rehandshake (TestConnection *test,
-                                     gconstpointer   data)
-{
-#ifdef BACKEND_IS_OPENSSL
-  g_test_skip ("this needs more research on openssl");
-  return;
-#endif
-
-  test->rehandshake = TRUE;
-  test_simultaneous_async (test, data);
-}
-
 static gpointer
 simul_read_thread (gpointer user_data)
 {
@@ -1977,19 +1917,6 @@ test_simultaneous_sync (TestConnection *test,
   g_assert_no_error (error);
 }
 
-static void
-test_simultaneous_sync_rehandshake (TestConnection *test,
-                                    gconstpointer   data)
-{
-#ifdef BACKEND_IS_OPENSSL
-  g_test_skip ("this needs more research on openssl");
-  return;
-#endif
-
-  test->rehandshake = TRUE;
-  test_simultaneous_sync (test, data);
-}
-
 static void
 test_close_immediately (TestConnection *test,
                         gconstpointer   data)
@@ -2593,8 +2520,6 @@ main (int   argc,
               setup_connection, test_invalid_chain_with_alternative_ca_cert, teardown_connection);
   g_test_add ("/tls/" BACKEND "/connection/client-auth", TestConnection, NULL,
               setup_connection, test_client_auth_connection, teardown_connection);
-  g_test_add ("/tls/" BACKEND "/connection/client-auth-rehandshake", TestConnection, NULL,
-              setup_connection, test_client_auth_rehandshake, teardown_connection);
   g_test_add ("/tls/" BACKEND "/connection/client-auth-failure", TestConnection, NULL,
               setup_connection, test_client_auth_failure, teardown_connection);
   g_test_add ("/tls/" BACKEND "/connection/client-auth-fail-missing-client-private-key", TestConnection, 
NULL,
@@ -2624,10 +2549,6 @@ main (int   argc,
               setup_connection, test_simultaneous_async, teardown_connection);
   g_test_add ("/tls/" BACKEND "/connection/simultaneous-sync", TestConnection, NULL,
               setup_connection, test_simultaneous_sync, teardown_connection);
-  g_test_add ("/tls/" BACKEND "/connection/simultaneous-async-rehandshake", TestConnection, NULL,
-              setup_connection, test_simultaneous_async_rehandshake, teardown_connection);
-  g_test_add ("/tls/" BACKEND "/connection/simultaneous-sync-rehandshake", TestConnection, NULL,
-              setup_connection, test_simultaneous_sync_rehandshake, teardown_connection);
   g_test_add ("/tls/" BACKEND "/connection/close-immediately", TestConnection, NULL,
               setup_connection, test_close_immediately, teardown_connection);
   g_test_add ("/tls/" BACKEND "/connection/unclean-close-by-server", TestConnection, NULL,


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