[gnome-network-displays/cc-tmp: 25/80] cc: state handling implemented
- From: Benjamin Berg <bberg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-network-displays/cc-tmp: 25/80] cc: state handling implemented
- Date: Fri, 9 Sep 2022 12:03:50 +0000 (UTC)
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]