xwords/common/comms.c
ehouse 321acd1d42 Add and set a compile-time option so comms will set a periodic timer
and use it to send and check for heartbeats over any transport.
Caller must supply a reset proc which is called when heartbeat hasn't
been received in too long.  No changes required to comms protocol, but
that means the heartbeat interval is fixed at compile time: can't be
negotiated, and the two ends had better agree.  Currently tested with
linux host and PalmOS guest, where only the first heartbeat failure is
recovered from.  So there's some debugging to be done still.
2007-11-18 23:43:27 +00:00

1523 lines
49 KiB
C

/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
/*
* Copyright 2001-2005 by Eric House (xwords@eehouse.org). All rights reserved.
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifdef USE_STDIO
# include <stdio.h>
#endif
#include "comms.h"
#include "util.h"
#include "game.h"
#include "xwstream.h"
#include "memstream.h"
#include "xwrelay.h"
#include "strutils.h"
#define cEND 0x65454e44
#define HEARTBEAT_NONE 0
#ifndef XWFEATURE_STANDALONE_ONLY
#if defined RELAY_HEARTBEAT && defined COMMS_HEARTBEAT
compilation_error_here( "Choose one or the other or none." );
#endif
#ifdef COMMS_HEARTBEAT
/* It might make sense for this to be a parameter or somehow tied to the
platform and transport. But in that case it'd have to be passed across
since all devices must agree. */
# define HB_INTERVAL 5
#endif
EXTERN_C_START
typedef struct MsgQueueElem {
struct MsgQueueElem* next;
XP_U8* msg;
XP_U16 len;
XP_U16 channelNo;
MsgID msgID; /* saved for ease of deletion */
} MsgQueueElem;
typedef struct AddressRecord {
struct AddressRecord* next;
CommsAddrRec addr;
#ifdef DEBUG
XP_U16 lastACK;
XP_U16 nUniqueBytes;
#endif
MsgID nextMsgID; /* on a per-channel basis */
MsgID lastMsgReceived; /* on a per-channel basis */
/* only used if COMMS_HEARTBEAT set except for serialization (to_stream) */
XP_U32 lastMsgRcvdTime; /* when did we set lastMsgReceived? */
XP_PlayerAddr channelNo;
struct {
XWHostID hostID; /* used for relay case */
} r;
} AddressRecord;
#define ADDRESSRECORD_SIZE_68K 20
typedef enum {
COMMS_RELAYSTATE_UNCONNECTED
, COMMS_RELAYSTATE_CONNECT_PENDING
, COMMS_RELAYSTATE_CONNECTED
, COMMS_RELAYSTATE_ALLCONNECTED
} CommsRelayState;
struct CommsCtxt {
XW_UtilCtxt* util;
XP_U32 connID; /* 0 means ignore; otherwise must match */
XP_U16 nextChannelNo;
AddressRecord* recs; /* return addresses */
TransportSend sendproc;
#ifdef COMMS_HEARTBEAT
TransportReset resetproc;
#endif
void* sendClosure;
MsgQueueElem* msgQueueHead;
MsgQueueElem* msgQueueTail;
XP_U16 queueLen;
#ifdef COMMS_HEARTBEAT
XP_Bool doHeartbeat;
#endif
/* The following fields, down to isServer, are only used if
XWFEATURE_RELAY is defined, but I'm leaving them in here so apps built
both ways can open each other's saved games files.*/
CommsAddrRec addr;
/* Stuff for relays */
struct {
XWHostID myHostID; /* 0 if unset, 1 if acting as server,
random for client */
CommsRelayState relayState; /* not saved: starts at UNCONNECTED */
CookieID cookieID; /* not saved; temp standin for cookie; set
by relay */
/* permanent globally unique name, set by relay and forever after
associated with this game. Used to reconnect. */
XP_UCHAR connName[MAX_CONNNAME_LEN+1];
/* heartbeat: for periodic pings if relay thinks the network the
device is on requires them. Not saved since only valid when
connected, and we reconnect for every game and after restarting. */
XP_U16 heartbeat;
XP_U16 nPlayersHere;
XP_U16 nPlayersTotal;
XP_Bool connecting;
} r;
XP_Bool isServer;
#ifdef DEBUG
XP_U16 nUniqueBytes;
#endif
MPSLOT
};
#ifdef XWFEATURE_BLUETOOTH
typedef enum {
BTMSG_DATA = 0
,BTMSG_RESET
} BTMsgType;
#endif
/****************************************************************************
* prototypes
****************************************************************************/
static AddressRecord* rememberChannelAddress( CommsCtxt* comms,
XP_PlayerAddr channelNo,
XWHostID id,
const CommsAddrRec* addr );
static XP_Bool channelToAddress( CommsCtxt* comms, XP_PlayerAddr channelNo,
const CommsAddrRec** addr );
static AddressRecord* getRecordFor( CommsCtxt* comms,
XP_PlayerAddr channelNo );
static XP_S16 sendMsg( CommsCtxt* comms, MsgQueueElem* elem );
static void addToQueue( CommsCtxt* comms, MsgQueueElem* newMsgElem );
static XP_U16 countAddrRecs( const CommsCtxt* comms );
#ifdef XWFEATURE_RELAY
static void relayConnect( CommsCtxt* comms );
static void relayDisconnect( CommsCtxt* comms );
static XP_Bool send_via_relay( CommsCtxt* comms, XWRELAY_Cmd cmd,
XWHostID destID, void* data, int dlen );
static XWHostID getDestID( CommsCtxt* comms, XP_PlayerAddr channelNo );
#endif
#if defined XWFEATURE_RELAY || defined COMMS_HEARTBEAT
static void setHeartbeatTimer( CommsCtxt* comms );
#endif
#ifdef XWFEATURE_BLUETOOTH
static XP_S16 send_via_bt( CommsCtxt* comms, BTMsgType typ,
XP_PlayerAddr channelNo,
void* data, int dlen );
static void btConnect( CommsCtxt* comms );
#endif
/****************************************************************************
* implementation
****************************************************************************/
CommsCtxt*
comms_make( MPFORMAL XW_UtilCtxt* util, XP_Bool isServer,
XP_U16 XP_UNUSED_RELAY(nPlayersHere),
XP_U16 XP_UNUSED_RELAY(nPlayersTotal),
TransportSend sendproc, IF_CH(TransportReset resetproc)
void* closure )
{
CommsCtxt* result = (CommsCtxt*)XP_MALLOC( mpool, sizeof(*result) );
XP_MEMSET( result, 0, sizeof(*result) );
MPASSIGN(result->mpool, mpool);
result->isServer = isServer;
result->sendproc = sendproc;
#ifdef COMMS_HEARTBEAT
result->resetproc = resetproc;
#endif
result->sendClosure = closure;
result->util = util;
#ifdef XWFEATURE_RELAY
result->r.myHostID = isServer? HOST_ID_SERVER: HOST_ID_NONE;
XP_LOGF( "set myHostID to %d", result->r.myHostID );
result->r.relayState = COMMS_RELAYSTATE_UNCONNECTED;
result->r.nPlayersHere = nPlayersHere;
result->r.nPlayersTotal = nPlayersTotal;
#endif
return result;
} /* comms_make */
static void
cleanupInternal( CommsCtxt* comms )
{
MsgQueueElem* msg;
MsgQueueElem* next;
for ( msg = comms->msgQueueHead; !!msg; msg = next ) {
next = msg->next;
XP_FREE( comms->mpool, msg->msg );
XP_FREE( comms->mpool, msg );
}
comms->queueLen = 0;
comms->msgQueueHead = comms->msgQueueTail = (MsgQueueElem*)NULL;
} /* cleanupInternal */
static void
cleanupAddrRecs( CommsCtxt* comms )
{
AddressRecord* recs;
AddressRecord* next;
for ( recs = comms->recs; !!recs; recs = next ) {
next = recs->next;
XP_FREE( comms->mpool, recs );
}
comms->recs = (AddressRecord*)NULL;
} /* cleanupAddrRecs */
void
comms_reset( CommsCtxt* comms, XP_Bool isServer,
XP_U16 XP_UNUSED_RELAY(nPlayersHere),
XP_U16 XP_UNUSED_RELAY(nPlayersTotal) )
{
LOG_FUNC();
#ifdef XWFEATURE_RELAY
relayDisconnect( comms );
#endif
cleanupInternal( comms );
comms->isServer = isServer;
cleanupAddrRecs( comms );
comms->nextChannelNo = 0;
comms->connID = CONN_ID_NONE;
#ifdef XWFEATURE_RELAY
comms->r.cookieID = COOKIE_ID_NONE;
comms->r.nPlayersHere = nPlayersHere;
comms->r.nPlayersTotal = nPlayersTotal;
relayConnect( comms );
#endif
LOG_RETURN_VOID();
} /* comms_reset */
void
comms_destroy( CommsCtxt* comms )
{
cleanupInternal( comms );
cleanupAddrRecs( comms );
XP_FREE( comms->mpool, comms );
} /* comms_destroy */
void
comms_setConnID( CommsCtxt* comms, XP_U32 connID )
{
comms->connID = connID;
XP_STATUSF( "set connID to %lx", connID );
} /* comms_setConnID */
static void
addrFromStream( CommsAddrRec* addrP, XWStreamCtxt* stream )
{
CommsAddrRec addr;
addr.conType = stream_getU8( stream );
switch( addr.conType ) {
case COMMS_CONN_UNUSED:
/* XP_ASSERT( 0 ); */
break;
case COMMS_CONN_BT:
stringFromStreamHere( stream, addr.u.bt.hostName,
sizeof(addr.u.bt.hostName) );
stream_getBytes( stream, &addr.u.bt.btAddr.bits,
sizeof(addr.u.bt.btAddr.bits) );
break;
case COMMS_CONN_IR:
/* nothing to save */
break;
case COMMS_CONN_IP_NOUSE:
stringFromStreamHere( stream, addr.u.ip.hostName_ip,
sizeof(addr.u.ip.hostName_ip) );
addr.u.ip.ipAddr_ip = stream_getU32( stream );
addr.u.ip.port_ip = stream_getU16( stream );
break;
case COMMS_CONN_RELAY:
stringFromStreamHere( stream, addr.u.ip_relay.cookie,
sizeof(addr.u.ip_relay.cookie) );
stringFromStreamHere( stream, addr.u.ip_relay.hostName,
sizeof(addr.u.ip_relay.hostName) );
addr.u.ip_relay.ipAddr = stream_getU32( stream );
addr.u.ip_relay.port = stream_getU16( stream );
break;
default:
/* shut up, compiler */
break;
}
XP_MEMCPY( addrP, &addr, sizeof(*addrP) );
} /* addrFromStream */
CommsCtxt*
comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util,
TransportSend sendproc,
IF_CH(TransportReset resetproc ) void* closure )
{
CommsCtxt* comms;
XP_Bool isServer;
XP_U16 nAddrRecs, nPlayersHere, nPlayersTotal;
AddressRecord** prevsAddrNext;
MsgQueueElem** prevsQueueNext;
XP_U16 version = stream_getVersion( stream );
CommsAddrRec addr;
short i;
isServer = stream_getU8( stream );
if ( version < STREAM_VERS_RELAY ) {
XP_MEMSET( &addr, 0, sizeof(addr) );
addr.conType = COMMS_CONN_IR; /* all there was back then */
} else {
addrFromStream( &addr, stream );
}
if ( addr.conType == COMMS_CONN_RELAY ) {
nPlayersHere = (XP_U16)stream_getBits( stream, 4 );
nPlayersTotal = (XP_U16)stream_getBits( stream, 4 );
} else {
nPlayersHere = 0;
nPlayersTotal = 0;
}
comms = comms_make( MPPARM(mpool) util, isServer,
nPlayersHere, nPlayersTotal,
sendproc, IF_CH(resetproc) closure );
XP_MEMCPY( &comms->addr, &addr, sizeof(comms->addr) );
comms->connID = stream_getU32( stream );
comms->nextChannelNo = stream_getU16( stream );
if ( addr.conType == COMMS_CONN_RELAY ) {
comms->r.myHostID = stream_getU8( stream );
stringFromStreamHere( stream, comms->r.connName,
sizeof(comms->r.connName) );
}
#ifdef DEBUG
comms->nUniqueBytes = stream_getU16( stream );
#endif
comms->queueLen = stream_getU8( stream );
nAddrRecs = stream_getU8( stream );
prevsAddrNext = &comms->recs;
for ( i = 0; i < nAddrRecs; ++i ) {
AddressRecord* rec = (AddressRecord*)XP_MALLOC( mpool, sizeof(*rec));
XP_MEMSET( rec, 0, sizeof(*rec) );
addrFromStream( &rec->addr, stream );
rec->nextMsgID = stream_getU16( stream );
rec->lastMsgReceived = stream_getU16( stream );
rec->channelNo = stream_getU16( stream );
if ( rec->addr.conType == COMMS_CONN_RELAY ) {
rec->r.hostID = stream_getU8( stream );
}
#ifdef DEBUG
rec->lastACK = stream_getU16( stream );
rec->nUniqueBytes = stream_getU16( stream );
#endif
*prevsAddrNext = rec;
prevsAddrNext = &rec->next;
}
prevsQueueNext = &comms->msgQueueHead;
for ( i = 0; i < comms->queueLen; ++i ) {
MsgQueueElem* msg = (MsgQueueElem*)XP_MALLOC( mpool, sizeof(*msg) );
msg->channelNo = stream_getU16( stream );
msg->msgID = stream_getU32( stream );
msg->len = stream_getU16( stream );
msg->msg = (XP_U8*)XP_MALLOC( mpool, msg->len );
stream_getBytes( stream, msg->msg, msg->len );
msg->next = (MsgQueueElem*)NULL;
*prevsQueueNext = comms->msgQueueTail = msg;
comms->msgQueueTail = msg;
prevsQueueNext = &msg->next;
}
#ifdef DEBUG
XP_ASSERT( stream_getU32( stream ) == cEND );
#endif
return comms;
} /* comms_makeFromStream */
void
comms_start( CommsCtxt* comms )
{
if ( 0 ) {
#ifdef XWFEATURE_RELAY
} else if ( comms->addr.conType == COMMS_CONN_RELAY ) {
comms->r.relayState = COMMS_RELAYSTATE_UNCONNECTED;
relayConnect( comms );
#endif
#ifdef XWFEATURE_BLUETOOTH
} else if ( comms->addr.conType == COMMS_CONN_BT ) {
btConnect( comms );
#endif
}
#ifdef COMMS_HEARTBEAT
comms->doHeartbeat = comms->addr.conType != COMMS_CONN_IR;
#endif
} /* comms_start */
static void
addrToStream( XWStreamCtxt* stream, const CommsAddrRec* addrP )
{
CommsAddrRec addr;
XP_MEMCPY( &addr, addrP, sizeof(addr) );
stream_putU8( stream, addr.conType );
switch( addr.conType ) {
#ifdef DEBUG
case COMMS_CONN_UNUSED:
case LAST_____FOO:
/* XP_ASSERT( 0 ); */
break;
#endif
case COMMS_CONN_BT:
stringToStream( stream, addr.u.bt.hostName );
/* sizeof(.bits) below defeats ARM's padding. */
stream_putBytes( stream, &addr.u.bt.btAddr.bits,
sizeof(addr.u.bt.btAddr.bits) );
break;
case COMMS_CONN_IR:
/* nothing to save */
break;
case COMMS_CONN_IP_NOUSE:
stringToStream( stream, addr.u.ip.hostName_ip );
stream_putU32( stream, addr.u.ip.ipAddr_ip );
stream_putU16( stream, addr.u.ip.port_ip );
break;
case COMMS_CONN_RELAY:
stringToStream( stream, addr.u.ip_relay.cookie );
stringToStream( stream, addr.u.ip_relay.hostName );
stream_putU32( stream, addr.u.ip_relay.ipAddr );
stream_putU16( stream, addr.u.ip_relay.port );
break;
}
} /* addrToStream */
void
comms_writeToStream( const CommsCtxt* comms, XWStreamCtxt* stream )
{
XP_U16 nAddrRecs;
AddressRecord* rec;
MsgQueueElem* msg;
stream_putU8( stream, (XP_U8)comms->isServer );
addrToStream( stream, &comms->addr );
if ( comms->addr.conType == COMMS_CONN_RELAY ) {
stream_putBits( stream, 4, comms->r.nPlayersHere );
stream_putBits( stream, 4, comms->r.nPlayersTotal );
}
stream_putU32( stream, comms->connID );
stream_putU16( stream, comms->nextChannelNo );
if ( comms->addr.conType == COMMS_CONN_RELAY ) {
stream_putU8( stream, comms->r.myHostID );
stringToStream( stream, comms->r.connName );
}
#ifdef DEBUG
stream_putU16( stream, comms->nUniqueBytes );
#endif
XP_ASSERT( comms->queueLen <= 255 );
stream_putU8( stream, (XP_U8)comms->queueLen );
nAddrRecs = countAddrRecs(comms);
stream_putU8( stream, (XP_U8)nAddrRecs );
for ( rec = comms->recs; !!rec; rec = rec->next ) {
CommsAddrRec* addr = &rec->addr;
addrToStream( stream, addr );
stream_putU16( stream, (XP_U16)rec->nextMsgID );
stream_putU16( stream, (XP_U16)rec->lastMsgReceived );
stream_putU16( stream, rec->channelNo );
if ( rec->addr.conType == COMMS_CONN_RELAY ) {
stream_putU8( stream, rec->r.hostID ); /* unneeded unless RELAY */
}
#ifdef DEBUG
stream_putU16( stream, rec->lastACK );
stream_putU16( stream, rec->nUniqueBytes );
#endif
}
for ( msg = comms->msgQueueHead; !!msg; msg = msg->next ) {
stream_putU16( stream, msg->channelNo );
stream_putU32( stream, msg->msgID );
stream_putU16( stream, msg->len );
stream_putBytes( stream, msg->msg, msg->len );
}
#ifdef DEBUG
stream_putU32( stream, cEND );
#endif
} /* comms_writeToStream */
void
comms_getAddr( const CommsCtxt* comms, CommsAddrRec* addr )
{
XP_ASSERT( !!comms );
XP_MEMCPY( addr, &comms->addr, sizeof(*addr) );
} /* comms_getAddr */
void
comms_setAddr( CommsCtxt* comms, const CommsAddrRec* addr )
{
XP_ASSERT( comms != NULL );
#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH
util_addrChange( comms->util, &comms->addr, addr );
#endif
XP_MEMCPY( &comms->addr, addr, sizeof(comms->addr) );
if ( 0 ) {
#ifdef XWFEATURE_RELAY
/* We should now have a cookie so we can connect??? */
} else if ( addr->conType == COMMS_CONN_RELAY ) {
relayConnect( comms );
#endif
#ifdef XWFEATURE_BLUETOOTH
} else if ( addr->conType == COMMS_CONN_BT ) {
btConnect( comms );
#endif
}
#ifdef COMMS_HEARTBEAT
comms->doHeartbeat = comms->addr.conType != COMMS_CONN_IR;
#endif
} /* comms_setAddr */
void
comms_getInitialAddr( CommsAddrRec* addr )
{
#if defined XWFEATURE_RELAY
addr->conType = COMMS_CONN_RELAY; /* for temporary ease in debugging */
addr->u.ip_relay.ipAddr = 0L; /* force 'em to set it */
addr->u.ip_relay.port = 10999;
{
char* name = "eehouse.org";
XP_MEMCPY( addr->u.ip_relay.hostName, name, XP_STRLEN(name)+1 );
}
addr->u.ip_relay.cookie[0] = '\0';
#elif defined PLATFORM_PALM
/* default values; default is still IR where there's a choice, at least on
Palm... */
addr->conType = COMMS_CONN_IR;
#else
addr->conType = COMMS_CONN_BT;
#endif
} /* comms_getInitialAddr */
CommsConnType
comms_getConType( const CommsCtxt* comms )
{
return comms->addr.conType;
} /* comms_getConType */
XP_Bool
comms_getIsServer( const CommsCtxt* comms )
{
XP_ASSERT( !!comms );
return comms->isServer;
}
static XP_S16
sendWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec,
XP_PlayerAddr channelNo, XWStreamCtxt* stream )
{
XP_U16 headerLen;
XP_U16 streamSize = NULL == stream? 0 : stream_getSize( stream );
MsgID lastMsgRcd = (!!rec)? rec->lastMsgReceived : 0;
MsgQueueElem* newMsgElem;
XWStreamCtxt* msgStream;
#ifdef DEBUG
if ( !!rec ) {
rec->nUniqueBytes += streamSize;
} else {
comms->nUniqueBytes += streamSize;
}
#endif
newMsgElem = (MsgQueueElem*)XP_MALLOC( comms->mpool,
sizeof( *newMsgElem ) );
newMsgElem->channelNo = channelNo;
newMsgElem->msgID = msgID;
msgStream = mem_stream_make( MPPARM(comms->mpool)
util_getVTManager(comms->util),
NULL, 0,
(MemStreamCloseCallback)NULL );
stream_open( msgStream );
stream_putU32( msgStream, comms->connID );
stream_putU16( msgStream, channelNo );
stream_putU32( msgStream, msgID );
stream_putU32( msgStream, lastMsgRcd );
headerLen = stream_getSize( msgStream );
newMsgElem->len = streamSize + headerLen;
newMsgElem->msg = (XP_U8*)XP_MALLOC( comms->mpool, newMsgElem->len );
stream_getBytes( msgStream, newMsgElem->msg, headerLen );
stream_destroy( msgStream );
if ( 0 < streamSize ) {
stream_getBytes( stream, newMsgElem->msg + headerLen, streamSize );
}
addToQueue( comms, newMsgElem );
return sendMsg( comms, newMsgElem );
} /* sendWithID */
/* Send a message using the sequentially next MsgID. Save the message so
* resend can work. */
XP_S16
comms_send( CommsCtxt* comms, XWStreamCtxt* stream )
{
XP_PlayerAddr channelNo = stream_getAddress( stream );
AddressRecord* rec = getRecordFor( comms, channelNo );
MsgID msgID = (!!rec)? ++rec->nextMsgID : 0;
XP_DEBUGF( "assigning msgID=" XP_LD " on chnl %d", msgID, channelNo );
return sendWithID( comms, msgID, rec, channelNo, stream );
} /* comms_send */
/* Add new message to the end of the list. The list needs to be kept in order
* by ascending msgIDs within each channel since if there's a resend that's
* the order in which they need to be sent.
*/
static void
addToQueue( CommsCtxt* comms, MsgQueueElem* newMsgElem )
{
newMsgElem->next = (MsgQueueElem*)NULL;
if ( !comms->msgQueueHead ) {
comms->msgQueueHead = comms->msgQueueTail = newMsgElem;
XP_ASSERT( comms->queueLen == 0 );
comms->queueLen = 1;
} else {
XP_ASSERT( !!comms->msgQueueTail );
comms->msgQueueTail->next = newMsgElem;
comms->msgQueueTail = newMsgElem;
XP_ASSERT( comms->queueLen > 0 );
++comms->queueLen;
}
XP_STATUSF( "addToQueue: queueLen now %d", comms->queueLen );
} /* addToQueue */
#ifdef DEBUG
static void
printQueue( CommsCtxt* comms )
{
MsgQueueElem* elem;
short i;
for ( elem = comms->msgQueueHead, i = 0; i < comms->queueLen;
elem = elem->next, ++i ) {
XP_STATUSF( "\t%d: channel: %d; msgID=" XP_LD,
i+1, elem->channelNo, elem->msgID );
}
}
#endif
/* We've received on some channel a message with a certain ID. This means
* that all messages sent on that channel with lower IDs have been received
* and can be removed from our queue. BUT: if this ID is higher than any
* we've sent, don't remove. We may be starting a new game but have a server
* that's still on the old one.
*/
static void
removeFromQueue( CommsCtxt* comms, XP_PlayerAddr channelNo, MsgID msgID )
{
XP_STATUSF( "%s: remove msgs <= " XP_LD " for channel %d (queueLen: %d)",
__func__, msgID, channelNo, comms->queueLen );
if ( (channelNo == 0) || !!getRecordFor(comms, channelNo) ) {
MsgQueueElem dummy;
MsgQueueElem* keep = &dummy;
MsgQueueElem* elem;
MsgQueueElem* next;
for ( elem = comms->msgQueueHead; !!elem; elem = next ) {
XP_Bool knownGood = XP_FALSE;
next = elem->next;
/* remove the 0-channel message if we've established a channel
number. Only clients should have any 0-channel messages in the
queue, and receiving something from the server is an implicit
ACK -- IFF it isn't left over from the last game. */
if ( (elem->channelNo == 0) && (channelNo != 0) ) {
XP_ASSERT( !comms->isServer );
XP_ASSERT( elem->msgID == 0 );
} else if ( elem->channelNo != channelNo ) {
knownGood = XP_TRUE;
}
if ( !knownGood && (elem->msgID <= msgID) ) {
XP_FREE( comms->mpool, elem->msg );
XP_FREE( comms->mpool, elem );
--comms->queueLen;
} else {
keep->next = elem;
keep = elem;
}
}
keep->next = NULL;
comms->msgQueueHead = dummy.next;
}
XP_STATUSF( "%s: queueLen now %d", __func__, comms->queueLen );
XP_ASSERT( comms->queueLen > 0 || comms->msgQueueHead == NULL );
#ifdef DEBUG
printQueue( comms );
#endif
} /* removeFromQueue */
static XP_S16
sendMsg( CommsCtxt* comms, MsgQueueElem* elem )
{
XP_S16 result = 0;
XP_PlayerAddr channelNo;
#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH
CommsConnType conType = comms_getConType( comms );
#endif
channelNo = elem->channelNo;
if ( 0 ) {
#ifdef XWFEATURE_RELAY
} else if ( conType == COMMS_CONN_RELAY ) {
if ( comms->r.relayState == COMMS_RELAYSTATE_ALLCONNECTED ) {
XWHostID destID = getDestID( comms, channelNo );
result = send_via_relay( comms, XWRELAY_MSG_TORELAY, destID,
elem->msg, elem->len );
} else {
XP_LOGF( "%s: skipping message: not connected", __func__ );
}
#endif
#ifdef XWFEATURE_BLUETOOTH
} else if ( conType == COMMS_CONN_BT ) {
result = send_via_bt( comms, BTMSG_DATA, channelNo,
elem->msg, elem->len );
#endif
} else {
const CommsAddrRec* addr;
(void)channelToAddress( comms, channelNo, &addr );
XP_ASSERT( !!comms->sendproc );
result = (*comms->sendproc)( elem->msg, elem->len, addr,
comms->sendClosure );
}
return result;
} /* sendMsg */
XP_S16
comms_resendAll( CommsCtxt* comms )
{
MsgQueueElem* msg;
XP_S16 result = 0;
XP_ASSERT( !!comms );
for ( msg = comms->msgQueueHead; !!msg; msg = msg->next ) {
XP_S16 oneResult = sendMsg( comms, msg );
if ( result == 0 && oneResult != 0 ) {
result = oneResult;
}
XP_STATUSF( "resend: msgID=" XP_LD "; rslt=%d",
msg->msgID, oneResult );
}
return result;
} /* comms_resend */
#ifdef XWFEATURE_RELAY
static XP_Bool
relayPreProcess( CommsCtxt* comms, XWStreamCtxt* stream, XWHostID* senderID )
{
XP_Bool consumed = XP_TRUE;
XWHostID destID, srcID;
CookieID cookieID;
XP_U8 relayErr;
XP_U8 hasName;
/* nothing for us to do here if not using relay */
XWRELAY_Cmd cmd = stream_getU8( stream );
switch( cmd ) {
case XWRELAY_CONNECT_RESP:
case XWRELAY_RECONNECT_RESP:
comms->r.relayState = COMMS_RELAYSTATE_CONNECTED;
comms->r.heartbeat = stream_getU16( stream );
comms->r.cookieID = stream_getU16( stream );
comms->r.myHostID = (XWHostID)stream_getU8( stream );
XP_LOGF( "got XWRELAY_CONNECTRESP; set cookieID = %d; "
"set hostid: %x",
comms->r.cookieID, comms->r.myHostID );
setHeartbeatTimer( comms );
break;
case XWRELAY_ALLHERE:
comms->r.relayState = COMMS_RELAYSTATE_ALLCONNECTED;
hasName = stream_getU8( stream );
if ( hasName ) {
stringFromStreamHere( stream, comms->r.connName,
sizeof(comms->r.connName) );
XP_LOGF( "read connName: %s", comms->r.connName );
} else {
XP_ASSERT( comms->r.connName[0] != '\0' );
}
/* We're [re-]connected now. Send any pending messages. This may
need to be done later since we're inside the platform's socket
read proc now. */
comms_resendAll( comms );
break;
case XWRELAY_MSG_FROMRELAY:
cookieID = stream_getU16( stream );
srcID = stream_getU8( stream );
destID = stream_getU8( stream );
XP_LOGF( "cookieID: %d; srcID: %x; destID: %x",
cookieID, srcID, destID );
/* If these values don't check out, drop it */
consumed = cookieID != comms->r.cookieID
|| destID != comms->r.myHostID;
if ( consumed ) {
XP_LOGF( "rejecting data message" );
} else {
*senderID = srcID;
}
break;
case XWRELAY_DISCONNECT_OTHER:
relayErr = stream_getU8( stream );
srcID = stream_getU8( stream );
XP_LOGF( "host id %x disconnected", srcID );
/* we will eventually want to tell the user which player's gone */
util_userError( comms->util, ERR_RELAY_BASE + relayErr );
break;
case XWRELAY_DISCONNECT_YOU: /* Close socket for this? */
case XWRELAY_CONNECTDENIED: /* Close socket for this? */
XP_LOGF( "XWRELAY_DISCONNECT_YOU|XWRELAY_CONNECTDENIED" );
relayErr = stream_getU8( stream );
util_userError( comms->util, ERR_RELAY_BASE + relayErr );
comms->r.relayState = COMMS_RELAYSTATE_UNCONNECTED;
/* fallthru */
default:
XP_LOGF( "dropping relay msg with cmd %d", (XP_U16)cmd );
}
return consumed;
} /* relayPreProcess */
#endif
#ifdef XWFEATURE_BLUETOOTH
static XP_Bool
btPreProcess( CommsCtxt* comms, XWStreamCtxt* stream )
{
BTMsgType typ = (BTMsgType)stream_getU8( stream );
XP_Bool consumed = typ != BTMSG_DATA;
if ( consumed ) {
XP_ASSERT( typ == BTMSG_RESET );
(void)comms_resendAll( comms );
}
return consumed;
} /* btPreProcess */
#endif
static XP_Bool
addressUnknown( CommsCtxt* comms, const CommsAddrRec* addr )
{
XP_Bool unknown = XP_TRUE;
if ( !!addr ) {
CommsConnType conType = addr->conType;
const AddressRecord* rec;
for ( rec = comms->recs; !!rec && unknown ; rec = rec->next ) {
XP_ASSERT( conType == rec->addr.conType );
switch( conType ) {
case COMMS_CONN_RELAY:
if ( (addr->u.ip_relay.ipAddr == rec->addr.u.ip_relay.ipAddr)
&& (addr->u.ip_relay.port == rec->addr.u.ip_relay.port ) ) {
unknown = XP_FALSE;
}
break;
case COMMS_CONN_BT:
if ( 0 == XP_MEMCMP( &addr->u.bt.btAddr, &rec->addr.u.bt.btAddr,
sizeof(addr->u.bt.btAddr) ) ) {
unknown = XP_FALSE;
}
break;
case COMMS_CONN_IR: /* no way to test */
default:
break;
}
}
}
return unknown;
} /* addressUnknown */
/* read a raw buffer into a stream, stripping off the headers and keeping
* any necessary stats.
*
* Keep track of return addresses by channel. If the message's channel number
* is 0, assign a new channel number and associate an address with it.
* Otherwise update the address, which may have changed since we last heard
* from this channel.
*
* There may be messages that are only about the comms 'protocol', that
* contain nothing to be passed to the server. In that case, return false
* indicating the caller that all processing is finished.
*
* conType tells both how to interpret the addr and whether to expect any
* special fields in the message itself. In the IP case, for example, the
* port component of a return address is in the message but the IP address
* component will be passed in.
*/
XP_Bool
comms_checkIncomingStream( CommsCtxt* comms, XWStreamCtxt* stream,
const CommsAddrRec* addr )
{
XP_U16 channelNo;
XP_U32 connID;
MsgID msgID;
MsgID lastMsgRcd;
XP_Bool validMessage = XP_FALSE;
AddressRecord* recs = (AddressRecord*)NULL;
XWHostID senderID = 0; /* unset; default for non-relay cases */
XP_Bool usingRelay = XP_FALSE;
XP_Bool channelWas0 = XP_FALSE;
XP_Bool done = XP_FALSE;
XP_ASSERT( addr == NULL || comms->addr.conType == addr->conType );
if ( 0 ) {
#ifdef XWFEATURE_RELAY
/* relayPreProcess returns true if consumes the message. May just eat the
header and leave a regular message to be processed below. */
} else if ( comms->addr.conType == COMMS_CONN_RELAY ) {
done = relayPreProcess( comms, stream, &senderID );
if ( !done ) {
usingRelay = comms->addr.conType == COMMS_CONN_RELAY;
}
#endif
#ifdef XWFEATURE_BLUETOOTH
} else if ( comms->addr.conType == COMMS_CONN_BT ) {
done = btPreProcess( comms, stream );
#endif
}
if ( !done ) {
if ( stream_getSize( stream ) >= sizeof(connID) ) {
connID = stream_getU32( stream );
XP_STATUSF( "%s: read connID of %lx", __func__, connID );
if ( comms->connID == connID || comms->connID == CONN_ID_NONE ) {
if ( stream_getSize( stream ) >= sizeof(channelNo)
+ sizeof(msgID) + sizeof(lastMsgRcd) ) {
channelNo = stream_getU16( stream );
XP_STATUSF( "read channelNo %d", channelNo );
msgID = stream_getU32( stream );
lastMsgRcd = stream_getU32( stream );
XP_DEBUGF( "rcd: msgID=" XP_LD " on chnl %d", msgID,
channelNo );
removeFromQueue( comms, channelNo, lastMsgRcd );
/* Problem: need to detect duplicate messages even before
the server's had a chance to assign channels.
Solution, which is a hack: since hostID does the same
thing, use it in the relay case. But in the relay-less
case, which still needs to work, do assign channels.
The dup message problem is far less common there. */
if ( channelNo != 0 ) {
validMessage = XP_TRUE;
} else {
/* If we've seen a channel0 msg from same addr, drop
it. It's most likely a dupe, and server can't
handle e.g. duplicate client reg messages.. */
validMessage = addressUnknown( comms, addr );
if ( validMessage ) {
XP_ASSERT( comms->isServer );
if ( usingRelay ) {
XP_ASSERT( senderID != 0 );
channelNo = senderID;
} else {
XP_ASSERT( msgID == 0 );
/* check that address isn't already associated
with an existing channel. */
channelNo = ++comms->nextChannelNo;
XP_LOGF( "%s: incrementled nextChannelNo "
"to %d", __FUNCTION__,
comms->nextChannelNo );
channelWas0 = XP_TRUE;
}
XP_STATUSF( "assigning channelNo=%d", channelNo );
}
}
if ( validMessage && (usingRelay || !channelWas0) ) {
recs = getRecordFor( comms, channelNo );
/* messageID for an incoming message should be one
* greater than the id most recently used for that
* channel. */
if ( !!recs ) {
#ifdef COMMS_HEARTBEAT
/* Good for heartbeat even if not "valid." In
fact, all HB messages are invalid by design. */
recs->lastMsgRcvdTime = util_getCurSeconds( comms->util );
#endif
if ( msgID != recs->lastMsgReceived + 1 ) {
XP_DEBUGF( "on channel %d, msgID=" XP_LD
" (next should be " XP_LD ")",
channelNo, msgID,
recs->lastMsgReceived+1 );
validMessage = XP_FALSE;
}
} else if ( msgID > 1 ) {
validMessage = XP_FALSE;
}
#ifdef DEBUG
if ( !!recs ) {
XP_ASSERT( lastMsgRcd <= recs->nextMsgID );
if ( lastMsgRcd > recs->nextMsgID ) {
XP_LOGF( "bad: got lastMsgRcd of %ld, "
"nextMsgID is %ld",
lastMsgRcd, recs->nextMsgID );
}
XP_ASSERT( lastMsgRcd < 0x0000FFFF );
recs->lastACK = (XP_U16)lastMsgRcd;
}
#endif
}
if ( validMessage ) {
XP_LOGF( "remembering senderID %x for channel %d",
senderID, channelNo );
recs = rememberChannelAddress( comms, channelNo,
senderID, addr );
stream_setAddress( stream, channelNo );
if ( !!recs ) {
recs->lastMsgReceived = msgID;
XP_STATUSF( "set channel %d's lastMsgReceived to "
XP_LD, channelNo, msgID );
}
}
} else {
XP_LOGF( "%s: message too small", __FUNCTION__ );
}
} else {
XP_STATUSF( "refusing non-matching connID; got %lx, "
"wanted %lx", connID, comms->connID );
}
} else {
XP_LOGF( "%s: message too small", __FUNCTION__ );
}
}
LOG_RETURNF( "%d", (XP_U16)validMessage );
return validMessage;
} /* comms_checkIncomingStream */
#ifdef COMMS_HEARTBEAT
/* Heartbeat.
*
* Goal is to allow all participants to detect when another is gone quickly.
* Assumption is that transport is cheap: sending extra packets doesn't cost
* much money or bother (meaning: don't do this over IR! :-).
*
* Keep track of last time we heard from each channel and of when we last sent
* a packet. Run a timer, and when it fires: 1) check if we haven't heard
* since 2x the timer interval. If so, call alert function and reset the
* underlying (ip, bt) channel. If not, check how long since we last sent a
* packet on each channel. If it's been longer than since the last timer, and
* if there are not already packets in the queue on that channel, fire a HB
* packet.
*
* A HB packet is one whose msg ID is lower than the most recent ACK'd so that
* it's sure to be dropped on the other end and not to interfere with packets
* that might be resent.
*/
static void
heartbeat_checks( CommsCtxt* comms )
{
XP_U32 now, tooLongAgo;
XP_U16 pendingPacks[MAX_NUM_PLAYERS] = { 0 };
AddressRecord* rec;
const MsgQueueElem* elem;
XP_Bool resetTimer = XP_FALSE;
LOG_FUNC();
for ( elem = comms->msgQueueHead; !!elem; elem = elem->next ) {
XP_ASSERT( elem->channelNo < MAX_NUM_PLAYERS );
++pendingPacks[elem->channelNo];
}
now = util_getCurSeconds( comms->util );
tooLongAgo = now - (HB_INTERVAL * 2);
for ( rec = comms->recs; !!rec; rec = rec->next ) {
XP_U32 lastMsgRcvdTime = rec->lastMsgRcvdTime;
if ( lastMsgRcvdTime == 0 ) { /* nothing received yet */
/* do nothing; or should we send? */
} else if ( (lastMsgRcvdTime > 0) && (lastMsgRcvdTime < tooLongAgo) ) {
XP_LOGF( "calling reset proc" );
(*comms->resetproc)(comms->sendClosure);
resetTimer = XP_FALSE;
break;
} else if ( 0 == pendingPacks[rec->channelNo] ) {
XP_LOGF( "sending heartbeat on channel %d", rec->channelNo );
sendWithID( comms, rec->lastMsgReceived, rec, rec->channelNo, NULL );
} else {
XP_LOGF( "All's well" );
resetTimer = XP_TRUE;
}
}
if ( resetTimer ) {
setHeartbeatTimer( comms );
}
} /* heartbeat_checks */
#endif
#if defined RELAY_HEARTBEAT || defined COMMS_HEARTBEAT
static void
p_comms_timerFired( void* closure, XWTimerReason XP_UNUSED_DBG(why) )
{
CommsCtxt* comms = (CommsCtxt*)closure;
XP_ASSERT( why == TIMER_HEARTBEAT );
XP_LOGF( "comms_timerFired" );
if (0 ) {
#ifdef RELAY_HEARTBEAT
} else if ( (comms->addr.conType == COMMS_CONN_RELAY )
&& (comms->r.heartbeat != HEARTBEAT_NONE) ) {
send_via_relay( comms, XWRELAY_HEARTBEAT, HOST_ID_NONE, NULL, 0 );
/* No need to reset timer. send_via_relay does that. */
#elif defined COMMS_HEARTBEAT
} else {
XP_ASSERT( comms->doHeartbeat );
heartbeat_checks( comms );
#endif
}
} /* p_comms_timerFired */
static void
setHeartbeatTimer( CommsCtxt* comms )
{
#ifdef RELAY_HEARTBEAT
if ( comms->addr.conType == COMMS_CONN_RELAY ) {
util_setTimer( comms->util, TIMER_HEARTBEAT, comms->r.heartbeat,
p_comms_timerFired, comms );
}
#elif defined COMMS_HEARTBEAT
if ( comms->doHeartbeat ) {
util_setTimer( comms->util, TIMER_HEARTBEAT, HB_INTERVAL,
p_comms_timerFired, comms );
}
#endif
} /* setHeartbeatTimer */
#endif
#ifdef DEBUG
void
comms_getStats( CommsCtxt* comms, XWStreamCtxt* stream )
{
XP_UCHAR buf[100];
AddressRecord* rec;
MsgQueueElem* elem;
XP_U32 now;
XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf),
(XP_UCHAR*)"msg queue len: %d\n", comms->queueLen );
stream_putString( stream, buf );
for ( elem = comms->msgQueueHead; !!elem; elem = elem->next ) {
XP_SNPRINTF( buf, sizeof(buf),
" - channelNo=%d; msgID=" XP_LD "; len=%d\n",
elem->channelNo, elem->msgID, elem->len );
stream_putString( stream, buf );
}
XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf),
(XP_UCHAR*)"channel-less bytes sent: %d\n",
comms->nUniqueBytes );
stream_putString( stream, buf );
now = util_getCurSeconds( comms->util );
for ( rec = comms->recs; !!rec; rec = rec->next ) {
XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf),
(XP_UCHAR*)" Stats for channel: %d\n",
rec->channelNo );
stream_putString( stream, buf );
XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf),
(XP_UCHAR*)"Last msg sent: " XP_LD "\n",
rec->nextMsgID );
stream_putString( stream, buf );
XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf),
(XP_UCHAR*)"Unique bytes sent: %d\n",
rec->nUniqueBytes );
stream_putString( stream, buf );
XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf),
(XP_UCHAR*)"Last message acknowledged: %d\n",
rec->lastACK);
stream_putString( stream, buf );
#ifdef COMMS_HEARTBEAT
XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf),
(XP_UCHAR*)"Last ack'd %ld secs ago\n",
now - rec->lastMsgRcvdTime );
stream_putString( stream, buf );
#endif
}
} /* comms_getStats */
#endif
static AddressRecord*
rememberChannelAddress( CommsCtxt* comms, XP_PlayerAddr channelNo,
XWHostID hostID, const CommsAddrRec* addr )
{
AddressRecord* recs = NULL;
recs = getRecordFor( comms, channelNo );
if ( !recs ) {
/* not found; add a new entry */
recs = (AddressRecord*)XP_MALLOC( comms->mpool, sizeof(*recs) );
recs->nextMsgID = 0;
recs->channelNo = channelNo;
recs->r.hostID = hostID;
#ifdef DEBUG
recs->nUniqueBytes = 0;
#endif
recs->next = comms->recs;
comms->recs = recs;
}
/* overwrite existing address with new one. I assume that's the right
move. */
if ( !!recs ) {
if ( !!addr ) {
XP_MEMCPY( &recs->addr, addr, sizeof(recs->addr) );
XP_ASSERT( recs->r.hostID == hostID );
} else {
XP_MEMSET( &recs->addr, 0, sizeof(recs->addr) );
recs->addr.conType = comms->addr.conType;
}
}
return recs;
} /* rememberChannelAddress */
static XP_Bool
channelToAddress( CommsCtxt* comms, XP_PlayerAddr channelNo,
const CommsAddrRec** addr )
{
AddressRecord* recs = getRecordFor( comms, channelNo );
XP_Bool found = !!recs;
*addr = found? &recs->addr : NULL;
return found;
} /* channelToAddress */
static AddressRecord*
getRecordFor( CommsCtxt* comms, XP_PlayerAddr channelNo )
{
AddressRecord* recs = NULL;
if ( channelNo != CHANNEL_NONE ) {
for ( recs = comms->recs; !!recs; recs = recs->next ) {
if ( recs->channelNo == channelNo ) {
break;
}
}
}
return recs;
} /* getRecordFor */
static XP_U16
countAddrRecs( const CommsCtxt* comms )
{
short count = 0;
AddressRecord* recs;
for ( recs = comms->recs; !!recs; recs = recs->next ) {
++count;
}
return count;
} /* countAddrRecs */
#ifdef XWFEATURE_RELAY
static XWHostID
getDestID( CommsCtxt* comms, XP_PlayerAddr channelNo )
{
XWHostID id = HOST_ID_NONE;
if ( channelNo == CHANNEL_NONE ) {
id = HOST_ID_SERVER;
} else {
AddressRecord* recs;
for ( recs = comms->recs; !!recs; recs = recs->next ) {
if ( recs->channelNo == channelNo ) {
id = recs->r.hostID;
}
}
}
XP_LOGF( "getDestID(%d) => %x", channelNo, id );
return id;
} /* getDestID */
static XP_Bool
send_via_relay( CommsCtxt* comms, XWRELAY_Cmd cmd, XWHostID destID,
void* data, int dlen )
{
XP_Bool success = XP_FALSE;
XP_U16 len = 0;
CommsAddrRec addr;
XWStreamCtxt* tmpStream;
XP_U8* buf;
comms_getAddr( comms, &addr );
tmpStream = mem_stream_make( MPPARM(comms->mpool)
util_getVTManager(comms->util),
NULL, 0,
(MemStreamCloseCallback)NULL );
if ( tmpStream != NULL ) {
stream_open( tmpStream );
stream_putU8( tmpStream, cmd );
switch ( cmd ) {
case XWRELAY_MSG_TORELAY:
stream_putU16( tmpStream, comms->r.cookieID );
stream_putU8( tmpStream, comms->r.myHostID );
stream_putU8( tmpStream, destID );
if ( data != NULL && dlen > 0 ) {
stream_putBytes( tmpStream, data, dlen );
}
break;
case XWRELAY_GAME_CONNECT:
stream_putU8( tmpStream, XWRELAY_PROTO_VERSION );
stringToStream( tmpStream, addr.u.ip_relay.cookie );
stream_putU8( tmpStream, comms->r.myHostID );
stream_putU8( tmpStream, comms->r.nPlayersHere );
stream_putU8( tmpStream, comms->r.nPlayersTotal );
comms->r.relayState = COMMS_RELAYSTATE_CONNECT_PENDING;
break;
case XWRELAY_GAME_RECONNECT:
stream_putU8( tmpStream, XWRELAY_PROTO_VERSION );
stream_putU8( tmpStream, comms->r.myHostID );
stream_putU8( tmpStream, comms->r.nPlayersHere );
stream_putU8( tmpStream, comms->r.nPlayersTotal );
stringToStream( tmpStream, comms->r.connName );
comms->r.relayState = COMMS_RELAYSTATE_CONNECT_PENDING;
break;
case XWRELAY_GAME_DISCONNECT:
stream_putU16( tmpStream, comms->r.cookieID );
stream_putU8( tmpStream, comms->r.myHostID );
break;
#ifdef RELAY_HEARTBEAT
case XWRELAY_HEARTBEAT:
/* Add these for grins. Server can assert they match the IP
address it expects 'em on. */
stream_putU16( tmpStream, comms->r.cookieID );
stream_putU8( tmpStream, comms->r.myHostID );
break;
#endif
default:
XP_ASSERT(0);
}
len = stream_getSize( tmpStream );
buf = XP_MALLOC( comms->mpool, len );
if ( buf != NULL ) {
stream_getBytes( tmpStream, buf, len );
}
stream_destroy( tmpStream );
if ( buf != NULL ) {
XP_U16 result;
XP_LOGF( "passing %d bytes to sendproc", len );
result = (*comms->sendproc)( buf, len, &addr, comms->sendClosure );
success = result == len;
if ( success ) {
setHeartbeatTimer( comms );
}
}
XP_FREE( comms->mpool, buf );
}
return success;
} /* send_via_relay */
/* Send a CONNECT message to the relay. This opens up a connection to the
* relay, and tells it our hostID and cookie so that it can associatate it
* with a socket. In the CONNECT_RESP we should get back what?
*/
static void
relayConnect( CommsCtxt* comms )
{
LOG_FUNC();
if ( comms->addr.conType == COMMS_CONN_RELAY && !comms->r.connecting ) {
comms->r.connecting = XP_TRUE;
send_via_relay( comms,
comms->r.connName[0] == '\0' ?
XWRELAY_GAME_CONNECT:XWRELAY_GAME_RECONNECT,
comms->r.myHostID, NULL, 0 );
comms->r.connecting = XP_FALSE;
}
} /* relayConnect */
#endif
#ifdef XWFEATURE_BLUETOOTH
static void
btConnect( CommsCtxt* comms )
{
XP_ASSERT( !!comms );
/* Ping the bt layer so it'll get sockets set up. PENDING: if I'm server
need to do this once per guest record with non-null address. Might as
well use real messages if we have 'em. Otherwise a fake size-0 msg. */
send_via_bt( comms, BTMSG_RESET, CHANNEL_NONE, NULL, 0 );
(void)( comms_resendAll( comms ) );
} /* btConnect */
static XP_S16
send_via_bt( CommsCtxt* comms, BTMsgType typ, XP_PlayerAddr channelNo,
void* data, int dlen )
{
XP_U8* buf;
XP_S16 nSent = -1;
buf = XP_MALLOC( comms->mpool, dlen + 1 );
if ( !!buf ) {
const CommsAddrRec* addr;
(void)channelToAddress( comms, channelNo, &addr );
buf[0] = typ;
if ( dlen > 0 ) {
XP_MEMCPY( &buf[1], data, dlen );
}
nSent = (*comms->sendproc)( buf, dlen+1, addr, comms->sendClosure );
XP_FREE( comms->mpool, buf );
setHeartbeatTimer( comms );
}
return nSent;
} /* send_via_bt */
#endif
#ifdef XWFEATURE_RELAY
static void
relayDisconnect( CommsCtxt* comms )
{
XP_LOGF( "relayDisconnect called" );
if ( comms->addr.conType == COMMS_CONN_RELAY ) {
if ( comms->r.relayState != COMMS_RELAYSTATE_UNCONNECTED ) {
comms->r.relayState = COMMS_RELAYSTATE_UNCONNECTED;
send_via_relay( comms, XWRELAY_GAME_DISCONNECT, HOST_ID_NONE,
NULL, 0 );
}
}
} /* relayDisconnect */
#endif
EXTERN_C_END
#endif /* #ifndef XWFEATURE_STANDALONE_ONLY */