Skip to content
Snippets Groups Projects
Commit bac7e341 authored by akwizgran's avatar akwizgran
Browse files

Add test for Bluetooth connections.

parent 37112a72
No related branches found
No related tags found
No related merge requests found
...@@ -20,8 +20,9 @@ import static org.briarproject.interferometer.MainViewModel.ConnectionState.DOWN ...@@ -20,8 +20,9 @@ import static org.briarproject.interferometer.MainViewModel.ConnectionState.DOWN
import static org.briarproject.interferometer.MainViewModel.ConnectionState.UPLOADING; import static org.briarproject.interferometer.MainViewModel.ConnectionState.UPLOADING;
import static org.briarproject.interferometer.MainViewModel.StartResult.INVALID_ADDRESS; import static org.briarproject.interferometer.MainViewModel.StartResult.INVALID_ADDRESS;
import static org.briarproject.interferometer.MainViewModel.StartResult.NO_BLUETOOTH; import static org.briarproject.interferometer.MainViewModel.StartResult.NO_BLUETOOTH;
import static org.briarproject.interferometer.MainViewModel.TestPhase.BASELINE; import static org.briarproject.interferometer.MainViewModel.TestPhase.BT_DISABLED;
import static org.briarproject.interferometer.MainViewModel.TestPhase.BLUETOOTH; import static org.briarproject.interferometer.MainViewModel.TestPhase.BT_ENABLED;
import static org.briarproject.interferometer.MainViewModel.TestPhase.CONNECTIONS;
import static org.briarproject.interferometer.MainViewModel.TestPhase.DISCOVERY; import static org.briarproject.interferometer.MainViewModel.TestPhase.DISCOVERY;
import static org.briarproject.interferometer.MainViewModel.TestPhase.READY; import static org.briarproject.interferometer.MainViewModel.TestPhase.READY;
...@@ -73,15 +74,17 @@ public class MainActivity extends AppCompatActivity { ...@@ -73,15 +74,17 @@ public class MainActivity extends AppCompatActivity {
if (phase == READY) { if (phase == READY) {
testButton.setText(R.string.start_test); testButton.setText(R.string.start_test);
testPhaseTextView.setText(null); testPhaseTextView.setText(null);
} else if (phase == BASELINE) { } else {
testButton.setText(R.string.stop_test);
testPhaseTextView.setText(R.string.testing_baseline);
} else if (phase == BLUETOOTH) {
testButton.setText(R.string.stop_test);
testPhaseTextView.setText(R.string.testing_bluetooth);
} else if (phase == DISCOVERY) {
testButton.setText(R.string.stop_test); testButton.setText(R.string.stop_test);
testPhaseTextView.setText(R.string.testing_discovery); if (phase == BT_DISABLED) {
testPhaseTextView.setText(R.string.testing_bt_disabled);
} else if (phase == BT_ENABLED) {
testPhaseTextView.setText(R.string.testing_bt_enabled);
} else if (phase == DISCOVERY) {
testPhaseTextView.setText(R.string.testing_discovery);
} else if (phase == CONNECTIONS) {
testPhaseTextView.setText(R.string.testing_connections);
}
} }
} }
......
package org.briarproject.interferometer; package org.briarproject.interferometer;
import android.annotation.SuppressLint;
import android.app.Application; import android.app.Application;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
...@@ -16,6 +19,8 @@ import java.net.Socket; ...@@ -16,6 +19,8 @@ import java.net.Socket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
...@@ -40,15 +45,16 @@ import static org.briarproject.interferometer.MainViewModel.ConnectionState.UPLO ...@@ -40,15 +45,16 @@ import static org.briarproject.interferometer.MainViewModel.ConnectionState.UPLO
import static org.briarproject.interferometer.MainViewModel.StartResult.INVALID_ADDRESS; import static org.briarproject.interferometer.MainViewModel.StartResult.INVALID_ADDRESS;
import static org.briarproject.interferometer.MainViewModel.StartResult.NO_BLUETOOTH; import static org.briarproject.interferometer.MainViewModel.StartResult.NO_BLUETOOTH;
import static org.briarproject.interferometer.MainViewModel.StartResult.STARTED; import static org.briarproject.interferometer.MainViewModel.StartResult.STARTED;
import static org.briarproject.interferometer.MainViewModel.TestPhase.BASELINE; import static org.briarproject.interferometer.MainViewModel.TestPhase.BT_DISABLED;
import static org.briarproject.interferometer.MainViewModel.TestPhase.BLUETOOTH; import static org.briarproject.interferometer.MainViewModel.TestPhase.BT_ENABLED;
import static org.briarproject.interferometer.MainViewModel.TestPhase.CONNECTIONS;
import static org.briarproject.interferometer.MainViewModel.TestPhase.DISCOVERY; import static org.briarproject.interferometer.MainViewModel.TestPhase.DISCOVERY;
import static org.briarproject.interferometer.MainViewModel.TestPhase.READY; import static org.briarproject.interferometer.MainViewModel.TestPhase.READY;
import static org.briarproject.interferometer.Statistics.mean; import static org.briarproject.interferometer.Statistics.mean;
public class MainViewModel extends AndroidViewModel { public class MainViewModel extends AndroidViewModel {
enum TestPhase {READY, BASELINE, BLUETOOTH, DISCOVERY} enum TestPhase {READY, BT_DISABLED, BT_ENABLED, DISCOVERY, CONNECTIONS}
enum ConnectionState {DISCONNECTED, DOWNLOADING, UPLOADING} enum ConnectionState {DISCONNECTED, DOWNLOADING, UPLOADING}
...@@ -68,6 +74,7 @@ public class MainViewModel extends AndroidViewModel { ...@@ -68,6 +74,7 @@ public class MainViewModel extends AndroidViewModel {
private final MutableLiveData<TestPhase> testPhase = new MutableLiveData<>(); private final MutableLiveData<TestPhase> testPhase = new MutableLiveData<>();
private final MutableLiveData<ConnectionState> connectionState = new MutableLiveData<>(); private final MutableLiveData<ConnectionState> connectionState = new MutableLiveData<>();
private final MutableLiveData<Long> throughput = new MutableLiveData<>(); private final MutableLiveData<Long> throughput = new MutableLiveData<>();
private final List<Socket> sockets = new CopyOnWriteArrayList<>();
@Nullable @Nullable
private Future<?> task = null; private Future<?> task = null;
...@@ -94,6 +101,7 @@ public class MainViewModel extends AndroidViewModel { ...@@ -94,6 +101,7 @@ public class MainViewModel extends AndroidViewModel {
if (hostPort == null) return INVALID_ADDRESS; if (hostPort == null) return INVALID_ADDRESS;
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) return NO_BLUETOOTH; if (adapter == null) return NO_BLUETOOTH;
Log.i(TAG, "Starting test");
task = executorService.submit(() -> { task = executorService.submit(() -> {
try { try {
if (!disableBluetooth(adapter)) { if (!disableBluetooth(adapter)) {
...@@ -101,27 +109,27 @@ public class MainViewModel extends AndroidViewModel { ...@@ -101,27 +109,27 @@ public class MainViewModel extends AndroidViewModel {
return; return;
} }
testPhase.postValue(BASELINE); testPhase.postValue(BT_DISABLED);
Log.i(TAG, "Testing baseline throughput"); Log.i(TAG, "Testing throughput with Bluetooth disabled");
List<Long> downloadBaseline = new ArrayList<>(); List<Long> downloadDisabled = new ArrayList<>();
List<Long> uploadBaseline = new ArrayList<>(); List<Long> uploadDisabled = new ArrayList<>();
if (!testThroughput(hostPort, downloadBaseline, uploadBaseline)) return; if (!testThroughput(hostPort, downloadDisabled, uploadDisabled)) return;
if (!enableBluetooth(adapter)) { if (!enableBluetooth(adapter)) {
Log.w(TAG, "Failed to enable Bluetooth"); Log.w(TAG, "Failed to enable Bluetooth");
return; return;
} }
testPhase.postValue(BLUETOOTH); testPhase.postValue(BT_ENABLED);
Log.i(TAG, "Testing throughput with Bluetooth enabled"); Log.i(TAG, "Testing throughput with Bluetooth enabled");
List<Long> downloadBt = new ArrayList<>(); List<Long> downloadEnabled = new ArrayList<>();
List<Long> uploadBt = new ArrayList<>(); List<Long> uploadEnabled = new ArrayList<>();
if (!testThroughput(hostPort, downloadBt, uploadBt)) return; if (!testThroughput(hostPort, downloadEnabled, uploadEnabled)) return;
testPhase.postValue(DISCOVERY);
Log.i(TAG, "Testing throughput with Bluetooth discovery");
Future<?> discoveryTask = startDiscovery(adapter); Future<?> discoveryTask = startDiscovery(adapter);
try { try {
testPhase.postValue(DISCOVERY);
Log.i(TAG, "Testing throughput with Bluetooth discovery");
List<Long> downloadDiscovery = new ArrayList<>(); List<Long> downloadDiscovery = new ArrayList<>();
List<Long> uploadDiscovery = new ArrayList<>(); List<Long> uploadDiscovery = new ArrayList<>();
if (!testThroughput(hostPort, downloadDiscovery, uploadDiscovery)) return; if (!testThroughput(hostPort, downloadDiscovery, uploadDiscovery)) return;
...@@ -129,6 +137,19 @@ public class MainViewModel extends AndroidViewModel { ...@@ -129,6 +137,19 @@ public class MainViewModel extends AndroidViewModel {
discoveryTask.cancel(true); discoveryTask.cancel(true);
} }
testPhase.postValue(CONNECTIONS);
Log.i(TAG, "Testing throughput with Bluetooth connections");
Future<?> connectionTask = startConnections(adapter);
try {
List<Long> downloadConnections = new ArrayList<>();
List<Long> uploadConnections = new ArrayList<>();
if (!testThroughput(hostPort, downloadConnections, uploadConnections)) return;
} finally {
// Interrupting the task may not work, so we also need to disable the adapter
connectionTask.cancel(true);
adapter.disable();
}
Log.i(TAG, "Test finished"); Log.i(TAG, "Test finished");
} finally { } finally {
testPhase.postValue(READY); testPhase.postValue(READY);
...@@ -139,6 +160,7 @@ public class MainViewModel extends AndroidViewModel { ...@@ -139,6 +160,7 @@ public class MainViewModel extends AndroidViewModel {
@UiThread @UiThread
void stopTest() { void stopTest() {
Log.i(TAG, "Stopping test");
cancelTask(); cancelTask();
} }
...@@ -155,6 +177,8 @@ public class MainViewModel extends AndroidViewModel { ...@@ -155,6 +177,8 @@ public class MainViewModel extends AndroidViewModel {
task.cancel(true); task.cancel(true);
task = null; task = null;
} }
// Interrupting a thread doesn't interrupt a socket read/write until it times out
for (Socket s : sockets) tryToClose(s);
} }
@Nullable @Nullable
...@@ -229,6 +253,39 @@ public class MainViewModel extends AndroidViewModel { ...@@ -229,6 +253,39 @@ public class MainViewModel extends AndroidViewModel {
}); });
} }
private Future<?> startConnections(BluetoothAdapter adapter) {
return executorService.submit(() -> {
byte[] mac = new byte[6];
byte[] uuidBytes = new byte[16];
Random random = new Random();
while (!Thread.currentThread().isInterrupted() && adapter.isEnabled()) {
random.nextBytes(mac);
random.nextBytes(uuidBytes);
UUID uuid = UUID.nameUUIDFromBytes(uuidBytes);
Log.i(TAG, "Connecting to fake Bluetooth device " + macToString(mac));
BluetoothDevice device = adapter.getRemoteDevice(mac);
try {
BluetoothSocket socket = device.createInsecureRfcommSocketToServiceRecord(uuid);
socket.connect();
Log.w(TAG, "Connected to fake Bluetooth device");
socket.close();
} catch (IOException e) {
// Expected
}
}
});
}
@SuppressLint("DefaultLocale")
private String macToString(byte[] mac) {
StringBuilder s = new StringBuilder();
for (byte b : mac) {
if (s.length() > 0) s.append(':');
s.append(String.format("%02x", b & 0xFF));
}
return s.toString();
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted") @SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean testThroughput(HostPort hostPort, List<Long> downloadThroughput, private boolean testThroughput(HostPort hostPort, List<Long> downloadThroughput,
List<Long> uploadThroughput) { List<Long> uploadThroughput) {
...@@ -258,11 +315,12 @@ public class MainViewModel extends AndroidViewModel { ...@@ -258,11 +315,12 @@ public class MainViewModel extends AndroidViewModel {
private long upload(HostPort hostPort) { private long upload(HostPort hostPort) {
connectionState.postValue(UPLOADING); connectionState.postValue(UPLOADING);
Socket s = new Socket(); Socket s = new Socket();
sockets.add(s);
try { try {
Log.i(TAG, "Connecting to " + hostPort); Log.i(TAG, "Connecting to test server " + hostPort);
s.connect(new InetSocketAddress(hostPort.host, hostPort.port)); s.connect(new InetSocketAddress(hostPort.host, hostPort.port));
Log.i(TAG, "Connected, requesting upload"); Log.i(TAG, "Connected to test server, requesting upload");
OutputStream out = s.getOutputStream(); OutputStream out = s.getOutputStream();
out.write(CONNECTION_TYPE_UPLOAD); out.write(CONNECTION_TYPE_UPLOAD);
out.flush(); out.flush();
...@@ -279,7 +337,7 @@ public class MainViewModel extends AndroidViewModel { ...@@ -279,7 +337,7 @@ public class MainViewModel extends AndroidViewModel {
while (now < end) { while (now < end) {
out.write(buf); out.write(buf);
if (Thread.currentThread().isInterrupted()) { if (Thread.currentThread().isInterrupted()) {
Log.i(TAG, "Upload to " + hostPort + " interrupted"); Log.i(TAG, "Upload interrupted");
return -1; return -1;
} }
bytesSinceLastUpdate += BUFFER_SIZE; bytesSinceLastUpdate += BUFFER_SIZE;
...@@ -293,15 +351,16 @@ public class MainViewModel extends AndroidViewModel { ...@@ -293,15 +351,16 @@ public class MainViewModel extends AndroidViewModel {
bytesSinceLastUpdate = 0; bytesSinceLastUpdate = 0;
} }
} }
Log.i(TAG, "Upload to " + hostPort + " finished"); Log.i(TAG, "Upload finished");
long msSinceStart = (now - start) / 1_000_000; long msSinceStart = (now - start) / 1_000_000;
return bytesTotal * 1000L / msSinceStart; return bytesTotal * 1000L / msSinceStart;
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, "Exception from " + hostPort + ": " + e); Log.w(TAG, "Exception from test server: " + e);
return -1; return -1;
} finally { } finally {
connectionState.postValue(DISCONNECTED); connectionState.postValue(DISCONNECTED);
throughput.postValue(0L); throughput.postValue(0L);
sockets.remove(s);
tryToClose(s); tryToClose(s);
} }
} }
...@@ -309,11 +368,12 @@ public class MainViewModel extends AndroidViewModel { ...@@ -309,11 +368,12 @@ public class MainViewModel extends AndroidViewModel {
private long download(HostPort hostPort) { private long download(HostPort hostPort) {
connectionState.postValue(DOWNLOADING); connectionState.postValue(DOWNLOADING);
Socket s = new Socket(); Socket s = new Socket();
sockets.add(s);
try { try {
Log.i(TAG, "Connecting to " + hostPort); Log.i(TAG, "Connecting to test server " + hostPort);
s.connect(new InetSocketAddress(hostPort.host, hostPort.port)); s.connect(new InetSocketAddress(hostPort.host, hostPort.port));
Log.i(TAG, "Connected, requesting download"); Log.i(TAG, "Connected to test server, requesting download");
OutputStream out = s.getOutputStream(); OutputStream out = s.getOutputStream();
out.write(CONNECTION_TYPE_DOWNLOAD); out.write(CONNECTION_TYPE_DOWNLOAD);
out.flush(); out.flush();
...@@ -330,11 +390,11 @@ public class MainViewModel extends AndroidViewModel { ...@@ -330,11 +390,11 @@ public class MainViewModel extends AndroidViewModel {
while (now < end) { while (now < end) {
int read = in.read(buf); int read = in.read(buf);
if (read == -1) { if (read == -1) {
Log.w(TAG, "Download from " + hostPort + " finished early"); Log.w(TAG, "Download finished early");
return -1; return -1;
} }
if (Thread.currentThread().isInterrupted()) { if (Thread.currentThread().isInterrupted()) {
Log.i(TAG, "Download from " + hostPort + " interrupted"); Log.i(TAG, "Download interrupted");
return -1; return -1;
} }
bytesSinceLastUpdate += read; bytesSinceLastUpdate += read;
...@@ -348,16 +408,17 @@ public class MainViewModel extends AndroidViewModel { ...@@ -348,16 +408,17 @@ public class MainViewModel extends AndroidViewModel {
bytesSinceLastUpdate = 0; bytesSinceLastUpdate = 0;
} }
} }
Log.i(TAG, "Download from " + hostPort + " finished"); Log.i(TAG, "Download finished");
now = System.nanoTime(); now = System.nanoTime();
long msSinceStart = (now - start) / 1_000_000; long msSinceStart = (now - start) / 1_000_000;
return bytesTotal * 1000L / msSinceStart; return bytesTotal * 1000L / msSinceStart;
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, "Exception from " + hostPort + ": " + e); Log.w(TAG, "Exception from test server: " + e);
return -1; return -1;
} finally { } finally {
connectionState.postValue(DISCONNECTED); connectionState.postValue(DISCONNECTED);
throughput.postValue(0L); throughput.postValue(0L);
sockets.remove(s);
tryToClose(s); tryToClose(s);
} }
} }
......
...@@ -9,9 +9,10 @@ ...@@ -9,9 +9,10 @@
<string name="start_test">Start test</string> <string name="start_test">Start test</string>
<string name="stop_test">Stop test</string> <string name="stop_test">Stop test</string>
<string name="testing_baseline">Testing with Bluetooth disabled</string> <string name="testing_bt_disabled">Testing with Bluetooth disabled</string>
<string name="testing_bluetooth">Testing with Bluetooth enabled</string> <string name="testing_bt_enabled">Testing with Bluetooth enabled</string>
<string name="testing_discovery">Testing with Bluetooth discovery</string> <string name="testing_discovery">Testing with Bluetooth discovery</string>
<string name="testing_connections">Testing with Bluetooth connections</string>
<string name="download_format">Download: %,d bytes/sec</string> <string name="download_format">Download: %,d bytes/sec</string>
<string name="upload_format">Upload: %,d bytes/sec</string> <string name="upload_format">Upload: %,d bytes/sec</string>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment