[glib-networking/wip/pwithnall/dtls: 13/24] gnutls: Implement half-duplex close in GTlsOutputStreamGnutls



commit f667974d443164e038ca7fc0c847d820efe3b4b1
Author: Philip Withnall <philip withnall collabora co uk>
Date:   Fri Sep 26 15:47:07 2014 +0100

    gnutls: Implement half-duplex close in GTlsOutputStreamGnutls
    
    Including unit tests.
    
    Based on a patch by Olivier CrĂȘte <olivier crete collabora com>.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=735754

 tls/gnutls/gtlsoutputstream-gnutls.c |   83 ++++++++++++++++++++++++
 tls/tests/connection.c               |  115 ++++++++++++++++++++++++++++++++++
 2 files changed, 198 insertions(+), 0 deletions(-)
---
diff --git a/tls/gnutls/gtlsoutputstream-gnutls.c b/tls/gnutls/gtlsoutputstream-gnutls.c
index e3a4f77..aa60f08 100644
--- a/tls/gnutls/gtlsoutputstream-gnutls.c
+++ b/tls/gnutls/gtlsoutputstream-gnutls.c
@@ -128,6 +128,86 @@ g_tls_output_stream_gnutls_pollable_write_nonblocking (GPollableOutputStream  *p
   return ret;
 }
 
+static gboolean
+g_tls_output_stream_gnutls_close (GOutputStream            *stream,
+                                  GCancellable             *cancellable,
+                                  GError                  **error)
+{
+  GTlsOutputStreamGnutls *tls_stream = G_TLS_OUTPUT_STREAM_GNUTLS (stream);
+  GIOStream *conn;
+  gboolean ret;
+
+  conn = g_weak_ref_get (&tls_stream->priv->weak_conn);
+
+  /* Special case here because this is called by the finalize
+   * of the main GTlsConnection object.
+   */
+  if (conn == NULL)
+    return TRUE;
+
+  ret = g_tls_connection_gnutls_close_internal (conn, G_TLS_DIRECTION_WRITE,
+                                                cancellable, error);
+
+  g_object_unref (conn);
+  return ret;
+}
+
+/* We do async close as synchronous-in-a-thread so we don't need to
+ * implement G_IO_IN/G_IO_OUT flip-flopping just for this one case
+ * (since handshakes are also done synchronously now).
+ */
+static void
+close_thread (GTask        *task,
+             gpointer      object,
+             gpointer      task_data,
+             GCancellable *cancellable)
+{
+  GTlsOutputStreamGnutls *tls_stream = object;
+  GError *error = NULL;
+  GIOStream *conn;
+
+  conn = g_weak_ref_get (&tls_stream->priv->weak_conn);
+
+  if (conn && !g_tls_connection_gnutls_close_internal (conn,
+                                                       G_TLS_DIRECTION_WRITE,
+                                                       cancellable, &error))
+    g_task_return_error (task, error);
+  else
+    g_task_return_boolean (task, TRUE);
+
+  if (conn)
+    g_object_unref (conn);
+}
+
+
+static void
+g_tls_output_stream_gnutls_close_async (GOutputStream            *stream,
+                                        int                       io_priority,
+                                        GCancellable             *cancellable,
+                                        GAsyncReadyCallback       callback,
+                                        gpointer                  user_data)
+{
+  GTask *task;
+
+  task = g_task_new (stream, cancellable, callback, user_data);
+  g_task_set_source_tag (task, g_tls_output_stream_gnutls_close_async);
+  g_task_set_priority (task, io_priority);
+  g_task_run_in_thread (task, close_thread);
+  g_object_unref (task);
+}
+
+static gboolean
+g_tls_output_stream_gnutls_close_finish (GOutputStream            *stream,
+                                         GAsyncResult             *result,
+                                         GError                  **error)
+{
+  g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
+  g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) ==
+                        g_tls_output_stream_gnutls_close_async, FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
 static void
 g_tls_output_stream_gnutls_class_init (GTlsOutputStreamGnutlsClass *klass)
 {
@@ -140,6 +220,9 @@ g_tls_output_stream_gnutls_class_init (GTlsOutputStreamGnutlsClass *klass)
   gobject_class->finalize = g_tls_output_stream_gnutls_finalize;
 
   output_stream_class->write_fn = g_tls_output_stream_gnutls_write;
+  output_stream_class->close_fn = g_tls_output_stream_gnutls_close;
+  output_stream_class->close_async = g_tls_output_stream_gnutls_close_async;
+  output_stream_class->close_finish = g_tls_output_stream_gnutls_close_finish;
 }
 
 static void
diff --git a/tls/tests/connection.c b/tls/tests/connection.c
index 60af5f6..d2bf8cb 100644
--- a/tls/tests/connection.c
+++ b/tls/tests/connection.c
@@ -1661,6 +1661,59 @@ test_close_during_handshake (TestConnection *test,
 }
 
 static void
+test_output_stream_close_during_handshake (TestConnection *test,
+                                           gconstpointer   data)
+{
+  GIOStream *connection;
+  GError *error = NULL;
+  GMainContext *context;
+  GMainLoop *loop;
+  gboolean handshake_complete = FALSE;
+
+  g_test_bug ("688751");
+
+  connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_REQUESTED, TRUE);
+  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_object_unref (connection);
+
+  loop = g_main_loop_new (NULL, FALSE);
+  g_signal_connect (test->client_connection, "notify::accepted-cas",
+                    G_CALLBACK (quit_loop_on_notify), loop);
+
+  context = g_main_context_new ();
+  g_main_context_push_thread_default (context);
+  g_tls_connection_handshake_async (G_TLS_CONNECTION (test->client_connection),
+                                   G_PRIORITY_DEFAULT, NULL,
+                                   handshake_completed, &handshake_complete);
+  g_main_context_pop_thread_default (context);
+
+  /* Now run the (default GMainContext) loop, which is needed for
+   * the server side of things. The client-side handshake will run in
+   * a thread, but its callback will never be invoked because its
+   * context isn't running.
+   */
+  g_main_loop_run (loop);
+  g_main_loop_unref (loop);
+
+  /* At this point handshake_thread() has started (and maybe
+   * finished), but handshake_thread_completed() (and thus
+   * finish_handshake()) has not yet run. Make sure close doesn't
+   * block.
+   */
+  g_output_stream_close (g_io_stream_get_output_stream (test->client_connection), NULL, &error);
+  g_assert_no_error (error);
+
+  /* We have to let the handshake_async() call finish now, or
+   * teardown_connection() will assert.
+   */
+  while (!handshake_complete)
+    g_main_context_iteration (context, TRUE);
+  g_main_context_unref (context);
+}
+
+
+static void
 test_write_during_handshake (TestConnection *test,
                            gconstpointer   data)
 {
@@ -1853,6 +1906,64 @@ test_fallback_subprocess (TestConnection *test,
   g_assert_no_error (error);
 }
 
+static void
+test_output_stream_close (TestConnection *test,
+                          gconstpointer   data)
+{
+  GIOStream *connection;
+  GError *error = NULL;
+  gboolean ret;
+  gboolean handshake_complete = FALSE;
+  gssize size;
+
+  connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE, TRUE);
+  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_object_unref (connection);
+
+  /* No validation at all in this test */
+  g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection),
+                                                0);
+
+  g_tls_connection_handshake_async (G_TLS_CONNECTION (test->client_connection),
+                                    G_PRIORITY_DEFAULT, NULL,
+                                    handshake_completed, &handshake_complete);
+
+  while (!handshake_complete)
+    g_main_context_iteration (NULL, TRUE);
+
+  ret = g_output_stream_close (g_io_stream_get_output_stream (test->client_connection),
+      NULL, &error);
+  g_assert_no_error (error);
+  g_assert (ret);
+
+
+  /* Verify that double close returns TRUE */
+  ret = g_output_stream_close (g_io_stream_get_output_stream (test->client_connection),
+      NULL, &error);
+  g_assert_no_error (error);
+  g_assert (ret);
+
+  size = g_output_stream_write (g_io_stream_get_output_stream (test->client_connection),
+                                "data", 4, NULL, &error);
+  g_assert (size == -1);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED);
+  g_clear_error (&error);
+
+  /* We closed the output stream, but not the input stream, so receiving
+   * data should still work.
+   */
+  read_test_data_async (test);
+  g_main_loop_run (test->loop);
+
+  g_assert_no_error (test->read_error);
+  g_assert_no_error (test->server_error);
+
+  ret = g_io_stream_close (test->client_connection, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (ret);
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -1933,10 +2044,14 @@ main (int   argc,
               setup_connection, test_close_immediately, teardown_connection);
   g_test_add ("/tls/connection/close-during-handshake", TestConnection, NULL,
               setup_connection, test_close_during_handshake, teardown_connection);
+  g_test_add ("/tls/connection/close-output-stream-during-handshake", TestConnection, NULL,
+              setup_connection, test_output_stream_close_during_handshake, teardown_connection);
   g_test_add ("/tls/connection/write-during-handshake", TestConnection, NULL,
               setup_connection, test_write_during_handshake, teardown_connection);
   g_test_add ("/tls/connection/async-implicit-handshake", TestConnection, NULL,
               setup_connection, test_async_implicit_handshake, teardown_connection);
+  g_test_add ("/tls/connection/output-stream-close", TestConnection, NULL,
+              setup_connection, test_output_stream_close, teardown_connection);
 
   g_test_add_data_func ("/tls/connection/fallback/SSL", PRIORITY_SSL_FALLBACK, test_fallback);
   g_test_add ("/tls/connection/fallback/subprocess/" PRIORITY_SSL_FALLBACK,


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