xwords/palm/palmbt.c
ehouse 3b98337eec Better handling of disconnect and reconnect; esp. slave will keep
trying to connect after losing contact with server.  With this
checkin, a game between palm and linux can be interrupted by either or
both and restarted, with either in either role.  Haven't tested
palm-on-palm yet.  App stop speed is much better now too.
2006-09-14 01:38:09 +00:00

1051 lines
35 KiB
C

/* -*-mode: C; fill-column: 77; c-basic-offset: 4; compile-command: "make ARCH=68K_ONLY MEMDEBUG=TRUE"; -*- */
/*
* Copyright 2006 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 XWFEATURE_BLUETOOTH
#include "xptypes.h"
#include "palmbt.h"
#include "strutils.h"
# include <BtLib.h>
# include <BtLibTypes.h>
#define L2CAPSOCKETMTU 500
#define SOCK_INVAL 0XFFFF
typedef enum { PBT_UNINIT = 0, PBT_MASTER, PBT_SLAVE } PBT_SetState;
typedef enum {
PBT_EVENT_NONE
, PBT_EVENT_CONNECT_ACL
, PBT_EVENT_CONNECT_L2C
, PBT_EVENT_GOTDATA
, PBT_EVENT_TRYSEND
} PBT_EVENT;
typedef enum {
PBTST_NONE
, PBTST_ACL_CONNECTING
, PBTST_ACL_CONNECTED
, PBTST_L2C_CONNECTING
, PBTST_L2C_CONNECTED
} PBT_STATE;
#define PBT_MAX_EVTS 4
#define HASWORK(s) ((s)->vol.queueCur != (s)->vol.queueNext)
#define MAX_INCOMING 4
typedef struct PalmBTStuff {
DataCb cb;
PalmAppGlobals* globals;
XP_U16 btLibRefNum;
struct {
XP_U16 lenOut;
XP_UCHAR bufOut[L2CAPSOCKETMTU]; /* what's the mmu? */
XP_U16 lens[MAX_INCOMING];
XP_U8 bufIn[L2CAPSOCKETMTU*2]; /* what's the mmu? */
XP_U16 queueCur;
XP_U16 queueNext;
PBT_EVENT evtQueue[PBT_MAX_EVTS];
XP_Bool sendInProgress;
XP_Bool sendPending;
} vol;
/* peer's addr: passed in by UI in case of slave, received via connection
in case of master. Piconet master will need an array of these. */
BtLibDeviceAddressType otherAddr;
BtLibSocketRef dataSocket;
PBT_SetState setState;
PBT_STATE p_connState;
BtLibAccessibleModeEnum accState;
union {
struct {
} slave;
struct {
BtLibSocketRef listenSocket;
} master;
} u;
BtLibSdpRecordHandle sdpRecordH;
} PalmBTStuff;
#define LOG_ERR(f,e) palm_bt_log( #f, __FUNCTION__, e )
#define CALL_ERR(e,f,...) \
XP_LOGF( "%s: calling %s", __FUNCTION__, #f ); \
e = f(__VA_ARGS__); \
LOG_ERR(f,e); \
if ( e == btLibErrFailed ) { XP_WARNF( "%s=>btLibErrFailed", #f ); }
/* WHAT SHOULD THIS BE? Copied from Whiteboard.... PENDING */
static const BtLibSdpUuidType XWORDS_UUID = {
btLibUuidSize128,
{ 0x83, 0xe0, 0x87, 0xae, 0x4e, 0x18, 0x46, 0xbe,
0x83, 0xe0, 0x7b, 0x3d, 0xe6, 0xa1, 0xc3, 0x3b } };
static PalmBTStuff* pbt_checkInit( PalmAppGlobals* globals );
static void palm_bt_log( const char* btfunc, const char* func, Err err );
static Err bpd_discover( PalmBTStuff* btStuff, BtLibDeviceAddressType* addr );
static void pbt_setup_slave( PalmBTStuff* btStuff, const CommsAddrRec* addr );
static void pbt_takedown_slave( PalmBTStuff* btStuff );
static void pbt_setup_master( PalmBTStuff* btStuff );
static void pbt_takedown_master( PalmBTStuff* btStuff );
static void pbt_do_work( PalmBTStuff* btStuff );
static void pbt_postpone( PalmBTStuff* btStuff, PBT_EVENT evt );
static void pbt_enqueIncoming( PalmBTStuff* btStuff, XP_U8* data, XP_U16 len );
static void pbt_processIncoming( PalmBTStuff* btStuff );
static void pbt_reset( PalmBTStuff* btStuff );
static void pbt_killL2C( PalmBTStuff* btStuff, BtLibSocketRef sock );
static void pbt_checkAddress( PalmBTStuff* btStuff, const CommsAddrRec* addr );
static void pbt_setstate( PalmBTStuff* btStuff, PBT_STATE newState,
const char* whence );
#define SET_STATE(b,s) pbt_setstate((b),(s),__FUNCTION__)
#define GET_STATE(b) ((b)->p_connState)
#ifdef DEBUG
static const char* btErrToStr( Err err );
static const char* btEvtToStr( BtLibSocketEventEnum evt );
static const char* mgmtEvtToStr( BtLibManagementEventEnum event );
static const char* evtToStr(PBT_EVENT evt);
static const char* stateToStr(PBT_STATE st);
static const char* connEnumToStr( BtLibAccessibleModeEnum mode );
#else
# define btErrToStr( err ) ""
# define btEvtToStr( evt ) ""
# define mgmtEvtToStr( evt ) ""
# define evtToStr(evt) ""
#endif
/* callbacks */
static void libMgmtCallback( BtLibManagementEventType* mEvent, UInt32 refCon );
static void l2SocketCallback( BtLibSocketEventType* sEvent, UInt32 refCon );
Err
palm_bt_init( PalmAppGlobals* globals, DataCb cb )
{
PalmBTStuff* btStuff;
btStuff = globals->btStuff;
if ( !btStuff ) {
btStuff = pbt_checkInit( globals );
} else {
pbt_reset( btStuff );
}
btStuff->cb = cb;
return errNone;
} /* palm_bt_init */
void
palm_bt_close( PalmAppGlobals* globals )
{
PalmBTStuff* btStuff = globals->btStuff;
if ( !!btStuff ) {
XP_U16 btLibRefNum = btStuff->btLibRefNum;
if ( btLibRefNum != 0 ) {
Err err;
if ( btStuff->setState == PBT_MASTER ) {
pbt_takedown_master( btStuff );
} else if ( btStuff->setState == PBT_SLAVE ) {
pbt_takedown_slave( btStuff );
}
/* Need to unregister callbacks */
CALL_ERR( err, BtLibUnregisterManagementNotification, btLibRefNum,
libMgmtCallback );
CALL_ERR( err, BtLibClose, btLibRefNum );
XP_ASSERT( errNone == err );
}
XP_FREE( globals->mpool, btStuff );
globals->btStuff = NULL;
} else {
XP_LOGF( "%s: btStuff null", __FUNCTION__ );
}
} /* palm_bt_close */
void
palm_bt_amendWaitTicks( PalmAppGlobals* globals, Int32* result )
{
PalmBTStuff* btStuff = globals->btStuff;
if ( !!btStuff && HASWORK(btStuff) ) {
*result = 0;
}
}
XP_Bool
palm_bt_doWork( PalmAppGlobals* globals )
{
PalmBTStuff* btStuff = globals->btStuff;
XP_Bool haveWork = !!btStuff && HASWORK(btStuff);
if ( haveWork ) {
pbt_do_work( btStuff );
}
return haveWork;
}
void
palm_bt_addrString( PalmAppGlobals* globals, XP_BtAddr* btAddr,
XP_BtAddrStr* str )
{
PalmBTStuff* btStuff = pbt_checkInit( globals );
str->chars[0] = '\0';
if ( !!btStuff ) {
Err err;
CALL_ERR( err, BtLibAddrBtdToA, btStuff->btLibRefNum,
(BtLibDeviceAddressType*)btAddr,
(char*)str, sizeof(*str) );
XP_LOGF( "BtLibAddrBtdToA=>%s from:", str );
LOG_HEX( btAddr, sizeof(*btAddr), "" );
}
} /* palm_bt_addrString */
XP_Bool
palm_bt_browse_device( PalmAppGlobals* globals, XP_BtAddr* btAddr,
XP_UCHAR* out, XP_U16 len )
{
Err err;
PalmBTStuff* btStuff;
btStuff = pbt_checkInit( globals );
if ( NULL != btStuff ) {
BtLibDeviceAddressType addr;
err = bpd_discover( btStuff, &addr );
if ( errNone == err ) {
UInt16 index;
CALL_ERR( err, BtLibSecurityFindTrustedDeviceRecord,
btStuff->btLibRefNum, &addr, &index );
CALL_ERR( err, BtLibSecurityGetTrustedDeviceRecordInfo,
btStuff->btLibRefNum, index, NULL, out, len,
NULL, NULL, NULL );
XP_ASSERT( sizeof(*btAddr) >= sizeof(addr) );
XP_MEMCPY( btAddr, &addr, sizeof(addr) );
LOG_HEX( &addr, sizeof(addr), __FUNCTION__ );
/* err = BtLibGetRemoteDeviceName( btStuff->btLibRefNum, */
/* BtLibDeviceAddressTypePtr */
/* remoteDeviceP, */
/* BtLibFriendlyNameType* nameP, */
/* BtLibGetNameEnum retrievalMethod ); */
/* err = BtLibAddrBtdToA( btStuff->btLibRefNum, */
/* &btStuff->u.slave.masterAddr, */
/* out, len ); */
}
} else {
XP_LOGF( "%s: err = %s", __FUNCTION__, btErrToStr(err) );
}
return errNone == err;
} /* palm_bt_browse_device */
static void
pbt_send_pending( PalmBTStuff* btStuff, const CommsAddrRec* addr )
{
Err err;
LOG_FUNC();
if ( btStuff->vol.sendPending && !btStuff->vol.sendInProgress ) {
if ( btStuff->dataSocket != SOCK_INVAL ) {
/* hack: zero-len send to cause connect */
if ( btStuff->vol.lenOut > 0 ) {
CALL_ERR( err, BtLibSocketSend, btStuff->btLibRefNum,
btStuff->dataSocket,
btStuff->vol.bufOut, btStuff->vol.lenOut );
if ( err == errNone ) {
// clear on receipt of btLibSocketEventSendComplete
btStuff->vol.sendInProgress = XP_TRUE;
}
} else {
btStuff->vol.sendPending = XP_FALSE;
}
} else {
/* No data socket? */
if ( btStuff->setState == PBT_SLAVE ) {
pbt_setup_slave( btStuff, addr );
}
}
}
} /* pbt_send_pending */
XP_S16
palm_bt_send( const XP_U8* buf, XP_U16 len, const CommsAddrRec* addr,
DataCb cb, PalmAppGlobals* globals )
{
XP_S16 nSent = -1;
PalmBTStuff* btStuff;
CommsAddrRec remoteAddr;
PBT_SetState setState;
XP_LOGF( "%s(len=%d)", __FUNCTION__, len );
btStuff = pbt_checkInit( globals );
if ( !btStuff->cb ) {
btStuff->cb = cb;
} else {
XP_ASSERT( cb == btStuff->cb );
}
if ( !addr ) {
comms_getAddr( globals->game.comms, &remoteAddr );
addr = &remoteAddr;
}
XP_ASSERT( !!addr );
setState = btStuff->setState;
if ( setState == PBT_UNINIT ) {
XP_Bool amMaster = comms_getIsServer( globals->game.comms );
setState = amMaster? PBT_MASTER : PBT_SLAVE;
}
pbt_checkAddress( btStuff, addr );
if ( !!btStuff ) {
if ( setState == PBT_MASTER ) {
pbt_setup_master( btStuff );
} else {
pbt_setup_slave( btStuff, addr );
}
if ( !btStuff->vol.sendInProgress ) {
if ( len > sizeof( btStuff->vol.bufOut ) ) {
len = sizeof( btStuff->vol.bufOut );
}
XP_MEMCPY( btStuff->vol.bufOut, buf, len );
btStuff->vol.lenOut = len;
btStuff->vol.sendPending = XP_TRUE;
pbt_send_pending( btStuff, addr );
nSent = len;
} else {
XP_LOGF( "%s: send ALREADY in progress", __FUNCTION__ );
}
}
LOG_RETURNF( "%d", nSent );
return nSent;
} /* palm_bt_send */
static void
pbt_setup_master( PalmBTStuff* btStuff )
{
if ( btStuff->setState == PBT_SLAVE ) {
pbt_takedown_slave( btStuff );
}
btStuff->setState = PBT_MASTER;
if ( btStuff->u.master.listenSocket == SOCK_INVAL ) {
/* Will eventually want to create a piconet here for more than two
devices to play.... */
Err err;
BtLibSocketListenInfoType listenInfo;
/* 1. BtLibSocketCreate: create an L2CAP socket. */
CALL_ERR( err, BtLibSocketCreate, btStuff->btLibRefNum,
&btStuff->u.master.listenSocket, l2SocketCallback,
(UInt32)btStuff, btLibL2CapProtocol );
/* 2. BtLibSocketListen: set up an L2CAP socket as a listener. */
XP_MEMSET( &listenInfo, 0, sizeof(listenInfo) );
listenInfo.data.L2Cap.localPsm = XW_PSM; // BT_L2CAP_RANDOM_PSM;
listenInfo.data.L2Cap.localMtu = L2CAPSOCKETMTU;
listenInfo.data.L2Cap.minRemoteMtu = L2CAPSOCKETMTU;
CALL_ERR( err, BtLibSocketListen, btStuff->btLibRefNum,
btStuff->u.master.listenSocket, &listenInfo );
/* 3. BtLibSdpServiceRecordCreate: allocate a memory chunk that
represents an SDP service record. */
CALL_ERR( err, BtLibSdpServiceRecordCreate,
btStuff->btLibRefNum, &btStuff->sdpRecordH );
/* 4. BtLibSdpServiceRecordSetAttributesForSocket: initialize an
SDP memory record so it can represent the newly-created L2CAP
listener socket as a service */
CALL_ERR( err, BtLibSdpServiceRecordSetAttributesForSocket,
btStuff->btLibRefNum, btStuff->u.master.listenSocket,
(BtLibSdpUuidType*)&XWORDS_UUID, 1, APPNAME,
StrLen(APPNAME), btStuff->sdpRecordH );
/* 5. BtLibSdpServiceRecordStartAdvertising: make an SDP memory
record representing a local SDP service record visible to
remote devices. */
CALL_ERR( err, BtLibSdpServiceRecordStartAdvertising,
btStuff->btLibRefNum, btStuff->sdpRecordH );
}
} /* pbt_setup_master */
static void
pbt_takedown_master( PalmBTStuff* btStuff )
{
XP_U16 btLibRefNum;
Err err;
LOG_FUNC();
XP_ASSERT( btStuff->setState == PBT_MASTER );
btLibRefNum = btStuff->btLibRefNum;
if ( SOCK_INVAL != btStuff->dataSocket ) {
CALL_ERR( err, BtLibSocketClose, btLibRefNum,
btStuff->dataSocket );
}
if ( !!btStuff->sdpRecordH ) {
CALL_ERR( err, BtLibSdpServiceRecordStopAdvertising,
btLibRefNum, btStuff->sdpRecordH );
XP_ASSERT( errNone == err ); /* no errors if it was being advertised */
CALL_ERR( err, BtLibSdpServiceRecordDestroy, btLibRefNum,
btStuff->sdpRecordH );
btStuff->sdpRecordH = NULL;
}
if ( SOCK_INVAL != btStuff->u.master.listenSocket ) {
CALL_ERR( err, BtLibSocketClose, btLibRefNum,
btStuff->u.master.listenSocket );
btStuff->u.master.listenSocket = SOCK_INVAL;
}
btStuff->setState = PBT_UNINIT;
}
static void
pbt_do_work( PalmBTStuff* btStuff )
{
PBT_EVENT evt;
Err err;
evt = btStuff->vol.evtQueue[btStuff->vol.queueCur++];
btStuff->vol.queueCur %= PBT_MAX_EVTS;
XP_LOGF( "%s: evt=%s; state=%s", __FUNCTION__, evtToStr(evt),
stateToStr(GET_STATE(btStuff)) );
switch( evt ) {
case PBT_EVENT_CONNECT_ACL:
if ( GET_STATE(btStuff) == PBTST_NONE ) {
/* sends btLibManagementEventACLConnectOutbound */
CALL_ERR( err, BtLibLinkConnect, btStuff->btLibRefNum,
&btStuff->otherAddr );
SET_STATE( btStuff, PBTST_ACL_CONNECTING );
} else {
err = btLibErrAlreadyConnected;
}
if ( btLibErrAlreadyConnected == err ) {
pbt_postpone( btStuff, PBT_EVENT_CONNECT_L2C );
}
break;
case PBT_EVENT_CONNECT_L2C:
if ( GET_STATE(btStuff) == PBTST_ACL_CONNECTED ) {
XP_ASSERT( SOCK_INVAL == btStuff->dataSocket );
CALL_ERR( err, BtLibSocketCreate, btStuff->btLibRefNum,
&btStuff->dataSocket,
l2SocketCallback, (UInt32)btStuff,
btLibL2CapProtocol );
if ( btLibErrNoError == err ) {
BtLibSocketConnectInfoType connInfo;
connInfo.data.L2Cap.remotePsm = XW_PSM;
connInfo.data.L2Cap.localMtu = L2CAPSOCKETMTU;
connInfo.data.L2Cap.minRemoteMtu = L2CAPSOCKETMTU;
connInfo.remoteDeviceP = &btStuff->otherAddr;
/* sends btLibSocketEventConnectedOutbound */
CALL_ERR( err, BtLibSocketConnect, btStuff->btLibRefNum,
btStuff->dataSocket, &connInfo );
SET_STATE( btStuff, PBTST_L2C_CONNECTING );
} else {
btStuff->dataSocket = SOCK_INVAL;
}
}
break;
case PBT_EVENT_GOTDATA:
pbt_processIncoming( btStuff );
break;
case PBT_EVENT_TRYSEND:
pbt_send_pending( btStuff, NULL );
break;
default:
XP_ASSERT( 0 );
}
LOG_RETURN_VOID();
} /* pbt_do_work */
static void
pbt_postpone( PalmBTStuff* btStuff, PBT_EVENT evt )
{
EventType eventToPost = { .eType = nilEvent };
XP_LOGF( "%s(%s)", __FUNCTION__, evtToStr(evt) );
EvtAddEventToQueue( &eventToPost );
btStuff->vol.evtQueue[ btStuff->vol.queueNext++ ] = evt;
btStuff->vol.queueNext %= PBT_MAX_EVTS;
XP_ASSERT( btStuff->vol.queueNext != btStuff->vol.queueCur );
}
static void
pbt_enqueIncoming( PalmBTStuff* btStuff, XP_U8* indata, XP_U16 inlen )
{
XP_U16 i;
XP_U16 total = 0;
for ( i = 0; i < MAX_INCOMING; ++i ) {
XP_U16 len = btStuff->vol.lens[i];
if ( !len ) {
break;
}
total += len;
}
if ( (i < MAX_INCOMING) &&
((total + inlen) < sizeof(btStuff->vol.bufIn)) ) {
btStuff->vol.lens[i] = inlen;
XP_MEMCPY( &btStuff->vol.bufIn[total], indata, inlen );
pbt_postpone( btStuff, PBT_EVENT_GOTDATA );
} else {
XP_LOGF( "%s: dropping packet of len %d", __FUNCTION__, inlen );
}
} /* pbt_enqueIncoming */
static void
pbt_processIncoming( PalmBTStuff* btStuff )
{
XP_U16 len = btStuff->vol.lens[0];
XP_ASSERT( !!btStuff->cb );
if ( !!btStuff->cb ) {
CommsAddrRec fromAddr;
fromAddr.conType = COMMS_CONN_BT;
XP_MEMCPY( &fromAddr.u.bt.btAddr, &btStuff->otherAddr,
sizeof(fromAddr.u.bt.btAddr) );
(*btStuff->cb)( btStuff->globals, &fromAddr, btStuff->vol.bufIn, len );
/* slide the remaining packets down */
XP_MEMCPY( &btStuff->vol.lens[0], &btStuff->vol.lens[1],
sizeof(btStuff->vol.lens) - sizeof(btStuff->vol.lens[0]) );
btStuff->vol.lens[MAX_INCOMING-1] = 0; /* be safe */
XP_MEMCPY( btStuff->vol.bufIn, btStuff->vol.bufIn + len,
sizeof(btStuff->vol.bufIn) - len );
}
} /* pbt_processIncoming */
static void
pbt_reset( PalmBTStuff* btStuff )
{
LOG_FUNC();
XP_MEMSET( &btStuff->vol, 0, sizeof(btStuff->vol) );
}
static Err
bpd_discover( PalmBTStuff* btStuff, BtLibDeviceAddressType* addr )
{
Err err;
const BtLibClassOfDeviceType deviceFilter
= btLibCOD_ServiceAny
| btLibCOD_Major_Any // btLibCOD_Major_Computer
| btLibCOD_Minor_Comp_Any; //btLibCOD_Minor_Comp_Palm;
CALL_ERR( err, BtLibDiscoverSingleDevice, btStuff->btLibRefNum,
"Crosswords host", (BtLibClassOfDeviceType*)&deviceFilter, 1,
addr, false, false );
LOG_RETURNF( "%s", btErrToStr(err) );
return err;
} /* bpd_discover */
static void
pbt_setup_slave( PalmBTStuff* btStuff, const CommsAddrRec* addr )
{
XP_LOGF( "%s; state=%s", __FUNCTION__, stateToStr(GET_STATE(btStuff)));
if ( btStuff->setState == PBT_MASTER ) {
pbt_takedown_master( btStuff );
}
btStuff->setState = PBT_SLAVE;
if ( !!addr ) {
char buf[64];
if ( errNone ==
BtLibAddrBtdToA( btStuff->btLibRefNum,
(BtLibDeviceAddressType*)&addr->u.bt.btAddr,
buf, sizeof(buf) ) ) {
XP_LOGF( "%s(%s)", __FUNCTION__, buf );
}
} else {
XP_LOGF( "null addr" );
}
if ( GET_STATE(btStuff) == PBTST_NONE ) {
pbt_postpone( btStuff, PBT_EVENT_CONNECT_ACL );
} else {
XP_LOGF( "%s: doing nothing", __FUNCTION__ );
}
LOG_RETURN_VOID();
} /* pbt_setup_slave */
static void
pbt_takedown_slave( PalmBTStuff* btStuff )
{
pbt_killL2C( btStuff, btStuff->dataSocket );
btStuff->setState = PBT_UNINIT;
}
static PalmBTStuff*
pbt_checkInit( PalmAppGlobals* globals )
{
PalmBTStuff* btStuff = globals->btStuff;
if ( !btStuff ) {
Err err;
XP_U16 btLibRefNum;
CALL_ERR( err, SysLibFind, btLibName, &btLibRefNum );
if ( errNone == err ) {
btStuff = XP_MALLOC( globals->mpool, sizeof(*btStuff) );
XP_ASSERT( !!btStuff );
globals->btStuff = btStuff;
XP_MEMSET( btStuff, 0, sizeof(*btStuff) );
btStuff->globals = globals;
btStuff->btLibRefNum = btLibRefNum;
btStuff->dataSocket = SOCK_INVAL;
btStuff->u.master.listenSocket = SOCK_INVAL;
CALL_ERR( err, BtLibOpen, btLibRefNum, false );
XP_ASSERT( errNone == err );
CALL_ERR( err, BtLibRegisterManagementNotification, btLibRefNum,
libMgmtCallback, (UInt32)btStuff );
}
}
return btStuff;
} /* pbt_checkInit */
static void
pbt_killL2C( PalmBTStuff* btStuff, BtLibSocketRef sock )
{
Err err;
XP_U16 btLibRefNum = btStuff->btLibRefNum;
XP_ASSERT( sock == btStuff->dataSocket );
if ( sock != SOCK_INVAL ) {
CALL_ERR( err, BtLibSocketClose, btLibRefNum, sock );
btStuff->dataSocket = SOCK_INVAL;
}
/* Harm in calling this when not connected? */
CALL_ERR( err, BtLibLinkDisconnect, btLibRefNum, &btStuff->otherAddr );
SET_STATE( btStuff, PBTST_NONE );
} /* pbt_killL2C */
static void
pbt_checkAddress( PalmBTStuff* btStuff, const CommsAddrRec* addr )
{
LOG_FUNC();
XP_ASSERT( !!addr );
if ( 0 != XP_MEMCMP( &btStuff->otherAddr, &addr->u.bt.btAddr,
sizeof(btStuff->otherAddr) ) ) {
LOG_HEX( &btStuff->otherAddr, sizeof(btStuff->otherAddr), "cur" );
LOG_HEX( &addr->u.bt.btAddr, sizeof(addr->u.bt.btAddr), "new" );
pbt_killL2C( btStuff, btStuff->dataSocket );
XP_MEMCPY( &btStuff->otherAddr, &addr->u.bt.btAddr,
sizeof(btStuff->otherAddr) );
}
} /* pbt_checkAddress */
static void
pbt_setstate( PalmBTStuff* btStuff, PBT_STATE newState, const char* whence )
{
btStuff->p_connState = newState;
XP_LOGF( "setting state to %s, from %s", stateToStr(newState), whence );
}
static void
l2SocketCallback( BtLibSocketEventType* sEvent, UInt32 refCon )
{
PalmBTStuff* btStuff = (PalmBTStuff*)refCon;
BtLibSocketEventEnum event = sEvent->event;
Err err;
XP_LOGF( "%s(%s); status:%s", __FUNCTION__, btEvtToStr(event),
btErrToStr(sEvent->status) );
switch( event ) {
case btLibSocketEventConnectRequest:
/* sends btLibSocketEventConnectedInbound */
CALL_ERR( err, BtLibSocketRespondToConnection, btStuff->btLibRefNum,
sEvent->socket, true );
break;
case btLibSocketEventConnectedInbound:
if ( sEvent->status == errNone ) {
btStuff->dataSocket = sEvent->eventData.newSocket;
XP_LOGF( "we have a data socket!!!" );
pbt_postpone( btStuff, PBT_EVENT_TRYSEND );
}
break;
case btLibSocketEventConnectedOutbound:
if ( errNone == sEvent->status ) {
SET_STATE( btStuff, PBTST_L2C_CONNECTED );
pbt_postpone( btStuff, PBT_EVENT_TRYSEND );
}
break;
case btLibSocketEventData:
XP_ASSERT( sEvent->status == errNone );
XP_ASSERT( sEvent->socket == btStuff->dataSocket );
pbt_enqueIncoming( btStuff, sEvent->eventData.data.data,
sEvent->eventData.data.dataLen );
break;
case btLibSocketEventSendComplete:
btStuff->vol.sendInProgress = XP_FALSE;
break;
case btLibSocketEventDisconnected:
pbt_killL2C( btStuff, sEvent->socket );
/* We'll see this as client if the host quits. What to do? I think
* we need to start trying to reconnect hoping the host got
* restarted. Presumably users will not sit there forever running
* the app once one of the players has taken his device and gone
* home. But there should probably be UI warning users that it's
* trying to connect....
*/
if ( PBT_SLAVE == btStuff->setState ) {
pbt_postpone( btStuff, PBT_EVENT_CONNECT_ACL );
}
break;
default:
break;
}
LOG_RETURN_VOID();
} /* l2SocketCallback */
/***********************************************************************
* Callbacks
***********************************************************************/
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),
btErrToStr(mEvent->status) );
switch( event ) {
case btLibManagementEventAccessibilityChange:
XP_LOGF( "%s", connEnumToStr(mEvent->eventData.accessible) );
btStuff->accState = mEvent->eventData.accessible;
break;
case btLibManagementEventRadioState:
XP_LOGF( "status: %s", btErrToStr(mEvent->status) );
break;
case btLibManagementEventACLConnectOutbound:
if ( btLibErrNoError == mEvent->status ) {
SET_STATE( btStuff, PBTST_ACL_CONNECTED );
XP_LOGF( "successful ACL connection to master!" );
pbt_postpone( btStuff, PBT_EVENT_CONNECT_L2C );
} else {
SET_STATE(btStuff, PBTST_NONE);
pbt_postpone( btStuff, PBT_EVENT_CONNECT_ACL );
}
break;
case btLibManagementEventACLConnectInbound:
if ( btLibErrNoError == mEvent->status ) {
SET_STATE(btStuff, PBTST_ACL_CONNECTED);
XP_LOGF( "successful ACL connection!" );
XP_MEMCPY( &btStuff->otherAddr,
&mEvent->eventData.bdAddr,
sizeof(btStuff->otherAddr) );
}
break;
case btLibManagementEventACLDisconnect:
XP_ASSERT( 0 == XP_MEMCMP( &mEvent->eventData.bdAddr,
&btStuff->otherAddr,
sizeof(btStuff->otherAddr) ) );
if ( SOCK_INVAL != btStuff->dataSocket ) {
CALL_ERR( err, BtLibSocketClose, btStuff->btLibRefNum,
btStuff->dataSocket );
btStuff->dataSocket = SOCK_INVAL;
}
SET_STATE(btStuff, PBTST_NONE);
/* See comment at btLibSocketEventDisconnected */
if ( PBT_SLAVE == btStuff->setState ) {
pbt_postpone( btStuff, PBT_EVENT_CONNECT_ACL );
}
break;
default:
break;
}
} /* libMgmtCallback */
/***********************************************************************
* Debug helpers for verbose logging
***********************************************************************/
#ifdef DEBUG
# define CASESTR(e) case(e): return #e
static const char*
stateToStr(PBT_STATE st)
{
switch( st ) {
CASESTR(PBTST_NONE);
CASESTR(PBTST_ACL_CONNECTING);
CASESTR(PBTST_ACL_CONNECTED);
CASESTR(PBTST_L2C_CONNECTING);
CASESTR(PBTST_L2C_CONNECTED);
default:
XP_ASSERT(0);
return "";
}
} /* stateToStr */
static const char*
evtToStr(PBT_EVENT evt)
{
switch( evt ) {
CASESTR(PBT_EVENT_NONE);
CASESTR(PBT_EVENT_CONNECT_ACL);
CASESTR(PBT_EVENT_CONNECT_L2C);
CASESTR(PBT_EVENT_GOTDATA);
CASESTR(PBT_EVENT_TRYSEND);
default:
XP_ASSERT(0);
return "";
}
} /* evtToStr */
static const char*
connEnumToStr( BtLibAccessibleModeEnum mode )
{
switch( mode ) {
CASESTR(btLibNotAccessible);
CASESTR(btLibConnectableOnly);
CASESTR(btLibDiscoverableAndConnectable);
default:
XP_ASSERT(0);
return "";
}
}
static const char*
btEvtToStr( BtLibSocketEventEnum evt )
{
switch( evt ) {
CASESTR(btLibSocketEventConnectRequest);
CASESTR(btLibSocketEventConnectedOutbound);
CASESTR(btLibSocketEventConnectedInbound);
CASESTR(btLibSocketEventDisconnected);
CASESTR(btLibSocketEventData);
CASESTR(btLibSocketEventSendComplete);
CASESTR(btLibSocketEventSdpServiceRecordHandle);
CASESTR(btLibSocketEventSdpGetAttribute);
CASESTR(btLibSocketEventSdpGetStringLen);
CASESTR(btLibSocketEventSdpGetNumListEntries);
CASESTR(btLibSocketEventSdpGetNumLists);
CASESTR(btLibSocketEventSdpGetRawAttribute);
CASESTR(btLibSocketEventSdpGetRawAttributeSize);
CASESTR(btLibSocketEventSdpGetServerChannelByUuid);
CASESTR(btLibSocketEventSdpGetPsmByUuid);
default:
XP_ASSERT(0);
return "";
}
} /* btEvtToStr */
static const char*
mgmtEvtToStr( BtLibManagementEventEnum event )
{
switch( event ) {
CASESTR(btLibManagementEventRadioState);
CASESTR(btLibManagementEventInquiryResult);
CASESTR(btLibManagementEventInquiryComplete);
CASESTR(btLibManagementEventInquiryCanceled);
CASESTR(btLibManagementEventACLDisconnect);
CASESTR(btLibManagementEventACLConnectInbound);
CASESTR(btLibManagementEventACLConnectOutbound);
CASESTR(btLibManagementEventPiconetCreated);
CASESTR(btLibManagementEventPiconetDestroyed);
CASESTR(btLibManagementEventModeChange);
CASESTR(btLibManagementEventAccessibilityChange);
CASESTR(btLibManagementEventEncryptionChange);
CASESTR(btLibManagementEventRoleChange);
CASESTR(btLibManagementEventNameResult);
CASESTR(btLibManagementEventLocalNameChange);
CASESTR(btLibManagementEventAuthenticationComplete);
CASESTR(btLibManagementEventPasskeyRequest);
CASESTR(btLibManagementEventPasskeyRequestComplete);
CASESTR(btLibManagementEventPairingComplete);
default:
XP_ASSERT(0);
return "unknown";
}
} /* mgmtEvtToStr */
static const char*
btErrToStr( Err err )
{
switch ( err ) {
CASESTR(errNone);
CASESTR(btLibErrError);
CASESTR(btLibErrNotOpen);
CASESTR(btLibErrBluetoothOff);
CASESTR(btLibErrNoPrefs);
CASESTR(btLibErrAlreadyOpen);
CASESTR(btLibErrOutOfMemory);
CASESTR(btLibErrFailed);
CASESTR(btLibErrInProgress);
CASESTR(btLibErrParamError);
CASESTR(btLibErrTooMany);
CASESTR(btLibErrPending);
CASESTR(btLibErrNotInProgress);
CASESTR(btLibErrRadioInitFailed);
CASESTR(btLibErrRadioFatal);
CASESTR(btLibErrRadioInitialized);
CASESTR(btLibErrRadioSleepWake);
CASESTR(btLibErrNoConnection);
CASESTR(btLibErrAlreadyRegistered);
CASESTR(btLibErrNoAclLink);
CASESTR(btLibErrSdpRemoteRecord);
CASESTR(btLibErrSdpAdvertised);
CASESTR(btLibErrSdpFormat);
CASESTR(btLibErrSdpNotAdvertised);
CASESTR(btLibErrSdpQueryVersion);
CASESTR(btLibErrSdpQueryHandle);
CASESTR(btLibErrSdpQuerySyntax);
CASESTR(btLibErrSdpQueryPduSize);
CASESTR(btLibErrSdpQueryContinuation);
CASESTR(btLibErrSdpQueryResources);
CASESTR(btLibErrSdpQueryDisconnect);
CASESTR(btLibErrSdpInvalidResponse);
CASESTR(btLibErrSdpAttributeNotSet);
CASESTR(btLibErrSdpMapped);
CASESTR(btLibErrSocket);
CASESTR(btLibErrSocketProtocol);
CASESTR(btLibErrSocketRole);
CASESTR(btLibErrSocketPsmUnavailable);
CASESTR(btLibErrSocketChannelUnavailable);
CASESTR(btLibErrSocketUserDisconnect);
CASESTR(btLibErrCanceled);
CASESTR(btLibErrBusy);
CASESTR(btLibMeStatusUnknownHciCommand);
CASESTR(btLibMeStatusNoConnection);
CASESTR(btLibMeStatusHardwareFailure);
CASESTR(btLibMeStatusPageTimeout);
CASESTR(btLibMeStatusAuthenticateFailure);
CASESTR(btLibMeStatusMissingKey);
CASESTR(btLibMeStatusMemoryFull);
CASESTR(btLibMeStatusConnnectionTimeout);
CASESTR(btLibMeStatusMaxConnections);
CASESTR(btLibMeStatusMaxScoConnections);
CASESTR(btLibMeStatusMaxAclConnections);
CASESTR(btLibMeStatusCommandDisallowed);
CASESTR(btLibMeStatusLimitedResources);
CASESTR(btLibMeStatusSecurityError);
CASESTR(btLibMeStatusPersonalDevice);
CASESTR(btLibMeStatusHostTimeout);
CASESTR(btLibMeStatusUnsupportedFeature);
CASESTR(btLibMeStatusInvalidHciParam);
CASESTR(btLibMeStatusUserTerminated);
CASESTR(btLibMeStatusLowResources);
CASESTR(btLibMeStatusPowerOff);
CASESTR(btLibMeStatusLocalTerminated);
CASESTR(btLibMeStatusRepeatedAttempts);
CASESTR(btLibMeStatusPairingNotAllowed);
CASESTR(btLibMeStatusUnknownLmpPDU);
CASESTR(btLibMeStatusUnsupportedRemote);
CASESTR(btLibMeStatusScoOffsetRejected);
CASESTR(btLibMeStatusScoIntervalRejected);
CASESTR(btLibMeStatusScoAirModeRejected);
CASESTR(btLibMeStatusInvalidLmpParam);
CASESTR(btLibMeStatusUnspecifiedError);
CASESTR(btLibMeStatusUnsupportedLmpParam);
CASESTR(btLibMeStatusRoleChangeNotAllowed);
CASESTR(btLibMeStatusLmpResponseTimeout);
CASESTR(btLibMeStatusLmpTransdCollision);
CASESTR(btLibMeStatusLmpPduNotAllowed);
CASESTR(btLibL2DiscReasonUnknown);
CASESTR(btLibL2DiscUserRequest);
CASESTR(btLibL2DiscRequestTimeout);
CASESTR(btLibL2DiscLinkDisc);
CASESTR(btLibL2DiscQosViolation);
CASESTR(btLibL2DiscSecurityBlock);
CASESTR(btLibL2DiscConnPsmUnsupported);
CASESTR(btLibL2DiscConnSecurityBlock);
CASESTR(btLibL2DiscConnNoResources);
CASESTR(btLibL2DiscConfigUnacceptable);
CASESTR(btLibL2DiscConfigReject);
CASESTR(btLibL2DiscConfigOptions);
CASESTR(btLibServiceShutdownAppUse);
CASESTR(btLibServiceShutdownPowerCycled);
CASESTR(btLibServiceShutdownAclDrop);
CASESTR(btLibServiceShutdownTimeout);
CASESTR(btLibServiceShutdownDetached);
CASESTR(btLibErrInUseByService);
CASESTR(btLibErrNoPiconet);
CASESTR(btLibErrRoleChange);
CASESTR(btLibErrSdpNotMapped);
CASESTR(btLibErrAlreadyConnected);
CASESTR(btLibErrStackNotOpen);
CASESTR(btLibErrBatteryTooLow);
CASESTR(btLibErrNotFound);
CASESTR(btLibNotYetSupported);
default:
return "unknown err";
}
} /* btErrToStr */
static void
palm_bt_log( const char* btfunc, const char* func, Err err )
{
/* if ( errNone != err ) { */
XP_LOGF( "%s=>%s (in %s)", btfunc, btErrToStr(err), func );
/* } */
}
#endif /* DEBUG */
/*
use piconet? With that, HOST sets it up and clients join. That establishes
ACL links that can then be used to open sockets. I think. How to get from
piconet advertising to clients connecting?
See http://www.palmos.com/dev/support/docs/palmos/BTCompanion.html
NOTE: I've read conflicting reports on whether a listening socket is good for
accepting more than one inbound connection. Confirm. Or just do a piconet.
*/
#endif /* #ifdef XWFEATURE_BLUETOOTH */