use insecure; get own mac addr

Try using both secure and insecure sockets. The latter appears to cause
fewer problems on OS/device combos with crappy BT. It's only possible if
I know the addr of the device I want to, so hack around that being
secret by passing it on request.
This commit is contained in:
Eric House 2020-11-21 16:25:49 -08:00
parent dbfe8083ca
commit 776cc5703d

View file

@ -61,10 +61,12 @@ public class BTUtils {
private static final String TAG = BTUtils.class.getSimpleName(); private static final String TAG = BTUtils.class.getSimpleName();
private static final String BOGUS_MARSHMALLOW_ADDR = "02:00:00:00:00:00"; private static final String BOGUS_MARSHMALLOW_ADDR = "02:00:00:00:00:00";
private static final int MAX_PACKET_LEN = 4 * 1024; private static final int MAX_PACKET_LEN = 4 * 1024;
private static int CONNECT_SLEEP_MS = 2500; private static final int CONNECT_SLEEP_MS = 2500;
private static final String KEY_OWN_MAC = TAG + ":own_mac";
private static Set<ScanListener> sListeners = new HashSet<>(); private static Set<ScanListener> sListeners = new HashSet<>();
private static Map<String, PacketAccumulator> sSenders = new HashMap<>(); private static Map<String, PacketAccumulator> sSenders = new HashMap<>();
private static Map<String, String> s_namesToAddrs; private static Map<String, String> s_namesToAddrs;
private static String sMyMacAddr = null;
private enum BTCmd { private enum BTCmd {
BAD_PROTO, BAD_PROTO,
@ -82,6 +84,8 @@ public class BTUtils {
MESG_GAMEGONE, MESG_GAMEGONE,
_REMOVE_FOR, // unused _REMOVE_FOR, // unused
INVITE_DUP_INVITE, INVITE_DUP_INVITE,
MAC_ASK, // ask peer what my mac address is
MAC_REPLY, // reply to above
}; };
interface ScanListener { interface ScanListener {
@ -152,6 +156,7 @@ public class BTUtils {
Log.d( TAG, "init()" ); Log.d( TAG, "init()" );
sAppName = appName; sAppName = appName;
sUUID = uuid; sUUID = uuid;
loadOwnMac( context );
onResume( context ); onResume( context );
} }
@ -165,7 +170,8 @@ public class BTUtils {
Log.d( TAG, "onResume()" ); Log.d( TAG, "onResume()" );
// Should only run this in the background if we have BT games // Should only run this in the background if we have BT games
// going. In the foreground we want to // going. In the foreground we want to
ListenThread.getOrStart(); SecureListenThread.getOrStart();
InsecureListenThread.getOrStart();
} }
static void onStop( Context context ) static void onStop( Context context )
@ -192,11 +198,7 @@ public class BTUtils {
String[] result = null; String[] result = null;
BluetoothAdapter adapter = getAdapterIf(); BluetoothAdapter adapter = getAdapterIf();
if ( null != adapter ) { if ( null != adapter ) {
String addr = adapter.getAddress(); result = new String[] { adapter.getName(), sMyMacAddr };
if ( isBogusAddr( addr ) ) {
addr = null;
}
result = new String[] { adapter.getName(), addr };
} }
return result; return result;
} }
@ -214,7 +216,8 @@ public class BTUtils {
private static void stopThreads() private static void stopThreads()
{ {
ListenThread.stopSelf(); SecureListenThread.stopSelf();
InsecureListenThread.stopSelf();
ReadThread.stopSelf(); ReadThread.stopSelf();
} }
@ -227,6 +230,17 @@ public class BTUtils {
return result; return result;
} }
private static void loadOwnMac( Context context )
{
sMyMacAddr = DBUtils.getStringFor( context, KEY_OWN_MAC, null );
}
private static void storeOwnMac( String macAddr )
{
Context context = getContext();
DBUtils.setStringFor( context, KEY_OWN_MAC, macAddr );
}
public static int sendPacket( Context context, byte[] buf, String msgID, public static int sendPacket( Context context, byte[] buf, String msgID,
CommsAddrRec targetAddr, int gameID ) CommsAddrRec targetAddr, int gameID )
{ {
@ -587,7 +601,7 @@ public class BTUtils {
private BTHelper mHelper; private BTHelper mHelper;
private boolean mPostOnResponse; private boolean mPostOnResponse;
PacketAccumulator( String addr ) { this(addr, 20000); } PacketAccumulator( String addr ) { this( addr, 20000 ); }
// Ping case -- used only once // Ping case -- used only once
PacketAccumulator( String addr, int timeoutMS ) PacketAccumulator( String addr, int timeoutMS )
@ -603,6 +617,11 @@ public class BTUtils {
Assert.assertTrueNR( null != mAdapter ); Assert.assertTrueNR( null != mAdapter );
mHelper = new BTHelper( mName, mAddr ); mHelper = new BTHelper( mName, mAddr );
mPostOnResponse = true; mPostOnResponse = true;
if ( null == sMyMacAddr ) {
addGetMac();
}
start(); start();
} }
@ -695,6 +714,15 @@ public class BTUtils {
} }
} }
private void addGetMac()
{
try {
append( BTCmd.MAC_ASK, new OutputPair() );
} catch ( IOException ioe ) {
Assert.failDbg();
}
}
private void append( BTCmd cmd, OutputPair op ) throws IOException private void append( BTCmd cmd, OutputPair op ) throws IOException
{ {
append( cmd, 0, null, op ); append( cmd, 0, null, op );
@ -837,16 +865,15 @@ public class BTUtils {
BluetoothSocket socket = null; BluetoothSocket socket = null;
try { try {
Log.d( TAG, "trySend(): attempting to connect to %s", mName ); Log.d( TAG, "trySend(): attempting to connect to %s", mName );
socket = mAdapter.getRemoteDevice( getBTAddr() ) BluetoothDevice dev = mAdapter.getRemoteDevice( getBTAddr() );
.createRfcommSocketToServiceRecord( sUUID ); socket = connect( dev, mTimeoutMS );
DataOutputStream dos = connect( socket, mTimeoutMS ); if ( null == socket ) {
if ( null == dos ) {
setNoHost(); setNoHost();
updateStatusOut( false ); updateStatusOut( false );
} else { } else {
Log.d( TAG, "PacketAccumulator.run(): connect(%s) => %s", Log.d( TAG, "PacketAccumulator.run(): connect(%s) => %s",
mName, dos ); mName, socket );
nDone += writeAndCheck( socket, dos ); nDone += writeAndCheck( socket );
updateStatusOut( true ); updateStatusOut( true );
if ( mPostOnResponse ) { if ( mPostOnResponse ) {
callListeners( socket.getRemoteDevice() ); callListeners( socket.getRemoteDevice() );
@ -864,11 +891,12 @@ public class BTUtils {
return nDone; return nDone;
} }
private int writeAndCheck( BluetoothSocket socket, DataOutputStream dos ) private int writeAndCheck( BluetoothSocket socket )
throws IOException throws IOException
{ {
DataOutputStream dos = new DataOutputStream( socket.getOutputStream() );
Log.d( TAG, "%s.writeAndCheck() IN", this ); Log.d( TAG, "%s.writeAndCheck() IN", this );
Assert.assertNotNull( dos ); dos.writeByte( BT_PROTO );
List<MsgElem> localElems = new ArrayList<>(); List<MsgElem> localElems = new ArrayList<>();
try ( DeadlockWatch dw = new DeadlockWatch( this ) ) { try ( DeadlockWatch dw = new DeadlockWatch( this ) ) {
@ -986,6 +1014,16 @@ public class BTUtils {
} }
break; break;
case MAC_ASK:
if ( BTCmd.MAC_REPLY == reply ) {
String mac = inStream.readUTF();
Assert.assertTrueNR( null == sMyMacAddr || sMyMacAddr.equals(mac) );
sMyMacAddr = mac;
Log.d( TAG, "got %s as my mac addr", sMyMacAddr );
storeOwnMac( sMyMacAddr );
}
break;
default: default:
Log.e( TAG, "handleReply(cmd=%s) case not handled", cmd ); Log.e( TAG, "handleReply(cmd=%s) case not handled", cmd );
Assert.failDbg(); // fired Assert.failDbg(); // fired
@ -996,9 +1034,9 @@ public class BTUtils {
} }
} }
private DataOutputStream connect( BluetoothSocket socket, int timeout ) private BluetoothSocket connect( BluetoothDevice remote, int timeout )
{ {
BluetoothDevice remote = socket.getRemoteDevice(); BluetoothSocket socket = null;
String name = remote.getName(); String name = remote.getName();
String addr = remote.getAddress(); String addr = remote.getAddress();
Log.w( TAG, "connect(%s/%s, timeout=%d) starting", name, addr, timeout ); Log.w( TAG, "connect(%s/%s, timeout=%d) starting", name, addr, timeout );
@ -1006,22 +1044,21 @@ public class BTUtils {
// Docs say always call cancelDiscovery before trying to connect // Docs say always call cancelDiscovery before trying to connect
mAdapter.cancelDiscovery(); mAdapter.cancelDiscovery();
DataOutputStream dos = null;
// Retry for some time. Some devices take a long time to generate and // Retry for some time. Some devices take a long time to generate and
// broadcast ACL conn ACTION // broadcast ACL conn ACTION
int nTries = 0; int nTries = 0;
for ( long end = timeout + System.currentTimeMillis(); ; ) { for ( long end = timeout + System.currentTimeMillis(); ; ) {
try { try {
// Log.d( TAG, "trying connect(%s/%s) (check accept() logs)", name, addr ); boolean useInsecure = 0 == nTries++ % 2;
++nTries; socket = useInsecure
? remote.createInsecureRfcommSocketToServiceRecord( sUUID )
: remote.createRfcommSocketToServiceRecord( sUUID );
socket.connect(); socket.connect();
Log.i( TAG, "connect(%s/%s) succeeded after %d tries", Log.i( TAG, "connect(%s/%s/useInsecure=%b) succeeded after %d tries",
name, addr, nTries ); name, addr, useInsecure, nTries );
dos = new DataOutputStream( socket.getOutputStream() );
dos.writeByte( BT_PROTO );
break; // success!!! break; // success!!!
} catch (IOException|SecurityException ioe) { } catch (IOException|SecurityException ioe) {
socket = null;
// Log.d( TAG, "connect(): %s", ioe.getMessage() ); // Log.d( TAG, "connect(): %s", ioe.getMessage() );
long msLeft = end - System.currentTimeMillis(); long msLeft = end - System.currentTimeMillis();
if ( msLeft <= 0 ) { if ( msLeft <= 0 ) {
@ -1034,8 +1071,8 @@ public class BTUtils {
} }
} }
} }
Log.e( TAG, "connect(%s/%s) => %s", name, addr, dos ); Log.e( TAG, "connect(%s/%s) => %s", name, addr, socket );
return dos; return socket;
} }
private void setNoHost() private void setNoHost()
@ -1076,26 +1113,27 @@ public class BTUtils {
} }
} // class PacketAccumulator } // class PacketAccumulator
private static class ListenThread extends Thread { private abstract static class ListenThread extends Thread {
private static AtomicReference<Thread> sInstance = new AtomicReference<>();
private BluetoothAdapter mAdapter; private BluetoothAdapter mAdapter;
private BluetoothServerSocket mServerSocket; private BluetoothServerSocket mServerSocket;
private ListenThread( BluetoothAdapter adapter ) private ListenThread( BluetoothAdapter adapter )
{ {
mAdapter = adapter; mAdapter = adapter;
sInstance.set( this );
} }
abstract BluetoothServerSocket openListener( BluetoothAdapter adapter )
throws IOException;
@Override @Override
public void run() public void run()
{ {
Log.d( TAG, "ListenThread: %s.run() starting", this ); String simpleName = getClass().getSimpleName();
Log.d( TAG, "%s.run() starting", simpleName );
try { try {
Assert.assertTrueNR( null != sAppName && null != sUUID ); Assert.assertTrueNR( null != sAppName && null != sUUID );
mServerSocket = mAdapter mServerSocket = openListener( mAdapter );
.listenUsingRfcommWithServiceRecord( sAppName, sUUID );
} catch ( IOException ioe ) { } catch ( IOException ioe ) {
Log.ex( TAG, ioe ); Log.ex( TAG, ioe );
mServerSocket = null; mServerSocket = null;
@ -1108,11 +1146,12 @@ public class BTUtils {
mServerSocket = null; mServerSocket = null;
} }
while ( null != mServerSocket && this == sInstance.get() ) { AtomicReference<Thread> wrapper = getWrapper();
Log.d( TAG, "%s.run(): calling accept()", this ); while ( null != mServerSocket && this == wrapper.get() ) {
Log.d( TAG, "%s.run(): calling accept()", simpleName );
try { try {
BluetoothSocket socket = mServerSocket.accept(); // blocks BluetoothSocket socket = mServerSocket.accept(); // blocks
Log.d( TAG, "%s.run(): accept() returned", this ); Log.d( TAG, "%s.run(): accept() returned", simpleName );
ReadThread.handle( socket ); ReadThread.handle( socket );
} catch ( IOException ioe ) { } catch ( IOException ioe ) {
Log.ex( TAG, ioe ); Log.ex( TAG, ioe );
@ -1120,46 +1159,121 @@ public class BTUtils {
} }
} }
clearInstance( sInstance, this ); clearInstance( wrapper, this );
Log.d( TAG, "ListenThread: %s.run() exiting", this ); Log.d( TAG, "%s.run() exiting", simpleName );
} }
private static ListenThread getOrStart() void closeListener()
{
BluetoothServerSocket serverSocket = mServerSocket;
if ( null != serverSocket ) {
try {
serverSocket.close();
} catch ( IOException ioe ) {
Log.ex( TAG, ioe );
}
}
}
abstract AtomicReference<Thread> getWrapper();
}
private static class SecureListenThread extends ListenThread {
private static AtomicReference<Thread> sInstance = new AtomicReference<>();
private SecureListenThread( BluetoothAdapter adapter )
{
super( adapter );
Assert.assertTrueNR( null == sInstance.get() );
sInstance.set( this );
}
@Override
BluetoothServerSocket openListener( BluetoothAdapter adapter )
throws IOException
{
return adapter.listenUsingRfcommWithServiceRecord( sAppName, sUUID );
}
@Override
AtomicReference<Thread> getWrapper() { return sInstance; }
private static void getOrStart()
{ {
ListenThread result = null;
BluetoothAdapter adapter = getAdapterIf(); BluetoothAdapter adapter = getAdapterIf();
if ( null != adapter ) { if ( null != adapter ) {
synchronized ( sInstance ) { synchronized ( sInstance ) {
result = (ListenThread)sInstance.get(); SecureListenThread thread = (SecureListenThread)sInstance.get();
if ( null == result ) { if ( null == thread ) {
result = new ListenThread( adapter ); thread = new SecureListenThread( adapter );
Assert.assertTrueNR( result == sInstance.get() ); Assert.assertTrueNR( thread == sInstance.get() );
result.start(); thread.start();
} }
} }
} }
return result;
} }
private static void stopSelf() private static void stopSelf()
{ {
synchronized ( sInstance ) { synchronized ( sInstance ) {
ListenThread self = (ListenThread)sInstance.get(); SecureListenThread self = (SecureListenThread)sInstance.get();
Log.d( TAG, "ListenThread.stopSelf(): self: %s", self ); Log.d( TAG, "SecureListenThread.stopSelf(): self: %s", self );
if ( null != self ) { if ( null != self ) {
sInstance.set( null ); sInstance.set( null );
self.closeListener();
}
}
}
}
BluetoothServerSocket serverSocket = self.mServerSocket; private static class InsecureListenThread extends ListenThread {
if ( null != serverSocket ) { private static AtomicReference<Thread> sInstance = new AtomicReference<>();
try {
serverSocket.close(); private InsecureListenThread( BluetoothAdapter adapter )
} catch ( IOException ioe ) { {
Log.ex( TAG, ioe ); super( adapter );
} Assert.assertTrueNR( null == sInstance.get() );
sInstance.set( this );
}
@Override
BluetoothServerSocket openListener( BluetoothAdapter adapter )
throws IOException
{
return adapter
.listenUsingInsecureRfcommWithServiceRecord( sAppName, sUUID );
}
@Override
AtomicReference<Thread> getWrapper() { return sInstance; }
private static void getOrStart()
{
BluetoothAdapter adapter = getAdapterIf();
if ( null != adapter ) {
synchronized ( sInstance ) {
InsecureListenThread thread = (InsecureListenThread)sInstance.get();
if ( null == thread ) {
thread = new InsecureListenThread( adapter );
Assert.assertTrueNR( thread == sInstance.get() );
thread.start();
} }
} }
} }
} }
private static void stopSelf()
{
synchronized ( sInstance ) {
InsecureListenThread self = (InsecureListenThread)sInstance.get();
Log.d( TAG, "InsecureListenThread.stopSelf(): self: %s", self );
if ( null != self ) {
sInstance.set( null );
self.closeListener();
}
}
}
} }
private static class ReadThread extends Thread { private static class ReadThread extends Thread {
@ -1169,9 +1283,8 @@ public class BTUtils {
static void handle( BluetoothSocket incoming ) static void handle( BluetoothSocket incoming )
{ {
Log.d( TAG, "read(%s)", incoming ); Log.d( TAG, "read(from=%s)", incoming.getRemoteDevice().getName() );
ReadThread self = getOrStart(); getOrStart().enqueue( incoming );
self.enqueue( incoming );
} }
private ReadThread() private ReadThread()
@ -1280,6 +1393,9 @@ public class BTUtils {
gameID = dis.readInt(); gameID = dis.readInt();
receiveGameGone( gameID, socket ); receiveGameGone( gameID, socket );
break; break;
case MAC_ASK:
receiveMacAsk( socket );
break;
default: default:
Assert.failDbg(); Assert.failDbg();
break; break;
@ -1355,6 +1471,14 @@ public class BTUtils {
writeBack( socket, BTCmd.MESG_ACCPT ); writeBack( socket, BTCmd.MESG_ACCPT );
} }
private void receiveMacAsk( BluetoothSocket socket ) throws IOException
{
DataOutputStream os = new DataOutputStream( socket.getOutputStream() );
os.writeByte( BTCmd.MAC_REPLY.ordinal() );
String addr = socket.getRemoteDevice().getAddress();
os.writeUTF( addr );
}
private void enqueue( BluetoothSocket socket ) private void enqueue( BluetoothSocket socket )
{ {
mQueue.add( socket ); mQueue.add( socket );