summaryrefslogtreecommitdiff
path: root/ccast/ccpacket.c
diff options
context:
space:
mode:
Diffstat (limited to 'ccast/ccpacket.c')
-rw-r--r--ccast/ccpacket.c501
1 files changed, 501 insertions, 0 deletions
diff --git a/ccast/ccpacket.c b/ccast/ccpacket.c
new file mode 100644
index 0000000..4c138f2
--- /dev/null
+++ b/ccast/ccpacket.c
@@ -0,0 +1,501 @@
+
+
+/*
+ * Class to deal with TLS connection to ChromCast,
+ * and send and recieve packat format data.
+ *
+ * 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>
+#ifndef SALONEINSTLIB
+#include "numlib.h"
+#else
+#include "numsup.h"
+#endif
+#include "conv.h"
+#include "ssl.h" /* axTLS header */
+
+#undef DEBUG
+#undef DUMPSDATA /* Send data */
+#undef DUMPRDATA /* Receive data */
+
+#if defined(NT) // Windows specific
+# if _WIN32_WINNT < 0x0400
+# undef _WIN32_WINNT
+# define _WIN32_WINNT 0x0400 // To make it link in VS2005
+# endif
+# define WIN32_LEAN_AND_MEAN
+# include <winsock2.h>
+# include <windows.h>
+# include <ws2tcpip.h>
+
+# include <process.h>
+# include <direct.h>
+# include <io.h>
+
+# define ERRNO GetLastError()
+# ifndef ETIMEDOUT
+# define ETIMEDOUT WSAETIMEDOUT
+# endif
+
+#else /* !NT = Unix, OS X */
+
+# include <errno.h>
+# include <sys/types.h>
+# include <sys/wait.h>
+# include <sys/socket.h>
+# include <sys/select.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
+# include <sys/time.h>
+# include <stdint.h>
+# include <inttypes.h>
+# include <netdb.h>
+
+# include <pwd.h>
+# include <unistd.h>
+# include <dirent.h>
+
+#undef SYNC_SSL /* Use lock to make sure ssl_read & write don't clash. */
+
+typedef int SOCKET;
+# define ERRNO errno
+
+# define INVALID_SOCKET -1
+# define SOCKET_ERROR -1
+
+#define closesocket(xxx) close(xxx);
+
+#endif
+
+#define CCPACKET_IMPL
+#include "ccpacket.h"
+
+/* ------------------------------------------------------------------- */
+
+#ifdef DEBUG
+# define dbgo stdout
+# define DBG(xxx) fprintf xxx ;
+void cc_dump_bytes(FILE *fp, char *pfx, unsigned char *buf, int len);
+#else
+# define DBG(xxx) ;
+#endif /* DEBUG */
+
+/* Error message from error number */
+char *ccpacket_emes(ccpacket_err rv) {
+#ifndef SERVER_ONLY
+ if (rv & 0x10000000) {
+ return ccpacket_emes(rv & 0x0fffffff);
+ }
+#endif
+
+ switch(rv) {
+ case ccpacket_OK:
+ return "Packet: OK";
+ case ccpacket_context:
+ return "Packet: getting a ssl contextfailed";
+ case ccpacket_malloc:
+ return "Packet: malloc failed";
+ case ccpacket_connect:
+ return "Packet: connecting to host failed";
+ case ccpacket_ssl:
+ return "Packet: ssl connect to host failed";
+
+ case ccpacket_timeout:
+ return "Packet:: i/o timed out";
+ case ccpacket_send:
+ return "Packet: message failed to send";
+ case ccpacket_recv:
+ return "Packet: failed to read message";
+ }
+
+ return "Uknown ccpacket error";
+}
+
+/* Establish an ccpacket connection - implementation */
+static ccpacket_err connect_ccpacket_imp(
+ ccpacket *p
+) {
+ struct sockaddr_in server; /* Server address */
+ uint8_t sesid[32] = { 0 };
+ int rv;
+
+#ifdef NT
+ WSADATA data;
+ WSAStartup(MAKEWORD(2,2), &data);
+#endif
+
+ server.sin_family = AF_INET;
+ server.sin_addr.s_addr = inet_addr(p->dip);
+ server.sin_port = htons((short)p->dport);
+
+ if ((p->ctx = ssl_ctx_new(SSL_SERVER_VERIFY_LATER, 1)) == NULL) {
+ DBG((dbgo, "connect ssl_ctx_new failed\n"))
+ return ccpacket_context;
+ }
+
+ /* Open socket */
+ if ((p->sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {
+ DBG((dbgo, "socket() failed with errno %d\n",ERRNO))
+ return ccpacket_connect;
+ }
+
+ /* Make recieve timeout after 100 msec, and send timeout after 2 seconds */
+ {
+ struct linger ling;
+#ifdef NT
+ DWORD tv;
+#ifdef SYNC_SSL
+ tv = 100;
+#else
+ tv = 2000;
+#endif
+ if ((rv = setsockopt(p->sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv,
+ sizeof(tv))) < 0) {
+ DBG((dbgo,"setsockopt timout failed with %d, errno %d",rv,ERRNO))
+ return ccpacket_connect;
+ }
+ tv = 2000;
+ if ((rv = setsockopt(p->sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv,
+ sizeof(tv))) < 0) {
+ DBG((dbgo,"setsockopt timout failed with %d, errno %d",rv,ERRNO))
+ return ccpacket_connect;
+ }
+#else
+ struct timeval tv;
+#ifdef SYNC_SSL
+ tv.tv_sec = 0;
+ tv.tv_usec = 100 * 1000;
+#else
+ tv.tv_sec = 2;
+ tv.tv_usec = 0;
+#endif
+ if ((rv = setsockopt(p->sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv,
+ sizeof(tv))) < 0) {
+ DBG((dbgo,"setsockopt timout failed with %d, errno %d",rv,ERRNO))
+ return ccpacket_connect;
+ }
+ tv.tv_sec = 2;
+ tv.tv_usec = 0;
+ if ((rv = setsockopt(p->sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv,
+ sizeof(tv))) < 0) {
+ DBG((dbgo,"setsockopt timout failed with %d, errno %d",rv,ERRNO))
+ return ccpacket_connect;
+ }
+#endif
+
+#ifdef NEVER /* This stops send timeout working - why ? */
+ ling.l_onoff = 1;
+ ling.l_linger = 2; /* Two seconds */
+ if ((rv = setsockopt(p->sock, SOL_SOCKET, SO_LINGER, (const char*)&ling,
+ sizeof(ling))) < 0) {
+ DBG((dbgo,"setsockopt timout failed with %d, errno %d",rv,ERRNO))
+ return ccpacket_connect;
+ }
+#endif /* NEVER */
+ }
+
+ /* Connect */
+ if (rv = (connect(p->sock, (struct sockaddr *)&server, sizeof(server))) != 0) {
+ DBG((dbgo, "TCP connect IP '%s' port %d failed with %d, errno %d\n",p->dip, p->dport,rv,ERRNO))
+ return ccpacket_connect;
+ }
+ DBG((dbgo, "socket connect IP '%s' port %d success\n",p->dip, p->dport))
+
+ /* Establish TLS */
+ /* Return nz if we can send PNG directly as base64 + bg RGB, */
+ /* else have to setup webserver and send URL */
+ if ((p->ssl = ssl_client_new(p->ctx, p->sock, sesid, 32)) == NULL) {
+ DBG((dbgo, "connect IP '%s' port %d ssl_ctx_new failed\n",p->dip, p->dport))
+ return ccpacket_ssl;
+ }
+ DBG((dbgo, "TLS connect IP '%s' port %d success\n",p->dip, p->dport))
+ return ccpacket_OK;
+}
+
+/* Establish an ccpacket connection */
+static ccpacket_err connect_ccpacket(
+ ccpacket *p,
+ char *dip, /* Destination IP address */
+ int dport /* Destination Port number */
+) {
+
+ if ((p->dip = strdup(dip)) == NULL)
+ return ccpacket_malloc;
+ p->dport = dport;
+
+ return connect_ccpacket_imp(p);
+}
+
+static void clear_ccpacket(ccpacket *p);
+
+/* Re-establish communication */
+static ccpacket_err re_connect_ccpacket(
+ ccpacket *p
+) {
+ clear_ccpacket(p);
+ return connect_ccpacket_imp(p);
+}
+
+/* It appears that the axTLS library can't deal with simultanous */
+/* send and recieve, due to the sharing of a single buffer and */
+/* intricate dependence on this in dealing with handshaking, so */
+/* rather than try and fix this limitation, we avoid the problem */
+/* with a lock. Note that we need to make sure that a receive */
+/* doesn't wait for too long, or it will block sends. */
+
+/* Send a message */
+/* Return ccpacket_err on error */
+static ccpacket_err send_ccpacket(ccpacket *p,
+ ORD8 *buf, ORD32 len /* Message body to send */
+) {
+ int lens, rv;
+ ORD8 *sbuf;
+ ORD32 slen;
+
+ if (p->ssl == NULL)
+ return ccpacket_ssl;
+
+ if ((sbuf = malloc(4 + len)) == NULL) {
+ return ccpacket_malloc;
+ }
+
+ write_ORD32_be(len, sbuf);
+ memcpy(sbuf+4, buf, len);
+ slen = len + 4;
+
+#if defined(DEBUG) && defined(DUMPSDATA)
+ printf("send_ccpacket sending packet:\n");
+ cc_dump_bytes(dbgo," ", sbuf, slen);
+#endif
+
+ for (lens = 0; lens < slen; lens += rv) {
+ DBG((dbgo, "Sending packet %d bytes\n",slen - lens))
+ if (p->ssl == NULL) {
+ return ccpacket_ssl;
+ }
+#ifdef SYNC_SSL
+ amutex_lock(p->lock);
+printf("~1 send locked\n");
+#endif
+ if ((rv = ssl_write(p->ssl, sbuf + lens, slen - lens)) < 0) {
+ DBG((dbgo, "TLS send body failed with '%s'\n",ssl_error_string(rv)))
+#ifdef SYNC_SSL
+printf("~1 send unlocked\n");
+ amutex_unlock(p->lock);
+#endif
+ free(sbuf);
+ if (rv == SSL_TIMEDOUT)
+ return ccpacket_timeout;
+ return ccpacket_send;
+ }
+#ifdef SYNC_SSL
+printf("~1 send unlocked\n");
+ amutex_unlock(p->lock);
+#endif
+ }
+ free(sbuf);
+
+ return ccpacket_OK;
+}
+
+/* Receive a message */
+/* Return ccpacket_err on error */
+static ccpacket_err receive_ccpacket(ccpacket *p,
+ ORD8 **pbuf, ORD32 *plen /* ccpacket received, free after use */
+) {
+ ORD8 *ibuf; /* Buffer from ssl_read() */
+ int ioff, ilen; /* Remaining data at ioff lenght ilen in ibuf */
+ ORD8 hbuf[4], *rbuf = hbuf; /* Header buffer, returned buffer */
+ int tlen, clen, rlen; /* Target length, copy length, return length */
+
+ if (p->ssl == NULL)
+ return ccpacket_ssl;
+
+#ifdef SYNC_SSL
+ amutex_lock(p->lock);
+printf("~1 receive locked\n");
+#endif
+
+ /* Until we have 4 bytes for the header */
+ for (tlen = 4, rlen = 0; rlen < tlen;) {
+ ioff = 0;
+ if ((ilen = ssl_read(p->ssl, &ibuf)) < 0) {
+ DBG((dbgo, "header recv failed with '%s'\n",ssl_error_string(ilen)))
+ if (ilen == SSL_OK) /* Hmm. */
+ continue;
+#ifdef SYNC_SSL
+printf("~1 receive unlocked\n");
+ amutex_unlock(p->lock);
+#endif
+ if (ilen == SSL_TIMEDOUT)
+ return ccpacket_timeout;
+ return ccpacket_recv;
+ }
+ DBG((dbgo, "receive_ccpacket read %d bytes\n",ilen))
+ if (ilen == 0)
+ continue;
+ if ((clen = ilen) > (tlen - rlen)) /* More than we need */
+ clen = tlen - rlen;
+ memcpy(rbuf + rlen, ibuf + ioff, clen);
+ rlen += clen;
+ ioff += clen;
+ ilen -= clen;
+ }
+ /* We have ilen left in ibuf at offset ioff */
+ DBG((dbgo, "receive_ccpacket %d bytes left over\n",ilen))
+
+ tlen = read_ORD32_be(ibuf);
+ DBG((dbgo, "receive_ccpacket expecting %d more bytes\n",tlen))
+
+ if (tlen < 0 || tlen > 64 * 2014) {
+#ifdef SYNC_SSL
+printf("~1 receive unlocked\n");
+ amutex_unlock(p->lock);
+#endif
+ DBG((dbgo, "receive_ccpacket got bad data length - returning error\n"))
+ return ccpacket_recv;
+ }
+
+ if ((rbuf = malloc(tlen)) == NULL) {
+#ifdef SYNC_SSL
+printf("~1 receive unlocked\n");
+ amutex_unlock(p->lock);
+#endif
+ DBG((dbgo, "receive_ccpacket malloc failed\n"))
+ return ccpacket_malloc;
+ }
+ rlen = 0;
+
+ /* Use any data we have left over from first read */
+ if (ilen > 0) {
+ if ((clen = ilen) > (tlen - rlen))
+ clen = tlen - rlen;
+ DBG((dbgo, "receive_ccpacket using %d spair bytesr\n",clen))
+ memcpy(rbuf + rlen, ibuf + ioff, clen);
+ rlen += clen;
+ ioff += clen;
+ ilen -= clen;
+ }
+
+ /* Get the remainder of the body if we need to */
+ for (; rlen < tlen;) {
+ ioff = 0;
+ if ((ilen = ssl_read(p->ssl, &ibuf)) < 0) {
+ DBG((dbgo, "body recv failed with '%s'\n",ssl_error_string(ilen)))
+ if (ilen == SSL_OK)
+ continue;
+#ifdef SYNC_SSL
+printf("~1 receive unlocked\n");
+ amutex_unlock(p->lock);
+#endif
+ if (ilen == SSL_TIMEDOUT)
+ return ccpacket_timeout;
+ return ccpacket_recv;
+ }
+ DBG((dbgo, "receive_ccpacket read %d bytes\n",ilen))
+ if (ilen == 0)
+ continue;
+ if ((clen = ilen) > (tlen - rlen))
+ clen = tlen - rlen;
+ memcpy(rbuf + rlen, ibuf + ioff, clen);
+ rlen += clen;
+ ioff += clen;
+ ilen -= clen;
+ }
+#ifdef SYNC_SSL
+printf("~1 receive unlocked\n");
+ amutex_unlock(p->lock);
+#endif
+ if (ilen > 0) { /* Hmm. We should keep this for the next read ?*/
+ DBG((dbgo, " ################## got %d byts left over ###########\n", ilen))
+ }
+#if defined(DEBUG) && defined(DUMPRDATA)
+ printf("receive_ccpacket got:\n");
+ cc_dump_bytes(dbgo," ", rbuf, rlen);
+#endif
+ *pbuf = rbuf;
+ *plen = rlen;
+
+ return ccpacket_OK;
+}
+
+/* Clear the ccpacket */
+static void clear_ccpacket(ccpacket *p) {
+ if (p != NULL) {
+
+ if (p->ssl != NULL) {
+ ssl_free(p->ssl);
+ p->ssl = NULL;
+ }
+ if (p->ctx != NULL) {
+ ssl_ctx_free(p->ctx);
+ p->ctx = NULL;
+ }
+ if (p->sock != INVALID_SOCKET) {
+ closesocket(p->sock);
+ p->sock = 0;
+ }
+ }
+}
+
+/* Delete the ccpacket */
+static void del_ccpacket(ccpacket *p) {
+ if (p != NULL) {
+ clear_ccpacket(p);
+ if (p->dip != NULL) {
+ free(p->dip);
+ p->dip = NULL;
+ }
+#ifdef SYNC_SSL
+ amutex_del(p->lock);
+#endif
+ free(p);
+ }
+}
+
+/* Create an ccpacket. */
+/* Return NULL on error */
+ccpacket *new_ccpacket() {
+ ccpacket *p = NULL;
+
+ if ((p = (ccpacket *)calloc(1, sizeof(ccpacket))) == NULL) {
+ DBG((dbgo, "calloc failed\n"))
+ return NULL;
+ }
+
+#ifdef SYNC_SSL
+ amutex_init(p->lock);
+#endif
+
+ /* Init method pointers */
+ p->del = del_ccpacket;
+ p->connect = connect_ccpacket;
+ p->reconnect = re_connect_ccpacket;
+ p->send = send_ccpacket;
+ p->receive = receive_ccpacket;
+
+ return p;
+}
+