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.
This commit is contained in:
ehouse 2007-11-18 23:43:27 +00:00
parent a91056bed2
commit 321acd1d42
17 changed files with 356 additions and 72 deletions

View file

@ -35,6 +35,17 @@
#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 {
@ -54,6 +65,8 @@ typedef struct AddressRecord {
#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 */
@ -78,12 +91,19 @@ struct CommsCtxt {
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.*/
@ -143,6 +163,8 @@ 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
@ -159,7 +181,8 @@ CommsCtxt*
comms_make( MPFORMAL XW_UtilCtxt* util, XP_Bool isServer,
XP_U16 XP_UNUSED_RELAY(nPlayersHere),
XP_U16 XP_UNUSED_RELAY(nPlayersTotal),
TransportSend sendproc, void* closure )
TransportSend sendproc, IF_CH(TransportReset resetproc)
void* closure )
{
CommsCtxt* result = (CommsCtxt*)XP_MALLOC( mpool, sizeof(*result) );
XP_MEMSET( result, 0, sizeof(*result) );
@ -168,6 +191,9 @@ comms_make( MPFORMAL XW_UtilCtxt* util, XP_Bool isServer,
result->isServer = isServer;
result->sendproc = sendproc;
#ifdef COMMS_HEARTBEAT
result->resetproc = resetproc;
#endif
result->sendClosure = closure;
result->util = util;
@ -297,7 +323,8 @@ addrFromStream( CommsAddrRec* addrP, XWStreamCtxt* stream )
CommsCtxt*
comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util,
TransportSend sendproc, void* closure )
TransportSend sendproc,
IF_CH(TransportReset resetproc ) void* closure )
{
CommsCtxt* comms;
XP_Bool isServer;
@ -325,7 +352,7 @@ comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util,
}
comms = comms_make( MPPARM(mpool) util, isServer,
nPlayersHere, nPlayersTotal,
sendproc, closure );
sendproc, IF_CH(resetproc) closure );
XP_MEMCPY( &comms->addr, &addr, sizeof(comms->addr) );
comms->connID = stream_getU32( stream );
@ -403,6 +430,10 @@ comms_start( CommsCtxt* comms )
btConnect( comms );
#endif
}
#ifdef COMMS_HEARTBEAT
comms->doHeartbeat = comms->addr.conType != COMMS_CONN_IR;
#endif
} /* comms_start */
static void
@ -531,6 +562,9 @@ comms_setAddr( CommsCtxt* comms, const CommsAddrRec* addr )
btConnect( comms );
#endif
}
#ifdef COMMS_HEARTBEAT
comms->doHeartbeat = comms->addr.conType != COMMS_CONN_IR;
#endif
} /* comms_setAddr */
void
@ -567,22 +601,16 @@ comms_getIsServer( const CommsCtxt* comms )
return comms->isServer;
}
/* Send a message using the sequentially next MsgID. Save the message so
* resend can work. */
XP_S16
comms_send( CommsCtxt* comms, XWStreamCtxt* stream )
static XP_S16
sendWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec,
XP_PlayerAddr channelNo, XWStreamCtxt* stream )
{
XP_U16 streamSize = stream_getSize( stream );
XP_U16 headerLen;
XP_PlayerAddr channelNo = stream_getAddress( stream );
AddressRecord* rec = getRecordFor( comms, channelNo );
MsgID msgID = (!!rec)? ++rec->nextMsgID : 0;
XP_U16 streamSize = NULL == stream? 0 : stream_getSize( stream );
MsgID lastMsgRcd = (!!rec)? rec->lastMsgReceived : 0;
MsgQueueElem* newMsgElem;
XWStreamCtxt* msgStream;
XP_DEBUGF( "assigning msgID=" XP_LD " on chnl %d", msgID, channelNo );
#ifdef DEBUG
if ( !!rec ) {
rec->nUniqueBytes += streamSize;
@ -614,11 +642,26 @@ comms_send( CommsCtxt* comms, XWStreamCtxt* stream )
stream_getBytes( msgStream, newMsgElem->msg, headerLen );
stream_destroy( msgStream );
stream_getBytes( stream, newMsgElem->msg + headerLen, streamSize );
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
@ -1013,6 +1056,11 @@ comms_checkIncomingStream( CommsCtxt* comms, XWStreamCtxt* stream,
* 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 ")",
@ -1047,9 +1095,9 @@ comms_checkIncomingStream( CommsCtxt* comms, XWStreamCtxt* stream,
if ( !!recs ) {
recs->lastMsgReceived = msgID;
XP_STATUSF( "set channel %d's lastMsgReceived to "
XP_LD, channelNo, msgID );
}
XP_STATUSF( "set channel %d's lastMsgReceived to "
XP_LD, channelNo, msgID );
}
} else {
XP_LOGF( "%s: message too small", __FUNCTION__ );
@ -1066,25 +1114,103 @@ comms_checkIncomingStream( CommsCtxt* comms, XWStreamCtxt* stream,
return validMessage;
} /* comms_checkIncomingStream */
#ifdef XWFEATURE_RELAY
#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 ( comms->r.heartbeat != HEARTBEAT_NONE ) {
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
}
} /* comms_timerFired */
} /* p_comms_timerFired */
static void
setHeartbeatTimer( CommsCtxt* comms )
{
util_setTimer( comms->util, TIMER_HEARTBEAT, comms->r.heartbeat,
p_comms_timerFired, 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
@ -1094,6 +1220,7 @@ 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 );
@ -1111,6 +1238,7 @@ comms_getStats( CommsCtxt* comms, XWStreamCtxt* stream )
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",
@ -1131,6 +1259,13 @@ comms_getStats( CommsCtxt* comms, XWStreamCtxt* stream )
(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
@ -1277,13 +1412,14 @@ send_via_relay( CommsCtxt* comms, XWRELAY_Cmd cmd, XWHostID destID,
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);
}
@ -1359,6 +1495,8 @@ send_via_bt( CommsCtxt* comms, BTMsgType typ, XP_PlayerAddr channelNo,
nSent = (*comms->sendproc)( buf, dlen+1, addr, comms->sendClosure );
XP_FREE( comms->mpool, buf );
setHeartbeatTimer( comms );
}
return nSent;
} /* send_via_bt */

View file

@ -54,6 +54,12 @@ typedef enum {
typedef struct XP_BtAddr { XP_U8 bits[6]; } XP_BtAddr;
typedef struct XP_BtAddrStr { XP_UCHAR chars[18]; } XP_BtAddrStr;
#ifdef COMMS_HEARTBEAT
# define IF_CH(a) a,
#else
# define IF_CH(a)
#endif
#define MAX_HOSTNAME_LEN 63
typedef struct CommsAddrRec {
CommsConnType conType;
@ -85,11 +91,13 @@ typedef struct CommsAddrRec {
typedef XP_S16 (*TransportSend)( const XP_U8* buf, XP_U16 len,
const CommsAddrRec* addr,
void* closure );
typedef void (*TransportReset)( void* closure );
CommsCtxt* comms_make( MPFORMAL XW_UtilCtxt* util,
XP_Bool isServer,
XP_U16 nPlayersHere, XP_U16 nPlayersTotal,
TransportSend sendproc, void* closure );
TransportSend sendproc, IF_CH(TransportReset resetproc)
void* closure );
void comms_reset( CommsCtxt* comms, XP_Bool isServer,
XP_U16 nPlayersHere, XP_U16 nPlayersTotal );
@ -107,6 +115,7 @@ XP_Bool comms_getIsServer( const CommsCtxt* comms );
CommsCtxt* comms_makeFromStream( MPFORMAL XWStreamCtxt* stream,
XW_UtilCtxt* util, TransportSend sendproc,
IF_CH(TransportReset resetproc)
void* closure );
void comms_start( CommsCtxt* comms );
void comms_writeToStream( const CommsCtxt* comms, XWStreamCtxt* stream );

View file

@ -103,7 +103,7 @@ typedef XP_U16 XP_PlayerAddr;
typedef enum {
TIMER_PENDOWN = 1, /* ARM doesn't like ids of 0... */
TIMER_TIMERTICK,
#ifdef XWFEATURE_RELAY
#if defined RELAY_HEARTBEAT || defined COMMS_HEARTBEAT
TIMER_HEARTBEAT,
#endif

View file

@ -73,7 +73,8 @@ void
game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi,
XW_UtilCtxt* util, DrawCtx* draw,
XP_U16 gameID, CommonPrefs* cp,
TransportSend sendproc, void* closure )
TransportSend sendproc, IF_CH( TransportReset resetproc )
void* closure )
{
XP_U16 nPlayersHere, nPlayersTotal;
@ -90,7 +91,7 @@ game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi,
game->comms = comms_make( MPPARM(mpool) util,
gi->serverRole != SERVER_ISCLIENT,
nPlayersHere, nPlayersTotal,
sendproc, closure );
sendproc, IF_CH(resetproc) closure );
} else {
game->comms = (CommsCtxt*)NULL;
}
@ -112,7 +113,7 @@ game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi,
void
game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, XW_UtilCtxt* util,
XP_U16 gameID, CommonPrefs* cp, TransportSend sendproc,
void* closure )
IF_CH(TransportReset resetproc) void* closure )
{
XP_U16 i;
XP_U16 nPlayersHere, nPlayersTotal;
@ -136,7 +137,7 @@ game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, XW_UtilCtxt* util,
game->comms = comms_make( MPPARM(mpool) util,
gi->serverRole != SERVER_ISCLIENT,
nPlayersHere, nPlayersTotal,
sendproc, closure );
sendproc, IF_CH(resetproc) closure );
}
#endif
@ -162,7 +163,8 @@ XP_Bool
game_makeFromStream( MPFORMAL XWStreamCtxt* stream, XWGame* game,
CurGameInfo* gi, DictionaryCtxt* dict,
XW_UtilCtxt* util, DrawCtx* draw, CommonPrefs* cp,
TransportSend sendProc, void* closure )
TransportSend sendProc, IF_CH(TransportReset resetProc)
void* closure )
{
XP_Bool success = XP_FALSE;
XP_U8 strVersion;
@ -181,7 +183,7 @@ game_makeFromStream( MPFORMAL XWStreamCtxt* stream, XWGame* game,
hasComms = stream_getU8( stream );
if ( hasComms ) {
game->comms = comms_makeFromStream( MPPARM(mpool) stream, util,
sendProc, closure );
sendProc, IF_CH(resetProc) closure );
} else {
game->comms = NULL;
}

View file

@ -81,16 +81,18 @@ typedef struct XWGame {
void game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi,
XW_UtilCtxt* util, DrawCtx* draw, XP_U16 gameID,
CommonPrefs* cp, TransportSend sendproc, void* closure);
CommonPrefs* cp, TransportSend sendproc,
IF_CH(TransportReset resetproc) void* closure);
void game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, XW_UtilCtxt* util,
XP_U16 gameID, CommonPrefs* cp, TransportSend sendproc,
void* closure );
IF_CH(TransportReset resetproc) void* closure );
XP_Bool game_makeFromStream( MPFORMAL XWStreamCtxt* stream, XWGame* game,
CurGameInfo* gi,
DictionaryCtxt* dict, XW_UtilCtxt* util,
DrawCtx* draw, CommonPrefs* cp,
TransportSend sendProc, void* closure );
TransportSend sendProc, IF_CH(TransportReset rp)
void* closure );
void game_saveToStream( const XWGame* game, const CurGameInfo* gi,
XWStreamCtxt* stream );

View file

@ -34,6 +34,7 @@
#define LETTER_NONE '\0'
typedef enum {
ERR_NONE, /* 0 is special case */
ERR_TILES_NOT_IN_LINE, /* scoring a move where tiles aren't in line */
ERR_NO_EMPTIES_IN_TURN,
ERR_TWO_TILES_FIRST_MOVE,

View file

@ -64,12 +64,19 @@ DEFINES += -DFEATURE_TRAY_EDIT
#DEFINES += -DDRAW_WITH_PRIMITIVES
# Bluetooth support
#BLUETOOTH = -DXWFEATURE_BLUETOOTH -DBT_USE_L2CAP
BLUETOOTH = -DXWFEATURE_BLUETOOTH -DBT_USE_RFCOMM
BLUETOOTH = -DXWFEATURE_BLUETOOTH -DBT_USE_L2CAP
#BLUETOOTH = -DXWFEATURE_BLUETOOTH -DBT_USE_RFCOMM
# DEFINES += -DXWFEATURE_IR
DEFINES += ${BLUETOOTH}
DEFINES += -DXWFEATURE_RELAY
# Choose one of these. RELAY_HEARTBEAT means relay (must be compiled
# with same -D) works with comms on heartbeat. Works only with relay.
# COMMS_HEARTBEAT should work on any comms transport (even IR, but
# user experience will be very bad!). Is particularly useful with BT.
# DEFINES += -DRELAY_HEARTBEAT
DEFINES += -DCOMMS_HEARTBEAT
# Let users pick the tiles going into their trays
#DEFINES += -DFEATURE_TRAY_EDIT
DEFINES += -DDONT_ABORT_ENGINE

View file

@ -1048,7 +1048,8 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
gameID = (XP_U16)util_getCurSeconds( globals.cGlobals.params->util );
game_makeNewGame( MEMPOOL &globals.cGlobals.game, &params->gi,
params->util, (DrawCtx*)globals.draw,
gameID, &globals.cp, linux_send, &globals );
gameID, &globals.cp, linux_send,
IF_CH(linux_reset) &globals );
if ( globals.cGlobals.game.comms ) {
CommsAddrRec addr;

View file

@ -308,7 +308,7 @@ createOrLoadObjects( GtkAppGlobals* globals )
params->dict, params->util,
(DrawCtx*)globals->draw,
&globals->cp,
linux_send, globals );
linux_send, IF_CH(linux_reset) globals );
stream_destroy( stream );
}
@ -334,7 +334,8 @@ createOrLoadObjects( GtkAppGlobals* globals )
game_makeNewGame( MEMPOOL &globals->cGlobals.game, &params->gi,
params->util, (DrawCtx*)globals->draw,
gameID, &globals->cp, linux_send, globals );
gameID, &globals->cp, linux_send,
IF_CH(linux_reset) globals );
addr.conType = params->conType;
if ( 0 ) {
@ -631,7 +632,8 @@ new_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals )
XP_STATUSF( "grabbed gameID: %ld\n", gameID );
game_reset( MEMPOOL &globals->cGlobals.game, gi,
globals->cGlobals.params->util,
gameID, &globals->cp, linux_send, globals );
gameID, &globals->cp, linux_send,
IF_CH(linux_reset) globals );
if ( isClient ) {
XWStreamCtxt* stream =
@ -1674,6 +1676,7 @@ acceptorInput( GIOChannel* source, GIOCondition condition, gpointer data )
if ( (condition & G_IO_IN) != 0 ) {
int listener = g_io_channel_unix_get_fd( source );
XP_LOGF( "%s: input on socket %d", __func__, listener );
keepSource = (*globals->acceptor)( listener, data );
} else {
keepSource = FALSE;
@ -1694,9 +1697,11 @@ gtk_socket_acceptor( int listener, Acceptor func, CommonGlobals* globals )
globals->acceptor = func;
channel = g_io_channel_unix_new( listener );
g_io_channel_set_close_on_unref( channel, TRUE );
result = g_io_add_watch( channel,
G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI,
acceptorInput, globals );
g_io_channel_unref( channel ); /* only main loop holds it now */
XP_LOGF( "%s: g_io_add_watch(%d) => %d", __FUNCTION__, listener, result );
}

View file

@ -76,19 +76,34 @@ static void
lbt_addSock( LinBtStuff* btStuff, const bdaddr_t* btaddr, int sock )
{
XP_U16 i;
XP_Bool done = XP_FALSE;
XP_ASSERT( btStuff->amMaster );
XP_ASSERT( btStuff->u.master.nSocks < MAX_CLIENTS - 1 );
/* first look for an older entry for the same device. If found, close the
socket and replace. No change in nSocks. */
for ( i = 0; i < MAX_CLIENTS; ++i ) {
BtaddrSockMap* mp = &btStuff->u.master.socks[i];
if ( mp->sock == -1 ) {
XP_MEMCPY( &mp->btaddr, btaddr, sizeof(mp->btaddr) );
if ( (mp->sock != -1) && (0 == memcmp( btaddr, &mp->btaddr, sizeof(*btaddr) )) ) {
(void)close( mp->sock );
mp->sock = sock;
++btStuff->u.master.nSocks;
done = XP_TRUE;
break;
}
}
if ( !done ) {
for ( i = 0; i < MAX_CLIENTS; ++i ) {
BtaddrSockMap* mp = &btStuff->u.master.socks[i];
if ( mp->sock == -1 ) {
XP_MEMCPY( &mp->btaddr, btaddr, sizeof(mp->btaddr) );
mp->sock = sock;
++btStuff->u.master.nSocks;
break;
}
}
}
XP_ASSERT( i < MAX_CLIENTS );
} /* lbt_addSock */
@ -98,17 +113,16 @@ lbt_removeSock( LinBtStuff* btStuff, int sock )
XP_U16 i;
XP_ASSERT( btStuff->amMaster );
XP_ASSERT( btStuff->u.master.nSocks > 0 );
for ( i = 0; i < MAX_CLIENTS; ++i ) {
BtaddrSockMap* mp = &btStuff->u.master.socks[i];
if ( mp->sock == sock ) {
mp->sock = -1;
XP_ASSERT( btStuff->u.master.nSocks > 0 );
--btStuff->u.master.nSocks;
break;
}
}
XP_ASSERT( i < MAX_CLIENTS );
} /* lbt_removeSock */
static LinBtStuff*
@ -232,7 +246,7 @@ lbt_connectSocket( LinBtStuff* btStuff, const CommsAddrRec* addrP )
(*globals->socketChanged)( globals->socketChangedClosure,
-1, sock );
} else {
XP_LOGF( "%s: connect->%s", __FUNCTION__, strerror(errno) );
XP_LOGF( "%s: connect->%s; closing socket %d", __FUNCTION__, strerror(errno), sock );
close( sock );
}
}
@ -250,11 +264,11 @@ lbt_accept( int listener, void* ctxt )
LOG_FUNC();
XP_LOGF( "%s: calling accept", __FUNCTION__ );
XP_LOGF( "%s: calling accept", __func__ );
slen = sizeof( inaddr );
XP_ASSERT( listener == btStuff->u.master.listener );
sock = accept( listener, (struct sockaddr *)&inaddr, &slen );
XP_LOGF( "%s: accept returned; sock = %d", __FUNCTION__, sock );
XP_LOGF( "%s: accept returned; socket = %d", __func__, sock );
success = sock >= 0;
if ( success ) {
@ -393,6 +407,7 @@ lbt_listenerSetup( CommonGlobals* globals )
}
#endif
XP_LOGF( "%s: calling listen on socket %d", __func__, listener );
listen( listener, MAX_CLIENTS );
lbt_register( btStuff, htobs( XW_PSM ), rc_channel );
@ -422,14 +437,36 @@ linux_bt_open( CommonGlobals* globals, XP_Bool amMaster )
}
} /* linux_bt_open */
void
linux_bt_reset( CommonGlobals* globals )
{
XP_Bool amMaster = globals->btStuff->amMaster;
LOG_FUNC();
linux_bt_close( globals );
linux_bt_open( globals, amMaster );
LOG_RETURN_VOID();
}
void
linux_bt_close( CommonGlobals* globals )
{
LinBtStuff* btStuff = globals->btStuff;
XP_U16 i;
if ( !!btStuff ) {
if ( btStuff->amMaster ) {
XP_LOGF( "%s: closing listener socket %d", __func__, btStuff->u.master.listener );
close( btStuff->u.master.listener );
btStuff->u.master.listener = -1;
for ( i = 0; i < MAX_CLIENTS; ++i ) {
BtaddrSockMap* mp = &btStuff->u.master.socks[i];
if ( mp->sock != -1 ) {
XP_LOGF( "%s: closing data socket %d", __func__, mp->sock );
(void)close( mp->sock );
}
}
sdp_close( btStuff->u.master.session );
XP_LOGF( "sleeping for Palm's sake..." );
sleep( 2 ); /* see if this gives palm a chance to not hang */

View file

@ -25,6 +25,7 @@
#include "main.h"
void linux_bt_open( CommonGlobals* globals, XP_Bool amMaster );
void linux_bt_reset( CommonGlobals* globals );
void linux_bt_close( CommonGlobals* globals );
XP_S16 linux_bt_send( const XP_U8* buf, XP_U16 buflen,

View file

@ -422,6 +422,22 @@ linux_tcp_send( const XP_U8* buf, XP_U16 buflen,
} /* linux_tcp_send */
#endif
#ifdef COMMS_HEARTBEAT
void
linux_reset( void* closure )
{
CommonGlobals* globals = (CommonGlobals*)closure;
CommsConnType conType = globals->params->conType;
if ( 0 ) {
#ifdef XWFEATURE_BLUETOOTH
} else if ( conType == COMMS_CONN_BT ) {
linux_bt_reset( globals );
#endif
}
}
#endif
XP_S16
linux_send( const XP_U8* buf, XP_U16 buflen,
const CommsAddrRec* addrRec,

View file

@ -39,6 +39,9 @@ DictionaryCtxt* linux_dictionary_make( MPFORMAL const char* dictFileName );
int initListenerSocket( int port );
XP_S16 linux_send( const XP_U8* buf, XP_U16 buflen,
const CommsAddrRec* addrRec, void* closure );
#ifdef COMMS_HEARTBEAT
void linux_reset( void* closure );
#endif
int linux_relay_receive( CommonGlobals* cGlobals, unsigned char* buf,
int bufSize );

View file

@ -116,7 +116,7 @@ MYDEFS_COMMON += -DXWFEATURE_SEARCHLIMIT
# MYDEFS_COMMON += -DXWFEATURE_RELAY
# turn on bluetooth comms option for 68K and ARM
BLUETOOTH = -DXWFEATURE_BLUETOOTH -DBT_USE_L2CAP
BLUETOOTH = -DXWFEATURE_BLUETOOTH -DBT_USE_L2CAP -DCOMMS_HEARTBEAT
#BLUETOOTH = -DXWFEATURE_BLUETOOTH -DBT_USE_RFCOMM
MYDEFS_COMMON += $(BLUETOOTH)

View file

@ -158,8 +158,8 @@ static XP_S16 pbt_enqueue( PBT_queue* queue, const XP_U8* data, XP_S16 len,
static void pbt_handoffIncoming( PalmBTStuff* btStuff, BtCbEvtProc proc );
static void waitACL( PalmBTStuff* btStuff );
static void pbt_reset( PalmBTStuff* btStuff );
static void pbt_killL2C( PalmBTStuff* btStuff, BtLibSocketRef sock );
static void pbt_reset_buffers( PalmBTStuff* btStuff );
static void pbt_killLinks( PalmBTStuff* btStuff, BtLibSocketRef sock );
static XP_Bool pbt_checkAddress( PalmBTStuff* btStuff, const CommsAddrRec* addr );
static void pbt_setstate( PalmBTStuff* btStuff, PBT_STATE newState,
const char* whence );
@ -202,7 +202,7 @@ palm_bt_init( PalmAppGlobals* globals, XP_Bool* userCancelled )
btStuff = pbt_checkInit( globals, userCancelled );
/* Should I start master/slave setup here? If not, how? */
} else {
pbt_reset( btStuff );
pbt_reset_buffers( btStuff );
}
/* If we're the master, and a new game is starting without shutting down,
@ -222,6 +222,28 @@ palm_bt_init( PalmAppGlobals* globals, XP_Bool* userCancelled )
return inited;
} /* palm_bt_init */
void
palm_bt_reset( PalmAppGlobals* globals )
{
PalmBTStuff* btStuff = globals->btStuff;
if ( !!btStuff ) {
if ( btStuff->dataSocket != SOCK_INVAL ) {
pbt_killLinks( btStuff, btStuff->dataSocket );
}
/* nuke all pending messages */
pbt_reset_buffers( btStuff );
btStuff->queueLen = 0;
if ( btStuff->picoRole == PBT_MASTER ) {
pbt_setup_master( btStuff );
} else if ( btStuff->picoRole == PBT_SLAVE ) {
CommsAddrRec remoteAddr;
comms_getAddr( globals->game.comms, &remoteAddr );
pbt_setup_slave( btStuff, &remoteAddr );
}
}
}
void
palm_bt_close( PalmAppGlobals* globals )
{
@ -431,6 +453,7 @@ pbt_send_pending( PalmBTStuff* btStuff )
#ifdef LOG_BTIO
LOG_HEX( buf, len, "to BtLibSocketSend" );
#endif
XP_LOGF( "sending on socket %d", btStuff->dataSocket );
CALL_ERR( err, BtLibSocketSend, btStuff->btLibRefNum,
btStuff->dataSocket, (char*)buf, len );
if ( btLibErrPending == err ) {
@ -578,6 +601,17 @@ pbt_close_datasocket( PalmBTStuff* btStuff )
}
}
static void
pbt_close_sdpsocket( PalmBTStuff* btStuff )
{
XP_ASSERT( PBT_SLAVE == btStuff->picoRole );
if ( SOCK_INVAL != btStuff->u.slave.sdpSocket ) {
Err err;
CALL_ERR( err, BtLibSocketClose, btStuff->btLibRefNum, btStuff->u.slave.sdpSocket );
btStuff->u.slave.sdpSocket = SOCK_INVAL;
}
}
static void
pbt_takedown_master( PalmBTStuff* btStuff )
{
@ -669,11 +703,13 @@ pbt_do_work( PalmBTStuff* btStuff, BtCbEvtProc proc )
case PBT_ACT_GETSDP:
if ( PBTST_ACL_CONNECTED == GET_STATE(btStuff) ) {
XP_ASSERT( SOCK_INVAL == btStuff->u.slave.sdpSocket );
CALL_ERR( err, BtLibSocketCreate, btStuff->btLibRefNum,
&btStuff->u.slave.sdpSocket, socketCallback, (UInt32)btStuff,
btLibSdpProtocol );
if ( err == errNone ) {
#if defined BT_USE_L2CAP
XP_LOGF( "sending on sdpSocket socket %d", btStuff->u.slave.sdpSocket );
CALL_ERR( err, BtLibSdpGetPsmByUuid, btStuff->btLibRefNum,
btStuff->u.slave.sdpSocket, &btStuff->otherAddr,
(BtLibSdpUuidType*)&XWORDS_UUID, 1 );
@ -699,6 +735,7 @@ pbt_do_work( PalmBTStuff* btStuff, BtCbEvtProc proc )
}
/* Presumably state's been reset since PBT_ACT_GETSDP issued */
XP_LOGF( "aborting b/c state wrong" );
pbt_close_sdpsocket( btStuff );
SET_STATE( btStuff, PBTST_NONE );
waitACL( btStuff );
break;
@ -745,7 +782,9 @@ pbt_do_work( PalmBTStuff* btStuff, BtCbEvtProc proc )
break;
case PBT_ACT_GOTDATA:
CALL_ERR( err, BtLibSocketAdvanceCredit, btLibRefNum, btStuff->dataSocket, 1 );
#ifdef BT_USE_RFCOMM
CALL_ERR( err, BtLibSocketAdvanceCredit, btLibRefNum, btStuff->dataSocket, 5 );
#endif
pbt_handoffIncoming( btStuff, proc );
break;
@ -892,7 +931,7 @@ pbt_handoffIncoming( PalmBTStuff* btStuff, BtCbEvtProc proc )
} /* pbt_handoffIncoming */
static void
pbt_reset( PalmBTStuff* btStuff )
pbt_reset_buffers( PalmBTStuff* btStuff )
{
LOG_FUNC();
XP_MEMSET( &btStuff->vol, 0, sizeof(btStuff->vol) );
@ -940,6 +979,7 @@ pbt_setup_slave( PalmBTStuff* btStuff, const CommsAddrRec* addr )
pbt_takedown_master( btStuff );
}
btStuff->picoRole = PBT_SLAVE;
btStuff->u.slave.sdpSocket = SOCK_INVAL;
if ( !!addr ) {
char buf[64];
@ -964,7 +1004,7 @@ pbt_setup_slave( PalmBTStuff* btStuff, const CommsAddrRec* addr )
static void
pbt_takedown_slave( PalmBTStuff* btStuff )
{
pbt_killL2C( btStuff, btStuff->dataSocket );
pbt_killLinks( btStuff, btStuff->dataSocket );
btStuff->picoRole = PBT_UNINIT;
}
@ -995,6 +1035,7 @@ pbt_checkInit( PalmAppGlobals* globals, XP_Bool* userCancelledP )
btStuff->dataSocket = SOCK_INVAL;
btStuff->u.master.listenSocket = SOCK_INVAL;
btStuff->u.slave.sdpSocket = SOCK_INVAL;
CALL_ERR( err, BtLibRegisterManagementNotification,
btLibRefNum, libMgmtCallback, (UInt32)btStuff );
@ -1010,13 +1051,17 @@ pbt_checkInit( PalmAppGlobals* globals, XP_Bool* userCancelledP )
} /* pbt_checkInit */
static void
pbt_killL2C( PalmBTStuff* btStuff, BtLibSocketRef sock )
pbt_killLinks( PalmBTStuff* btStuff, BtLibSocketRef sock )
{
Err err;
XP_ASSERT( sock == btStuff->dataSocket );
pbt_close_datasocket( btStuff );
if ( PBT_SLAVE == btStuff->picoRole ) {
pbt_close_sdpsocket( btStuff );
}
/* Harm in calling this when not connected? */
if ( GET_STATE(btStuff) != PBTST_NONE ) {
SET_STATE( btStuff, PBTST_NONE ); /* set first */
@ -1024,7 +1069,7 @@ pbt_killL2C( PalmBTStuff* btStuff, BtLibSocketRef sock )
CALL_ERR( err, BtLibLinkDisconnect, btStuff->btLibRefNum,
&btStuff->otherAddr );
}
} /* pbt_killL2C */
} /* pbt_killLinks */
static XP_Bool
pbt_checkAddress( PalmBTStuff* btStuff, const CommsAddrRec* addr )
@ -1041,7 +1086,7 @@ pbt_checkAddress( PalmBTStuff* btStuff, const CommsAddrRec* addr )
LOG_HEX( &addr->u.bt.btAddr.bits, sizeof(addr->u.bt.btAddr.bits),
"new" );
pbt_killL2C( btStuff, btStuff->dataSocket );
pbt_killLinks( btStuff, btStuff->dataSocket );
XP_MEMCPY( &btStuff->otherAddr, &addr->u.bt.btAddr,
sizeof(btStuff->otherAddr) );
@ -1204,7 +1249,7 @@ socketCallback( BtLibSocketEventType* sEvent, UInt32 refCon )
}
if ( PBT_SLAVE == btStuff->picoRole ) {
pbt_killL2C( btStuff, sEvent->socket );
pbt_killLinks( btStuff, sEvent->socket );
waitACL( btStuff );
} else if ( PBT_MASTER == btStuff->picoRole ) {
pbt_close_datasocket( btStuff );
@ -1215,9 +1260,7 @@ socketCallback( BtLibSocketEventType* sEvent, UInt32 refCon )
case btLibSocketEventSdpGetPsmByUuid:
case btLibSocketEventSdpGetServerChannelByUuid:
XP_ASSERT( sEvent->socket == btStuff->u.slave.sdpSocket );
CALL_ERR( err, BtLibSocketClose, btStuff->btLibRefNum, sEvent->socket );
XP_ASSERT( err == errNone );
btStuff->u.slave.sdpSocket = SOCK_INVAL;
pbt_close_sdpsocket( btStuff );
if ( sEvent->status == errNone ) {
#if defined BT_USE_L2CAP
btStuff->u.slave.remotePsm = sEvent->eventData.sdpByUuid.param.psm;
@ -1269,7 +1312,6 @@ socketCallback( BtLibSocketEventType* sEvent, UInt32 refCon )
static void
libMgmtCallback( BtLibManagementEventType* mEvent, UInt32 refCon )
{
Err err;
PalmBTStuff* btStuff = (PalmBTStuff*)refCon;
BtLibManagementEventEnum event = mEvent->event;
XP_LOGF( "%s(%s); status=%s", __FUNCTION__, mgmtEvtToStr(event),
@ -1312,11 +1354,7 @@ libMgmtCallback( BtLibManagementEventType* mEvent, UInt32 refCon )
call!!!! */
XP_ASSERT( 0 == XP_MEMCMP( &mEvent->eventData.bdAddr,
&btStuff->otherAddr, 6 ) );
if ( SOCK_INVAL != btStuff->dataSocket ) {
CALL_ERR( err, BtLibSocketClose, btStuff->btLibRefNum,
btStuff->dataSocket );
btStuff->dataSocket = SOCK_INVAL;
}
pbt_close_datasocket( btStuff );
SET_STATE( btStuff, PBTST_NONE );
/* See comment at btLibSocketEventDisconnected */
if ( PBT_SLAVE == btStuff->picoRole ) {

View file

@ -48,6 +48,7 @@
void palm_bt_amendWaitTicks( PalmAppGlobals* globals, Int32* result );
XP_Bool palm_bt_init( PalmAppGlobals* globals, XP_Bool* userCancelled );
void palm_bt_reset( PalmAppGlobals* globals );
void palm_bt_close( PalmAppGlobals* globals );
typedef enum { BTCBEVT_CONN, BTCBEVT_DATA, BTCBEVT_HOSTFAIL } BtCbEvt;

View file

@ -91,6 +91,9 @@ static XP_Bool timeForTimer( PalmAppGlobals* globals, XWTimerReason* why,
XP_U32* when );
static XP_S16 palm_send( const XP_U8* buf, XP_U16 len,
const CommsAddrRec* addr, void* closure );
#ifdef COMMS_HEARTBEAT
static void palm_reset( void* closure );
#endif
static void palm_send_on_close( XWStreamCtxt* stream, void* closure );
/* callbacks */
@ -597,7 +600,7 @@ loadCurrentGame( PalmAppGlobals* globals, XP_U16 gIndex,
success = game_makeFromStream( MEMPOOL recStream, game, ginfo,
dict, &globals->util,
globals->draw, &globals->gState.cp,
palm_send, globals );
palm_send, IF_CH(palm_reset) globals );
}
stream_destroy( recStream );
@ -1190,7 +1193,7 @@ startApplication( PalmAppGlobals** globalsP )
game_makeNewGame( MEMPOOL &globals->game, &globals->gameInfo,
&globals->util, globals->draw, gameID,
&globals->gState.cp,
palm_send, globals );
palm_send, IF_CH(palm_reset) globals );
FrmPopupForm( XW_NEWGAMES_FORM );
}
@ -1989,7 +1992,7 @@ initAndStartBoard( PalmAppGlobals* globals, XP_Bool newGame )
XP_U32 newGameID = TimGetSeconds();
game_reset( MEMPOOL &globals->game, &globals->gameInfo,
&globals->util, newGameID, &globals->gState.cp,
palm_send, globals );
palm_send, IF_CH(palm_reset) globals );
#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_RELAY || defined XWFEATURE_IR
if ( !!globals->game.comms ) {
comms_setAddr( globals->game.comms,
@ -3812,7 +3815,7 @@ palm_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why,
now += PALM_TIMER_DELAY;
} else if ( why == TIMER_TIMERTICK ) {
now += SysTicksPerSecond();
#ifdef XWFEATURE_RELAY
#if defined XWFEATURE_RELAY || defined COMMS_HEARTBEAT
} else if ( why == TIMER_HEARTBEAT ) {
now += (secsFromNow * SysTicksPerSecond());
#endif
@ -3927,6 +3930,26 @@ palm_send( const XP_U8* buf, XP_U16 len,
return result;
} /* palm_send */
#ifdef COMMS_HEARTBEAT
static void
palm_reset( void* closure )
{
PalmAppGlobals* globals = (PalmAppGlobals*)closure;
XP_ASSERT( !!globals->game.comms );
switch( comms_getConType( globals->game.comms ) ) {
#ifdef XWFEATURE_BLUETOOTH
case COMMS_CONN_BT:
palm_bt_reset( globals );
break;
#endif
default:
XP_ASSERT(0);
break;
}
}
#endif
void
checkAndDeliver( PalmAppGlobals* globals, const CommsAddrRec* addr,
XWStreamCtxt* instream )