

/*
 * 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;
}

