diff --git a/api/net/sf/briar/api/plugins/BatchPlugin.java b/api/net/sf/briar/api/plugins/BatchPlugin.java index 47f1d5a5de9b2e29d5aea466b026eedd33e57d33..26d41bd48741d5c334d6820b20b8f2cb5d9c7ec7 100644 --- a/api/net/sf/briar/api/plugins/BatchPlugin.java +++ b/api/net/sf/briar/api/plugins/BatchPlugin.java @@ -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); } diff --git a/api/net/sf/briar/api/plugins/StreamPlugin.java b/api/net/sf/briar/api/plugins/StreamPlugin.java index 1cd88937059982707000b79544f83ec720ce3ec7..bceae8ec947a475f4fa3c534dd303747ac259a63 100644 --- a/api/net/sf/briar/api/plugins/StreamPlugin.java +++ b/api/net/sf/briar/api/plugins/StreamPlugin.java @@ -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); } diff --git a/components/net/sf/briar/db/DatabaseCleanerImpl.java b/components/net/sf/briar/db/DatabaseCleanerImpl.java index 37748ad18289dbe6a2b0d9c80800476c1cdf40be..b4199a6b087e9b260e8ffd5b2ec514b5a9558aac 100644 --- a/components/net/sf/briar/db/DatabaseCleanerImpl.java +++ b/components/net/sf/briar/db/DatabaseCleanerImpl.java @@ -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)) diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java index cafa95ec9f465347c146a33854214a03abba8be1..16e691135118dc18378a28f2096064d51cfd60c3 100644 --- a/components/net/sf/briar/db/JdbcDatabase.java +++ b/components/net/sf/briar/db/JdbcDatabase.java @@ -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(); diff --git a/components/net/sf/briar/i18n/I18nImpl.java b/components/net/sf/briar/i18n/I18nImpl.java index 106b3032fb792e0045666da449def991e4c2b32c..6bda4a2a13570a0aa71078fcff93e774f8649f34 100644 --- a/components/net/sf/briar/i18n/I18nImpl.java +++ b/components/net/sf/briar/i18n/I18nImpl.java @@ -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()); + } } } } diff --git a/components/net/sf/briar/plugins/AbstractPlugin.java b/components/net/sf/briar/plugins/AbstractPlugin.java index 1b1c1ff189bc106f52a6457d8c3870af95e852fc..6a779ef7a8857dfd88eb577d31c491b7f1a6f51d 100644 --- a/components/net/sf/briar/plugins/AbstractPlugin.java +++ b/components/net/sf/briar/plugins/AbstractPlugin.java @@ -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; diff --git a/components/net/sf/briar/plugins/PollerImpl.java b/components/net/sf/briar/plugins/PollerImpl.java index 5905fd243f31c92835750360537ed71272c107d2..87ee77c0d4fd85937e6c325621aade127c70f94b 100644 --- a/components/net/sf/briar/plugins/PollerImpl.java +++ b/components/net/sf/briar/plugins/PollerImpl.java @@ -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()); + } } } } diff --git a/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java b/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java index d5db89a7200030a36623da072187e524051e9e6d..d63eeaa600e4165987001e8ed2fdd8f58341b49f 100644 --- a/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java +++ b/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java @@ -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,42 +140,52 @@ class BluetoothPlugin extends AbstractPlugin implements StreamPlugin { return uuid; } - private void startListener() { + 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()); + } + } + + private synchronized void setLocalBluetoothAddress(String address) { + assert started; + TransportProperties p = callback.getLocalProperties(); + p.put("address", address); + callback.setLocalProperties(p); + } + + private void startContactAccepterThread() { new Thread() { @Override public void run() { - listen(); + acceptContactConnections(); } }.start(); } - private void listen() { + private void acceptContactConnections() { while(true) { StreamConnectionNotifier scn; StreamConnection s; synchronized(this) { if(!started) return; - scn = streamConnectionNotifier; + scn = socket; } try { s = scn.acceptAndOpen(); } 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; } - BluetoothTransportConnection conn = - new BluetoothTransportConnection(s); - callback.incomingConnectionCreated(conn); + callback.incomingConnectionCreated( + new BluetoothTransportConnection(s)); } } - private synchronized void setLocalBluetoothAddress(String address) { - assert started; - TransportProperties p = callback.getLocalProperties(); - p.put("address", address); - callback.setLocalProperties(p); - } - 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()); + } + } } diff --git a/components/net/sf/briar/plugins/bluetooth/ConnectionCallback.java b/components/net/sf/briar/plugins/bluetooth/ConnectionCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..fdcaebca11caeafab33553d5fab274e14e823cf6 --- /dev/null +++ b/components/net/sf/briar/plugins/bluetooth/ConnectionCallback.java @@ -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()); + } + } + } +} diff --git a/components/net/sf/briar/plugins/bluetooth/BluetoothListener.java b/components/net/sf/briar/plugins/bluetooth/ContactListener.java similarity index 64% rename from components/net/sf/briar/plugins/bluetooth/BluetoothListener.java rename to components/net/sf/briar/plugins/bluetooth/ContactListener.java index 414abe7a6107505b20608742849ceb5fe77e7725..492fd4a1bc08718e09e173cc5d2c5addb080e078 100644 --- a/components/net/sf/briar/plugins/bluetooth/BluetoothListener.java +++ b/components/net/sf/briar/plugins/bluetooth/ContactListener.java @@ -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(); } } diff --git a/components/net/sf/briar/plugins/bluetooth/InvitationListener.java b/components/net/sf/briar/plugins/bluetooth/InvitationListener.java new file mode 100644 index 0000000000000000000000000000000000000000..96a94fc2091bb4b6147c37464052dcee1282880f --- /dev/null +++ b/components/net/sf/briar/plugins/bluetooth/InvitationListener.java @@ -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(); + } + } + } +} diff --git a/components/net/sf/briar/plugins/file/FileListener.java b/components/net/sf/briar/plugins/file/FileListener.java new file mode 100644 index 0000000000000000000000000000000000000000..d71de06e4833296d260057dd200e36f9554c8677 --- /dev/null +++ b/components/net/sf/briar/plugins/file/FileListener.java @@ -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(); + } + } +} diff --git a/components/net/sf/briar/plugins/file/FilePlugin.java b/components/net/sf/briar/plugins/file/FilePlugin.java index b97d532be362e8b56e57b55c0df45e1d7778e9e5..3e9f080300d4eec6bd151e036cd92c643cc05ff4 100644 --- a/components/net/sf/briar/plugins/file/FilePlugin.java +++ b/components/net/sf/briar/plugins/file/FilePlugin.java @@ -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()); + } } } } diff --git a/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java b/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java index 853899befc1d5855ff29146878f21192840c5567..0d582bc3505d3475981a10c483b870269214e907 100644 --- a/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java +++ b/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java @@ -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(); diff --git a/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java b/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java index 7aafe10e7e918699e07b4ae2e038cb8a916d0357..9a2633fdc9716cdb76ac96434a8cfd83f7a362f2 100644 --- a/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java +++ b/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java @@ -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); + } } } diff --git a/components/net/sf/briar/plugins/socket/SimpleSocketPlugin.java b/components/net/sf/briar/plugins/socket/SimpleSocketPlugin.java index 8c57f555de058e8ee716a92ee95a0eb13fd21502..09d2e96fb9add3f858325f4f3527fe692560cf6b 100644 --- a/components/net/sf/briar/plugins/socket/SimpleSocketPlugin.java +++ b/components/net/sf/briar/plugins/socket/SimpleSocketPlugin.java @@ -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; + } } diff --git a/components/net/sf/briar/plugins/socket/SocketPlugin.java b/components/net/sf/briar/plugins/socket/SocketPlugin.java index f90852374a39d36851eaaae084227be40b62a396..9c9fe08698484e51cd03cb976298651d87bf0212 100644 --- a/components/net/sf/briar/plugins/socket/SocketPlugin.java +++ b/components/net/sf/briar/plugins/socket/SocketPlugin.java @@ -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); diff --git a/components/net/sf/briar/transport/FrameScheduler.java b/components/net/sf/briar/transport/FrameScheduler.java index 955f6ab89790dfd77d1bea18a9cb88627a1658c3..94bc075549199b1e6e1e435f817bfadcfacc452f 100644 --- a/components/net/sf/briar/transport/FrameScheduler.java +++ b/components/net/sf/briar/transport/FrameScheduler.java @@ -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; diff --git a/components/net/sf/briar/transport/stream/StreamConnection.java b/components/net/sf/briar/transport/stream/StreamConnection.java index 419909437688c580aad2a5ed859c465be93d4936..51c2e1ee133c329c0ac24c6a509c8e08ea533318 100644 --- a/components/net/sf/briar/transport/stream/StreamConnection.java +++ b/components/net/sf/briar/transport/stream/StreamConnection.java @@ -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; diff --git a/test/net/sf/briar/LockFairnessTest.java b/test/net/sf/briar/LockFairnessTest.java index 4860eba5b60537c719be5c27241f4cb856707587..081c89fc71911198a4a43814bdc1e746bd4095b9 100644 --- a/test/net/sf/briar/LockFairnessTest.java +++ b/test/net/sf/briar/LockFairnessTest.java @@ -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(); } diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java index e2ac13f2c9bc5fefb59373e926726368d50ded24..904b04714a8bdb426d952c47d75a0bb0e0abb8ca 100644 --- a/test/net/sf/briar/db/H2DatabaseTest.java +++ b/test/net/sf/briar/db/H2DatabaseTest.java @@ -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); diff --git a/test/net/sf/briar/plugins/bluetooth/BluetoothClientTest.java b/test/net/sf/briar/plugins/bluetooth/BluetoothClientTest.java index fdb352325d7f91b24e5de41e1a075e89f7b9c482..e53cb47077212c223880ad91c279b3d7342e2b3b 100644 --- a/test/net/sf/briar/plugins/bluetooth/BluetoothClientTest.java +++ b/test/net/sf/briar/plugins/bluetooth/BluetoothClientTest.java @@ -1,65 +1,81 @@ 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; - 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); - } + 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", args[0]); + p.put("address", serverAddress); p.put("uuid", BluetoothServerTest.UUID); callback.remote.put(contactId, p); // 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(); // Try to connect to the server System.out.println("Creating connection"); - StreamTransportConnection conn = plugin.createConnection(contactId); - if(conn == null) { + 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"); - 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); + 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); + } + new BluetoothClientTest(args[0]).run(); + } + private static class ClientCallback implements StreamPluginCallback { private TransportConfig config = new TransportConfig(); diff --git a/test/net/sf/briar/plugins/bluetooth/BluetoothServerTest.java b/test/net/sf/briar/plugins/bluetooth/BluetoothServerTest.java index ec9a54bb7ace051b2b7373a70eb94c1fa19d1889..ab7ed23ab947da5aa482023b6d568c4a95bb3345 100644 --- a/test/net/sf/briar/plugins/bluetooth/BluetoothServerTest.java +++ b/test/net/sf/briar/plugins/bluetooth/BluetoothServerTest.java @@ -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(); } diff --git a/test/net/sf/briar/plugins/bluetooth/BluetoothTest.java b/test/net/sf/briar/plugins/bluetooth/BluetoothTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ba8452cc3c2e7a59ad62f84193cb36e9bb76bfea --- /dev/null +++ b/test/net/sf/briar/plugins/bluetooth/BluetoothTest.java @@ -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); + } + } +} \ No newline at end of file diff --git a/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java b/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java index 05ec383485485d9f420a734224bd5abda6b9b11e..3cf909647896322670a2be6a81d57cc0a18b4bb9 100644 --- a/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java +++ b/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java @@ -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(); diff --git a/test/net/sf/briar/plugins/socket/SimpleSocketPluginTest.java b/test/net/sf/briar/plugins/socket/SimpleSocketPluginTest.java index 28e590fc5e0646e03212236b48eea9765b55e2d9..4faf6e16aa9e364334c0a6d27ed45e36dbfedda0 100644 --- a/test/net/sf/briar/plugins/socket/SimpleSocketPluginTest.java +++ b/test/net/sf/briar/plugins/socket/SimpleSocketPluginTest.java @@ -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