mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 03:09:04 +01:00
Invitation API and two (untested) implementations.
This commit is contained in:
@@ -21,8 +21,8 @@ import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.TransportConfig;
|
||||
import net.sf.briar.api.TransportId;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.plugins.StreamPluginCallback;
|
||||
import net.sf.briar.api.plugins.StreamPlugin;
|
||||
import net.sf.briar.api.plugins.StreamPluginCallback;
|
||||
import net.sf.briar.api.transport.StreamTransportConnection;
|
||||
import net.sf.briar.plugins.AbstractPlugin;
|
||||
import net.sf.briar.util.OsUtils;
|
||||
@@ -36,11 +36,12 @@ class BluetoothPlugin extends AbstractPlugin implements StreamPlugin {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BluetoothPlugin.class.getName());
|
||||
|
||||
private final Object discoveryLock = new Object();
|
||||
private final StreamPluginCallback callback;
|
||||
private final long pollingInterval;
|
||||
|
||||
private LocalDevice localDevice = null;
|
||||
private StreamConnectionNotifier streamConnectionNotifier = null;
|
||||
private LocalDevice localDevice = null; // Locking: this
|
||||
private StreamConnectionNotifier socket = null; // Locking: this
|
||||
|
||||
BluetoothPlugin(Executor executor, StreamPluginCallback callback,
|
||||
long pollingInterval) {
|
||||
@@ -72,40 +73,35 @@ class BluetoothPlugin extends AbstractPlugin implements StreamPlugin {
|
||||
}
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
executor.execute(createBinder());
|
||||
executor.execute(createContactSocketBinder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void stop() throws IOException {
|
||||
super.stop();
|
||||
if(streamConnectionNotifier != null) {
|
||||
streamConnectionNotifier.close();
|
||||
streamConnectionNotifier = null;
|
||||
if(socket != null) {
|
||||
socket.close();
|
||||
socket = null;
|
||||
}
|
||||
}
|
||||
|
||||
private Runnable createBinder() {
|
||||
private Runnable createContactSocketBinder() {
|
||||
return new Runnable() {
|
||||
public void run() {
|
||||
bind();
|
||||
bindContactSocket();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
private void bindContactSocket() {
|
||||
String uuid;
|
||||
synchronized(this) {
|
||||
if(!started) return;
|
||||
uuid = getUuid();
|
||||
makeDeviceDiscoverable();
|
||||
}
|
||||
// Try to make the device discoverable (requires root on Linux)
|
||||
try {
|
||||
localDevice.setDiscoverable(DiscoveryAgent.GIAC);
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
}
|
||||
// Bind the port
|
||||
String url = "btspp://localhost:" + uuid + ";name=" + uuid;
|
||||
// Bind the socket
|
||||
String url = "btspp://localhost:" + uuid + ";name=RFCOMM";
|
||||
StreamConnectionNotifier scn;
|
||||
try {
|
||||
scn = (StreamConnectionNotifier) Connector.open(url);
|
||||
@@ -123,10 +119,10 @@ class BluetoothPlugin extends AbstractPlugin implements StreamPlugin {
|
||||
}
|
||||
return;
|
||||
}
|
||||
streamConnectionNotifier = scn;
|
||||
socket = scn;
|
||||
setLocalBluetoothAddress(localDevice.getBluetoothAddress());
|
||||
}
|
||||
startListener();
|
||||
startContactAccepterThread();
|
||||
}
|
||||
|
||||
private synchronized String getUuid() {
|
||||
@@ -144,32 +140,13 @@ class BluetoothPlugin extends AbstractPlugin implements StreamPlugin {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
private void startListener() {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
listen();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
private void listen() {
|
||||
while(true) {
|
||||
StreamConnectionNotifier scn;
|
||||
StreamConnection s;
|
||||
synchronized(this) {
|
||||
if(!started) return;
|
||||
scn = streamConnectionNotifier;
|
||||
}
|
||||
try {
|
||||
s = scn.acceptAndOpen();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
return;
|
||||
}
|
||||
BluetoothTransportConnection conn =
|
||||
new BluetoothTransportConnection(s);
|
||||
callback.incomingConnectionCreated(conn);
|
||||
private synchronized void makeDeviceDiscoverable() {
|
||||
assert started;
|
||||
// Try to make the device discoverable (requires root on Linux)
|
||||
try {
|
||||
localDevice.setDiscoverable(DiscoveryAgent.GIAC);
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +157,35 @@ class BluetoothPlugin extends AbstractPlugin implements StreamPlugin {
|
||||
callback.setLocalProperties(p);
|
||||
}
|
||||
|
||||
private void startContactAccepterThread() {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
acceptContactConnections();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
private void acceptContactConnections() {
|
||||
while(true) {
|
||||
StreamConnectionNotifier scn;
|
||||
StreamConnection s;
|
||||
synchronized(this) {
|
||||
if(!started) return;
|
||||
scn = socket;
|
||||
}
|
||||
try {
|
||||
s = scn.acceptAndOpen();
|
||||
} catch(IOException e) {
|
||||
// This is expected when the socket is closed
|
||||
if(LOG.isLoggable(Level.INFO)) LOG.info(e.getMessage());
|
||||
return;
|
||||
}
|
||||
callback.incomingConnectionCreated(
|
||||
new BluetoothTransportConnection(s));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
}
|
||||
@@ -202,58 +208,53 @@ class BluetoothPlugin extends AbstractPlugin implements StreamPlugin {
|
||||
}
|
||||
|
||||
private void connectAndCallBack() {
|
||||
Map<ContactId, String> discovered = discover();
|
||||
Map<ContactId, String> discovered = discoverContactUrls();
|
||||
for(Entry<ContactId, String> e : discovered.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
String url = e.getValue();
|
||||
StreamTransportConnection conn = connect(c, url);
|
||||
if(conn != null) callback.outgoingConnectionCreated(c, conn);
|
||||
StreamTransportConnection s = connect(c, url);
|
||||
if(s != null) callback.outgoingConnectionCreated(c, s);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<ContactId, String> discover() {
|
||||
private Map<ContactId, String> discoverContactUrls() {
|
||||
DiscoveryAgent discoveryAgent;
|
||||
Map<String, ContactId> addresses;
|
||||
Map<ContactId, String> uuids;
|
||||
Map<ContactId, TransportProperties> remote;
|
||||
synchronized(this) {
|
||||
if(!started) return Collections.emptyMap();
|
||||
discoveryAgent = localDevice.getDiscoveryAgent();
|
||||
addresses = new HashMap<String, ContactId>();
|
||||
uuids = new HashMap<ContactId, String>();
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for(Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
TransportProperties p = e.getValue();
|
||||
String address = p.get("address");
|
||||
String uuid = p.get("uuid");
|
||||
if(address != null && uuid != null) {
|
||||
addresses.put(address, c);
|
||||
uuids.put(c, uuid);
|
||||
}
|
||||
remote = callback.getRemoteProperties();
|
||||
}
|
||||
Map<String, ContactId> addresses = new HashMap<String, ContactId>();
|
||||
Map<ContactId, String> uuids = new HashMap<ContactId, String>();
|
||||
for(Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
TransportProperties p = e.getValue();
|
||||
String address = p.get("address");
|
||||
String uuid = p.get("uuid");
|
||||
if(address != null && uuid != null) {
|
||||
addresses.put(address, c);
|
||||
uuids.put(c, uuid);
|
||||
}
|
||||
}
|
||||
BluetoothListener listener =
|
||||
new BluetoothListener(discoveryAgent, addresses, uuids);
|
||||
try {
|
||||
synchronized(listener) {
|
||||
ContactListener listener = new ContactListener(discoveryAgent,
|
||||
addresses, uuids);
|
||||
synchronized(discoveryLock) {
|
||||
try {
|
||||
discoveryAgent.startInquiry(DiscoveryAgent.GIAC, listener);
|
||||
listener.wait();
|
||||
return listener.waitForUrls();
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
} catch(InterruptedException ignored) {}
|
||||
return listener.getUrls();
|
||||
}
|
||||
}
|
||||
|
||||
private StreamTransportConnection connect(ContactId c, String url) {
|
||||
synchronized(this) {
|
||||
if(!started) return null;
|
||||
}
|
||||
try {
|
||||
synchronized(this) {
|
||||
if(!started) return null;
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
if(!remote.containsKey(c)) return null;
|
||||
}
|
||||
StreamConnection s = (StreamConnection) Connector.open(url);
|
||||
return new BluetoothTransportConnection(s);
|
||||
} catch(IOException e) {
|
||||
@@ -263,8 +264,137 @@ class BluetoothPlugin extends AbstractPlugin implements StreamPlugin {
|
||||
}
|
||||
|
||||
public StreamTransportConnection createConnection(ContactId c) {
|
||||
Map<ContactId, String> discovered = discover();
|
||||
String url = discovered.get(c);
|
||||
String url = discoverContactUrls().get(c);
|
||||
return url == null ? null : connect(c, url);
|
||||
}
|
||||
|
||||
public StreamTransportConnection sendInvitation(int code, long timeout) {
|
||||
return createInvitationConnection(code, timeout);
|
||||
}
|
||||
|
||||
public StreamTransportConnection acceptInvitation(int code, long timeout) {
|
||||
return createInvitationConnection(code, timeout);
|
||||
}
|
||||
|
||||
private StreamTransportConnection createInvitationConnection(int code,
|
||||
long timeout) {
|
||||
// The invitee's device may not be discoverable, so both parties must
|
||||
// try to initiate connections
|
||||
String uuid = convertInvitationCodeToUuid(code);
|
||||
ConnectionCallback c = new ConnectionCallback(uuid, timeout);
|
||||
startOutgoingInvitationThread(c);
|
||||
startIncomingInvitationThread(c);
|
||||
StreamConnection s = c.waitForConnection();
|
||||
return s == null ? null : new BluetoothTransportConnection(s);
|
||||
}
|
||||
|
||||
private String convertInvitationCodeToUuid(int code) {
|
||||
byte[] b = new byte[16];
|
||||
new Random(code).nextBytes(b);
|
||||
return StringUtils.toHexString(b);
|
||||
}
|
||||
|
||||
private void startOutgoingInvitationThread(final ConnectionCallback c) {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
createInvitationConnection(c);
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
private void createInvitationConnection(ConnectionCallback c) {
|
||||
DiscoveryAgent discoveryAgent;
|
||||
synchronized(this) {
|
||||
if(!started) return;
|
||||
discoveryAgent = localDevice.getDiscoveryAgent();
|
||||
}
|
||||
// Try to discover the other party until the invitation times out
|
||||
long end = System.currentTimeMillis() + c.getTimeout();
|
||||
String url = null;
|
||||
while(url == null && System.currentTimeMillis() < end) {
|
||||
InvitationListener listener = new InvitationListener(discoveryAgent,
|
||||
c.getUuid());
|
||||
synchronized(discoveryLock) {
|
||||
try {
|
||||
discoveryAgent.startInquiry(DiscoveryAgent.GIAC, listener);
|
||||
url = listener.waitForUrl();
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning(e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
synchronized(this) {
|
||||
if(!started) return;
|
||||
}
|
||||
}
|
||||
if(url == null) return;
|
||||
// Try to connect to the other party
|
||||
try {
|
||||
StreamConnection s = (StreamConnection) Connector.open(url);
|
||||
c.addConnection(s);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void startIncomingInvitationThread(final ConnectionCallback c) {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
bindInvitationSocket(c);
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
private void bindInvitationSocket(ConnectionCallback c) {
|
||||
synchronized(this) {
|
||||
if(!started) return;
|
||||
makeDeviceDiscoverable();
|
||||
}
|
||||
// Bind the socket
|
||||
String url = "btspp://localhost:" + c.getUuid() + ";name=RFCOMM";
|
||||
StreamConnectionNotifier scn;
|
||||
try {
|
||||
scn = (StreamConnectionNotifier) Connector.open(url);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
return;
|
||||
}
|
||||
startInvitationAccepterThread(c, scn);
|
||||
// Close the socket when the invitation times out
|
||||
try {
|
||||
Thread.sleep(c.getTimeout());
|
||||
scn.close();
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void startInvitationAccepterThread(final ConnectionCallback c,
|
||||
final StreamConnectionNotifier scn) {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
acceptInvitationConnection(c, scn);
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
private void acceptInvitationConnection(ConnectionCallback c,
|
||||
StreamConnectionNotifier scn) {
|
||||
synchronized(this) {
|
||||
if(!started) return;
|
||||
}
|
||||
try {
|
||||
StreamConnection s = scn.acceptAndOpen();
|
||||
c.addConnection(s);
|
||||
} catch(IOException e) {
|
||||
// This is expected when the socket is closed
|
||||
if(LOG.isLoggable(Level.INFO)) LOG.info(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user