xwords/palm/palmbt.c
ehouse a88d079029 Add debug menu and in response dump bt state to stream for display.
Track sends and receives.  Add listening state for master, and don't
reset its acl when slave goes away.
2006-09-23 22:58:33 +00:00

1116 lines
37 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_ACT_NONE
, PBT_ACT_CONNECT_ACL
, PBT_ACT_CONNECT_L2C
, PBT_ACT_GOTDATA
, PBT_ACT_TRYSEND
} PBT_ACTION;
typedef enum {
PBTST_NONE
, PBTST_LISTENING /* master */
, PBTST_ACL_CONNECTING /* slave */
, PBTST_ACL_CONNECTED /* slave */
, PBTST_L2C_CONNECTING /* slave */
, PBTST_L2C_CONNECTED /* slave */
} PBT_STATE;
#define PBT_MAX_ACTS 4
#define HASWORK(s) ((s)->queueCur != (s)->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_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;
XP_U16 queueCur;
XP_U16 queueNext;
PBT_ACTION actQueue[PBT_MAX_ACTS];
union {
struct {
} slave;
struct {
BtLibSocketRef listenSocket;
} master;
} u;
#ifdef DEBUG
struct {
XP_U32 totalSent;
XP_U32 totalRcvd;
} stats;
#endif
} PalmBTStuff;
#ifdef DEBUG
static void palm_bt_log( const char* btfunc, const char* func, Err err );
#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 ); }
#else
#define CALL_ERR(e,f,...) e = f(__VA_ARGS__)
#endif
/* 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 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_ACTION act );
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* actToStr(PBT_ACTION act);
static const char* stateToStr(PBT_STATE st);
static const char* connEnumToStr( BtLibAccessibleModeEnum mode );
#else
# define btErrToStr( err ) ""
# define btEvtToStr( evt ) ""
# define mgmtEvtToStr( evt ) ""
# define actToStr(act) ""
# define stateToStr(st) ""
# define connEnumToStr(mode) ""
#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 )
{
XP_Bool success = XP_FALSE;
PalmBTStuff* btStuff;
btStuff = pbt_checkInit( globals );
if ( NULL != btStuff ) {
BtLibDeviceAddressType addr;
Err 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 ); */
}
success = errNone == err;
}
return success;
} /* palm_bt_browse_device */
#ifdef DEBUG
void
palm_bt_getStats( PalmAppGlobals* globals, XWStreamCtxt* stream )
{
PalmBTStuff* btStuff = globals->btStuff;
if ( !btStuff ) {
stream_putString( stream, "bt not initialized" );
} else {
char buf[64];
XP_U16 cur;
XP_SNPRINTF( buf, sizeof(buf), "Role: %s\n",
btStuff->setState == PBT_MASTER? "master":"slave" );
stream_putString( stream, buf );
XP_SNPRINTF( buf, sizeof(buf), "State: %s\n",
stateToStr( GET_STATE(btStuff)) );
stream_putString( stream, buf );
XP_SNPRINTF( buf, sizeof(buf), "%d actions queued:\n",
((btStuff->queueNext + PBT_MAX_ACTS)
- btStuff->queueCur) % PBT_MAX_ACTS );
stream_putString( stream, buf );
for ( cur = btStuff->queueCur; cur != btStuff->queueNext;
cur = (cur + 1) % PBT_MAX_ACTS ) {
XP_SNPRINTF( buf, sizeof(buf), " - %s\n",
actToStr( btStuff->actQueue[cur] ) );
stream_putString( stream, buf );
}
XP_SNPRINTF( buf, sizeof(buf), "total sent: %ld\n",
btStuff->stats.totalSent );
stream_putString( stream, buf );
XP_SNPRINTF( buf, sizeof(buf), "total rcvd: %ld\n",
btStuff->stats.totalRcvd );
stream_putString( stream, buf );
}
}
#endif
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 );
XP_ASSERT( errNone == err );
/* 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;
/* Doesn't send events; returns errNone unless no resources avail. */
CALL_ERR( err, BtLibSocketListen, btStuff->btLibRefNum,
btStuff->u.master.listenSocket, &listenInfo );
if ( errNone == err ) {
/* Set state here to indicate I'm available, at least for
debugging? */
SET_STATE( btStuff, PBTST_LISTENING );
}
}
} /* 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 ( 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;
SET_STATE( btStuff, PBTST_NONE );
} /* pbt_takedown_master */
static void
pbt_do_work( PalmBTStuff* btStuff )
{
PBT_ACTION act;
Err err;
act = btStuff->actQueue[btStuff->queueCur++];
btStuff->queueCur %= PBT_MAX_ACTS;
XP_LOGF( "%s: evt=%s; state=%s", __FUNCTION__, actToStr(act),
stateToStr(GET_STATE(btStuff)) );
switch( act ) {
case PBT_ACT_CONNECT_ACL:
if ( GET_STATE(btStuff) == PBTST_NONE ) {
/* sends btLibManagementEventACLConnectOutbound */
CALL_ERR( err, BtLibLinkConnect, btStuff->btLibRefNum,
&btStuff->otherAddr );
if ( btLibErrPending == err ) {
SET_STATE( btStuff, PBTST_ACL_CONNECTING );
} else if ( btLibErrAlreadyConnected == err ) {
SET_STATE( btStuff, PBTST_ACL_CONNECTED );
pbt_postpone( btStuff, PBT_ACT_CONNECT_L2C );
}
}
break;
case PBT_ACT_CONNECT_L2C:
if ( GET_STATE(btStuff) == PBTST_ACL_CONNECTED ) {
XP_ASSERT( SOCK_INVAL == btStuff->dataSocket ); /* fired */
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 );
if ( errNone == err ) {
SET_STATE( btStuff, PBTST_L2C_CONNECTED );
} else if ( btLibErrPending == err ) {
SET_STATE( btStuff, PBTST_L2C_CONNECTING );
} else {
SET_STATE( btStuff, PBTST_NONE );
pbt_postpone( btStuff, PBT_ACT_CONNECT_ACL );
}
} else {
btStuff->dataSocket = SOCK_INVAL;
}
}
break;
case PBT_ACT_GOTDATA:
pbt_processIncoming( btStuff );
break;
case PBT_ACT_TRYSEND:
pbt_send_pending( btStuff, NULL );
break;
default:
XP_ASSERT( 0 );
}
LOG_RETURN_VOID();
} /* pbt_do_work */
static void
pbt_postpone( PalmBTStuff* btStuff, PBT_ACTION act )
{
EventType eventToPost = { .eType = nilEvent };
XP_LOGF( "%s(%s)", __FUNCTION__, actToStr(act) );
EvtAddEventToQueue( &eventToPost );
btStuff->actQueue[ btStuff->queueNext++ ] = act;
btStuff->queueNext %= PBT_MAX_ACTS;
XP_ASSERT( btStuff->queueNext != btStuff->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_ACT_GOTDATA );
#ifdef DEBUG
btStuff->stats.totalRcvd += inlen;
#endif
} 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_ACT_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? */
if ( GET_STATE(btStuff) != PBTST_NONE ) {
SET_STATE( btStuff, PBTST_NONE ); /* set first */
/* sends btLibManagementEventACLDisconnect */
CALL_ERR( err, BtLibLinkDisconnect, btLibRefNum, &btStuff->otherAddr );
}
} /* 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:
XP_ASSERT( btStuff->setState == PBT_MASTER );
/* sends btLibSocketEventConnectedInbound */
CALL_ERR( err, BtLibSocketRespondToConnection, btStuff->btLibRefNum,
sEvent->socket, true );
break;
case btLibSocketEventConnectedInbound:
XP_ASSERT( btStuff->setState == PBT_MASTER );
if ( sEvent->status == errNone ) {
btStuff->dataSocket = sEvent->eventData.newSocket;
XP_LOGF( "we have a data socket!!!" );
pbt_postpone( btStuff, PBT_ACT_TRYSEND );
SET_STATE( btStuff, PBTST_L2C_CONNECTED );
}
break;
case btLibSocketEventConnectedOutbound:
if ( errNone == sEvent->status ) {
SET_STATE( btStuff, PBTST_L2C_CONNECTED );
pbt_postpone( btStuff, PBT_ACT_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;
#ifdef DEBUG
btStuff->stats.totalSent += btStuff->vol.lenOut;
#endif
break;
case btLibSocketEventDisconnected:
/* 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_killL2C( btStuff, sEvent->socket );
pbt_postpone( btStuff, PBT_ACT_CONNECT_ACL );
} else if ( PBT_MASTER == btStuff->setState ) {
XP_ASSERT( btStuff->dataSocket == sEvent->socket );
CALL_ERR( err, BtLibSocketClose, btStuff->btLibRefNum,
sEvent->socket );
btStuff->dataSocket = SOCK_INVAL;
SET_STATE( btStuff, PBTST_LISTENING );
}
break;
case btLibL2DiscConnPsmUnsupported:
/* Probably need to warn the user when this happens since not having
established trust will be a common error. Or: figure out if
there's a way to fall back and establish trust programatically.
For alpha just do the error message. :-) Also, no point in
continuing to try to connect. User will have to quit in order to
establish trust. So warn once per inited session. */
XP_LOGF( "Crosswords not running on host or host not trusted." );
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_ACT_CONNECT_L2C );
} else {
SET_STATE( btStuff, PBTST_NONE );
pbt_postpone( btStuff, PBT_ACT_CONNECT_ACL );
}
break;
case btLibManagementEventACLConnectInbound:
if ( btLibErrNoError == mEvent->status ) {
XP_LOGF( "successful ACL connection!" );
XP_MEMCPY( &btStuff->otherAddr,
&mEvent->eventData.bdAddr,
sizeof(btStuff->otherAddr) );
SET_STATE( btStuff, PBTST_ACL_CONNECTED );
}
break;
case btLibManagementEventACLDisconnect:
/* This is getting called from inside the BtLibLinkDisconnect call!!!! */
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_ACT_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_LISTENING);
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*
actToStr(PBT_ACTION act)
{
switch( act ) {
CASESTR(PBT_ACT_NONE);
CASESTR(PBT_ACT_CONNECT_ACL);
CASESTR(PBT_ACT_CONNECT_L2C);
CASESTR(PBT_ACT_GOTDATA);
CASESTR(PBT_ACT_TRYSEND);
default:
XP_ASSERT(0);
return "";
}
} /* actToStr */
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 */