Files
argyllcms/ccast/ccmes.c
2026-08-20 20:28:40 +01:00

335 lines
8.5 KiB
C

/*
* Class to deal with protobuf messages
* to/from ChromCast.
*
* Author: Graeme W. Gill
* Date: 3/9/2014
*
* Copyright 2014 Graeme W. Gill
* All rights reserved.
*
* This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
* see the License2.txt file for licencing details.
*
*/
#include <time.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <stdarg.h>
#include <math.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include "copyright.h"
#include "aconfig.h"
#include <sys/types.h>
#include "numlib.h"
#ifndef USING_AXTLS
# include "openssl/ssl.h" /* OpenSSL header */
#else
# include "ssl.h" /* axTLS header */
#endif
#include "yajl.h"
#include "conv.h"
#include "ccpacket.h"
#include "cast_channel.pb-c.h"
#include "ccmes.h"
#undef LOWVERBTRACE /* [und] Low verboseness message trace */
#undef DEBUG /* [und] Full message trace + debug */
/* ------------------------------------------------------------------- */
#ifdef DEBUG
# define DBG(xxx) a1logd xxx ;
#else
# define DBG(xxx) ;
#endif /* DEBUG */
/* Error message from error number */
char *ccmessv_emes(ccmessv_err rv) {
#ifndef SERVER_ONLY
if (rv & 0x10000000) {
return ccmessv_emes(rv & 0x0fffffff);
}
#endif
switch(rv) {
case ccmessv_OK:
return "ccmes: OK";
case ccmessv_context:
return "ccmes: getting a ssl contextfailed";
case ccmessv_malloc:
return "ccmes: malloc failed";
case ccmessv_connect:
return "ccmes: connecting to host failed";
case ccmessv_ssl:
return "ccmes: ssl connect to host failed";
case ccmessv_send:
return "ccmes: message failed to send";
case ccmessv_recv:
return "ccmes: failed to receive";
case ccmessv_unpack:
return "ccmes: failed to unpack";
case ccmessv_timeout:
return "ccmes: i/o has timed out";
case ccmessv_closed:
return "ccmes: connection has been closed";
}
return "Unknown ccmessv error";
}
#if defined(LOWVERBTRACE) || defined(DEBUG)
static void mes_dump(ccmes *mes, char *pfx) {
#ifdef DEBUG
a1logd(g_log,0,"%s message:\n",pfx);
a1logd(g_log,0," source_id = '%s'\n",mes->source_id);
a1logd(g_log,0," destination_id = '%s'\n",mes->destination_id);
a1logd(g_log,0," namespace = '%s'\n",mes->namespace);
if (mes->binary) {
a1logd(g_log,0," payload =\n");
adump_bytes(g_log," ", mes->data, 0, mes->bin_len);
} else {
a1logd(g_log,0," payload = '%s'\n",mes->data);
}
#else
a1logd(g_log,0,"%s '%s'",pfx,mes->namespace);
#endif
if (mes->binary) {
#ifdef DEBUG
a1logd(g_log,0," %d bytes of binary data:\n",mes->bin_len);
adump_bytes(g_log," ", mes->data, 0, mes->bin_len);
#else
a1logd(g_log,0,", %d bytes of bin data:\n",mes->bin_len);
#endif
} else {
/* Would like to pretty print the JSON data */
/* ie. convert json_reformat.c to a function */
#ifdef DEBUG
a1logd(g_log,0," %d bytes of text data:\n",strlen((char *)mes->data));
a1logd(g_log,0," '%s'\n",mes->data);
#else
yajl_val tnode, v, i;
int len = strlen((char *)mes->data);
tnode = yajl_tree_parse((char *)mes->data, NULL, 0);
if (tnode == NULL) {
a1logd(g_log,0,", %d bytes of text data\n",len);
} else {
if ((v = yajl_tree_get_first(tnode, "type", yajl_t_string)) != NULL) {
char *mtype = YAJL_GET_STRING(v);
if ((i = yajl_tree_get_first(tnode, "requestId", yajl_t_number)) != NULL) {
int rqid = YAJL_GET_INTEGER(i);
a1logd(g_log,0,", %d bytes of JSON data, type '%s' id %d\n",
len,mtype,rqid);
} else {
a1logd(g_log,0,", %d bytes of JSON data, type '%s'\n",len,mtype);
}
} else {
a1logd(g_log,0,", %d bytes of JSON data\n",len);
}
}
#endif
}
}
#endif
/* Transfer the data from one message to another */
void ccmes_transfer(ccmes *dst, ccmes *src) {
*dst = *src; /* Struct copy */
memset((void *)src, 0, sizeof(*src));
}
/* Initialise a message */
void ccmes_init(ccmes *mes) {
memset((void *)mes, 0, sizeof(*mes));
}
/* Free up just the contents */
void ccmes_empty(ccmes *mes) {
if (mes->tnode != NULL)
yajl_tree_free(mes->tnode);
if (mes->data != NULL)
free(mes->data);
memset((void *)mes, 0, sizeof(*mes));
}
/* Free up the message and its contents */
void ccmes_del(ccmes *mes) {
if (mes != NULL) {
ccmes_empty(mes);
free(mes);
}
}
/* Send a normal raw message */
/* Return ccmessv_err on error */
ccmessv_err send_ccmessv(ccmessv *p, ccmes *mes) {
ccpacket_err perr;
ORD8 *buf;
unsigned int len;
Extensions__Api__CastChannel__CastMessage msg
= EXTENSIONS__API__CAST_CHANNEL__CAST_MESSAGE__INIT;
if (p->pk == NULL)
return ccmessv_closed;
#if defined(LOWVERBTRACE) || defined(DEBUG)
mes_dump(mes, "Send");
#endif
msg.protocol_version
= EXTENSIONS__API__CAST_CHANNEL__CAST_MESSAGE__PROTOCOL_VERSION__CASTV2_1_0;
msg.source_id = mes->source_id;
msg.destination_id = mes->destination_id;
msg.namespace_ = mes->namespace;
if (mes->binary) {
msg.payload_type = EXTENSIONS__API__CAST_CHANNEL__CAST_MESSAGE__PAYLOAD_TYPE__BINARY;
msg.has_payload_binary = 1;
msg.payload_binary.len = mes->bin_len;
msg.payload_binary.data = (uint8_t *)mes->data;
msg.payload_utf8 = NULL;
} else {
msg.payload_type = EXTENSIONS__API__CAST_CHANNEL__CAST_MESSAGE__PAYLOAD_TYPE__STRING;
msg.payload_utf8 = (char *)mes->data;
msg.has_payload_binary = 0;
msg.payload_binary.len = 0;
msg.payload_binary.data = NULL;
}
len = extensions__api__cast_channel__cast_message__get_packed_size(&msg);
if ((buf = malloc(len)) == NULL)
return ccmessv_malloc;
extensions__api__cast_channel__cast_message__pack(&msg, buf);
amutex_lock(p->slock);
if ((perr = p->pk->send(p->pk, buf, len)) != ccpacket_OK) {
amutex_unlock(p->slock);
free(buf);
if (perr == ccpacket_timeout)
return ccmessv_timeout;
return ccmessv_send;
}
amutex_unlock(p->slock);
free(buf);
return ccmessv_OK;
}
/* Receive a raw message. ccmes_clear() or ccmes_del() afterwards */
/* Fills in tnode, mtype, rqid */
/* Return ccmessv_err on error */
ccmessv_err receive_ccmessv(ccmessv *p, ccmes *mes) {
ccpacket_err perr;
ORD8 *buf;
unsigned int len;
Extensions__Api__CastChannel__CastMessage *msg;
if (p->pk == NULL)
return ccmessv_closed;
if ((perr = p->pk->receive(p->pk, &buf, &len)) != ccpacket_OK) {
if (perr == ccpacket_timeout)
return ccmessv_timeout;
return ccmessv_recv;
}
msg = extensions__api__cast_channel__cast_message__unpack(NULL, len, buf);
if (msg == NULL)
return ccmessv_unpack;
ccmes_init(mes);
if ((mes->source_id = strdup(msg->source_id)) == NULL)
return ccmessv_malloc;
if ((mes->destination_id = strdup(msg->destination_id)) == NULL)
return ccmessv_malloc;
if ((mes->namespace = strdup(msg->namespace_)) == NULL)
return ccmessv_malloc;
if (msg->payload_type == EXTENSIONS__API__CAST_CHANNEL__CAST_MESSAGE__PAYLOAD_TYPE__BINARY) {
mes->binary = 1;
if ((mes->data = malloc(msg->payload_binary.len)) == NULL)
return ccmessv_malloc;
memcpy(mes->data, msg->payload_binary.data, msg->payload_binary.len);
mes->bin_len = msg->payload_binary.len;
} else {
mes->binary = 0;
if ((mes->data = (ORD8 *)strdup(msg->payload_utf8)) == NULL)
return ccmessv_malloc;
}
extensions__api__cast_channel__cast_message__free_unpacked(msg, NULL);
#if defined(LOWVERBTRACE) || defined(DEBUG)
mes_dump(mes, "Recv");
#endif
/* Parse out some information if we can */
mes->mtype = NULL;
mes->rqid = 0;
if (!mes->binary && mes->tnode == NULL) {
yajl_val tyn, idn;
char errbuf[1024];
if ((mes->tnode = yajl_tree_parse((char *)mes->data, errbuf, sizeof(errbuf))) == NULL) {
DBG((g_log,0,"ccthread: yajl_tree_parse failed with '%s'\n",errbuf))
} else if ((tyn = yajl_tree_get_first(mes->tnode, "type", yajl_t_string)) == NULL) {
DBG((g_log,0,"ccthread: no type\n"))
} else {
mes->mtype = YAJL_GET_STRING(tyn);
if ((idn = yajl_tree_get_first(mes->tnode, "requestId", yajl_t_number)) != NULL)
mes->rqid = YAJL_GET_INTEGER(idn);
else {
DBG((g_log,0,"ccthread: no id\n"))
}
}
}
return ccmessv_OK;
}
/* Delete the ccmessv */
void del_ccmessv(ccmessv *p) {
if (p != NULL) {
amutex_del(p->slock);
if (p->pk != NULL)
p->pk->del(p->pk);
free(p);
}
}
/* Create an ccmessv. We take ownership of pk */
/* Return NULL on error */
ccmessv *new_ccmessv(ccpacket *pk) {
ccmessv *p = NULL;
if ((p = (ccmessv *)calloc(1, sizeof(ccmessv))) == NULL) {
DBG((g_log,0, "calloc failed\n"))
return NULL;
}
amutex_init(p->slock);
p->pk = pk;
/* Init method pointers */
p->del = del_ccmessv;
p->send = send_ccmessv;
p->receive = receive_ccmessv;
return p;
}