xwords/xwords4/palm/palmbt.c
ehouse 0c915e61b0 First shot at bluetooth support (turned off in Makefile by default).
A full robot vs. robot game now works between two Treos.  Added UI to
choose BT as transport mechanism, and added new send proc to establish
socket connection between host and guest.  Works only for two devices:
no piconet yet.  No error recovery, ability to quit game in middle,
start new game, etc.
2006-08-23 04:44:55 +00:00

619 lines
22 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_PALM_BLUETOOTH
#include "xptypes.h"
#include "palmbt.h"
# include <BtLib.h>
# include <BtLibTypes.h>
#define L2CAPSOCKETMTU 500
#define SOCK_INVAL 0XFFFF
typedef struct PalmBTStuff {
DataCb cb;
PalmAppGlobals* globals;
XP_U16 btLibRefNum;
XP_U16 lenOut;
XP_UCHAR bufOut[L2CAPSOCKETMTU]; /* what's the mmu? */
BtLibSocketRef dataSocket;
XP_Bool sendInProgress;
XP_Bool sendPending;
XP_Bool amMaster;
union {
struct {
BtLibDeviceAddressType masterAddr;
BtLibSocketRef spdSocket;
} slave;
struct {
BtLibSocketRef listenSocket;
} master;
} u;
BtLibSdpRecordHandle sdpRecordH;
} PalmBTStuff;
#define LOG_ERR(f,e) palm_bt_log( #f, __FUNCTION__, e )
/* 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 void initBTStuff( PalmAppGlobals* globals, DataCb cb, XP_Bool amMaster );
static void palm_bt_log( const char* btfunc, const char* func, Err err );
static void pbt_connect_slave( PalmBTStuff* btStuff, BtLibL2CapPsmType psm );
#ifdef DEBUG
static const char* btErrToStr( Err err );
static const char* btEvtToStr( BtLibSocketEventEnum evt );
static const char* mgmtEvtToStr( BtLibManagementEventEnum event );
#else
# define btErrToStr( err ) ""
# define btEvtToStr( evt ) ""
# define mgmtEvtToStr( evt ) ""
#endif
/* callbacks */
static void libMgmtCallback( BtLibManagementEventType* mEvent, UInt32 refCon );
static void spdSocketCallback( BtLibSocketEventType* sEvent, UInt32 refCon );
static void l2SocketCallback( BtLibSocketEventType* sEvent, UInt32 refCon );
void
palm_bt_init( PalmAppGlobals* globals, DataCb cb, XP_Bool amMaster )
{
XP_LOGF( "%s(amMaster=%d)", __FUNCTION__, (XP_U16)amMaster );
initBTStuff( globals, cb, amMaster );
}
void
palm_bt_close( PalmAppGlobals* globals )
{
PalmBTStuff* btStuff = globals->btStuff;
if ( !!btStuff ) {
/* Need to unregister callbacks */
(void)BtLibUnregisterManagementNotification( btStuff->btLibRefNum,
libMgmtCallback );
if ( btStuff->amMaster
&& btStuff->u.master.listenSocket != SOCK_INVAL ) {
(void)BtLibSocketClose( btStuff->btLibRefNum,
btStuff->u.master.listenSocket );
btStuff->u.master.listenSocket = SOCK_INVAL;
}
if ( btStuff->dataSocket != SOCK_INVAL ) {
(void)BtLibSocketClose( btStuff->btLibRefNum,
btStuff->u.master.listenSocket );
btStuff->dataSocket = SOCK_INVAL;
}
if ( btStuff->btLibRefNum != 0 ) {
Err err = BtLibClose( btStuff->btLibRefNum );
LOG_ERR( BtLibClose, err );
XP_ASSERT( errNone == err );
}
XP_FREE( globals->mpool, btStuff );
globals->btStuff = NULL;
}
} /* palm_bt_close */
XP_Bool
btSocketIsOpen( PalmAppGlobals* globals )
{
return (NULL != globals->btStuff)
&& (globals->btStuff->dataSocket != 0);
}
static void
pbt_send_pending( PalmBTStuff* btStuff )
{
LOG_FUNC();
if ( btStuff->sendPending && !btStuff->sendInProgress ) {
if ( !!btStuff->dataSocket ) {
Err err = BtLibSocketSend( btStuff->btLibRefNum, btStuff->dataSocket,
btStuff->bufOut, btStuff->lenOut );
LOG_ERR( BtLibSocketSend, err );
if ( err == errNone ) {
// clear on receipt of btLibSocketEventSendComplete
btStuff->sendInProgress = XP_TRUE;
}
}
}
}
XP_S16
palm_bt_send( const XP_U8* buf, XP_U16 len, PalmAppGlobals* globals )
{
PalmBTStuff* btStuff;
XP_LOGF( "%s(len=%d)", __FUNCTION__, len );
btStuff = globals->btStuff;
XP_ASSERT( !!btStuff );
if ( !!btStuff ) {
if ( !btStuff->sendInProgress ) {
if ( len > sizeof( btStuff->bufOut ) ) {
len = sizeof( btStuff->bufOut );
}
XP_MEMCPY( btStuff->bufOut, buf, len );
btStuff->lenOut = len;
btStuff->sendPending = XP_TRUE;
pbt_send_pending( btStuff );
}
}
return -1;
} /* palm_bt_send */
static void
pbt_find_psm( PalmBTStuff* btStuff )
{
Err err;
XP_ASSERT( !btStuff->amMaster );
err = BtLibSocketCreate( btStuff->btLibRefNum, &btStuff->u.slave.spdSocket,
spdSocketCallback,
(UInt32)btStuff, btLibSdpProtocol );
LOG_ERR( BtLibSocketCreate, err );
err = BtLibSdpGetPsmByUuid( btStuff->btLibRefNum,
btStuff->u.slave.spdSocket,
&btStuff->u.slave.masterAddr,
(BtLibSdpUuidType*)&XWORDS_UUID, 1 );
LOG_ERR( BtLibSdpGetPSMByUuid, err );
}
static void
pbt_connect_slave( PalmBTStuff* btStuff, BtLibL2CapPsmType psm )
{
Err err;
LOG_FUNC();
XP_ASSERT( !btStuff->amMaster );
err = BtLibSocketCreate( btStuff->btLibRefNum, &btStuff->dataSocket,
l2SocketCallback, (UInt32)btStuff,
btLibL2CapProtocol );
LOG_ERR( BtLibSocketCreate, err );
if ( btLibErrNoError == err ) {
BtLibSocketConnectInfoType connInfo;
connInfo.data.L2Cap.remotePsm = psm;
connInfo.data.L2Cap.localMtu = L2CAPSOCKETMTU;
connInfo.data.L2Cap.minRemoteMtu = L2CAPSOCKETMTU;
connInfo.remoteDeviceP = &btStuff->u.slave.masterAddr;
err = BtLibSocketConnect( btStuff->btLibRefNum,
btStuff->dataSocket, &connInfo );
LOG_ERR( BtLibSocketConnect, err );
}
}
static void
pbt_setup_master( PalmBTStuff* btStuff )
{
/* Will eventually want to create a piconet here for more than two
devices to play.... */
Err err;
BtLibSocketListenInfoType listenInfo;
btStuff->u.master.listenSocket = SOCK_INVAL;
/* 1. BtLibSocketCreate: create an L2CAP socket. */
err = BtLibSocketCreate( btStuff->btLibRefNum,
&btStuff->u.master.listenSocket, l2SocketCallback,
(UInt32)btStuff, btLibL2CapProtocol );
LOG_ERR( BtLibSocketCreate, err );
/* 2. BtLibSocketListen: set up an L2CAP socket as a listener. */
XP_MEMSET( &listenInfo, 0, sizeof(listenInfo) );
listenInfo.data.L2Cap.localPsm = BT_L2CAP_RANDOM_PSM;
listenInfo.data.L2Cap.localMtu = L2CAPSOCKETMTU;
listenInfo.data.L2Cap.minRemoteMtu = L2CAPSOCKETMTU;
err = BtLibSocketListen( btStuff->btLibRefNum,
btStuff->u.master.listenSocket, &listenInfo );
LOG_ERR( BtLibSocketListen, err );
/* 3. BtLibSdpServiceRecordCreate: allocate a memory chunk that
represents an SDP service record. */
err = BtLibSdpServiceRecordCreate(btStuff->btLibRefNum, &btStuff->sdpRecordH );
LOG_ERR( BtLibSdpServiceRecordCreate, err );
/* 4. BtLibSdpServiceRecordSetAttributesForSocket: initialize an SDP
memory record so it can represent the newly-created L2CAP listener
socket as a service */
err = BtLibSdpServiceRecordSetAttributesForSocket(
btStuff->btLibRefNum, btStuff->u.master.listenSocket,
(BtLibSdpUuidType*)&XWORDS_UUID, 1, APPNAME,
StrLen(APPNAME), btStuff->sdpRecordH );
LOG_ERR( BtLibSdpServiceRecordSetAttributesForSocket, err );
/* 5. BtLibSdpServiceRecordStartAdvertising: make an SDP memory record
representing a local SDP service record visible to remote
devices. */
err = BtLibSdpServiceRecordStartAdvertising( btStuff->btLibRefNum,
btStuff->sdpRecordH );
LOG_ERR( BtLibSdpServiceRecordStartAdvertising, err );
} /* pbt_setup_master */
static void
pbt_setup_slave( PalmBTStuff* btStuff )
{
Err err;
static const BtLibClassOfDeviceType deviceFilter
= btLibCOD_ServiceAny
| btLibCOD_Major_Any // btLibCOD_Major_Computer
| btLibCOD_Minor_Comp_Any; //btLibCOD_Minor_Comp_Palm;
XP_ASSERT( !btStuff->amMaster );
err = BtLibDiscoverSingleDevice( btStuff->btLibRefNum, "Crosswords host",
(BtLibClassOfDeviceType*)&deviceFilter, 1,
&btStuff->u.slave.masterAddr,
false, false );
LOG_ERR( BtLibDiscoverSingleDevice, err );
if ( errNone == err ) {
err = BtLibLinkConnect( btStuff->btLibRefNum,
&btStuff->u.slave.masterAddr );
LOG_ERR( BtLibLinkConnect, err );
XP_ASSERT( err == btLibErrPending );
}
} /* pbt_setup_slave */
static void
initBTStuff( PalmAppGlobals* globals, DataCb cb, XP_Bool amMaster )
{
PalmBTStuff* btStuff;
Err err;
XP_U16 btLibRefNum;
btStuff = globals->btStuff;
if ( btStuff != NULL ) {
if ( btStuff->amMaster == amMaster ) {
/* nothing to do */
} else {
/* role change. Adapt... */
XP_ASSERT( 0 );
}
} else {
btStuff = XP_MALLOC( globals->mpool, sizeof(*btStuff) );
XP_ASSERT( !!btStuff );
XP_MEMSET( btStuff, 0, sizeof(*btStuff) );
globals->btStuff = btStuff;
btStuff->globals = globals;
btStuff->cb = cb;
btStuff->amMaster = amMaster;
btStuff->dataSocket = SOCK_INVAL;
err = SysLibFind( btLibName, &btLibRefNum );
XP_LOGF( "%s: SysLibFind(%s) => %d\n", __FUNCTION__, btLibName, err );
XP_ASSERT( errNone == err );
btStuff->btLibRefNum = btLibRefNum;
err = BtLibOpen( btLibRefNum, false );
LOG_ERR( BtLibOpen, err );
XP_ASSERT( errNone == err );
err = BtLibRegisterManagementNotification( btLibRefNum, libMgmtCallback,
(UInt32)btStuff );
LOG_ERR( BtLibRegisterManagementNotification, err );
if ( btStuff->amMaster ) {
pbt_setup_master( btStuff );
} else {
pbt_setup_slave( btStuff );
}
}
} /* initBTStuff */
static void
l2SocketCallback( BtLibSocketEventType* sEvent, UInt32 refCon )
{
PalmBTStuff* btStuff = (PalmBTStuff*)refCon;
BtLibSocketEventEnum event = sEvent->event;
Err err;
XP_LOGF( "%s(%s)", __FUNCTION__, btEvtToStr(event) );
switch( event ) {
case btLibSocketEventConnectRequest:
err = BtLibSocketRespondToConnection( btStuff->btLibRefNum,
sEvent->socket, true );
LOG_ERR( BtLibSocketRespondToConnection, err );
break;
case btLibSocketEventConnectedInbound:
if ( sEvent->status == errNone ) {
btStuff->dataSocket = sEvent->eventData.newSocket;
XP_LOGF( "we have a data socket!!!" );
pbt_send_pending( btStuff );
} else {
XP_LOGF( "%s: status = %d(%s)", __FUNCTION__,
sEvent->status, btErrToStr(sEvent->status) );
}
break;
case btLibSocketEventConnectedOutbound:
pbt_send_pending( btStuff );
break;
case btLibSocketEventData:
(*btStuff->cb)( btStuff->globals, sEvent->eventData.data.data,
sEvent->eventData.data.dataLen );
break;
default:
break;
}
} /* l2SocketCallback */
/***********************************************************************
* Callbacks
***********************************************************************/
static void
spdSocketCallback( BtLibSocketEventType* sEvent, UInt32 refCon )
{
Err err;
PalmBTStuff* btStuff = (PalmBTStuff*)refCon;
BtLibSocketEventEnum event = sEvent->event;
XP_LOGF( "%s(%s)", __FUNCTION__, btEvtToStr(event) );
XP_ASSERT( sEvent->socket == btStuff->u.slave.spdSocket );
XP_ASSERT( !btStuff->amMaster );
switch( event ) {
case btLibSocketEventSdpGetPsmByUuid:
if ( btLibErrNoError == sEvent->status ) {
err = BtLibSocketClose( btStuff->btLibRefNum,
sEvent->socket );
LOG_ERR( BtLibSocketClose, err );
btStuff->u.slave.spdSocket = SOCK_INVAL;
pbt_connect_slave( btStuff, sEvent->eventData.sdpByUuid.param.psm );
}
break;
default: /* happy now, compiler? */
break;
}
} /* spdSocketCallback */
static void
libMgmtCallback( BtLibManagementEventType* mEvent, UInt32 refCon )
{
PalmBTStuff* btStuff = (PalmBTStuff*)refCon;
BtLibManagementEventEnum event = mEvent->event;
XP_LOGF( "%s(%s)", __FUNCTION__, mgmtEvtToStr(event) );
switch( event ) {
case btLibManagementEventACLConnectOutbound:
if ( btLibErrNoError == mEvent->status ) {
XP_LOGF( "successful ACL connection to master!" );
pbt_find_psm( btStuff );
}
break;
case btLibManagementEventACLConnectInbound:
if ( btLibErrNoError == mEvent->status ) {
XP_LOGF( "successful ACL connection!" );
}
break;
default:
break;
}
} /* libMgmtCallback */
/***********************************************************************
* Debug helpers for verbose logging
***********************************************************************/
#ifdef DEBUG
# define CASESTR(e) case(e): return #e
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(btLibErrNoError);
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 from %s called in %s", btErrToStr(err), btfunc, 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_PALM_BLUETOOTH */