mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 11:19:04 +01:00
Invitation API and two (untested) implementations.
This commit is contained in:
@@ -23,4 +23,28 @@ public interface BatchPlugin extends Plugin {
|
||||
* Returns null if a writer could not be created.
|
||||
*/
|
||||
BatchTransportWriter createWriter(ContactId c);
|
||||
|
||||
/**
|
||||
* Starts the invitation process from the inviter's side. Returns null if
|
||||
* no connection can be established within the given timeout.
|
||||
*/
|
||||
BatchTransportWriter sendInvitation(int code, long timeout);
|
||||
|
||||
/**
|
||||
* Starts the invitation process from the invitee's side. Returns null if
|
||||
* no connection can be established within the given timeout.
|
||||
*/
|
||||
BatchTransportReader acceptInvitation(int code, long timeout);
|
||||
|
||||
/**
|
||||
* Continues the invitation process from the invitee's side. Returns null
|
||||
* if no connection can be established within the given timeout.
|
||||
*/
|
||||
BatchTransportWriter sendInvitationResponse(int code, long timeout);
|
||||
|
||||
/**
|
||||
* Continues the invitation process from the inviter's side. Returns null
|
||||
* if no connection can be established within the given timeout.
|
||||
*/
|
||||
BatchTransportReader acceptInvitationResponse(int code, long timeout);
|
||||
}
|
||||
|
||||
@@ -15,4 +15,16 @@ public interface StreamPlugin extends Plugin {
|
||||
* Returns null if a connection could not be created.
|
||||
*/
|
||||
StreamTransportConnection createConnection(ContactId c);
|
||||
|
||||
/**
|
||||
* Starts the invitation process from the inviter's side. Returns null if
|
||||
* no connection can be established within the given timeout.
|
||||
*/
|
||||
StreamTransportConnection sendInvitation(int code, long timeout);
|
||||
|
||||
/**
|
||||
* Starts the invitation process from the invitee's side. Returns null if
|
||||
* no connection can be established within the given timeout.
|
||||
*/
|
||||
StreamTransportConnection acceptInvitation(int code, long timeout);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,10 @@ class DatabaseCleanerImpl implements DatabaseCleaner, Runnable {
|
||||
} else {
|
||||
try {
|
||||
wait(msBetweenSweeps);
|
||||
} catch(InterruptedException ignored) {}
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
} catch(DbException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
|
||||
@@ -362,7 +362,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
while(closed) {
|
||||
try {
|
||||
connections.wait();
|
||||
} catch(InterruptedException ignored) {}
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
txn = connections.poll();
|
||||
}
|
||||
@@ -433,7 +436,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " open connections");
|
||||
try {
|
||||
connections.wait();
|
||||
} catch(InterruptedException ignored) {}
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning(e.getMessage());
|
||||
}
|
||||
for(Connection c : connections) c.close();
|
||||
openConnections -= connections.size();
|
||||
connections.clear();
|
||||
|
||||
@@ -12,15 +12,17 @@ import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.UIManager;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import net.sf.briar.api.i18n.FontManager;
|
||||
import net.sf.briar.api.i18n.I18n;
|
||||
import net.sf.briar.util.FileUtils;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
// Needs to be public for installer
|
||||
public class I18nImpl implements I18n {
|
||||
|
||||
@@ -70,6 +72,9 @@ public class I18nImpl implements I18n {
|
||||
"ProgressMonitor.progressText"
|
||||
};
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(I18nImpl.class.getName());
|
||||
|
||||
private final Object bundleLock = new Object();
|
||||
private final ClassLoader loader = I18n.class.getClassLoader();
|
||||
private final Set<Listener> listeners = new HashSet<Listener>();
|
||||
@@ -97,7 +102,10 @@ public class I18nImpl implements I18n {
|
||||
for(String key : uiManagerKeys) {
|
||||
try {
|
||||
UIManager.put(key, bundle.getString(key));
|
||||
} catch(MissingResourceException ignored) {}
|
||||
} catch(MissingResourceException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ public abstract class AbstractPlugin implements Plugin {
|
||||
|
||||
protected final Executor executor;
|
||||
|
||||
// This field must only be accessed with this's lock held
|
||||
protected boolean started = false;
|
||||
protected boolean started = false; // Locking: this
|
||||
|
||||
protected AbstractPlugin(Executor executor) {
|
||||
this.executor = executor;
|
||||
|
||||
@@ -51,7 +51,10 @@ class PollerImpl implements Poller, Runnable {
|
||||
} else {
|
||||
try {
|
||||
wait(p.time - now);
|
||||
} catch(InterruptedException ignored) {}
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.microedition.io.StreamConnection;
|
||||
|
||||
class ConnectionCallback {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConnectionCallback.class.getName());
|
||||
|
||||
private final String uuid;
|
||||
private final long timeout;
|
||||
private final long end;
|
||||
|
||||
private StreamConnection connection = null; // Locking: this
|
||||
|
||||
ConnectionCallback(String uuid, long timeout) {
|
||||
this.uuid = uuid;
|
||||
this.timeout = timeout;
|
||||
end = System.currentTimeMillis() + timeout;
|
||||
}
|
||||
|
||||
String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
synchronized StreamConnection waitForConnection() {
|
||||
long now = System.currentTimeMillis();
|
||||
while(connection == null && now < end) {
|
||||
try {
|
||||
wait(end - now);
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
}
|
||||
now = System.currentTimeMillis();
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
synchronized void addConnection(StreamConnection s) {
|
||||
if(connection == null) {
|
||||
connection = s;
|
||||
notifyAll();
|
||||
} else {
|
||||
// Redundant connection
|
||||
try {
|
||||
s.close();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -8,6 +9,7 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.DataElement;
|
||||
import javax.bluetooth.DeviceClass;
|
||||
import javax.bluetooth.DiscoveryAgent;
|
||||
import javax.bluetooth.DiscoveryListener;
|
||||
@@ -17,12 +19,10 @@ import javax.bluetooth.UUID;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
|
||||
class BluetoothListener implements DiscoveryListener {
|
||||
class ContactListener implements DiscoveryListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BluetoothListener.class.getName());
|
||||
|
||||
private static final int[] ATTRIBUTES = { 0x100 }; // Service name
|
||||
Logger.getLogger(ContactListener.class.getName());
|
||||
|
||||
private final AtomicInteger searches = new AtomicInteger(1);
|
||||
private final DiscoveryAgent discoveryAgent;
|
||||
@@ -30,7 +30,9 @@ class BluetoothListener implements DiscoveryListener {
|
||||
private final Map<ContactId, String> uuids;
|
||||
private final Map<ContactId, String> urls;
|
||||
|
||||
BluetoothListener(DiscoveryAgent discoveryAgent,
|
||||
private boolean finished = false; // Locking: this
|
||||
|
||||
ContactListener(DiscoveryAgent discoveryAgent,
|
||||
Map<String, ContactId> addresses, Map<ContactId, String> uuids) {
|
||||
this.discoveryAgent = discoveryAgent;
|
||||
this.addresses = addresses;
|
||||
@@ -38,7 +40,14 @@ class BluetoothListener implements DiscoveryListener {
|
||||
urls = Collections.synchronizedMap(new HashMap<ContactId, String>());
|
||||
}
|
||||
|
||||
public Map<ContactId, String> getUrls() {
|
||||
public synchronized Map<ContactId, String> waitForUrls() {
|
||||
while(!finished) {
|
||||
try {
|
||||
wait();
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
@@ -52,7 +61,7 @@ class BluetoothListener implements DiscoveryListener {
|
||||
UUID[] uuids = new UUID[] { new UUID(uuid, false) };
|
||||
// Try to discover the services associated with the UUID
|
||||
try {
|
||||
discoveryAgent.searchServices(ATTRIBUTES, uuids, device, this);
|
||||
discoveryAgent.searchServices(null, uuids, device, this);
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
}
|
||||
@@ -62,6 +71,7 @@ class BluetoothListener implements DiscoveryListener {
|
||||
public void inquiryCompleted(int discoveryType) {
|
||||
if(searches.decrementAndGet() == 0) {
|
||||
synchronized(this) {
|
||||
finished = true;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
@@ -73,16 +83,33 @@ class BluetoothListener implements DiscoveryListener {
|
||||
RemoteDevice device = record.getHostDevice();
|
||||
ContactId c = addresses.get(device.getBluetoothAddress());
|
||||
if(c == null) continue;
|
||||
// Store the URL
|
||||
String url = record.getConnectionURL(
|
||||
// Do we have a UUID for this contact?
|
||||
String uuid = uuids.get(c);
|
||||
if(uuid == null) return;
|
||||
// Does this service have a URL?
|
||||
String serviceUrl = record.getConnectionURL(
|
||||
ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
|
||||
if(url != null) urls.put(c, url);
|
||||
if(serviceUrl == null) continue;
|
||||
// Does this service have the UUID we're looking for?
|
||||
DataElement classIds = record.getAttributeValue(0x1);
|
||||
if(classIds == null) continue;
|
||||
@SuppressWarnings("unchecked")
|
||||
Enumeration<DataElement> e =
|
||||
(Enumeration<DataElement>) classIds.getValue();
|
||||
for(DataElement classId : Collections.list(e)) {
|
||||
UUID serviceUuid = (UUID) classId.getValue();
|
||||
if(uuid.equals(serviceUuid.toString())) {
|
||||
// The UUID matches - store the URL
|
||||
urls.put(c, serviceUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void serviceSearchCompleted(int transaction, int response) {
|
||||
if(searches.decrementAndGet() == 0) {
|
||||
synchronized(this) {
|
||||
finished = true;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.DataElement;
|
||||
import javax.bluetooth.DeviceClass;
|
||||
import javax.bluetooth.DiscoveryAgent;
|
||||
import javax.bluetooth.DiscoveryListener;
|
||||
import javax.bluetooth.RemoteDevice;
|
||||
import javax.bluetooth.ServiceRecord;
|
||||
import javax.bluetooth.UUID;
|
||||
|
||||
class InvitationListener implements DiscoveryListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(InvitationListener.class.getName());
|
||||
|
||||
private final AtomicInteger searches = new AtomicInteger(1);
|
||||
private final DiscoveryAgent discoveryAgent;
|
||||
private final String uuid;
|
||||
|
||||
private String url = null; // Locking: this
|
||||
private boolean finished = false; // Locking: this
|
||||
|
||||
InvitationListener(DiscoveryAgent discoveryAgent, String uuid) {
|
||||
this.discoveryAgent = discoveryAgent;
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
synchronized String waitForUrl() {
|
||||
while(!finished) {
|
||||
try {
|
||||
wait();
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
public void deviceDiscovered(RemoteDevice device, DeviceClass deviceClass) {
|
||||
UUID[] uuids = new UUID[] { new UUID(uuid, false) };
|
||||
// Try to discover the services associated with the UUID
|
||||
try {
|
||||
discoveryAgent.searchServices(null, uuids, device, this);
|
||||
searches.incrementAndGet();
|
||||
} catch(BluetoothStateException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void inquiryCompleted(int discoveryType) {
|
||||
if(searches.decrementAndGet() == 0) {
|
||||
synchronized(this) {
|
||||
finished = true;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void servicesDiscovered(int transaction, ServiceRecord[] services) {
|
||||
for(ServiceRecord record : services) {
|
||||
// Does this service have a URL?
|
||||
String serviceUrl = record.getConnectionURL(
|
||||
ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
|
||||
if(serviceUrl == null) continue;
|
||||
// Does this service have the UUID we're looking for?
|
||||
DataElement classIds = record.getAttributeValue(0x1);
|
||||
if(classIds == null) continue;
|
||||
@SuppressWarnings("unchecked")
|
||||
Enumeration<DataElement> e =
|
||||
(Enumeration<DataElement>) classIds.getValue();
|
||||
for(DataElement classId : Collections.list(e)) {
|
||||
UUID serviceUuid = (UUID) classId.getValue();
|
||||
if(uuid.equals(serviceUuid.toString())) {
|
||||
// The UUID matches - store the URL
|
||||
synchronized(this) {
|
||||
url = serviceUrl;
|
||||
finished = true;
|
||||
notifyAll();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void serviceSearchCompleted(int transaction, int response) {
|
||||
if(searches.decrementAndGet() == 0) {
|
||||
synchronized(this) {
|
||||
finished = true;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
components/net/sf/briar/plugins/file/FileListener.java
Normal file
41
components/net/sf/briar/plugins/file/FileListener.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package net.sf.briar.plugins.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class FileListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(FileListener.class.getName());
|
||||
|
||||
private final String filename;
|
||||
private final long end;
|
||||
|
||||
private File file = null; // Locking: this
|
||||
|
||||
FileListener(String filename, long timeout) {
|
||||
this.filename = filename;
|
||||
end = System.currentTimeMillis() + timeout;
|
||||
}
|
||||
|
||||
synchronized File waitForFile() {
|
||||
long now = System.currentTimeMillis();
|
||||
while(file == null && now < end) {
|
||||
try {
|
||||
wait(end - now);
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
}
|
||||
now = System.currentTimeMillis();
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
synchronized void addFile(File f) {
|
||||
if(filename.equals(f.getName())) {
|
||||
file = f;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,14 @@ import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.plugins.BatchPluginCallback;
|
||||
import net.sf.briar.api.plugins.BatchPlugin;
|
||||
import net.sf.briar.api.plugins.BatchPluginCallback;
|
||||
import net.sf.briar.api.transport.BatchTransportReader;
|
||||
import net.sf.briar.api.transport.BatchTransportWriter;
|
||||
import net.sf.briar.api.transport.TransportConstants;
|
||||
@@ -26,7 +27,11 @@ abstract class FilePlugin extends AbstractPlugin implements BatchPlugin {
|
||||
|
||||
protected final BatchPluginCallback callback;
|
||||
|
||||
private final Object listenerLock = new Object();
|
||||
private FileListener listener = null; // Locking: listenerLock
|
||||
|
||||
protected abstract File chooseOutputDirectory();
|
||||
protected abstract Collection<File> findFilesByName(String filename);
|
||||
protected abstract void writerFinished(File f);
|
||||
protected abstract void readerFinished(File f);
|
||||
|
||||
@@ -40,12 +45,28 @@ abstract class FilePlugin extends AbstractPlugin implements BatchPlugin {
|
||||
}
|
||||
|
||||
public BatchTransportWriter createWriter(ContactId c) {
|
||||
return createWriter(createConnectionFilename());
|
||||
}
|
||||
|
||||
private String createConnectionFilename() {
|
||||
StringBuilder s = new StringBuilder(12);
|
||||
for(int i = 0; i < 8; i++) s.append((char) ('a' + Math.random() * 26));
|
||||
s.append(".dat");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
boolean isPossibleConnectionFilename(String filename) {
|
||||
return filename.toLowerCase().matches("[a-z]{8}\\.dat");
|
||||
}
|
||||
|
||||
private BatchTransportWriter createWriter(String filename) {
|
||||
synchronized(this) {
|
||||
if(!started) return null;
|
||||
}
|
||||
File dir = chooseOutputDirectory();
|
||||
if(dir == null || !dir.exists() || !dir.isDirectory()) return null;
|
||||
File f = new File(dir, createFilename());
|
||||
File f = new File(dir, filename);
|
||||
try {
|
||||
long capacity = getCapacity(dir.getPath());
|
||||
if(capacity < TransportConstants.MIN_CONNECTION_LENGTH) return null;
|
||||
@@ -58,13 +79,6 @@ abstract class FilePlugin extends AbstractPlugin implements BatchPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private String createFilename() {
|
||||
StringBuilder s = new StringBuilder(12);
|
||||
for(int i = 0; i < 8; i++) s.append((char) ('a' + Math.random() * 26));
|
||||
s.append(".dat");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
private long getCapacity(String path) throws IOException {
|
||||
return FileSystemUtils.freeSpaceKb(path) * 1024L;
|
||||
}
|
||||
@@ -74,9 +88,60 @@ abstract class FilePlugin extends AbstractPlugin implements BatchPlugin {
|
||||
executor.execute(new ReaderCreator(f));
|
||||
}
|
||||
|
||||
public BatchTransportWriter sendInvitation(int code, long timeout) {
|
||||
return createWriter(createInvitationFilename(code, false));
|
||||
}
|
||||
|
||||
public BatchTransportReader acceptInvitation(int code, long timeout) {
|
||||
String filename = createInvitationFilename(code, false);
|
||||
return createInvitationReader(filename, timeout);
|
||||
}
|
||||
|
||||
public BatchTransportWriter sendInvitationResponse(int code, long timeout) {
|
||||
return createWriter(createInvitationFilename(code, true));
|
||||
}
|
||||
|
||||
public BatchTransportReader acceptInvitationResponse(int code,
|
||||
long timeout) {
|
||||
String filename = createInvitationFilename(code, true);
|
||||
return createInvitationReader(filename, timeout);
|
||||
}
|
||||
|
||||
private BatchTransportReader createInvitationReader(String filename,
|
||||
long timeout) {
|
||||
Collection<File> files;
|
||||
synchronized(listenerLock) {
|
||||
// Find any matching files that have already arrived
|
||||
files = findFilesByName(filename);
|
||||
if(files.isEmpty()) {
|
||||
// Wait for a matching file to arrive
|
||||
listener = new FileListener(filename, timeout);
|
||||
File f = listener.waitForFile();
|
||||
if(f != null) files.add(f);
|
||||
listener = null;
|
||||
}
|
||||
}
|
||||
// Return the first match that can be opened
|
||||
for(File f : files) {
|
||||
try {
|
||||
FileInputStream in = new FileInputStream(f);
|
||||
return new FileTransportReader(f, in, FilePlugin.this);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String createInvitationFilename(int code, boolean response) {
|
||||
assert code >= 0;
|
||||
assert code < 10 * 1000 * 1000;
|
||||
return String.format("%c%7d.dat", response ? 'b' : 'a', code);
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
boolean isPossibleConnectionFilename(String filename) {
|
||||
return filename.toLowerCase().matches("[a-z]{8}\\.dat");
|
||||
boolean isPossibleInvitationFilename(String filename) {
|
||||
return filename.toLowerCase().matches("[ab][0-9]{7}.dat");
|
||||
}
|
||||
|
||||
private class ReaderCreator implements Runnable {
|
||||
@@ -88,14 +153,21 @@ abstract class FilePlugin extends AbstractPlugin implements BatchPlugin {
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if(!isPossibleConnectionFilename(f.getName())) return;
|
||||
if(f.length() < TransportConstants.MIN_CONNECTION_LENGTH) return;
|
||||
try {
|
||||
FileInputStream in = new FileInputStream(f);
|
||||
callback.readerCreated(new FileTransportReader(f, in,
|
||||
FilePlugin.this));
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
String filename = f.getName();
|
||||
if(isPossibleInvitationFilename(filename)) {
|
||||
synchronized(listenerLock) {
|
||||
if(listener != null) listener.addFile(f);
|
||||
}
|
||||
}
|
||||
if(isPossibleConnectionFilename(f.getName())) {
|
||||
try {
|
||||
FileInputStream in = new FileInputStream(f);
|
||||
callback.readerCreated(new FileTransportReader(f, in,
|
||||
FilePlugin.this));
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,14 @@ package net.sf.briar.plugins.file;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(PollingRemovableDriveMonitor.class.getName());
|
||||
|
||||
private final RemovableDriveFinder finder;
|
||||
private final long pollingInterval;
|
||||
private final Object pollingLock = new Object();
|
||||
@@ -47,7 +52,10 @@ class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
|
||||
synchronized(pollingLock) {
|
||||
try {
|
||||
pollingLock.wait(pollingInterval);
|
||||
} catch(InterruptedException ignored) {}
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
if(!running) return;
|
||||
List<File> newDrives = finder.findRemovableDrives();
|
||||
|
||||
@@ -2,6 +2,8 @@ package net.sf.briar.plugins.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Level;
|
||||
@@ -85,8 +87,29 @@ implements RemovableDriveMonitor.Callback {
|
||||
callback.showMessage("REMOVABLE_DRIVE_WRITE_FINISHED");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<File> findFilesByName(String filename) {
|
||||
Collection<File> matches = new ArrayList<File>();
|
||||
try {
|
||||
for(File drive : finder.findRemovableDrives()) {
|
||||
File[] files = drive.listFiles();
|
||||
if(files != null) {
|
||||
for(File f : files) {
|
||||
if(f.isFile() && filename.equals(f.getName()))
|
||||
matches.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
public void driveInserted(File root) {
|
||||
File[] files = root.listFiles();
|
||||
if(files != null) for(File f : files) createReaderFromFile(f);
|
||||
if(files != null) {
|
||||
for(File f : files) if(f.isFile()) createReaderFromFile(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.sf.briar.plugins.socket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
@@ -11,6 +12,7 @@ import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.TransportId;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.plugins.StreamPluginCallback;
|
||||
import net.sf.briar.api.transport.StreamTransportConnection;
|
||||
|
||||
class SimpleSocketPlugin extends SocketPlugin {
|
||||
|
||||
@@ -67,7 +69,8 @@ class SimpleSocketPlugin extends SocketPlugin {
|
||||
TransportProperties p) {
|
||||
assert started;
|
||||
assert p != null;
|
||||
String host = p.get("host");
|
||||
String host = p.get("external");
|
||||
if(host == null) host = p.get("internal");
|
||||
String portString = p.get("port");
|
||||
if(host == null || portString == null) return null;
|
||||
int port;
|
||||
@@ -85,12 +88,22 @@ class SimpleSocketPlugin extends SocketPlugin {
|
||||
if(!(s instanceof InetSocketAddress))
|
||||
throw new IllegalArgumentException();
|
||||
InetSocketAddress i = (InetSocketAddress) s;
|
||||
String host = i.getAddress().getHostAddress();
|
||||
String port = String.valueOf(i.getPort());
|
||||
// FIXME: Special handling for private IP addresses?
|
||||
InetAddress addr = i.getAddress();
|
||||
TransportProperties p = callback.getLocalProperties();
|
||||
p.put("host", host);
|
||||
p.put("port", port);
|
||||
if(addr.isLinkLocalAddress() || addr.isSiteLocalAddress())
|
||||
p.put("internal", addr.getHostAddress());
|
||||
else p.put("external", addr.getHostAddress());
|
||||
p.put("port", String.valueOf(i.getPort()));
|
||||
callback.setLocalProperties(p);
|
||||
}
|
||||
|
||||
public StreamTransportConnection sendInvitation(int code, long timeout) {
|
||||
// FIXME
|
||||
return null;
|
||||
}
|
||||
|
||||
public StreamTransportConnection acceptInvitation(int code, long timeout) {
|
||||
// FIXME
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@ abstract class SocketPlugin extends AbstractPlugin implements StreamPlugin {
|
||||
|
||||
protected final StreamPluginCallback callback;
|
||||
|
||||
// This field must only be accessed with this's lock held
|
||||
protected ServerSocket socket = null;
|
||||
protected ServerSocket socket = null; // Locking: this
|
||||
|
||||
protected abstract void setLocalSocketAddress(SocketAddress s);
|
||||
|
||||
@@ -85,10 +84,10 @@ abstract class SocketPlugin extends AbstractPlugin implements StreamPlugin {
|
||||
socket = ss;
|
||||
setLocalSocketAddress(ss.getLocalSocketAddress());
|
||||
}
|
||||
startListener();
|
||||
startListenerThread();
|
||||
}
|
||||
|
||||
private void startListener() {
|
||||
private void startListenerThread() {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -108,7 +107,8 @@ abstract class SocketPlugin extends AbstractPlugin implements StreamPlugin {
|
||||
try {
|
||||
s = ss.accept();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
|
||||
// This is expected when the socket is closed
|
||||
if(LOG.isLoggable(Level.INFO)) LOG.info(e.getMessage());
|
||||
return;
|
||||
}
|
||||
SocketTransportConnection conn = new SocketTransportConnection(s);
|
||||
|
||||
@@ -2,6 +2,9 @@ package net.sf.briar.transport;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* A thread that calls the writeFullFrame() method of a PaddedConnectionWriter
|
||||
* at regular intervals. The interval between calls is determined by a target
|
||||
@@ -10,6 +13,9 @@ import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
*/
|
||||
class FrameScheduler extends Thread {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(FrameScheduler.class.getName());
|
||||
|
||||
private final PaddedConnectionWriter writer;
|
||||
private final int millisPerFrame;
|
||||
|
||||
@@ -27,7 +33,10 @@ class FrameScheduler extends Thread {
|
||||
if(nextCall > now) {
|
||||
try {
|
||||
Thread.sleep(nextCall - now);
|
||||
} catch(InterruptedException ignored) {}
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
lastCall = System.currentTimeMillis();
|
||||
if(!writer.writeFullFrame()) return;
|
||||
|
||||
@@ -219,7 +219,10 @@ abstract class StreamConnection implements DatabaseListener {
|
||||
while(writerFlags == 0) {
|
||||
try {
|
||||
wait();
|
||||
} catch(InterruptedException ignored) {}
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
flags = writerFlags;
|
||||
writerFlags = 0;
|
||||
@@ -256,7 +259,10 @@ abstract class StreamConnection implements DatabaseListener {
|
||||
while(writerFlags == 0) {
|
||||
try {
|
||||
wait();
|
||||
} catch(InterruptedException ignored) {}
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(Level.WARNING))
|
||||
LOG.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
flags = writerFlags;
|
||||
writerFlags = 0;
|
||||
|
||||
@@ -76,7 +76,8 @@ public class LockFairnessTest extends TestCase {
|
||||
try {
|
||||
Thread.sleep(sleepTime);
|
||||
finished.add(this);
|
||||
} catch(InterruptedException ignored) {
|
||||
} catch(InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
@@ -99,7 +100,8 @@ public class LockFairnessTest extends TestCase {
|
||||
try {
|
||||
Thread.sleep(sleepTime);
|
||||
finished.add(this);
|
||||
} catch(InterruptedException ignored) {
|
||||
} catch(InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
|
||||
@@ -737,9 +737,7 @@ public class H2DatabaseTest extends TestCase {
|
||||
for(int i = 0; i < ids.length; i++) {
|
||||
db.addOutstandingBatch(txn, contactId, ids[i],
|
||||
Collections.<MessageId>emptySet());
|
||||
try {
|
||||
Thread.sleep(5);
|
||||
} catch(InterruptedException ignored) {}
|
||||
Thread.sleep(5);
|
||||
}
|
||||
|
||||
// The contact acks the batches in reverse order. The first
|
||||
@@ -779,9 +777,7 @@ public class H2DatabaseTest extends TestCase {
|
||||
for(int i = 0; i < ids.length; i++) {
|
||||
db.addOutstandingBatch(txn, contactId, ids[i],
|
||||
Collections.<MessageId>emptySet());
|
||||
try {
|
||||
Thread.sleep(5);
|
||||
} catch(InterruptedException ignored) {}
|
||||
Thread.sleep(5);
|
||||
}
|
||||
|
||||
// The contact acks the batches in the order they were sent - nothing
|
||||
@@ -946,9 +942,7 @@ public class H2DatabaseTest extends TestCase {
|
||||
};
|
||||
t.start();
|
||||
// Do whatever the transaction needs to do
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch(InterruptedException ignored) {}
|
||||
Thread.sleep(10);
|
||||
transactionFinished.set(true);
|
||||
// Commit the transaction
|
||||
db.commitTransaction(txn);
|
||||
@@ -981,9 +975,7 @@ public class H2DatabaseTest extends TestCase {
|
||||
};
|
||||
t.start();
|
||||
// Do whatever the transaction needs to do
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch(InterruptedException ignored) {}
|
||||
Thread.sleep(10);
|
||||
transactionFinished.set(true);
|
||||
// Abort the transaction
|
||||
db.abortTransaction(txn);
|
||||
|
||||
@@ -1,63 +1,79 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.TransportConfig;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.plugins.StreamPluginCallback;
|
||||
import net.sf.briar.api.transport.StreamTransportConnection;
|
||||
import net.sf.briar.plugins.ImmediateExecutor;
|
||||
|
||||
// This is not a JUnit test - it has to be run manually while the server test
|
||||
// is running on another machine
|
||||
public class BluetoothClientTest {
|
||||
public class BluetoothClientTest extends BluetoothTest {
|
||||
|
||||
public static final String RESPONSE = "Carrots!";
|
||||
private final String serverAddress;
|
||||
|
||||
BluetoothClientTest(String serverAddress) {
|
||||
this.serverAddress = serverAddress;
|
||||
}
|
||||
|
||||
void run() throws IOException {
|
||||
ContactId contactId = new ContactId(0);
|
||||
ClientCallback callback = new ClientCallback();
|
||||
// Store the server's Bluetooth address and UUID
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("address", serverAddress);
|
||||
p.put("uuid", BluetoothServerTest.UUID);
|
||||
callback.remote.put(contactId, p);
|
||||
// Create the plugin
|
||||
Executor e = Executors.newCachedThreadPool();
|
||||
BluetoothPlugin plugin = new BluetoothPlugin(e, callback, 0L);
|
||||
// Start the plugin
|
||||
System.out.println("Starting plugin");
|
||||
plugin.start();
|
||||
// Try to connect to the server
|
||||
System.out.println("Creating connection");
|
||||
StreamTransportConnection s = plugin.createConnection(contactId);
|
||||
if(s == null) {
|
||||
System.out.println("Connection failed");
|
||||
} else {
|
||||
System.out.println("Connection created");
|
||||
receiveChallengeAndSendResponse(s);
|
||||
}
|
||||
// Try to send an invitation
|
||||
System.out.println("Sending invitation");
|
||||
s = plugin.sendInvitation(123, INVITATION_TIMEOUT);
|
||||
if(s == null) {
|
||||
System.out.println("Connection failed");
|
||||
} else {
|
||||
System.out.println("Connection created");
|
||||
receiveChallengeAndSendResponse(s);
|
||||
}
|
||||
// Try to accept an invitation
|
||||
System.out.println("Accepting invitation");
|
||||
s = plugin.acceptInvitation(456, INVITATION_TIMEOUT);
|
||||
if(s == null) {
|
||||
System.out.println("Connection failed");
|
||||
} else {
|
||||
System.out.println("Connection created");
|
||||
sendChallengeAndReceiveResponse(s);
|
||||
}
|
||||
// Stop the plugin
|
||||
System.out.println("Stopping plugin");
|
||||
plugin.stop();
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if(args.length != 1) {
|
||||
System.err.println("Please specify the server's Bluetooth address");
|
||||
System.exit(1);
|
||||
}
|
||||
ContactId contactId = new ContactId(0);
|
||||
ClientCallback callback = new ClientCallback();
|
||||
// Store the server's Bluetooth address and UUID
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("address", args[0]);
|
||||
p.put("uuid", BluetoothServerTest.UUID);
|
||||
callback.remote.put(contactId, p);
|
||||
// Create the plugin
|
||||
BluetoothPlugin plugin =
|
||||
new BluetoothPlugin(new ImmediateExecutor(), callback, 0L);
|
||||
// Start the plugin
|
||||
System.out.println("Starting plugin");
|
||||
plugin.start();
|
||||
// Try to connect to the server
|
||||
System.out.println("Creating connection");
|
||||
StreamTransportConnection conn = plugin.createConnection(contactId);
|
||||
if(conn == null) {
|
||||
System.out.println("Connection failed");
|
||||
} else {
|
||||
System.out.println("Connection created");
|
||||
Scanner in = new Scanner(conn.getInputStream());
|
||||
String challenge = in.nextLine();
|
||||
System.out.println("Received challenge: " + challenge);
|
||||
if(BluetoothServerTest.CHALLENGE.equals(challenge)) {
|
||||
PrintStream out = new PrintStream(conn.getOutputStream());
|
||||
out.println(RESPONSE);
|
||||
System.out.println("Sent response: " + RESPONSE);
|
||||
} else {
|
||||
System.out.println("Incorrect challenge");
|
||||
}
|
||||
conn.dispose(true);
|
||||
}
|
||||
// Stop the plugin
|
||||
System.out.println("Stopping plugin");
|
||||
plugin.stop();
|
||||
new BluetoothClientTest(args[0]).run();
|
||||
}
|
||||
|
||||
private static class ClientCallback implements StreamPluginCallback {
|
||||
|
||||
@@ -1,32 +1,27 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.TransportConfig;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.plugins.StreamPluginCallback;
|
||||
import net.sf.briar.api.transport.StreamTransportConnection;
|
||||
import net.sf.briar.plugins.ImmediateExecutor;
|
||||
|
||||
//This is not a JUnit test - it has to be run manually while the server test
|
||||
//is running on another machine
|
||||
public class BluetoothServerTest {
|
||||
public class BluetoothServerTest extends BluetoothTest {
|
||||
|
||||
public static final String UUID = "CABBA6E5CABBA6E5CABBA6E5CABBA6E5";
|
||||
public static final String CHALLENGE = "Potatoes!";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
void run() throws Exception {
|
||||
ServerCallback callback = new ServerCallback();
|
||||
// Store the UUID
|
||||
callback.config.put("uuid", UUID);
|
||||
// Create the plugin
|
||||
BluetoothPlugin plugin =
|
||||
new BluetoothPlugin(new ImmediateExecutor(), callback, 0L);
|
||||
Executor e = Executors.newCachedThreadPool();
|
||||
BluetoothPlugin plugin = new BluetoothPlugin(e, callback, 0L);
|
||||
// Start the plugin
|
||||
System.out.println("Starting plugin");
|
||||
plugin.start();
|
||||
@@ -35,12 +30,35 @@ public class BluetoothServerTest {
|
||||
synchronized(callback) {
|
||||
callback.wait();
|
||||
}
|
||||
// Try to accept an invitation
|
||||
System.out.println("Accepting invitation");
|
||||
StreamTransportConnection s = plugin.acceptInvitation(123,
|
||||
INVITATION_TIMEOUT);
|
||||
if(s == null) {
|
||||
System.out.println("Connection failed");
|
||||
} else {
|
||||
System.out.println("Connection created");
|
||||
sendChallengeAndReceiveResponse(s);
|
||||
}
|
||||
// Try to send an invitation
|
||||
System.out.println("Sending invitation");
|
||||
s = plugin.sendInvitation(456, INVITATION_TIMEOUT);
|
||||
if(s == null) {
|
||||
System.out.println("Connection failed");
|
||||
} else {
|
||||
System.out.println("Connection created");
|
||||
receiveChallengeAndSendResponse(s);
|
||||
}
|
||||
// Stop the plugin
|
||||
System.out.println("Stopping plugin");
|
||||
plugin.stop();
|
||||
}
|
||||
|
||||
private static class ServerCallback implements StreamPluginCallback {
|
||||
public static void main(String[] args) throws Exception {
|
||||
new BluetoothServerTest().run();
|
||||
}
|
||||
|
||||
private class ServerCallback implements StreamPluginCallback {
|
||||
|
||||
private TransportConfig config = new TransportConfig();
|
||||
private TransportProperties local = new TransportProperties();
|
||||
@@ -77,24 +95,9 @@ public class BluetoothServerTest {
|
||||
|
||||
public void showMessage(String... message) {}
|
||||
|
||||
public void incomingConnectionCreated(StreamTransportConnection conn) {
|
||||
public void incomingConnectionCreated(StreamTransportConnection s) {
|
||||
System.out.println("Connection received");
|
||||
try {
|
||||
PrintStream out = new PrintStream(conn.getOutputStream());
|
||||
out.println(CHALLENGE);
|
||||
System.out.println("Sent challenge: " + CHALLENGE);
|
||||
Scanner in = new Scanner(conn.getInputStream());
|
||||
String response = in.nextLine();
|
||||
System.out.println("Received response: " + response);
|
||||
if(BluetoothClientTest.RESPONSE.equals(response)) {
|
||||
System.out.println("Correct response");
|
||||
} else {
|
||||
System.out.println("Incorrect response");
|
||||
}
|
||||
conn.dispose(true);
|
||||
} catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
sendChallengeAndReceiveResponse(s);
|
||||
synchronized(this) {
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
54
test/net/sf/briar/plugins/bluetooth/BluetoothTest.java
Normal file
54
test/net/sf/briar/plugins/bluetooth/BluetoothTest.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package net.sf.briar.plugins.bluetooth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Scanner;
|
||||
|
||||
import net.sf.briar.api.transport.StreamTransportConnection;
|
||||
|
||||
abstract class BluetoothTest {
|
||||
|
||||
protected static final String UUID = "CABBA6E5CABBA6E5CABBA6E5CABBA6E5";
|
||||
protected static final String CHALLENGE = "Carrots!";
|
||||
protected static final String RESPONSE = "Potatoes!";
|
||||
protected static final long INVITATION_TIMEOUT = 30 * 1000;
|
||||
|
||||
void sendChallengeAndReceiveResponse(StreamTransportConnection s) {
|
||||
try {
|
||||
PrintStream out = new PrintStream(s.getOutputStream());
|
||||
out.println(CHALLENGE);
|
||||
System.out.println("Sent challenge: " + CHALLENGE);
|
||||
Scanner in = new Scanner(s.getInputStream());
|
||||
String response = in.nextLine();
|
||||
System.out.println("Received response: " + response);
|
||||
if(BluetoothClientTest.RESPONSE.equals(response)) {
|
||||
System.out.println("Correct response");
|
||||
} else {
|
||||
System.out.println("Incorrect response");
|
||||
}
|
||||
s.dispose(true);
|
||||
} catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
s.dispose(false);
|
||||
}
|
||||
}
|
||||
|
||||
void receiveChallengeAndSendResponse(StreamTransportConnection s) {
|
||||
try {
|
||||
Scanner in = new Scanner(s.getInputStream());
|
||||
String challenge = in.nextLine();
|
||||
System.out.println("Received challenge: " + challenge);
|
||||
if(BluetoothServerTest.CHALLENGE.equals(challenge)) {
|
||||
PrintStream out = new PrintStream(s.getOutputStream());
|
||||
out.println(RESPONSE);
|
||||
System.out.println("Sent response: " + RESPONSE);
|
||||
} else {
|
||||
System.out.println("Incorrect challenge");
|
||||
}
|
||||
s.dispose(true);
|
||||
} catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
s.dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,35 +339,6 @@ public class RemovableDrivePluginTest extends TestCase {
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallFileIsIgnored() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final BatchPluginCallback callback =
|
||||
context.mock(BatchPluginCallback.class);
|
||||
final RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
final RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(
|
||||
new ImmediateExecutor(), callback, finder, monitor);
|
||||
plugin.start();
|
||||
|
||||
File f = new File(testDir, "abcdefgh.dat");
|
||||
OutputStream out = new FileOutputStream(f);
|
||||
out.write(new byte[TransportConstants.MIN_CONNECTION_LENGTH - 1]);
|
||||
out.flush();
|
||||
out.close();
|
||||
assertEquals(TransportConstants.MIN_CONNECTION_LENGTH - 1, f.length());
|
||||
plugin.driveInserted(testDir);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReaderIsCreated() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
|
||||
@@ -27,13 +27,13 @@ public class SimpleSocketPluginTest extends TestCase {
|
||||
@Test
|
||||
public void testIncomingConnection() throws Exception {
|
||||
StreamCallback callback = new StreamCallback();
|
||||
callback.local.put("host", "127.0.0.1");
|
||||
callback.local.put("internal", "127.0.0.1");
|
||||
callback.local.put("port", "0");
|
||||
SimpleSocketPlugin plugin =
|
||||
new SimpleSocketPlugin(new ImmediateExecutor(), callback, 0L);
|
||||
plugin.start();
|
||||
// The plugin should have bound a socket and stored the port number
|
||||
String host = callback.local.get("host");
|
||||
String host = callback.local.get("internal");
|
||||
assertNotNull(host);
|
||||
assertEquals("127.0.0.1", host);
|
||||
String portString = callback.local.get("port");
|
||||
@@ -84,7 +84,7 @@ public class SimpleSocketPluginTest extends TestCase {
|
||||
}.start();
|
||||
// Tell the plugin about the port
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("host", "127.0.0.1");
|
||||
p.put("internal", "127.0.0.1");
|
||||
p.put("port", String.valueOf(port));
|
||||
callback.remote.put(contactId, p);
|
||||
// Connect to the port
|
||||
|
||||
Reference in New Issue
Block a user