619 lines
14 KiB
C
619 lines
14 KiB
C
|
|
|
|
/*
|
|
* Class to deal with TLS connection to ChromCast,
|
|
* and send and recieve packet 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>
|
|
#include "numlib.h"
|
|
#include "conv.h"
|
|
|
|
#ifdef USING_AXTLS
|
|
# include "ssl.h" /* axTLS header */
|
|
#else
|
|
# include "openssl/ssl.h" /* OpenSSL header */
|
|
#endif
|
|
|
|
#undef DEBUG /* [und] */
|
|
#undef DUMPSDATA /* [und] Send data */
|
|
#undef DUMPRDATA /* [und] 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 DBG(xxx) a1logd xxx ;
|
|
#else
|
|
# define DBG(xxx) ;
|
|
#endif /* DEBUG */
|
|
|
|
/* Some OpenSSL's support
|
|
ERR_print_errors_fp(stderr);
|
|
*/
|
|
|
|
/* 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 "Unknown 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);
|
|
|
|
#ifdef USING_AXTLS
|
|
// if ((p->ctx = ssl_ctx_new(SSL_SERVER_VERIFY_LATER, 1)) == NULL)
|
|
if ((p->ctx = ssl_ctx_new(0, 1)) == NULL)
|
|
|
|
// Want to use TLS_client_method(), but older OpenSSL doesn't have it...
|
|
#elif OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
if ((p->ctx = SSL_CTX_new(TLSv1_client_method())) == NULL)
|
|
#else
|
|
if ((p->ctx = SSL_CTX_new(TLS_client_method())) == NULL)
|
|
#endif
|
|
{
|
|
DBG((g_log,0, "connect ssl_ctx_new failed\n"))
|
|
return ccpacket_context;
|
|
}
|
|
|
|
#ifndef USING_AXTLS
|
|
# if OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
SSL_load_error_strings();
|
|
SSL_library_init();
|
|
SSL_CTX_set_verify(p->ctx, SSL_VERIFY_NONE, NULL);
|
|
# else
|
|
OPENSSL_init_ssl(0, NULL);
|
|
# endif
|
|
#endif /* !defined USING_AXTLS */
|
|
|
|
/* Open socket */
|
|
if ((p->sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {
|
|
DBG((g_log,0, "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((g_log,0,"setsockopt timeout 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((g_log,0,"setsockopt timeout 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((g_log,0,"setsockopt timeout 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((g_log,0,"setsockopt timeout 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((g_log,0,"setsockopt timeout 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((g_log,0, "TCP connect IP '%s' port %d failed with %d, errno %d\n",p->dip, p->dport,rv,ERRNO))
|
|
return ccpacket_connect;
|
|
}
|
|
DBG((g_log,0, "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 */
|
|
#ifdef USING_AXTLS
|
|
if ((p->ssl = ssl_client_new(p->ctx, p->sock, sesid, 32, NULL)) == NULL)
|
|
#else
|
|
if ((p->ssl = SSL_new(p->ctx)) == NULL
|
|
|| SSL_set_fd(p->ssl, p->sock) != 1
|
|
|| SSL_connect(p->ssl) != 1)
|
|
#endif
|
|
{
|
|
DBG((g_log,0, "TLS connect IP '%s' port %d ssl_ctx_new failed\n",p->dip, p->dport))
|
|
return ccpacket_ssl;
|
|
}
|
|
DBG((g_log,0, "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, ilen;
|
|
ORD8 *sbuf;
|
|
ORD32 slen;
|
|
|
|
if (p->ssl == NULL)
|
|
return ccpacket_ssl;
|
|
|
|
if ((sbuf = malloc(4 + len)) == NULL) {
|
|
return ccpacket_malloc;
|
|
}
|
|
|
|
write_ORD32_be(sbuf, len);
|
|
memcpy(sbuf+4, buf, len);
|
|
slen = len + 4;
|
|
|
|
#if defined(DEBUG) && defined(DUMPSDATA)
|
|
printf("send_ccpacket sending packet:\n");
|
|
adump_bytes(g_log," ", sbuf, 0, slen);
|
|
#endif
|
|
|
|
for (lens = 0; lens < slen; lens += ilen) {
|
|
DBG((g_log,0, "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
|
|
#ifdef USING_AXTLS
|
|
if ((ilen = ssl_write(p->ssl, sbuf + lens, slen - lens)) < 0)
|
|
#else
|
|
if ((ilen = SSL_write(p->ssl, sbuf + lens, slen - lens)) < 0)
|
|
#endif
|
|
{
|
|
#ifdef USING_AXTLS
|
|
DBG((g_log,0, "send failed with '%s'\n",ssl_error_string(ilen)))
|
|
#else
|
|
DBG((g_log,0, "send failed with %d\n",SSL_get_error(p->ssl, ilen)))
|
|
#endif
|
|
#ifdef SYNC_SSL
|
|
printf("~1 send unlocked\n");
|
|
amutex_unlock(p->lock);
|
|
#endif
|
|
free(sbuf);
|
|
|
|
#ifdef USING_AXTLS
|
|
if (ilen == SSL_TIMEDOUT)
|
|
#else
|
|
if (SSL_get_error(p->ssl, ilen) == SSL_ERROR_WANT_READ
|
|
|| SSL_get_error(p->ssl, ilen) == SSL_ERROR_WANT_WRITE)
|
|
#endif
|
|
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
|
|
|
|
tlen = 4;
|
|
|
|
#ifndef USING_AXTLS
|
|
if ((ibuf = malloc(tlen)) == NULL) {
|
|
return ccpacket_malloc;
|
|
}
|
|
ibuf[0] = ibuf[1] = ibuf[2] = ibuf[2] = 0;
|
|
#endif
|
|
|
|
/* Until we have 4 bytes for the header */
|
|
for (rlen = 0; rlen < tlen;) {
|
|
ioff = 0;
|
|
#ifdef USING_AXTLS
|
|
if ((ilen = ssl_read(p->ssl, &ibuf)) < 0)
|
|
#else
|
|
if ((ilen = SSL_read(p->ssl, ibuf, tlen)) < 0)
|
|
#endif
|
|
{
|
|
#ifdef USING_AXTLS
|
|
DBG((g_log,0, "header recv failed with '%s'\n",ssl_error_string(ilen)))
|
|
#else
|
|
DBG((g_log,0, "header recv failed with %d\n",SSL_get_error(p->ssl, ilen)))
|
|
#endif
|
|
#ifdef SYNC_SSL
|
|
printf("~1 receive unlocked\n");
|
|
amutex_unlock(p->lock);
|
|
#endif
|
|
#ifdef USING_AXTLS
|
|
if (ilen == SSL_TIMEDOUT)
|
|
#else
|
|
free(ibuf);
|
|
if (SSL_get_error(p->ssl, ilen) == SSL_ERROR_WANT_READ
|
|
|| SSL_get_error(p->ssl, ilen) == SSL_ERROR_WANT_WRITE)
|
|
#endif
|
|
return ccpacket_timeout;
|
|
return ccpacket_recv;
|
|
}
|
|
DBG((g_log,0, "receive_ccpacket read %d bytes\n",ilen))
|
|
if (ilen == 0) {
|
|
#ifdef USING_AXTLS
|
|
continue;
|
|
#else
|
|
DBG((g_log,0, "SSL_read failed\n"))
|
|
free(ibuf);
|
|
return ccpacket_recv;
|
|
#endif
|
|
}
|
|
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((g_log,0, "receive_ccpacket %d bytes left over\n",ilen))
|
|
|
|
tlen = read_ORD32_be(rbuf);
|
|
DBG((g_log,0, "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((g_log,0, "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((g_log,0, "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((g_log,0, "receive_ccpacket using %d spair bytesr\n",clen))
|
|
memcpy(rbuf + rlen, ibuf + ioff, clen);
|
|
rlen += clen;
|
|
ioff += clen;
|
|
ilen -= clen;
|
|
}
|
|
|
|
#ifndef USING_AXTLS
|
|
free(ibuf);
|
|
if ((ibuf = malloc(tlen)) == NULL) {
|
|
return ccpacket_malloc;
|
|
}
|
|
#endif
|
|
|
|
/* Get the remainder of the body if we need to */
|
|
for (; rlen < tlen;) {
|
|
ioff = 0;
|
|
#ifdef USING_AXTLS
|
|
if ((ilen = ssl_read(p->ssl, &ibuf)) < 0)
|
|
#else
|
|
if ((ilen = SSL_read(p->ssl, ibuf, tlen)) < 0)
|
|
#endif
|
|
{
|
|
#ifdef USING_AXTLS
|
|
DBG((g_log,0, "body recv failed with '%s'\n",ssl_error_string(ilen)))
|
|
#else
|
|
DBG((g_log,0, "body recv failed with %d\n",SSL_get_error(p->ssl, ilen)))
|
|
#endif
|
|
#ifdef SYNC_SSL
|
|
printf("~1 receive unlocked\n");
|
|
amutex_unlock(p->lock);
|
|
#endif
|
|
#ifdef USING_AXTLS
|
|
if (ilen == SSL_TIMEDOUT)
|
|
#else
|
|
free(ibuf);
|
|
if (SSL_get_error(p->ssl, ilen) == SSL_ERROR_WANT_READ
|
|
|| SSL_get_error(p->ssl, ilen) == SSL_ERROR_WANT_WRITE)
|
|
#endif
|
|
return ccpacket_timeout;
|
|
return ccpacket_recv;
|
|
}
|
|
DBG((g_log,0, "receive_ccpacket read %d bytes\n",ilen))
|
|
if (ilen == 0) {
|
|
#ifdef USING_AXTLS
|
|
continue;
|
|
#else
|
|
DBG((g_log,0, "SSL_read failed\n"))
|
|
free(ibuf);
|
|
return ccpacket_recv;
|
|
#endif
|
|
}
|
|
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((g_log,0, " ################## got %d byts left over ###########\n", ilen))
|
|
}
|
|
#ifndef USING_AXTLS
|
|
free(ibuf);
|
|
#endif
|
|
#if defined(DEBUG) && defined(DUMPRDATA)
|
|
printf("receive_ccpacket got:\n");
|
|
adump_bytes(g_log," ", rbuf, 0, 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) {
|
|
#ifdef USING_AXTLS
|
|
ssl_free(p->ssl);
|
|
#else
|
|
SSL_free(p->ssl);
|
|
#endif
|
|
p->ssl = NULL;
|
|
}
|
|
if (p->ctx != NULL) {
|
|
#ifdef USING_AXTLS
|
|
ssl_ctx_free(p->ctx);
|
|
#else
|
|
SSL_CTX_free(p->ctx);
|
|
#endif
|
|
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;
|
|
|
|
#ifndef USING_AXTLS
|
|
SSL_load_error_strings();
|
|
SSL_library_init();
|
|
#endif
|
|
|
|
if ((p = (ccpacket *)calloc(1, sizeof(ccpacket))) == NULL) {
|
|
DBG((g_log,0, "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;
|
|
}
|
|
|