[gnome-network-displays/cc-tmp: 25/80] cc: state handling implemented




commit 0c893fda8a198ef9983fa560bebe435f3ddfb80c
Author: Anupam Kumar <kyteinsky gmail com>
Date:   Tue Aug 23 15:21:44 2022 +0530

    cc: state handling implemented
    
    may have some bugs in the corners

 src/cc/cc-comm.c        | 449 +++++++++++++++++++++++----------------------
 src/cc/cc-comm.h        |  86 +++++++--
 src/cc/cc-common.h      |  64 +++++++
 src/cc/cc-ctrl.c        | 476 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/cc/cc-ctrl.h        |  74 ++++++++
 src/cc/cc-json-helper.c | 237 ++++++++++++++++++++++++
 src/cc/cc-json-helper.h |  48 +++++
 src/cc/meson.build      |   2 +
 src/nd-cc-sink.c        | 124 +++----------
 9 files changed, 1221 insertions(+), 339 deletions(-)
---
diff --git a/src/cc/cc-comm.c b/src/cc/cc-comm.c
index 36ebac8..65a5b3e 100644
--- a/src/cc/cc-comm.c
+++ b/src/cc/cc-comm.c
@@ -17,7 +17,6 @@
  */
 
 #include "cc-comm.h"
-#include "cast_channel.pb-c.h"
 
 // function decl
 static void cc_comm_listen (CcComm *comm);
@@ -26,113 +25,107 @@ static void cc_comm_read (CcComm  *comm,
                           gboolean read_header);
 
 
-static gboolean
-cc_comm_load_media_cb (CcComm *comm)
-{
-  if (!cc_comm_send_request (comm, MESSAGE_TYPE_MEDIA, "{ \"type\": \"LOAD\", \"media\": { \"contentId\": 
\"https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/mp4/BigBuckBunny.mp4\";, 
\"streamType\": \"BUFFERED\", \"contentType\": \"video/mp4\" }, \"requestId\": 4 }", NULL))
-    g_warning ("NdCCSink: something went wrong with load media");
-
-  return FALSE;
-}
-
-static void
-cc_comm_dump_message (guint8 *msg, gsize length)
-{
-  g_autoptr(GString) line = NULL;
-
-  line = g_string_new ("");
-  /* Dump the buffer. */
-  for (gint i = 0; i < length; i++)
-    {
-      g_string_append_printf (line, "%02x ", msg[i]);
-      if ((i + 1) % 16 == 0)
-        {
-          g_debug ("%s", line->str);
-          g_string_set_size (line, 0);
-        }
-    }
-
-  if (line->len)
-    g_debug ("%s", line->str);
-}
-
-static void
-cc_comm_dump_json_message (Cast__Channel__CastMessage *message)
-{
-  g_debug ("{ source_id: %s, destination_id: %s, namespace_: %s, payload_type: %d, payload_utf8: %s }",
-           message->source_id,
-           message->destination_id,
-           message->namespace_,
-           message->payload_type,
-           message->payload_utf8);
-}
-
-// returns FALSE if message is PONG
-// returns TRUE if the message is to be logged
-static gboolean
-cc_comm_parse_json_data (CcComm *comm, char *payload)
-{
-  g_autoptr(GError) error = NULL;
-  g_autoptr(JsonParser) parser = NULL;
-  g_autoptr(JsonReader) reader = NULL;
-
-  parser = json_parser_new ();
-  if (!json_parser_load_from_data (parser, payload, -1, &error))
-    {
-      g_warning ("NdCCSink: Error parsing received messaage JSON: %s", error->message);
-      return TRUE;
-    }
-
-  reader = json_reader_new (json_parser_get_root (parser));
-
-  json_reader_read_member (reader, "type");
-  const char *message_type = json_reader_get_string_value (reader);
-  json_reader_end_member (reader);
-
-  if (g_strcmp0 (message_type, "PONG") == 0)
-    return FALSE;
-
-  if (g_strcmp0 (message_type, "RECEIVER_STATUS") == 0)
-    {
-      if (json_reader_read_member (reader, "status"))
-        {
-          if (json_reader_read_member (reader, "applications"))
-            {
-              if (json_reader_read_element (reader, 0))
-                {
-                  if (json_reader_read_member (reader, "appId"))
-                    {
-                      const char *app_id = json_reader_get_string_value (reader);
-                      if (g_strcmp0 (app_id, "CC1AD845") == 0)
-                        {
-                          json_reader_end_member (reader);
-                          json_reader_read_member (reader, "transportId");
-                          const char *transport_id = json_reader_get_string_value (reader);
-                          g_debug ("CcComm: Transport Id: %s!", transport_id);
-
-                          // start a new virtual connection
-                          comm->destination_id = g_strdup (transport_id);
-                          g_debug ("CcComm: Sending second connect request");
-                          if (!cc_comm_send_request (comm, MESSAGE_TYPE_CONNECT, NULL, NULL))
-                            {
-                              g_warning ("CcComm: Something went wrong with VC request for media");
-                              return TRUE;
-                            }
-                          // call the LOAD media request after 2 seconds
-                          g_timeout_add_seconds (2, G_SOURCE_FUNC (cc_comm_load_media_cb), comm);
-                        }
-                      json_reader_end_member (reader);
-                    }
-                  json_reader_end_element (reader);
-                }
-              json_reader_end_member (reader);
-            }
-          json_reader_end_member (reader);
-        }
-    }
-
-  return TRUE;
-}
+// DEBUG HEX DUMP
+
+// static void
+// cc_comm_dump_message (guint8 *msg, gsize length)
+// {
+//   g_autoptr(GString) line = NULL;
+
+//   line = g_string_new ("");
+//   /* Dump the buffer. */
+//   for (gint i = 0; i < length; i++)
+//     {
+//       g_string_append_printf (line, "%02x ", msg[i]);
+//       if ((i + 1) % 16 == 0)
+//         {
+//           g_debug ("%s", line->str);
+//           g_string_set_size (line, 0);
+//         }
+//     }
+
+//   if (line->len)
+//     g_debug ("%s", line->str);
+// }
+
+
+
+
+// static gboolean
+// cc_comm_load_media_cb (CcComm *comm)
+// {
+//   if (!cc_comm_send_request (comm, CC_MESSAGE_TYPE_MEDIA, "{ \"type\": \"LOAD\", \"media\": { 
\"contentId\": 
\"https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/mp4/BigBuckBunny.mp4\";, 
\"streamType\": \"BUFFERED\", \"contentType\": \"video/mp4\" }, \"requestId\": 4 }", NULL))
+//     g_warning ("NdCCSink: something went wrong with load media");
+
+//   return FALSE;
+// }
+
+// // returns FALSE if message is PONG
+// // returns TRUE if the message is to be logged
+// static gboolean
+// cc_comm_parse_json_data (CcComm *comm, char *payload)
+// {
+//   g_autoptr(GError) error = NULL;
+//   g_autoptr(JsonParser) parser = NULL;
+//   g_autoptr(JsonReader) reader = NULL;
+
+//   parser = json_parser_new ();
+//   if (!json_parser_load_from_data (parser, payload, -1, &error))
+//     {
+//       g_warning ("NdCCSink: Error parsing received messaage JSON: %s", error->message);
+//       return TRUE;
+//     }
+
+//   reader = json_reader_new (json_parser_get_root (parser));
+
+//   json_reader_read_member (reader, "type");
+//   const char *message_type = json_reader_get_string_value (reader);
+//   json_reader_end_member (reader);
+
+//   if (g_strcmp0 (message_type, "PONG") == 0)
+//     return FALSE;
+
+//   if (g_strcmp0 (message_type, "RECEIVER_STATUS") == 0)
+//     {
+//       if (json_reader_read_member (reader, "status"))
+//         {
+//           if (json_reader_read_member (reader, "applications"))
+//             {
+//               if (json_reader_read_element (reader, 0))
+//                 {
+//                   if (json_reader_read_member (reader, "appId"))
+//                     {
+//                       const char *app_id = json_reader_get_string_value (reader);
+//                       if (g_strcmp0 (app_id, "CC1AD845") == 0)
+//                         {
+//                           json_reader_end_member (reader);
+//                           json_reader_read_member (reader, "transportId");
+//                           const char *transport_id = json_reader_get_string_value (reader);
+//                           g_debug ("CcComm: Transport Id: %s!", transport_id);
+
+//                           // start a new virtual connection
+//                           comm->destination_id = g_strdup (transport_id);
+//                           g_debug ("CcComm: Sending second connect request");
+//                           if (!cc_comm_send_request (comm, CC_MESSAGE_TYPE_CONNECT, NULL, NULL))
+//                             {
+//                               g_warning ("CcComm: Something went wrong with VC request for media");
+//                               return TRUE;
+//                             }
+//                           // call the LOAD media request after 2 seconds
+//                           g_timeout_add_seconds (2, G_SOURCE_FUNC (cc_comm_load_media_cb), comm);
+//                         }
+//                       json_reader_end_member (reader);
+//                     }
+//                   json_reader_end_element (reader);
+//                 }
+//               json_reader_end_member (reader);
+//             }
+//           json_reader_end_member (reader);
+//         }
+//     }
+
+//   return TRUE;
+// }
 
 static void
 cc_comm_parse_received_data (CcComm *comm, uint8_t * input_buffer, gssize input_size)
@@ -146,11 +139,21 @@ cc_comm_parse_received_data (CcComm *comm, uint8_t * input_buffer, gssize input_
       return;
     }
 
-  if (cc_comm_parse_json_data (comm, message->payload_utf8))
-    {
-      g_debug ("CcComm: Received message:");
-      cc_comm_dump_json_message (message);
-    }
+  g_clear_pointer (&comm->message_buffer, g_free);
+
+  // go for another round while we process this one
+  cc_comm_listen (comm);
+
+  CcReceivedMessageType type = cc_json_helper_get_message_type (message, NULL);
+
+  if (type == CC_RWAIT_TYPE_PING || type == CC_RWAIT_TYPE_PONG || type == -1)
+    return;
+
+  g_debug ("CcComm: Received message:");
+  cc_json_helper_dump_message (message);
+
+  // actual message handling
+  comm->closure->message_received_cb (comm->closure, message);
 
   cast__channel__cast_message__free_unpacked (message, NULL);
 }
@@ -181,12 +184,6 @@ cc_comm_accept_certificate (GTlsClientConnection *conn,
 
 // LISTENER
 
-static guint32
-cc_comm_to_message_size (CcComm *comm)
-{
-  return GINT32_FROM_BE (*(guint32 *) comm->header_buffer);
-}
-
 // async callback for message read
 static void
 cc_comm_message_read_cb (GObject      *source_object,
@@ -235,10 +232,6 @@ cc_comm_message_read_cb (GObject      *source_object,
   // cc_comm_dump_message (comm->message_buffer, io_bytes);
   cc_comm_parse_received_data (comm, comm->message_buffer, io_bytes);
 
-  g_clear_pointer (&comm->message_buffer, g_free);
-
-  // go for another round
-  cc_comm_listen (comm);
 }
 
 // async callback for header read
@@ -287,10 +280,7 @@ cc_comm_header_read_cb (GObject      *source_object,
     }
 
   // if everything is well, read all `io_bytes`
-  g_debug ("CcComm: Raw header dump:");
-  cc_comm_dump_message (comm->header_buffer, 4);
-
-  message_size = cc_comm_to_message_size (comm);
+  message_size = GINT32_FROM_BE (*(guint32 *) comm->header_buffer);
   g_debug ("CcComm: Message size: %d", message_size);
 
   g_clear_pointer (&comm->header_buffer, g_free);
@@ -419,6 +409,28 @@ cc_comm_make_connection (CcComm *comm, gchar *remote_address, GError **error)
   return TRUE;
 }
 
+void
+cc_comm_close_connection (CcComm *comm)
+{
+  g_autoptr (GError) error = NULL;
+  gboolean close_ok;
+
+  if (comm->con != NULL)
+    {
+      close_ok = g_io_stream_close (G_IO_STREAM (comm->con), NULL, &error);
+      if (!close_ok)
+        {
+          if (error != NULL)
+            g_warning ("CcComm: Error closing communication client connection: %s", error->message);
+          else
+            g_warning ("CcComm: Error closing communication client connection");
+        }
+
+      g_clear_object (&comm->con);
+      g_debug ("CcComm: Client connection removed");
+    }
+}
+
 static gboolean
 cc_comm_tls_send (CcComm  * comm,
                   uint8_t * message,
@@ -448,12 +460,11 @@ cc_comm_tls_send (CcComm  * comm,
       if (io_bytes <= 0)
         {
           g_warning ("CcComm: Failed to write: %s", (*error)->message);
+          comm->closure->fatal_error_cb (comm->closure, error);
           g_clear_error (error);
           return FALSE;
         }
 
-      // g_debug ("CcComm: Sent %" G_GSSIZE_FORMAT " bytes", io_bytes);
-
       size -= io_bytes;
     }
 
@@ -461,41 +472,80 @@ cc_comm_tls_send (CcComm  * comm,
 }
 
 // builds message based on available types
-static Cast__Channel__CastMessage
-cc_comm_build_message (gchar                                  *namespace_,
+static gboolean
+cc_comm_build_message (Cast__Channel__CastMessage             *message,
+                       gchar                                  *sender_id,
+                       gchar                                  *destination_id,
+                       CcMessageType                           message_type,
                        Cast__Channel__CastMessage__PayloadType payload_type,
-                       ProtobufCBinaryData                   * binary_payload,
+                       ProtobufCBinaryData                    *binary_payload,
                        gchar                                  *utf8_payload)
 {
-  Cast__Channel__CastMessage message;
+  cast__channel__cast_message__init (message);
+
+  message->protocol_version = CAST__CHANNEL__CAST_MESSAGE__PROTOCOL_VERSION__CASTV2_1_0;
+  // pray we don't free these pointers before being used
+  message->source_id = sender_id;
+  message->destination_id = destination_id;
+
+  switch (message_type)
+    {
+    case CC_MESSAGE_TYPE_AUTH:
+      message->namespace_ = CC_NAMESPACE_AUTH;
+      break;
+
+    case CC_MESSAGE_TYPE_CONNECT:
+    case CC_MESSAGE_TYPE_DISCONNECT:
+      message->namespace_ = CC_NAMESPACE_CONNECTION;
+      break;
+
+    case CC_MESSAGE_TYPE_PING:
+    case CC_MESSAGE_TYPE_PONG:
+      message->namespace_ = CC_NAMESPACE_HEARTBEAT;
+      break;
 
-  cast__channel__cast_message__init (&message);
+    case CC_MESSAGE_TYPE_RECEIVER:
+      message->namespace_ = CC_NAMESPACE_RECEIVER;
+      break;
+
+    case CC_MESSAGE_TYPE_MEDIA:
+      message->namespace_ = CC_NAMESPACE_MEDIA;
+      break;
+
+    case CC_MESSAGE_TYPE_WEBRTC:
+      message->namespace_ = CC_NAMESPACE_WEBRTC;
+      break;
 
-  message.protocol_version = CAST__CHANNEL__CAST_MESSAGE__PROTOCOL_VERSION__CASTV2_1_0;
-  message.source_id = "sender-gnd";
-  message.destination_id = "receiver-0";
-  message.namespace_ = namespace_;
-  message.payload_type = payload_type;
+    default:
+      return FALSE;
+    }
 
+  message->payload_type = payload_type;
   switch (payload_type)
     {
     case CAST__CHANNEL__CAST_MESSAGE__PAYLOAD_TYPE__BINARY:
-      message.payload_binary = *binary_payload;
-      message.has_payload_binary = 1;
+      message->payload_binary = *binary_payload;
+      message->has_payload_binary = 1;
       break;
 
     case CAST__CHANNEL__CAST_MESSAGE__PAYLOAD_TYPE__STRING:
-    default:
-      message.payload_utf8 = utf8_payload;
-      message.has_payload_binary = 0;
+      message->payload_utf8 = utf8_payload;
+      message->has_payload_binary = 0;
       break;
+
+    default:
+      return FALSE;
     }
 
-  return message;
+  return TRUE;
 }
 
 gboolean
-cc_comm_send_request (CcComm * comm, MessageType message_type, char *utf8_payload, GError **error)
+cc_comm_send_request (CcComm       *comm,
+                      gchar        *destination_id,
+                      CcMessageType message_type,
+                      gchar        *utf8_payload,
+                      GError      **error)
 {
   Cast__Channel__CastMessage message;
   guint32 packed_size = 0;
@@ -503,71 +553,38 @@ cc_comm_send_request (CcComm * comm, MessageType message_type, char *utf8_payloa
 
   switch (message_type)
     {
-    case MESSAGE_TYPE_AUTH:
+    // CAST__CHANNEL__CAST_MESSAGE__PROTOCOL_VERSION__CASTV2_1_3 allows for binary payloads over utf8
+    case CC_MESSAGE_TYPE_AUTH:
       ProtobufCBinaryData binary_payload;
       binary_payload.data = NULL;
       binary_payload.len = 0;
 
-      message = cc_comm_build_message (
-        "urn:x-cast:com.google.cast.tp.deviceauth",
-        CAST__CHANNEL__CAST_MESSAGE__PAYLOAD_TYPE__BINARY,
-        &binary_payload,
-        NULL);
-      break;
-
-    case MESSAGE_TYPE_CONNECT:
-      message = cc_comm_build_message (
-        "urn:x-cast:com.google.cast.tp.connection",
-        CAST__CHANNEL__CAST_MESSAGE__PAYLOAD_TYPE__STRING,
-        NULL,
-        // "{ \"type\": \"CONNECT\" }");
-        "{ \"type\": \"CONNECT\", \"userAgent\": \"GND/0.90.5  (X11; Linux x86_64)\", \"connType\": 0, 
\"origin\": {}, \"senderInfo\": { \"sdkType\": 2, \"version\": \"X11; Linux x86_64\", \"browserVersion\": 
\"X11; Linux x86_64\", \"platform\": 6, \"connectionType\": 1 } }");
-      message.destination_id = comm->destination_id;
-      break;
-
-    case MESSAGE_TYPE_DISCONNECT:
-      message = cc_comm_build_message (
-        "urn:x-cast:com.google.cast.tp.connection",
-        CAST__CHANNEL__CAST_MESSAGE__PAYLOAD_TYPE__STRING,
-        NULL,
-        "{ \"type\": \"CLOSE\" }");
-      break;
-
-    case MESSAGE_TYPE_PING:
-      message = cc_comm_build_message (
-        "urn:x-cast:com.google.cast.tp.heartbeat",
-        CAST__CHANNEL__CAST_MESSAGE__PAYLOAD_TYPE__STRING,
-        NULL,
-        "{ \"type\": \"PING\" }");
-      break;
-
-    case MESSAGE_TYPE_PONG:
-      message = cc_comm_build_message (
-        "urn:x-cast:com.google.cast.tp.heartbeat",
-        CAST__CHANNEL__CAST_MESSAGE__PAYLOAD_TYPE__STRING,
-        NULL,
-        "{ \"type\": \"PONG\" }");
-      break;
-
-    case MESSAGE_TYPE_RECEIVER:
-      message = cc_comm_build_message (
-        "urn:x-cast:com.google.cast.receiver",
-        CAST__CHANNEL__CAST_MESSAGE__PAYLOAD_TYPE__STRING,
-        NULL,
-        utf8_payload);
-      break;
-
-    case MESSAGE_TYPE_MEDIA:
-      message = cc_comm_build_message (
-        "urn:x-cast:com.google.cast.media",
-        CAST__CHANNEL__CAST_MESSAGE__PAYLOAD_TYPE__STRING,
-        NULL,
-        utf8_payload);
-      message.destination_id = comm->destination_id;
+      if (!cc_comm_build_message (&message,
+                                  CC_DEFAULT_SENDER_ID,
+                                  destination_id,
+                                  message_type,
+                                  CAST__CHANNEL__CAST_MESSAGE__PAYLOAD_TYPE__BINARY,
+                                  &binary_payload,
+                                  NULL))
+        {
+          *error = g_error_new (1, 1, "Auth message building failed!");
+          return FALSE;
+        }
       break;
 
     default:
-      return FALSE;
+      if (!cc_comm_build_message (&message,
+                                  CC_DEFAULT_SENDER_ID,
+                                  destination_id,
+                                  message_type,
+                                  CAST__CHANNEL__CAST_MESSAGE__PAYLOAD_TYPE__STRING,
+                                  NULL,
+                                  utf8_payload))
+        {
+          *error = g_error_new (1, 1, "Message building failed for message type: %d", message_type);
+          return FALSE;
+        }
+      break;
     }
 
   packed_size = cast__channel__cast_message__get_packed_size (&message);
@@ -578,10 +595,10 @@ cc_comm_send_request (CcComm * comm, MessageType message_type, char *utf8_payloa
   memcpy (sock_buffer, &packed_size_be, 4);
   cast__channel__cast_message__pack (&message, 4 + sock_buffer);
 
-  if (message_type != MESSAGE_TYPE_PING && message_type != MESSAGE_TYPE_PONG)
+  if (message_type != CC_MESSAGE_TYPE_PING && message_type != CC_MESSAGE_TYPE_PONG)
     {
       g_debug ("CcComm: Sending message:");
-      cc_comm_dump_json_message (&message);
+      cc_json_helper_dump_message (&message);
     }
 
   return cc_comm_tls_send (comm,
@@ -589,23 +606,3 @@ cc_comm_send_request (CcComm * comm, MessageType message_type, char *utf8_payloa
                            packed_size + 4,
                            error);
 }
-
-gboolean
-cc_comm_send_ping (CcComm * comm)
-{
-  g_autoptr(GError) error = NULL;
-
-  // if this errors out, we cancel the periodic ping by returning FALSE
-  if (!cc_comm_send_request (comm, MESSAGE_TYPE_PING, NULL, &error))
-    {
-      if (error != NULL)
-        {
-          g_warning ("CcComm: Failed to send ping message: %s", error->message);
-          return G_SOURCE_REMOVE;
-        }
-      g_warning ("CcComm: Failed to send ping message");
-      return G_SOURCE_REMOVE;
-    }
-
-  return G_SOURCE_CONTINUE;
-}
diff --git a/src/cc/cc-comm.h b/src/cc/cc-comm.h
index 139e4e5..0ac7441 100644
--- a/src/cc/cc-comm.h
+++ b/src/cc/cc-comm.h
@@ -19,42 +19,98 @@
 #pragma once
 
 #include <json-glib-1.0/json-glib/json-glib.h>
+#include <glib-object.h>
+#include "cast_channel.pb-c.h"
+#include "cc-json-helper.h"
+#include "cc-common.h"
 
 G_BEGIN_DECLS
 
-#define MAX_MSG_SIZE (64 * 1024)
+// #define CC_MAX_MSG_SIZE (64 * 1024) // 64KB
+// #define CC_MAX_MESSAGE_TIMEOUT (20) // 20 seconds
+// // this might pose a problem when there are two gnd applications around
+// #define CC_DEFAULT_SENDER_ID "sender-gnd"
+// #define CC_DEFAULT_RECEIVER_ID "receiver-0"
+// #define CC_MIRRORING_APP_ID "0F5096E8"
+
+// #define CC_NAMESPACE_AUTH "urn:x-cast:com.google.cast.tp.deviceauth"
+// #define CC_NAMESPACE_CONNECTION "urn:x-cast:com.google.cast.tp.connection"
+// #define CC_NAMESPACE_HEARTBEAT "urn:x-cast:com.google.cast.tp.heartbeat"
+// #define CC_NAMESPACE_RECEIVER "urn:x-cast:com.google.cast.receiver"
+// #define CC_NAMESPACE_MEDIA "urn:x-cast:com.google.cast.media"
+// #define CC_NAMESPACE_WEBRTC "urn:x-cast:com.google.cast.webrtc"
+
+struct _CcCommClosure
+{
+  gpointer userdata;
+  void (*message_received_cb) (struct _CcCommClosure *closure,
+                               Cast__Channel__CastMessage *message);
+  void (*fatal_error_cb) (struct _CcCommClosure *closure, GError **error);
+};
+
+typedef struct _CcCommClosure CcCommClosure;
 
 struct _CcComm
 {
   /*< public >*/
   GIOStream    *con;
 
-  gchar        *destination_id;
   guint8       *header_buffer;
   guint8       *message_buffer;
 
   GCancellable *cancellable;
+
+  CcCommClosure    *closure;
 };
 
 typedef struct _CcComm CcComm;
 
 typedef enum {
-  MESSAGE_TYPE_AUTH,
-  MESSAGE_TYPE_CONNECT,
-  MESSAGE_TYPE_DISCONNECT,
-  MESSAGE_TYPE_PING,
-  MESSAGE_TYPE_PONG,
-  MESSAGE_TYPE_RECEIVER,
-  MESSAGE_TYPE_MEDIA,
-} MessageType;
+  CC_MESSAGE_TYPE_AUTH,
+  CC_MESSAGE_TYPE_CONNECT,
+  CC_MESSAGE_TYPE_DISCONNECT,
+  CC_MESSAGE_TYPE_PING,
+  CC_MESSAGE_TYPE_PONG,
+  CC_MESSAGE_TYPE_RECEIVER,
+  CC_MESSAGE_TYPE_MEDIA,
+  CC_MESSAGE_TYPE_WEBRTC,
+} CcMessageType;
+
+// typedef enum {
+//   CC_RWAIT_TYPE_NONE                 = 0b0,
+//   CC_RWAIT_TYPE_GET_APP_AVAILABILITY = 0b1 << 0, /* key is `responseType` */
+//   CC_RWAIT_TYPE_LAUNCH_ERROR         = 0b1 << 1, /* all other keys are `type` */
+//   CC_RWAIT_TYPE_ANSWER               = 0b1 << 2,
+//   CC_RWAIT_TYPE_RECEIVER_STATUS      = 0b1 << 3,
+//   CC_RWAIT_TYPE_MEDIA_STATUS         = 0b1 << 4,
+//   CC_RWAIT_TYPE_PING                 = 0b1 << 5,
+//   CC_RWAIT_TYPE_PONG                 = 0b1 << 6,
+//   CC_RWAIT_TYPE_CLOSE                = 0b1 << 7,
+//   CC_RWAIT_TYPE_UNKNOWN              = 0b1 << 8,
+// } CcReceivedMessageType;
+
+// // typedef CcReceivedMessageType CcWaitingFor;
+// #define CcWaitingFor CcReceivedMessageType
+
+// typedef enum {
+//   CC_WAITING_FOR_NOTHING         =  0b0,
+//   CC_WAITING_FOR_APP_AVAILABLE   =  0b1 << 0,
+//   CC_WAITING_FOR_BROADCAST       =  0b1 << 1,
+//   CC_WAITING_FOR_STATUS          =  0b1 << 2,
+//   CC_WAITING_FOR_ANSWER          =  0b1 << 3,
+//   CC_WAITING_FOR_PONG            =  0b1 << 4,
+// } CcWaitingFor;
 
 gboolean cc_comm_make_connection (CcComm  *comm,
                                   gchar   *remote_address,
                                   GError **error);
-gboolean cc_comm_send_request (CcComm      *sink,
-                               MessageType message_type,
-                               char        *utf8_payload,
+
+void cc_comm_close_connection (CcComm *comm);
+
+gboolean cc_comm_send_request (CcComm       *comm,
+                               gchar        *destination_id,
+                               CcMessageType message_type,
+                               gchar        *utf8_payload,
                                GError      **error);
-gboolean cc_comm_send_ping (CcComm *sink);
 
-G_END_DECLS
+G_END_DECLS // TODO: separate out cc stuff in a different header file
diff --git a/src/cc/cc-common.h b/src/cc/cc-common.h
new file mode 100644
index 0000000..2a68fe9
--- /dev/null
+++ b/src/cc/cc-common.h
@@ -0,0 +1,64 @@
+/* cc-common.h
+ *
+ * Copyright 2022 Anupam Kumar <kyteinsky gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define CC_MAX_MSG_SIZE (64 * 1024) // 64KB
+#define CC_MAX_MESSAGE_TIMEOUT (20) // 20 seconds
+// this might pose a problem when there are two gnd applications around
+#define CC_DEFAULT_SENDER_ID "sender-gnd"
+#define CC_DEFAULT_RECEIVER_ID "receiver-0"
+#define CC_MIRRORING_APP_ID "0F5096E8"
+
+#define CC_NAMESPACE_AUTH "urn:x-cast:com.google.cast.tp.deviceauth"
+#define CC_NAMESPACE_CONNECTION "urn:x-cast:com.google.cast.tp.connection"
+#define CC_NAMESPACE_HEARTBEAT "urn:x-cast:com.google.cast.tp.heartbeat"
+#define CC_NAMESPACE_RECEIVER "urn:x-cast:com.google.cast.receiver"
+#define CC_NAMESPACE_MEDIA "urn:x-cast:com.google.cast.media"
+#define CC_NAMESPACE_WEBRTC "urn:x-cast:com.google.cast.webrtc"
+
+// string switch case
+#define cc_switch(x) \
+  const gchar *to_cmp = x; \
+  do \
+
+#define cc_case(x) if (g_strcmp0 (to_cmp, x) == 0)
+
+#define cc_end while (0);
+
+
+typedef enum {
+  CC_RWAIT_TYPE_NONE                 = 0b0,
+  CC_RWAIT_TYPE_GET_APP_AVAILABILITY = 0b1 << 0, /* key is `responseType` */
+  CC_RWAIT_TYPE_LAUNCH_ERROR         = 0b1 << 1, /* all other keys are `type` */
+  CC_RWAIT_TYPE_ANSWER               = 0b1 << 2,
+  CC_RWAIT_TYPE_RECEIVER_STATUS      = 0b1 << 3,
+  CC_RWAIT_TYPE_MEDIA_STATUS         = 0b1 << 4,
+  CC_RWAIT_TYPE_PING                 = 0b1 << 5,
+  CC_RWAIT_TYPE_PONG                 = 0b1 << 6,
+  CC_RWAIT_TYPE_CLOSE                = 0b1 << 7,
+  CC_RWAIT_TYPE_UNKNOWN              = 0b1 << 8,
+} CcReceivedMessageType;
+
+typedef CcReceivedMessageType CcWaitingFor;
+
+G_END_DECLS
diff --git a/src/cc/cc-ctrl.c b/src/cc/cc-ctrl.c
new file mode 100644
index 0000000..229c1b7
--- /dev/null
+++ b/src/cc/cc-ctrl.c
@@ -0,0 +1,476 @@
+/* cc-ctrl.c
+ *
+ * Copyright 2022 Anupam Kumar <kyteinsky gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "cc-ctrl.h"
+#include "cc-comm.h"
+
+// SEND HELPER FUNCTIONS
+
+static gboolean
+cc_ctrl_send_auth (CcCtrl *ctrl, GError **error)
+{
+  g_debug ("CcCtrl: Sending auth");
+
+  return cc_comm_send_request (&ctrl->comm,
+                               CC_DEFAULT_RECEIVER_ID,
+                               CC_MESSAGE_TYPE_AUTH,
+                               NULL,
+                               error);
+}
+
+static gboolean
+cc_ctrl_send_connect (CcCtrl *ctrl, gchar *destination_id, GError **error)
+{
+  g_debug ("CcCtrl: Sending CONNECT");
+
+  return cc_comm_send_request (&ctrl->comm,
+                               destination_id,
+                               CC_MESSAGE_TYPE_CONNECT,
+                               "{ \"type\": \"CONNECT\", \"userAgent\": \"GND/0.90.5  (X11; Linux x86_64)\", 
\"connType\": 0, \"origin\": {}, \"senderInfo\": { \"sdkType\": 2, \"version\": \"X11; Linux x86_64\", 
\"browserVersion\": \"X11; Linux x86_64\", \"platform\": 6, \"connectionType\": 1 } }",
+                               error);
+}
+
+static gboolean
+cc_ctrl_send_disconnect (CcCtrl *ctrl, gchar *destination_id, GError **error)
+{
+  g_debug ("CcCtrl: Sending CLOSE");
+
+  return cc_comm_send_request (&ctrl->comm,
+                               destination_id,
+                               CC_MESSAGE_TYPE_DISCONNECT,
+                               "{ \"type\": \"CLOSE\" }",
+                               error);
+}
+
+static gboolean
+cc_ctrl_send_get_status (CcCtrl *ctrl, gchar *destination_id, GError **error)
+{
+  g_debug ("CcCtrl: Sending GET_STATUS");
+
+  g_autoptr (GString) json = g_string_new ("{ \"type\": \"GET_STATUS\", ");
+  g_string_append_printf (json, "\"requestId\": %d }", ctrl->request_id++);
+  return cc_comm_send_request (&ctrl->comm,
+                               destination_id,
+                               CC_MESSAGE_TYPE_RECEIVER,
+                               json->str,
+                               error);
+}
+
+static gboolean
+cc_ctrl_send_get_app_availability (CcCtrl *ctrl, gchar *destination_id, gchar *appId, GError **error)
+{
+  g_debug ("CcCtrl: Sending GET_APP_AVAILABILITY");
+
+  g_autoptr (GString) json = g_string_new ("{ \"type\": \"GET_APP_AVAILABILITY\", ");
+  g_string_append_printf (json, "\"appId\": [\"%s\"], \"requestId\": %d }", appId, ctrl->request_id++);
+  return cc_comm_send_request (&ctrl->comm,
+                               destination_id,
+                               CC_MESSAGE_TYPE_RECEIVER,
+                               json->str,
+                               error);
+}
+
+static gboolean
+cc_ctrl_send_launch_app (CcCtrl *ctrl, gchar *destination_id, gchar *appId, GError **error)
+{
+  g_debug ("CcCtrl: Sending LAUNCH");
+
+  g_autoptr (GString) json = g_string_new ("{ \"type\": \"LAUNCH\", \"language\": \"en-US\", ");
+  g_string_append_printf (json, "\"appId\": \"%s\", \"requestId\": %d }", appId, ctrl->request_id++);
+  return cc_comm_send_request (&ctrl->comm,
+                               destination_id,
+                               CC_MESSAGE_TYPE_RECEIVER,
+                               json->str,
+                               error);
+}
+
+static gboolean
+cc_ctrl_send_close_app (CcCtrl *ctrl, gchar *sessionId, GError **error)
+{
+  g_debug ("CcCtrl: Sending STOP");
+
+  g_autoptr (GString) json = g_string_new ("{ \"type\": \"STOP\", ");
+  g_string_append_printf (json, "\"sessionId\": \"%s\", \"requestId\": %d }", sessionId, ctrl->request_id++);
+  return cc_comm_send_request (&ctrl->comm,
+                               sessionId,
+                               CC_MESSAGE_TYPE_RECEIVER,
+                               json->str,
+                               error);
+}
+
+static gboolean
+cc_ctrl_send_offer (CcCtrl *ctrl, gchar *destination_id, GError **error)
+{
+  g_debug ("CcCtrl: Sending OFFER");
+
+  /* look into [ adaptive_playout_delay, rtpExtensions, rtpPayloadType, rtpProfile, aes stuff, ssrc 
increment in received msg ] */
+  return cc_comm_send_request (&ctrl->comm,
+                               destination_id,
+                               CC_MESSAGE_TYPE_WEBRTC,
+                               "{ \"offer\": { \"castMode\": \"mirroring\", \"receiverGetStatus\": true, 
\"supportedStreams\": [ { \"aesIvMask\": \"1D20EA1C710E5598ECF80FB26ABC57B0\", \"aesKey\": 
\"BB0CAE24F76EA1CAC9A383CFB1CFD54E\", \"bitRate\": 102000, \"channels\": 2, \"codecName\": \"aac\", 
\"index\": 0, \"receiverRtcpEventLog\": true, \"rtpExtensions\": \"adaptive_playout_delay\", 
\"rtpPayloadType\": 127, \"rtpProfile\": \"cast\", \"sampleRate\": 48000, \"ssrc\": 144842, \"targetDelay\": 
400, \"timeBase\": \"1/48000\", \"type\": \"audio_source\" }, { \"aesIvMask\": 
\"1D20EA1C710E5598ECF80FB26ABC57B0\", \"aesKey\": \"BB0CAE24F76EA1CAC9A383CFB1CFD54E\", \"codecName\": 
\"h264\", \"index\": 1, \"maxBitRate\": 5000000, \"maxFrameRate\": \"30000/1000\", \"receiverRtcpEventLog\": 
true, \"renderMode\": \"video\", \"resolutions\": [{ \"height\": 1080, \"width\": 1920 }], \"rtpExtensions\": 
\"adaptive_playout_delay\", \"rtpPayloadType\": 96, \"rtpProfile\": \"cast\", \"ssrc\": 545579, \"
 targetDelay\": 400, \"timeBase\": \"1/90000\", \"type\": \"video_source\" } ] }, \"seqNum\": 730137397, 
\"type\": \"OFFER\" }",
+                               error);
+}
+
+// WAITING FOR
+
+static void
+cc_ctrl_set_waiting_for (CcCtrl *ctrl, CcWaitingFor waiting_for)
+{
+  ctrl->waiting_for |= waiting_for;
+}
+
+static void
+cc_ctrl_unset_waiting_for (CcCtrl *ctrl, CcWaitingFor waiting_for)
+{
+  ctrl->waiting_for &= ~waiting_for;
+}
+
+static gboolean
+cc_ctrl_is_waiting_for (CcCtrl *ctrl, CcWaitingFor waiting_for)
+{
+  return (ctrl->waiting_for & waiting_for) > CC_RWAIT_TYPE_NONE;
+}
+
+// INTERVAL FUNCTIONS
+
+// if we are waiting for something for longer than said interval, then our connection has some problem
+static gboolean
+cc_ctrl_check_waiting_for (CcCtrl *ctrl)
+{
+  if (ctrl->waiting_for == CC_RWAIT_TYPE_NONE)
+    return G_SOURCE_CONTINUE;
+
+  g_warning ("CcCtrl: Timed out waiting for %d", ctrl->waiting_for);
+  ctrl->closure->end_stream (ctrl->closure);
+
+  return G_SOURCE_REMOVE;
+}
+
+static gboolean
+cc_ctrl_send_ping (CcCtrl *ctrl)
+{
+  g_debug ("CcCtrl: Sending PING");
+  g_autoptr(GError) error = NULL;
+
+  // if this errors out, we cancel the periodic ping by returning FALSE
+  if (!cc_comm_send_request (&ctrl->comm,
+                             CC_DEFAULT_RECEIVER_ID,
+                             CC_MESSAGE_TYPE_PING,
+                             NULL,
+                             &error))
+    {
+      if (error != NULL)
+        {
+          g_warning ("CcCtrl: Failed to send ping message: %s", error->message);
+          return G_SOURCE_REMOVE;
+        }
+      g_warning ("CcCtrl: Failed to send ping message");
+      return G_SOURCE_REMOVE;
+    }
+
+  cc_ctrl_set_waiting_for (ctrl, CC_RWAIT_TYPE_PONG);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+cc_ctrl_send_gaa_cb (CcCtrl *ctrl)
+{
+  g_autoptr (GError) error = NULL;
+  if (!cc_ctrl_send_get_app_availability (ctrl, CC_DEFAULT_RECEIVER_ID, CC_MIRRORING_APP_ID, &error))
+    g_warning ("CcCtrl: Failed to send GET_APP_AVAILABILITY to the mirroring app: %s", error->message);
+  return FALSE;
+}
+
+static void
+cc_ctrl_mirroring_app_init (CcCtrl *ctrl, GError **error)
+{
+  if (!cc_ctrl_send_connect (ctrl, ctrl->session_id, error))
+    {
+      g_warning ("CcCtrl: Failed to send CONNECT to the mirroring app: %s", (*error)->message);
+      return;
+    }
+
+  // send get_app_availability message after 2 seconds
+  g_timeout_add_seconds (2, G_SOURCE_FUNC (cc_ctrl_send_gaa_cb), ctrl);
+}
+
+// HANDLE MESSAGE
+
+// should be status received callback
+static void
+cc_ctrl_handle_get_app_availability (CcCtrl *ctrl, JsonReader *reader)
+{
+  g_autoptr (GError) error = NULL;
+
+  // TODO: reader
+  if (!cc_ctrl_send_offer (ctrl, ctrl->session_id, &error))
+    {
+      g_warning ("CcCtrl: Failed to send offer: %s", error->message);
+      return;
+    }
+}
+
+// handler messages for received messages
+static void
+cc_ctrl_handle_receiver_status (CcCtrl *ctrl, JsonParser *parser)
+{
+  // reports all the open apps (the relevant stuff)
+  // if the app is open, it has a sessionId: hijack the session
+  // connect to it, send a stop, and then propose an offer
+
+  g_autoptr (GError) error = NULL;
+       g_autoptr (JsonNode) app_status = NULL;
+       g_autoptr (JsonPath) path = json_path_new();
+       json_path_compile(path, "$.status.applications[0]", NULL);
+       app_status = json_path_match(path, json_parser_get_root(parser));
+
+       g_autoptr (JsonGenerator) generator = json_generator_new();
+       json_generator_set_root(generator, app_status);
+  gsize size;
+       json_generator_to_data(generator, &size);
+
+  if (size == 2) // empty array []
+    {
+      g_debug ("CcCtrl: No apps open");
+      if (ctrl->state == CC_CTRL_STATE_LAUNCH_SENT)
+        return;
+
+      if (ctrl->state >= CC_CTRL_STATE_APP_OPEN) // app closed unexpectedly
+        g_debug ("CcCtrl: App closed unexpectedly");
+
+      if (!cc_ctrl_send_launch_app (ctrl, CC_DEFAULT_RECEIVER_ID, CC_MIRRORING_APP_ID, &error))
+        {
+          g_warning ("CcCtrl: Failed to launch the app: %s", error->message);
+          return;
+        }
+
+      cc_ctrl_set_waiting_for (ctrl, CC_RWAIT_TYPE_RECEIVER_STATUS);
+      ctrl->state = CC_CTRL_STATE_LAUNCH_SENT;
+      return;
+    }
+
+  // one or more apps is/are open
+  g_autoptr (JsonReader) reader = json_reader_new(app_status);
+
+  if (json_reader_read_element (reader, 0))
+    {
+      if (json_reader_read_member (reader, "appId"))
+        {
+          const gchar *appId = json_reader_get_string_value (reader);
+          json_reader_end_member (reader);
+
+          if (json_reader_read_member (reader, "sessionId"))
+            {
+              const gchar *sessionId = json_reader_get_string_value (reader); /* pointer can be modified */
+              g_debug ("CcCtrl: Session id for app %s: %s", appId, sessionId);
+              json_reader_end_member (reader);
+
+              if (g_strcmp0 (appId, CC_MIRRORING_APP_ID) == 0)
+                {
+                  // takeover the session, doesn't matter which sender opened it
+                  g_debug ("CcCtrl: Mirroring app is open,");
+                  ctrl->state = CC_CTRL_STATE_APP_OPEN;
+                  /* is this freed automatically? */
+                  ctrl->session_id = g_strdup (sessionId);
+
+                  cc_ctrl_mirroring_app_init (ctrl, &error);
+                  cc_ctrl_set_waiting_for (ctrl, CC_RWAIT_TYPE_GET_APP_AVAILABILITY);
+
+                  return;
+                }
+
+              if (!cc_ctrl_send_launch_app (ctrl, CC_DEFAULT_RECEIVER_ID, CC_MIRRORING_APP_ID, &error))
+                {
+                  g_warning ("CcCtrl: Failed to launch the app: %s", error->message);
+                  return;
+                }
+
+              cc_ctrl_set_waiting_for (ctrl, CC_RWAIT_TYPE_RECEIVER_STATUS);
+              ctrl->state = CC_CTRL_STATE_LAUNCH_SENT;
+            }
+        }
+    }
+}
+
+void
+cc_ctrl_handle_received_msg (CcCommClosure *closure,
+                             Cast__Channel__CastMessage *message)
+{
+  CcCtrl *ctrl = (CcCtrl *) closure->userdata;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(JsonParser) parser = NULL;
+  g_autoptr(JsonReader) reader = NULL;
+
+  parser = json_parser_new ();
+  if (!json_parser_load_from_data (parser, message->payload_utf8, -1, &error))
+    {
+      cc_json_helper_dump_message (message);
+      g_warning ("CcCtrl: Error parsing received messaage JSON: %s", error->message);
+      return;
+    }
+
+  reader = json_reader_new (json_parser_get_root (parser));
+
+  CcReceivedMessageType type = cc_json_helper_get_message_type (message, reader);
+
+  switch (type)
+    {
+    case CC_RWAIT_TYPE_RECEIVER_STATUS:
+      cc_ctrl_unset_waiting_for (ctrl, CC_RWAIT_TYPE_RECEIVER_STATUS);
+      cc_ctrl_handle_receiver_status (ctrl, parser);
+      break;
+    case CC_RWAIT_TYPE_GET_APP_AVAILABILITY:
+      cc_ctrl_unset_waiting_for (ctrl, CC_RWAIT_TYPE_GET_APP_AVAILABILITY);
+      cc_ctrl_handle_get_app_availability (ctrl, reader);
+      break;
+    case CC_RWAIT_TYPE_LAUNCH_ERROR:
+      // cc_ctrl_handle_launch_error (ctrl, reader);
+      break;
+    case CC_RWAIT_TYPE_ANSWER:
+      cc_ctrl_unset_waiting_for (ctrl, CC_RWAIT_TYPE_ANSWER);
+      // cc_ctrl_handle_answer (ctrl, reader);
+      break;
+    case CC_RWAIT_TYPE_MEDIA_STATUS:
+      cc_ctrl_unset_waiting_for (ctrl, CC_RWAIT_TYPE_MEDIA_STATUS);
+      // cc_ctrl_handle_media_status (ctrl, reader);
+      break;
+    case CC_RWAIT_TYPE_PING:
+      cc_ctrl_unset_waiting_for (ctrl, CC_RWAIT_TYPE_PING);
+      break;
+    case CC_RWAIT_TYPE_PONG:
+      cc_ctrl_unset_waiting_for (ctrl, CC_RWAIT_TYPE_PONG);
+      break;
+    case CC_RWAIT_TYPE_CLOSE:
+      // cc_ctrl_handle_close (ctrl, reader);
+      break;
+    case CC_RWAIT_TYPE_UNKNOWN:
+    default:
+      g_warning ("CcCtrl: Unknown message type");
+      break;
+    }
+}
+
+void
+cc_ctrl_fatal_error (CcCommClosure *closure, GError **error)
+{
+  // XXX
+  CcCtrl *ctrl = (CcCtrl *) closure->userdata;
+  ctrl->closure->end_stream (ctrl->closure);
+}
+
+CcCommClosure *
+cc_ctrl_get_callback_closure (CcCtrl *ctrl)
+{
+  CcCommClosure *closure = (CcCommClosure *) g_malloc (sizeof (CcCommClosure));
+  closure->userdata = ctrl;
+  closure->message_received_cb = cc_ctrl_handle_received_msg;
+  closure->fatal_error_cb = cc_ctrl_fatal_error;
+  return closure;
+}
+
+gboolean
+cc_ctrl_connection_init (CcCtrl *ctrl, gchar *remote_address)
+{
+  // pay attn to the receiver ids sent before the messages
+
+  g_autoptr (GError) error = NULL;
+
+  ctrl->state = CC_CTRL_STATE_DISCONNECTED;
+  ctrl->comm.cancellable = ctrl->cancellable;
+
+  // register all the callbacks
+  ctrl->comm.closure = cc_ctrl_get_callback_closure (ctrl);
+
+  if (!cc_comm_make_connection (&ctrl->comm, remote_address, &error))
+    {
+      g_warning ("CcCtrl: Failed to make connection to %s: %s", remote_address, error->message);
+      return FALSE;
+    }
+
+  if (!cc_ctrl_send_auth (ctrl, &error))
+    {
+      g_warning ("CcCtrl: Failed to send auth: %s", error->message);
+      return FALSE;
+    }
+
+  if (!cc_ctrl_send_connect (ctrl, CC_DEFAULT_RECEIVER_ID, &error))
+    {
+      g_warning ("CcCtrl: Failed to send connect: %s", error->message);
+      return FALSE;
+    }
+  
+  // since tls_send is a synchronous call
+  ctrl->state = CC_CTRL_STATE_CONNECTED;
+  
+  // send pings to device every 5 seconds
+  ctrl->ping_timeout_handle = g_timeout_add_seconds (5, G_SOURCE_FUNC (cc_ctrl_send_ping), ctrl);
+
+  // check waiting for every 15 seconds
+  ctrl->waiting_check_timeout_handle = g_timeout_add_seconds (15, G_SOURCE_FUNC (cc_ctrl_check_waiting_for), 
ctrl);
+
+  // we can skip some message interchange if the mirroring app is already up
+  if (!cc_ctrl_send_get_status (ctrl, CC_DEFAULT_RECEIVER_ID, &error))
+    {
+      g_warning ("CcCtrl: Failed to send get status: %s", error->message);
+      return FALSE;
+    }
+  cc_ctrl_set_waiting_for (ctrl, CC_RWAIT_TYPE_RECEIVER_STATUS);
+
+  return TRUE;
+}
+
+void
+cc_ctrl_finish (CcCtrl *ctrl, GError **r_error)
+{
+  g_autoptr(GError) err = NULL;
+
+  // stop both the ping and the waiting check timeout
+  g_clear_handle_id (&ctrl->ping_timeout_handle, g_source_remove);
+  g_clear_handle_id (&ctrl->waiting_check_timeout_handle, g_source_remove);
+
+  // close app if open
+  if (ctrl->state >= CC_CTRL_STATE_APP_OPEN)
+    {
+      if (!cc_ctrl_send_disconnect (ctrl, CC_DEFAULT_RECEIVER_ID, &err))
+        {
+          g_warning ("CcCtrl: Error closing virtual connection to app: %s", err->message);
+          g_clear_error (&err);
+        }
+      if (!cc_ctrl_send_close_app (ctrl, ctrl->session_id, &err))
+        {
+          g_warning ("CcCtrl: Error closing app: %s", err->message);
+          g_clear_error (&err);
+        }
+      g_clear_pointer (&ctrl->session_id, g_free);
+    }
+
+  // close the virtual connection
+  if (!cc_ctrl_send_disconnect (ctrl, CC_DEFAULT_RECEIVER_ID, NULL))
+    g_warning ("CcCtrl: Error closing virtual connection: %s", err->message);
+
+  // free up the resources?
+  g_clear_pointer (&ctrl->comm.closure, g_free);
+
+  // safe to call multiple times
+  g_cancellable_cancel (ctrl->cancellable);
+  g_clear_object (&ctrl->cancellable);
+
+  // close the socket connection
+  cc_comm_close_connection (&ctrl->comm);
+}
diff --git a/src/cc/cc-ctrl.h b/src/cc/cc-ctrl.h
new file mode 100644
index 0000000..4633978
--- /dev/null
+++ b/src/cc/cc-ctrl.h
@@ -0,0 +1,74 @@
+/* cc-ctrl.h
+ *
+ * Copyright 2022 Anupam Kumar <kyteinsky gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include "cc-comm.h"
+#include "cc-json-helper.h"
+#include "cc-common.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+  CC_CTRL_STATE_DISCONNECTED,
+  CC_CTRL_STATE_CONNECTED,
+  CC_CTRL_STATE_LAUNCH_SENT,
+  CC_CTRL_STATE_APP_OPEN,
+  CC_CTRL_STATE_OFFER_SENT,
+  CC_CTRL_STATE_ANSWER_RECEIVED,
+  CC_CTRL_STATE_START_STREAM,
+  CC_CTRL_STATE_ERROR,
+} CcCtrlState;
+
+struct _CcCtrlClosure
+{
+  gpointer userdata;
+  // TODO
+  void (*end_stream) (struct _CcCtrlClosure *closure);
+  void (*start_stream) (struct _CcCtrlClosure *closure);
+};
+
+typedef struct _CcCtrlClosure CcCtrlClosure;
+
+struct _CcCtrl
+{
+  /*< public >*/
+  CcComm         comm;
+
+  CcCtrlState    state;
+  gchar         *session_id;
+  guint          request_id;
+  guint8         waiting_for;
+  guint          ping_timeout_handle;
+  guint          waiting_check_timeout_handle;
+
+  GCancellable  *cancellable;
+  CcCtrlClosure *closure;
+};
+
+typedef struct _CcCtrl CcCtrl;
+
+// public functions
+gboolean cc_ctrl_connection_init (CcCtrl *ctrl, gchar *remote_address);
+void cc_ctrl_finish (CcCtrl *ctrl, GError **error);
+
+// XXX: is this required?
+// G_DEFINE_AUTOPTR_CLEANUP_FUNC (CcCtrl, g_object_unref)
+
+G_END_DECLS
diff --git a/src/cc/cc-json-helper.c b/src/cc/cc-json-helper.c
new file mode 100644
index 0000000..cbf33d8
--- /dev/null
+++ b/src/cc/cc-json-helper.c
@@ -0,0 +1,237 @@
+/* cc-json-helper.c
+ *
+ * Copyright 2022 Anupam Kumar <kyteinsky gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "cc-json-helper.h"
+
+// static void
+// cc_json_helper_add_type_value (JsonBuilder *builder,
+//                                CcJsonType   type,
+//                                gpointer     value)
+// {
+//   switch (type)
+//     {
+//     case CC_JSON_TYPE_STRING:
+//       json_builder_add_string_value (builder, (gchar *) *value);
+//       break;
+//     case CC_JSON_TYPE_INT:
+//       json_builder_add_int_value (builder, (gint) *value);
+//       break;
+//     case CC_JSON_TYPE_DOUBLE:
+//       json_builder_add_double_value (builder, (gdouble) *value);
+//       break;
+//     case CC_JSON_TYPE_BOOLEAN:
+//       json_builder_add_boolean_value (builder, (gboolean) *value);
+//       break;
+//     case CC_JSON_TYPE_NULL: /* no additional arg is required here */
+//       json_builder_add_null_value (builder);
+//       break;
+//     case CC_JSON_TYPE_OBJECT:
+//       json_builder_begin_object (builder);
+//       json_builder_add_value (builder, (JsonNode *) value);
+//       json_builder_end_object (builder);
+//       break;
+//     /* only 1D arrays supported */
+//     }
+// }
+
+// void
+// cc_json_helper_build_root (JsonBuilder *builder,
+//                            const gchar *first_key,
+//                            va_list      var_args)
+// {
+//   gchar *key = first_key;
+
+//   while (key)
+//     {
+//       json_builder_set_member_name (builder, key);
+//       CcJsonType type = va_arg (var_args, CcJsonType);
+
+//       if (type == CC_JSON_TYPE_ARRAY)
+//         {
+//           json_builder_begin_array (builder);
+//           gint length = va_arg (var_args, gint);
+//           for (gint i = 0; i < length; i++)
+//             {
+//               cc_json_helper_add_type_value (builder, type, va_arg (var_args, gpointer));
+//             }
+//           json_builder_end_array (builder);
+//         }
+//       switch (type)
+//         {
+//         case CC_JSON_TYPE_STRING:
+//           json_builder_add_string_value (builder, va_arg (var_args, gchar *));
+//           break;
+//         case CC_JSON_TYPE_INT:
+//           json_builder_add_int_value (builder, va_arg (var_args, gint));
+//           break;
+//         case CC_JSON_TYPE_DOUBLE:
+//           json_builder_add_double_value (builder, va_arg (var_args, gdouble));
+//           break;
+//         case CC_JSON_TYPE_BOOLEAN:
+//           json_builder_add_boolean_value (builder, va_arg (var_args, gboolean));
+//           break;
+//         case CC_JSON_TYPE_NULL: /* no additional arg is required here */
+//           json_builder_add_null_value (builder);
+//           break;
+//         case CC_JSON_TYPE_OBJECT:
+//           json_builder_begin_object (builder);
+//           json_builder_add_value (builder, va_arg (var_args, JsonNode *));
+//           json_builder_end_object (builder);
+//           break;
+//         case CC_JSON_TYPE_ARRAY: /* type for array elements is also required here */
+//           json_builder_begin_array (builder);
+//           CcJsonType array_type = va_arg (var_args, CcJsonType);
+//           /* GArray */
+//           json_builder_end_array (builder);
+//           break;
+//         default:
+//           output = NULL;
+//           return;
+//         }
+
+//       key = va_arg (var_args, gchar *);
+//     }
+// }
+
+// void
+// cc_json_helper_build_string (gchar       *output,
+//                              const gchar *first_key,
+//                              ...)
+// {
+//   va_list var_args;
+//   va_start (var_args, first_key);
+
+//   JsonBuilder *builder = json_builder_new ();
+
+//   json_builder_begin_object (builder);
+//   cc_json_helper_build_root (builder, first_key, var_args);
+//   json_builder_end_object (builder);
+
+//   JsonGenerator *gen = json_generator_new ();
+//   JsonNode *root = json_builder_get_root (builder);
+//   json_generator_set_root (gen, root);
+
+//   output = json_generator_to_data (gen, NULL);
+
+//   va_end (var_args);
+//   json_node_free (root);
+//   g_object_unref (gen);
+//   g_object_unref (builder);
+// }
+
+// void
+// cc_json_helper_build_string (gchar       *output,
+//                              const gchar *first_key,
+//                              ...)
+// {
+//   va_list var_args;
+//   va_start (var_args, first_key);
+
+//   JsonBuilder *builder = json_builder_new ();
+
+//   json_builder_begin_object (builder);
+//   cc_json_helper_build_root (builder, first_key, var_args);
+//   json_builder_end_object (builder);
+
+//   JsonGenerator *gen = json_generator_new ();
+//   JsonNode *root = json_builder_get_root (builder);
+//   json_generator_set_root (gen, root);
+
+//   output = json_generator_to_data (gen, NULL);
+
+//   va_end (var_args);
+//   json_node_free (root);
+//   g_object_unref (gen);
+//   g_object_unref (builder);
+// }
+
+
+
+CcReceivedMessageType
+cc_json_helper_get_message_type (Cast__Channel__CastMessage *message,
+                                 JsonReader *reader)
+{
+  const gchar *message_type;
+  g_autoptr (GError) error = NULL;
+
+  if (reader == NULL)
+    {
+      g_autoptr(JsonParser) parser = NULL;
+
+      parser = json_parser_new ();
+      if (!json_parser_load_from_data (parser, message->payload_utf8, -1, &error))
+        {
+          cc_json_helper_dump_message (message);
+          g_warning ("CcJsonHelper: Error parsing received message JSON: %s", error->message);
+          return -1;
+        }
+
+      reader = json_reader_new (json_parser_get_root (parser));
+    }
+
+  gboolean typeExists = json_reader_read_member (reader, "type");
+  if (typeExists)
+    message_type = json_reader_get_string_value (reader);
+  else
+    {
+      json_reader_end_member (reader);
+      if (json_reader_read_member (reader, "responseType"))
+        message_type = json_reader_get_string_value (reader);
+      else
+        {
+          cc_json_helper_dump_message (message);
+          g_warning ("CcJsonHelper: Error parsing received message JSON: no type or responseType keys");
+          return -1;
+        }
+    }
+  json_reader_end_member (reader);
+
+  cc_switch (message_type)
+    {
+      cc_case ("RECEIVER_STATUS") 
+        return CC_RWAIT_TYPE_RECEIVER_STATUS;
+      cc_case ("GET_APP_AVAILABILITY") 
+        return CC_RWAIT_TYPE_GET_APP_AVAILABILITY;
+      cc_case ("LAUNCH_ERROR") 
+        return CC_RWAIT_TYPE_LAUNCH_ERROR;
+      cc_case ("ANSWER") 
+        return CC_RWAIT_TYPE_ANSWER;
+      cc_case ("MEDIA_STATUS") 
+        return CC_RWAIT_TYPE_MEDIA_STATUS;
+      cc_case ("PING") 
+        return CC_RWAIT_TYPE_PING;
+      cc_case ("PONG") 
+        return CC_RWAIT_TYPE_PONG;
+      cc_case ("CLOSE") 
+        return CC_RWAIT_TYPE_CLOSE;
+      // default
+        return CC_RWAIT_TYPE_UNKNOWN;
+    } cc_end
+}
+
+void
+cc_json_helper_dump_message (Cast__Channel__CastMessage *message)
+{
+  // TODO: pretty print json object
+  g_debug ("{ source_id: %s, destination_id: %s, namespace_: %s, payload_type: %d, payload_utf8: %s }",
+    message->source_id,
+    message->destination_id,
+    message->namespace_,
+    message->payload_type,
+    message->payload_utf8);
+}
diff --git a/src/cc/cc-json-helper.h b/src/cc/cc-json-helper.h
new file mode 100644
index 0000000..0f22124
--- /dev/null
+++ b/src/cc/cc-json-helper.h
@@ -0,0 +1,48 @@
+/* cc-json-helper.h
+ *
+ * Copyright 2022 Anupam Kumar <kyteinsky gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <json-glib-1.0/json-glib/json-glib.h>
+#include "cast_channel.pb-c.h"
+#include "cc-common.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+  CC_JSON_TYPE_STRING,
+  CC_JSON_TYPE_INT,
+  CC_JSON_TYPE_DOUBLE,
+  CC_JSON_TYPE_BOOLEAN,
+  CC_JSON_TYPE_NULL,
+  CC_JSON_TYPE_OBJECT,
+
+  CC_JSON_TYPE_ARRAY_STRING,
+  CC_JSON_TYPE_ARRAY_INT,
+  CC_JSON_TYPE_ARRAY_DOUBLE,
+  CC_JSON_TYPE_ARRAY_BOOLEAN,
+  CC_JSON_TYPE_ARRAY_NULL,
+  CC_JSON_TYPE_ARRAY_OBJECT,
+} CcJsonType;
+
+CcReceivedMessageType cc_json_helper_get_message_type (Cast__Channel__CastMessage *message,
+                                                       JsonReader *json_reader);
+void cc_json_helper_dump_message (Cast__Channel__CastMessage *message);
+
+G_END_DECLS
diff --git a/src/cc/meson.build b/src/cc/meson.build
index cbf8275..3349a3e 100644
--- a/src/cc/meson.build
+++ b/src/cc/meson.build
@@ -3,6 +3,8 @@ cc_sources = [
   'cc-client.c',
   'cast_channel.pb-c.c',
   'cc-comm.c',
+  'cc-ctrl.c',
+  'cc-json-helper.c',
 ]
 
 #enum_headers = files()
diff --git a/src/nd-cc-sink.c b/src/nd-cc-sink.c
index 49b7c91..ff83e9a 100644
--- a/src/nd-cc-sink.c
+++ b/src/nd-cc-sink.c
@@ -23,7 +23,7 @@
 #include "cc/cc-client.h"
 #include "wfd/wfd-media-factory.h"
 #include "wfd/wfd-server.h"
-#include "cc/cc-comm.h"
+#include "cc/cc-ctrl.h"
 
 // TODO: add cancellable everywhere
 
@@ -43,8 +43,7 @@ struct _NdCCSink
   gchar         *remote_name;
 
   GSocketClient *comm_client;
-  CcComm         comm;
-  guint          ping_timeout_handle;
+  CcCtrl         ctrl;
 
   WfdServer     *server;
   guint          server_source_id;
@@ -263,8 +262,6 @@ static void
 closed_cb (NdCCSink *sink, CCClient *client)
 {
   /* Connection was closed, do a clean shutdown */
-  cc_comm_send_request (&sink->comm, MESSAGE_TYPE_DISCONNECT, NULL, NULL);
-
   nd_cc_sink_sink_stop_stream (ND_SINK (sink));
 }
 
@@ -312,20 +309,27 @@ server_create_audio_source_cb (NdCCSink *sink, WfdServer *server)
   return res;
 }
 
-static gboolean
-nd_cc_sink_launch_default_app_cb (NdCCSink *sink)
+static void
+nd_cc_sink_start_webrtc_stream (CcCtrlClosure *closure)
+{
+  // TODO
+  g_debug ("Received webrtc stream signal from ctrl");
+}
+
+static void
+nd_cc_sink_error_in_ctrl (CcCtrlClosure *closure)
 {
-  if (!cc_comm_send_request (&sink->comm, MESSAGE_TYPE_RECEIVER, "{ \"type\": \"LAUNCH\", \"appId\": 
\"CC1AD845\", \"requestId\": 3 }", NULL))
-    g_warning ("NdCCSink: something went wrong with default app launch");
-  return FALSE;
+  nd_cc_sink_sink_stop_stream (ND_SINK (closure->userdata));
 }
 
-static gboolean
-nd_cc_sink_get_status_cb (NdCCSink *sink)
+CcCtrlClosure *
+nd_cc_sink_get_callback_closure (NdCCSink *sink)
 {
-  if (!cc_comm_send_request (&sink->comm, MESSAGE_TYPE_RECEIVER, "{ \"type\": \"GET_STATUS\", \"requestId\": 
2 }", NULL))
-    g_warning ("NdCCSink: something went wrong with get status");
-  return FALSE;
+  CcCtrlClosure *closure = (CcCtrlClosure *) g_malloc (sizeof (CcCtrlClosure));
+  closure->userdata = sink;
+  closure->end_stream = nd_cc_sink_error_in_ctrl;
+  closure->start_stream = nd_cc_sink_start_webrtc_stream;
+  return closure;
 }
 
 static NdSink *
@@ -346,34 +350,13 @@ nd_cc_sink_sink_start_stream (NdSink *sink)
   self->state = ND_SINK_STATE_WAIT_SOCKET;
   g_object_notify (G_OBJECT (self), "state");
 
-  g_debug ("NdCCSink: Attempting connection to Chromecast: %s", self->remote_name);
-
-  self->comm.destination_id = "receiver-0";
-  self->comm.cancellable = self->cancellable;
+  self->ctrl.cancellable = self->cancellable;
+  self->ctrl.closure = nd_cc_sink_get_callback_closure (self);
 
-  // open a TLS connection to the CC device
-  if (!cc_comm_make_connection (&self->comm, self->remote_address, &error))
-    {
-      self->state = ND_SINK_STATE_ERROR;
-      g_object_notify (G_OBJECT (self), "state");
-      g_clear_object (&self->server);
-
-      return NULL;
-    }
-
-  // authenticate with the CC device
-  if (!cc_comm_send_request (&self->comm, MESSAGE_TYPE_AUTH, NULL, NULL))
-    {
-      self->state = ND_SINK_STATE_ERROR;
-      g_object_notify (G_OBJECT (self), "state");
-      g_clear_object (&self->server);
-
-      return NULL;
-    }
-
-  // open up a virtual connection to the device
-  if (!cc_comm_send_request (&self->comm, MESSAGE_TYPE_CONNECT, NULL, NULL))
+  g_debug ("NdCCSink: Attempting connection to Chromecast: %s", self->remote_name);
+  if (!cc_ctrl_connection_init (&self->ctrl, self->remote_address))
     {
+      g_warning ("NdCCSink: Failed to init cc-ctrl");
       self->state = ND_SINK_STATE_ERROR;
       g_object_notify (G_OBJECT (self), "state");
       g_clear_object (&self->server);
@@ -381,19 +364,10 @@ nd_cc_sink_sink_start_stream (NdSink *sink)
       return NULL;
     }
 
-  // send pings to device every 5 seconds
-  self->ping_timeout_handle = g_timeout_add_seconds (5, G_SOURCE_FUNC (cc_comm_send_ping), &self->comm);
-
-  // send req to get status
-  g_debug ("NdCCSink: Get Status");
-  g_timeout_add_seconds (2, G_SOURCE_FUNC (nd_cc_sink_get_status_cb), self);
-
-  g_debug ("NdCCSink: Launching Default Media App");
-  g_timeout_add_seconds (6, G_SOURCE_FUNC (nd_cc_sink_launch_default_app_cb), self);
-
   self->state = ND_SINK_STATE_STREAMING;
   g_object_notify (G_OBJECT (self), "state");
 
+  // TODO: maybe we don't need this part
   self->server = wfd_server_new ();
   self->server_source_id = gst_rtsp_server_attach (GST_RTSP_SERVER (self->server), NULL);
 
@@ -437,59 +411,13 @@ nd_cc_sink_sink_start_stream (NdSink *sink)
 static void
 nd_cc_sink_sink_stop_stream_int (NdCCSink *self)
 {
-  g_autoptr(GError) error = NULL;
-  gboolean close_ok;
-
-  // Close the app before closing the connection
-  // hack to know if the app is already up
-  if (g_strcmp0 (self->comm.destination_id, "receiver-0") != 0)
-    {
-      g_autoptr(GString) close_app_message = NULL;
-      close_app_message = g_string_new ("{ \"type\": \"STOP\", \"requestId\": 5, \"sessionId\": \"");
-      g_string_append (close_app_message, self->comm.destination_id);
-      g_string_append (close_app_message, "\" }");
-
-      if (!cc_comm_send_request (&self->comm, MESSAGE_TYPE_RECEIVER, close_app_message->str, &error))
-        {
-          if (error != NULL)
-            g_warning ("NdCCSink: Error closing the cast app: %s", error->message);
-          else
-            g_warning ("NdCCSink: Error closing the cast app");
-        }
-
-      g_clear_error (&error);
-      g_clear_pointer (&self->comm.destination_id, g_free);
-      self->comm.destination_id = g_strdup ("receiver-0");
-    }
-
-  if (!cc_comm_send_request (&self->comm, MESSAGE_TYPE_DISCONNECT, NULL, NULL))
-    g_warning ("NdCCSink: Error closing virtual connection");
+  cc_ctrl_finish (&self->ctrl, NULL);
 
   g_cancellable_cancel (self->cancellable);
   g_clear_object (&self->cancellable);
 
   self->cancellable = g_cancellable_new ();
 
-  // cancel ping timeout
-  g_clear_handle_id (&self->ping_timeout_handle, g_source_remove);
-
-  /* Close the client connection
-   * TODO: This should be moved into cc-comm.c */
-  if (self->comm.con != NULL)
-    {
-      close_ok = g_io_stream_close (G_IO_STREAM (self->comm.con), NULL, &error);
-      if (!close_ok)
-        {
-          if (error != NULL)
-            g_warning ("NdCCSink: Error closing communication client connection: %s", error->message);
-          else
-            g_warning ("NdCCSink: Communication client connection not closed");
-        }
-
-      g_clear_object (&self->comm.con);
-      g_debug ("NdCCSink: Client connection removed");
-    }
-
   /* Destroy the server that is streaming. */
   if (self->server_source_id)
     {


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