Commit bac7e341 authored by akwizgran's avatar akwizgran
Browse files

Add test for Bluetooth connections.

parent 37112a72
......@@ -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.StartResult.INVALID_ADDRESS;
import static org.briarproject.interferometer.MainViewModel.StartResult.NO_BLUETOOTH;
import static org.briarproject.interferometer.MainViewModel.TestPhase.BASELINE;
import static org.briarproject.interferometer.MainViewModel.TestPhase.BLUETOOTH;
import static org.briarproject.interferometer.MainViewModel.TestPhase.BT_DISABLED;
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.READY;
......@@ -73,15 +74,17 @@ public class MainActivity extends AppCompatActivity {
if (phase == READY) {
testButton.setText(R.string.start_test);
testPhaseTextView.setText(null);
} else if (phase == BASELINE) {
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) {
} else {
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;
import android.annotation.SuppressLint;
import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
......@@ -16,6 +19,8 @@ import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
......@@ -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.NO_BLUETOOTH;
import static org.briarproject.interferometer.MainViewModel.StartResult.STARTED;
import static org.briarproject.interferometer.MainViewModel.TestPhase.BASELINE;
import static org.briarproject.interferometer.MainViewModel.TestPhase.BLUETOOTH;
import static org.briarproject.interferometer.MainViewModel.TestPhase.BT_DISABLED;
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.READY;
import static org.briarproject.interferometer.Statistics.mean;
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}
......@@ -68,6 +74,7 @@ public class MainViewModel extends AndroidViewModel {
private final MutableLiveData<TestPhase> testPhase = new MutableLiveData<>();
private final MutableLiveData<ConnectionState> connectionState = new MutableLiveData<>();
private final MutableLiveData<Long> throughput = new MutableLiveData<>();
private final List<Socket> sockets = new CopyOnWriteArrayList<>();
@Nullable
private Future<?> task = null;
......@@ -94,6 +101,7 @@ public class MainViewModel extends AndroidViewModel {
if (hostPort == null) return INVALID_ADDRESS;
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) return NO_BLUETOOTH;
Log.i(TAG, "Starting test");
task = executorService.submit(() -> {
try {
if (!disableBluetooth(adapter)) {
......@@ -101,27 +109,27 @@ public class MainViewModel extends AndroidViewModel {
return;
}
testPhase.postValue(BASELINE);
Log.i(TAG, "Testing baseline throughput");
List<Long> downloadBaseline = new ArrayList<>();
List<Long> uploadBaseline = new ArrayList<>();
if (!testThroughput(hostPort, downloadBaseline, uploadBaseline)) return;
testPhase.postValue(BT_DISABLED);
Log.i(TAG, "Testing throughput with Bluetooth disabled");
List<Long> downloadDisabled = new ArrayList<>();
List<Long> uploadDisabled = new ArrayList<>();
if (!testThroughput(hostPort, downloadDisabled, uploadDisabled)) return;
if (!enableBluetooth(adapter)) {
Log.w(TAG, "Failed to enable Bluetooth");
return;
}
testPhase.postValue(BLUETOOTH);
testPhase.postValue(BT_ENABLED);
Log.i(TAG, "Testing throughput with Bluetooth enabled");
List<Long> downloadBt = new ArrayList<>();
List<Long> uploadBt = new ArrayList<>();
if (!testThroughput(hostPort, downloadBt, uploadBt)) return;
List<Long> downloadEnabled = new ArrayList<>();
List<Long> uploadEnabled = new ArrayList<>();
if (!testThroughput(hostPort, downloadEnabled, uploadEnabled)) return;
testPhase.postValue(DISCOVERY);
Log.i(TAG, "Testing throughput with Bluetooth discovery");
Future<?> discoveryTask = startDiscovery(adapter);
try {
testPhase.postValue(DISCOVERY);
Log.i(TAG, "Testing throughput with Bluetooth discovery");
List<Long> downloadDiscovery = new ArrayList<>();
List<Long> uploadDiscovery = new ArrayList<>();
if (!testThroughput(hostPort, downloadDiscovery, uploadDiscovery)) return;
......@@ -129,6 +137,19 @@ public class MainViewModel extends AndroidViewModel {
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");
} finally {
testPhase.postValue(READY);
......@@ -139,6 +160,7 @@ public class MainViewModel extends AndroidViewModel {
@UiThread
void stopTest() {
Log.i(TAG, "Stopping test");
cancelTask();
}
......@@ -155,6 +177,8 @@ public class MainViewModel extends AndroidViewModel {
task.cancel(true);
task = null;
}
// Interrupting a thread doesn't interrupt a socket read/write until it times out
for (Socket s : sockets) tryToClose(s);
}
@Nullable
......@@ -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")
private boolean testThroughput(HostPort hostPort, List<Long> downloadThroughput,
List<Long> uploadThroughput) {
......@@ -258,11 +315,12 @@ public class MainViewModel extends AndroidViewModel {
private long upload(HostPort hostPort) {
connectionState.postValue(UPLOADING);
Socket s = new Socket();
sockets.add(s);
try {
Log.i(TAG, "Connecting to " + hostPort);
Log.i(TAG, "Connecting to test server " + hostPort);
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();
out.write(CONNECTION_TYPE_UPLOAD);
out.flush();
......@@ -279,7 +337,7 @@ public class MainViewModel extends AndroidViewModel {
while (now < end) {
out.write(buf);
if (Thread.currentThread().isInterrupted()) {
Log.i(TAG, "Upload to " + hostPort + " interrupted");
Log.i(TAG, "Upload interrupted");
return -1;
}
bytesSinceLastUpdate += BUFFER_SIZE;
......@@ -293,15 +351,16 @@ public class MainViewModel extends AndroidViewModel {
bytesSinceLastUpdate = 0;
}
}
Log.i(TAG, "Upload to " + hostPort + " finished");
Log.i(TAG, "Upload finished");
long msSinceStart = (now - start) / 1_000_000;
return bytesTotal * 1000L / msSinceStart;
} catch (IOException e) {
Log.w(TAG, "Exception from " + hostPort + ": " + e);
Log.w(TAG, "Exception from test server: " + e);
return -1;
} finally {
connectionState.postValue(DISCONNECTED);
throughput.postValue(0L);
sockets.remove(s);
tryToClose(s);
}
}
......@@ -309,11 +368,12 @@ public class MainViewModel extends AndroidViewModel {
private long download(HostPort hostPort) {
connectionState.postValue(DOWNLOADING);
Socket s = new Socket();
sockets.add(s);
try {
Log.i(TAG, "Connecting to " + hostPort);
Log.i(TAG, "Connecting to test server " + hostPort);
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();
out.write(CONNECTION_TYPE_DOWNLOAD);
out.flush();
......@@ -330,11 +390,11 @@ public class MainViewModel extends AndroidViewModel {
while (now < end) {
int read = in.read(buf);
if (read == -1) {
Log.w(TAG, "Download from " + hostPort + " finished early");
Log.w(TAG, "Download finished early");
return -1;
}
if (Thread.currentThread().isInterrupted()) {
Log.i(TAG, "Download from " + hostPort + " interrupted");
Log.i(TAG, "Download interrupted");
return -1;
}
bytesSinceLastUpdate += read;
......@@ -348,16 +408,17 @@ public class MainViewModel extends AndroidViewModel {
bytesSinceLastUpdate = 0;
}
}
Log.i(TAG, "Download from " + hostPort + " finished");
Log.i(TAG, "Download finished");
now = System.nanoTime();
long msSinceStart = (now - start) / 1_000_000;
return bytesTotal * 1000L / msSinceStart;
} catch (IOException e) {
Log.w(TAG, "Exception from " + hostPort + ": " + e);
Log.w(TAG, "Exception from test server: " + e);
return -1;
} finally {
connectionState.postValue(DISCONNECTED);
throughput.postValue(0L);
sockets.remove(s);
tryToClose(s);
}
}
......
......@@ -9,9 +9,10 @@
<string name="start_test">Start test</string>
<string name="stop_test">Stop test</string>
<string name="testing_baseline">Testing with Bluetooth disabled</string>
<string name="testing_bluetooth">Testing with Bluetooth enabled</string>
<string name="testing_bt_disabled">Testing with Bluetooth disabled</string>
<string name="testing_bt_enabled">Testing with Bluetooth enabled</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="upload_format">Upload: %,d bytes/sec</string>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment