discovery: a new discovery api
- Make 'Update' as an interface
- Refactor libs and directory structure
- Fix bugs
* Couldn't test android-lib yet, but will do.
MultiPart: 4/4
Change-Id: I1043d9e46848b55e716ddbee8f7c8a9e81770da3
diff --git a/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/NativeScanHandler.java b/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/NativeScanHandler.java
new file mode 100644
index 0000000..c1b62da
--- /dev/null
+++ b/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/NativeScanHandler.java
@@ -0,0 +1,38 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.android.impl.google.discovery.plugins;
+
+import io.v.x.ref.lib.discovery.AdInfo;
+
+import io.v.impl.google.lib.discovery.Plugin;
+
+/**
+ * An implementation of the {@link Plugin.ScanHandler} for use by the discovery framework.
+ * <p>
+ * This handler is used to pass results from a Java plugin to the Go wrapper to passed
+ * on to the discovery instance.
+ */
+class NativeScanHandler implements Plugin.ScanHandler {
+ // A pointer to the the native channel.
+ private final long nativeChan;
+
+ private NativeScanHandler(long nativeChan) {
+ this.nativeChan = nativeChan;
+ }
+
+ private native void nativeHandleUpdate(long chan, AdInfo adinfo);
+
+ private native void nativeFinalize(long chan);
+
+ @Override
+ public void handleUpdate(AdInfo adinfo) {
+ nativeHandleUpdate(nativeChan, adinfo);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ nativeFinalize(nativeChan);
+ }
+}
diff --git a/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/ble/BlePlugin.java b/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/ble/BlePlugin.java
new file mode 100644
index 0000000..28b793d
--- /dev/null
+++ b/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/ble/BlePlugin.java
@@ -0,0 +1,354 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.android.impl.google.discovery.plugins.ble;
+
+import android.Manifest;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanRecord;
+import android.bluetooth.le.ScanSettings;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import org.joda.time.Duration;
+
+import io.v.v23.context.VContext;
+import io.v.v23.discovery.AdId;
+
+import io.v.x.ref.lib.discovery.AdInfo;
+
+import io.v.impl.google.lib.discovery.UUIDUtil;
+import io.v.impl.google.lib.discovery.Plugin;
+
+/**
+ * The discovery plugin interface for BLE.
+ */
+public class BlePlugin implements Plugin {
+ private static final String TAG = "BlePlugin";
+
+ // We are using a constant for the MTU because Android and paypal/gatt don't get along
+ // when the paypal gatt client sends a setMTU message. The Android server seems to send
+ // a malformed L2CAP message.
+ private static final int MTU = 23;
+
+ // Default device cache expiration timeout.
+ private static final Duration defaultCacheDuration = Duration.standardSeconds(90);
+
+ // Random generator for stamp.
+ private final SecureRandom random = new SecureRandom();
+
+ private final Context androidContext;
+
+ // Set of Ble objects that will be interacted with to perform operations.
+ private BluetoothLeAdvertiser bluetoothLeAdvertise;
+ private BluetoothLeScanner bluetoothLeScanner;
+ private BluetoothGattServer bluetoothGattServer;
+
+ private Map<AdId, BluetoothGattService> advertisements;
+ private AdvertiseCallback advertiseCallback;
+
+ private Set<Plugin.ScanHandler> scanners;
+ private Set<String> pendingConnections;
+ private DeviceCache deviceCache;
+ private ScanCallback scanCallback;
+
+ // If isEnabled is false, then all operations on the ble plugin are no-oped. This will only
+ // be false if the ble hardware is inaccessible.
+ private boolean isEnabled = false;
+
+ private boolean hasPermission(String perm) {
+ return ContextCompat.checkSelfPermission(androidContext, perm)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ public BlePlugin(String host, Context androidContext) {
+ this.androidContext = androidContext;
+ BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
+ return;
+ }
+ if (!hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
+ && !hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
+ return;
+ }
+
+ bluetoothLeAdvertise = bluetoothAdapter.getBluetoothLeAdvertiser();
+ bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
+ BluetoothManager manager =
+ (BluetoothManager) androidContext.getSystemService(Context.BLUETOOTH_SERVICE);
+ bluetoothGattServer =
+ manager.openGattServer(
+ androidContext,
+ new BluetoothGattServerCallback() {
+ @Override
+ public void onConnectionStateChange(
+ BluetoothDevice device, int status, int newState) {
+ super.onConnectionStateChange(device, status, newState);
+ }
+
+ @Override
+ public void onCharacteristicReadRequest(
+ BluetoothDevice device,
+ int requestId,
+ int offset,
+ BluetoothGattCharacteristic characteristic) {
+ super.onCharacteristicReadRequest(
+ device, requestId, offset, characteristic);
+ byte[] total = characteristic.getValue();
+ byte[] res = {};
+ // Only send MTU - 1 bytes. The first byte of all packets is the op code.
+ if (offset < total.length) {
+ int finalByte = offset + MTU - 1;
+ if (finalByte > total.length) {
+ finalByte = total.length;
+ }
+ res = Arrays.copyOfRange(total, offset, finalByte);
+ bluetoothGattServer.sendResponse(
+ device, requestId, BluetoothGatt.GATT_SUCCESS, 0, res);
+ } else {
+ // This should probably be an error, but a bug in the paypal/gatt code causes an
+ // infinite loop if this returns an error rather than the empty value.
+ bluetoothGattServer.sendResponse(
+ device, requestId, BluetoothGatt.GATT_SUCCESS, 0, res);
+ }
+ }
+ });
+
+ advertisements = new HashMap<>();
+ scanners = new HashSet<>();
+ pendingConnections = new HashSet<>();
+ deviceCache = new DeviceCache(defaultCacheDuration);
+ isEnabled = true;
+ }
+
+ public void startAdvertising(AdInfo adInfo) throws Exception {
+ if (!isEnabled) {
+ throw new IllegalStateException("BlePlugin not enabled");
+ }
+
+ BluetoothGattService service =
+ new BluetoothGattService(
+ UUIDUtil.serviceUUID(adInfo.getAd().getInterfaceName()),
+ BluetoothGattService.SERVICE_TYPE_PRIMARY);
+ for (Map.Entry<UUID, byte[]> entry : ConvertUtil.toGattAttrs(adInfo).entrySet()) {
+ BluetoothGattCharacteristic c =
+ new BluetoothGattCharacteristic(
+ entry.getKey(),
+ BluetoothGattCharacteristic.PROPERTY_READ,
+ BluetoothGattCharacteristic.PERMISSION_READ);
+ c.setValue(entry.getValue());
+ service.addCharacteristic(c);
+ }
+
+ synchronized (advertisements) {
+ advertisements.put(adInfo.getAd().getId(), service);
+ bluetoothGattServer.addService(service);
+ updateAdvertising();
+ }
+ }
+
+ public void stopAdvertising(AdInfo adInfo) {
+ synchronized (advertisements) {
+ BluetoothGattService service = advertisements.remove(adInfo.getAd().getId());
+ if (service != null) {
+ bluetoothGattServer.removeService(service);
+ updateAdvertising();
+ }
+ }
+ }
+
+ private long genStamp() {
+ // We use 8-byte stamp to reflect the current services of the current device.
+ //
+ // TODO(bjornick): 8-byte random number might not be good enough for
+ // global uniqueness. We might want to consider a better way to generate
+ // stamp like using a unique device id with sequence number.
+ return new BigInteger(64, random).longValue();
+ }
+
+ private void updateAdvertising() {
+ if (advertiseCallback != null) {
+ bluetoothLeAdvertise.stopAdvertising(advertiseCallback);
+ advertiseCallback = null;
+ }
+ if (advertisements.size() == 0) {
+ return;
+ }
+
+ AdvertiseData.Builder builder = new AdvertiseData.Builder();
+ ByteBuffer buf = ByteBuffer.allocate(9);
+ buf.put((byte) 8);
+ buf.putLong(genStamp());
+ builder.addManufacturerData(1001, buf.array());
+ AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder();
+ settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY);
+ settingsBuilder.setConnectable(true);
+ advertiseCallback =
+ new AdvertiseCallback() {
+ @Override
+ public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+ Log.d(TAG, "started " + settingsInEffect);
+ }
+
+ @Override
+ public void onStartFailure(int errorCode) {
+ Log.e(TAG, "failed to start advertising " + errorCode);
+ }
+ };
+ bluetoothLeAdvertise.startAdvertising(
+ settingsBuilder.build(), builder.build(), advertiseCallback);
+ }
+
+ public void startScan(String interfaceName, Plugin.ScanHandler handler) throws Exception {
+ if (!isEnabled) {
+ throw new IllegalStateException("BlePlugin not enabled");
+ }
+
+ synchronized (scanners) {
+ if (!scanners.add(handler)) {
+ throw new IllegalArgumentException("handler already registered");
+ }
+ deviceCache.addScanner(interfaceName, handler);
+ updateScan();
+ }
+ }
+
+ public void stopScan(Plugin.ScanHandler handler) {
+ synchronized (scanners) {
+ if (!scanners.remove(handler)) {
+ return;
+ }
+ deviceCache.removeScanner(handler);
+ updateScan();
+ }
+ }
+
+ private void updateScan() {
+ // TODO(jhahn): Verify whether we need to stop scanning while connect to remote GATT servers.
+ if (scanners.isEmpty()) {
+ if (pendingConnections.isEmpty()) {
+ bluetoothLeScanner.stopScan(scanCallback);
+ scanCallback = null;
+ }
+ return;
+ }
+ if (scanCallback != null) {
+ return;
+ }
+
+ final List<ScanFilter> scanFilters =
+ ImmutableList.of(
+ new ScanFilter.Builder()
+ .setManufacturerData(1001, new byte[0], new byte[0])
+ .build());
+ final ScanSettings scanSettings =
+ new ScanSettings.Builder()
+ .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
+ .setScanMode(ScanSettings.SCAN_MODE_BALANCED)
+ .build();
+ scanCallback =
+ new ScanCallback() {
+ @Override
+ public void onScanResult(int callbackType, ScanResult result) {
+ ScanRecord record = result.getScanRecord();
+ // Use 1001 to denote that this is a Vanadium device. We picked an id that is
+ // currently not in use.
+ byte[] data = record.getManufacturerSpecificData(1001);
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+ final long stamp = buffer.getLong();
+ final String deviceId = result.getDevice().getAddress();
+ if (deviceCache.haveSeenStamp(stamp, deviceId)) {
+ return;
+ }
+
+ BluetoothGattReader.Handler handler =
+ new BluetoothGattReader.Handler() {
+ @Override
+ public void handle(Map<UUID, Map<UUID, byte[]>> services) {
+ if (services != null) {
+ List<AdInfo> adInfos = new ArrayList<>();
+ for (Map.Entry<UUID, Map<UUID, byte[]>> entry :
+ services.entrySet()) {
+ try {
+ AdInfo adInfo =
+ ConvertUtil.toAdInfo(entry.getValue());
+ adInfos.add(adInfo);
+ } catch (IOException e) {
+ Log.e(
+ TAG,
+ "failed to convert advertisement" + e);
+ }
+ }
+ deviceCache.saveDevice(stamp, deviceId, adInfos);
+ }
+ synchronized (scanners) {
+ pendingConnections.remove(deviceId);
+ if (pendingConnections.isEmpty()) {
+ if (scanners.isEmpty()) {
+ scanCallback = null;
+ return;
+ }
+ bluetoothLeScanner.startScan(
+ scanFilters, scanSettings, scanCallback);
+ }
+ }
+ }
+ };
+ BluetoothGattReader cb = new BluetoothGattReader(handler);
+ synchronized (scanners) {
+ if (scanners.isEmpty()) {
+ return;
+ }
+ if (!pendingConnections.add(deviceId)) {
+ return;
+ }
+ if (pendingConnections.size() == 1) {
+ bluetoothLeScanner.stopScan(scanCallback);
+ }
+ }
+ Log.d(TAG, "connecting to " + result.getDevice());
+ result.getDevice().connectGatt(androidContext, false, cb);
+ }
+
+ @Override
+ public void onBatchScanResults(List<ScanResult> results) {}
+
+ @Override
+ public void onScanFailed(int errorCode) {}
+ };
+ bluetoothLeScanner.startScan(scanFilters, scanSettings, scanCallback);
+ }
+}
diff --git a/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/ble/BluetoothGattReader.java b/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/ble/BluetoothGattReader.java
new file mode 100644
index 0000000..b73a8a2
--- /dev/null
+++ b/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/ble/BluetoothGattReader.java
@@ -0,0 +1,113 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.android.impl.google.discovery.plugins.ble;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * A handler for responses from a GattServer.
+ */
+class BluetoothGattReader extends BluetoothGattCallback {
+ private static final String TAG = "BluetoothGattClientCallback";
+
+ // A handler that will get called when all the services from a GATT service are read.
+ interface Handler {
+ /**
+ * Called with the map of service ids to their attributes.
+ *
+ * @param services A map from service id to (characteristics uuid to values).
+ */
+ void handle(Map<UUID, Map<UUID, byte[]>> services);
+ }
+
+ // We want to ignore the GATT and GAP services, which are 1800 and 1801 respectively.
+ static final String GATT_AND_GAP_PREFIX = "0000180";
+
+ private final Handler handler;
+ private final Map<UUID, Map<UUID, byte[]>> services = new HashMap<>();
+
+ private BluetoothGatt gatt;
+
+ private final List<BluetoothGattCharacteristic> characteristics = new ArrayList<>();
+ private int characteristicsIndex;
+
+ BluetoothGattReader(Handler handler) {
+ this.handler = handler;
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ for (BluetoothGattService service : gatt.getServices()) {
+ Log.d(TAG, "found service" + service.getUuid().toString());
+ // Skip the GATT AND GAP Services.
+ if (service.getUuid().toString().startsWith(GATT_AND_GAP_PREFIX)) {
+ continue;
+ }
+
+ services.put(service.getUuid(), new HashMap<UUID, byte[]>());
+ // We only keep track of the characteristics that can be read.
+ for (BluetoothGattCharacteristic c : service.getCharacteristics()) {
+ if ((c.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) != 0) {
+ characteristics.add(c);
+ } else {
+ Log.d(TAG, "skipping non read property");
+ }
+ }
+ }
+ characteristicsIndex = 0;
+ maybeReadNextCharacteristic();
+ }
+
+ // Reads the next characteristic if there is one. Otherwise calls handler and
+ // closes the GATT connection.
+ private void maybeReadNextCharacteristic() {
+ if (characteristicsIndex >= characteristics.size()) {
+ gatt.disconnect();
+ gatt.close();
+ handler.handle(services);
+ return;
+ }
+ BluetoothGattCharacteristic c = characteristics.get(characteristicsIndex++);
+ if (!gatt.readCharacteristic(c)) {
+ Log.w(TAG, "failed to read characteristic " + c.getUuid());
+ maybeReadNextCharacteristic();
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(
+ BluetoothGatt gatt, BluetoothGattCharacteristic c, int status) {
+ UUID serviceUuid = c.getService().getUuid();
+ Log.d(TAG, "got characteristic [" + serviceUuid + "]" + c.getUuid() + "=" + c.getValue());
+
+ services.get(serviceUuid).put(c.getUuid(), c.getValue());
+ maybeReadNextCharacteristic();
+ }
+
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ Log.d(TAG, "new connections state is " + newState);
+
+ this.gatt = gatt;
+ if (status != BluetoothGatt.GATT_SUCCESS || newState != BluetoothGatt.STATE_CONNECTED) {
+ Log.w(TAG, "failed to connect with status " + status + " state" + newState);
+ gatt.close();
+ handler.handle(null);
+ return;
+ }
+ gatt.discoverServices();
+ }
+}
diff --git a/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/ble/ConvertUtil.java b/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/ble/ConvertUtil.java
new file mode 100644
index 0000000..5ba958b
--- /dev/null
+++ b/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/ble/ConvertUtil.java
@@ -0,0 +1,144 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.android.impl.google.discovery.plugins.ble;
+
+import com.google.common.primitives.Bytes;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.logging.Logger;
+
+import io.v.v23.discovery.Advertisement;
+import io.v.v23.discovery.AdId;
+
+import io.v.x.ref.lib.discovery.AdInfo;
+import io.v.x.ref.lib.discovery.AdHash;
+import io.v.x.ref.lib.discovery.EncryptionAlgorithm;
+import io.v.x.ref.lib.discovery.EncryptionKey;
+import io.v.x.ref.lib.discovery.plugins.ble.Constants;
+
+import io.v.impl.google.lib.discovery.EncodingUtil;
+import io.v.impl.google.lib.discovery.UUIDUtil;
+
+/**
+ * Converts from {@link AdInfo} to GATT characteristics and vice-versa.
+ */
+class ConvertUtil {
+ private static final Logger logger = Logger.getLogger(ConvertUtil.class.getName());
+
+ // We use "ISO8859-1" to preserve data in a string without any interpretation.
+ private static final Charset ENC = Charset.forName("ISO8859-1");
+
+ private static final UUID UUID_ID = UUID.fromString(Constants.ID_UUID);
+ private static final UUID UUID_INTERFACE_NAME = UUID.fromString(Constants.INTERFACE_NAME_UUID);
+ private static final UUID UUID_ADDRESSES = UUID.fromString(Constants.ADDRESSES_UUID);
+ private static final UUID UUID_ENCRYPTION = UUID.fromString(Constants.ENCRYPTION_UUID);
+ private static final UUID UUID_HASH = UUID.fromString(Constants.HASH_UUID);
+ private static final UUID UUID_DIR_ADDRS = UUID.fromString(Constants.DIR_ADDRS_UUID);
+
+ /**
+ * Converts from {@link AdInfo} to GATT characteristics.
+ *
+ * @param adinfo an advertisement information to convert
+ * @return a map of GATT characteristics corresponding to the {@link adinfo}
+ * @throws IOException if the advertisement can't be converted
+ */
+ static Map<UUID, byte[]> toGattAttrs(AdInfo adinfo) throws IOException {
+ Map<UUID, byte[]> gatt = new HashMap<>();
+ Advertisement ad = adinfo.getAd();
+ gatt.put(UUID_ID, ad.getId().toPrimitiveArray());
+ gatt.put(UUID_INTERFACE_NAME, ad.getInterfaceName().getBytes(ENC));
+ gatt.put(UUID_ADDRESSES, EncodingUtil.packAddresses(ad.getAddresses()));
+
+ Map<String, String> attributes = ad.getAttributes();
+ if (attributes != null && attributes.size() > 0) {
+ for (Map.Entry<String, String> entry : attributes.entrySet()) {
+ String key = entry.getKey();
+ String data = key + "=" + entry.getValue();
+ gatt.put(UUIDUtil.attributeUUID(key), data.getBytes(ENC));
+ }
+ }
+
+ Map<String, byte[]> attachments = ad.getAttachments();
+ if (attachments != null && attachments.size() > 0) {
+ for (Map.Entry<String, byte[]> entry : attachments.entrySet()) {
+ String key = Constants.ATTACHMENT_NAME_PREFIX + entry.getKey();
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ buf.write(key.getBytes(ENC));
+ buf.write((byte) '=');
+ buf.write(entry.getValue());
+ gatt.put(UUIDUtil.attributeUUID(key), buf.toByteArray());
+ }
+ }
+ if (adinfo.getEncryptionAlgorithm() != io.v.x.ref.lib.discovery.Constants.NO_ENCRYPTION) {
+ gatt.put(
+ UUID_ENCRYPTION,
+ EncodingUtil.packEncryptionKeys(
+ adinfo.getEncryptionAlgorithm(), adinfo.getEncryptionKeys()));
+ }
+ List<String> dirAddrs = adinfo.getDirAddrs();
+ if (dirAddrs != null && !dirAddrs.isEmpty()) {
+ gatt.put(UUID_DIR_ADDRS, EncodingUtil.packAddresses(dirAddrs));
+ }
+ gatt.put(UUID_HASH, adinfo.getHash().toPrimitiveArray());
+ return gatt;
+ }
+
+ /**
+ * Converts from GATT characteristics to {@link AdInfo}.
+ *
+ * @param attrs a map of GATT characteristics
+ * @return an advertisement information corresponding to the {@link attrs}
+ * @throws IOException if the GATT characteristics can't be converted
+ */
+ static AdInfo toAdInfo(Map<UUID, byte[]> attrs) throws IOException {
+ AdInfo adinfo = new AdInfo();
+ Advertisement ad = adinfo.getAd();
+ for (Map.Entry<UUID, byte[]> entry : attrs.entrySet()) {
+ UUID uuid = entry.getKey();
+ byte[] data = entry.getValue();
+
+ if (uuid.equals(UUID_ID)) {
+ ad.setId(new AdId(data));
+ } else if (uuid.equals(UUID_INTERFACE_NAME)) {
+ ad.setInterfaceName(new String(data, ENC));
+ } else if (uuid.equals(UUID_ADDRESSES)) {
+ ad.setAddresses(EncodingUtil.unpackAddresses(data));
+ } else if (uuid.equals(UUID_ENCRYPTION)) {
+ List<EncryptionKey> keys = new ArrayList<>();
+ EncryptionAlgorithm algo = EncodingUtil.unpackEncryptionKeys(data, keys);
+ adinfo.setEncryptionAlgorithm(algo);
+ adinfo.setEncryptionKeys(keys);
+ } else if (uuid.equals(UUID_DIR_ADDRS)) {
+ adinfo.setDirAddrs(EncodingUtil.unpackAddresses(data));
+ } else if (uuid.equals(UUID_HASH)) {
+ adinfo.setHash(new AdHash(data));
+ } else {
+ int index = Bytes.indexOf(data, (byte) '=');
+ if (index < 0) {
+ logger.severe("Failed to parse data for " + uuid);
+ continue;
+ }
+ String key = new String(data, 0, index, ENC);
+ if (key.startsWith(Constants.ATTACHMENT_NAME_PREFIX)) {
+ key = key.substring(Constants.ATTACHMENT_NAME_PREFIX.length());
+ byte[] value = Arrays.copyOfRange(data, index + 1, data.length);
+ ad.getAttachments().put(key, value);
+ } else {
+ String value = new String(data, index + 1, data.length - index - 1, ENC);
+ ad.getAttributes().put(key, value);
+ }
+ }
+ }
+ return adinfo;
+ }
+}
diff --git a/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/ble/DeviceCache.java b/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/ble/DeviceCache.java
new file mode 100644
index 0000000..6e9ab59
--- /dev/null
+++ b/android-lib/src/main/java/io/v/android/impl/google/discovery/plugins/ble/DeviceCache.java
@@ -0,0 +1,254 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.android.impl.google.discovery.plugins.ble;
+
+import com.google.common.base.Equivalence;
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
+
+import org.joda.time.Duration;
+import org.joda.time.Instant;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import io.v.x.ref.lib.discovery.AdInfo;
+
+import io.v.impl.google.lib.discovery.Plugin;
+
+/**
+ * A cache of ble devices that were seen recently.
+ * <p>
+ * The current Vanadium BLE protocol requires connecting to the advertiser
+ * to grab the attributes and the addrs. This can be expensive so we only
+ * refetch the data if its stamp changed.
+ */
+class DeviceCache {
+ // Stores an interface name and a {@link Plugin.ScanHandler}.
+ private static class Scanner {
+ private final String interfaceName;
+ private final Plugin.ScanHandler handler;
+
+ private Scanner(String interfaceName, Plugin.ScanHandler handler) {
+ this.interfaceName = interfaceName;
+ this.handler = handler;
+ }
+ }
+
+ // Stores advertisements from a device with a stamp.
+ private static class CacheEntry {
+ private final long stamp;
+ private String deviceId;
+ private Set<Equivalence.Wrapper<AdInfo>> adInfos;
+ private Instant lastSeen;
+
+ private CacheEntry(long stamp, String deviceId, Set<Equivalence.Wrapper<AdInfo>> adInfos) {
+ this.stamp = stamp;
+ this.deviceId = deviceId;
+ this.adInfos = adInfos;
+ this.lastSeen = new Instant();
+ }
+ }
+
+ private static final Equivalence<AdInfo> ADINFO_EQUIVALENCE =
+ new Equivalence<AdInfo>() {
+ @Override
+ protected boolean doEquivalent(AdInfo a, AdInfo b) {
+ return a.getAd().getId().equals(b.getAd().getId())
+ && a.getHash().equals(b.getHash());
+ }
+
+ @Override
+ protected int doHash(AdInfo adinfo) {
+ return Objects.hashCode(adinfo.getAd().getId(), adinfo.getHash());
+ }
+ };
+ private static final Function<AdInfo, Equivalence.Wrapper<AdInfo>> ADINFO_WRAPPER =
+ new Function<AdInfo, Equivalence.Wrapper<AdInfo>>() {
+ @Override
+ public Equivalence.Wrapper<AdInfo> apply(AdInfo adinfo) {
+ return ADINFO_EQUIVALENCE.wrap(adinfo);
+ }
+ };
+
+ private final Map<Long, CacheEntry> cacheByStamp = new HashMap<>();
+ private final Map<String, CacheEntry> cacheByDeviceId = new HashMap<>();
+
+ private final SetMultimap<String, Equivalence.Wrapper<AdInfo>> adInfosByInterfaceName =
+ HashMultimap.create();
+
+ private final SetMultimap<String, Scanner> scannersByInterfaceName = HashMultimap.create();
+ private final Map<Plugin.ScanHandler, Scanner> scannersByHandler = new HashMap<>();
+
+ private final Duration maxAge;
+ private ScheduledExecutorService timer;
+
+ DeviceCache(Duration maxAge) {
+ this.maxAge = maxAge;
+ this.timer = Executors.newSingleThreadScheduledExecutor();
+ long periodicity = maxAge.getMillis() / 2;
+ timer.scheduleAtFixedRate(
+ new Runnable() {
+ @Override
+ public void run() {
+ removeStaleEntries();
+ }
+ },
+ periodicity,
+ periodicity,
+ TimeUnit.MILLISECONDS);
+ }
+
+ private void removeStaleEntries() {
+ synchronized (this) {
+ Iterator<Map.Entry<Long, CacheEntry>> it = cacheByStamp.entrySet().iterator();
+ while (it.hasNext()) {
+ CacheEntry entry = it.next().getValue();
+ if (entry.lastSeen.plus(maxAge).isBeforeNow()) {
+ it.remove();
+ cacheByDeviceId.remove(entry.deviceId);
+ for (Equivalence.Wrapper<AdInfo> wrapper : entry.adInfos) {
+ AdInfo adinfo = wrapper.get();
+ adInfosByInterfaceName.remove(adinfo.getAd().getInterfaceName(), wrapper);
+ adinfo.setLost(true);
+ handleUpdate(adinfo);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Cleans up the cache's state and shutdowns the eviction thread.
+ */
+ void shutdownCache() {
+ timer.shutdown();
+ }
+
+ /**
+ * Returns whether this stamp has been seen before.
+ *
+ * @param stamp the stamp of the advertisement
+ * @param deviceId the deviceId of the advertisement (used to handle rotating ids)
+ * @return true iff this stamp is in the cache
+ */
+ boolean haveSeenStamp(long stamp, String deviceId) {
+ synchronized (this) {
+ CacheEntry entry = cacheByStamp.get(stamp);
+ if (entry != null) {
+ entry.lastSeen = new Instant();
+ if (!entry.deviceId.equals(deviceId)) {
+ // This probably happened because a device has changed it's ble mac address.
+ // We need to update the mac address for this entry.
+ cacheByDeviceId.remove(entry.deviceId);
+ entry.deviceId = deviceId;
+ cacheByDeviceId.put(deviceId, entry);
+ }
+ }
+ return entry != null;
+ }
+ }
+
+ /**
+ * Saves the set of advertisements and stamp for this device.
+ *
+ * @param stamp the stamp provided by the device
+ * @param deviceId the id of the device
+ * @param adinfos the advertisements exposed by the device
+ */
+ void saveDevice(long stamp, String deviceId, Iterable<AdInfo> adInfos) {
+ Set<Equivalence.Wrapper<AdInfo>> newAdInfos =
+ FluentIterable.from(adInfos).transform(ADINFO_WRAPPER).toSet();
+ CacheEntry entry = new CacheEntry(stamp, deviceId, newAdInfos);
+ synchronized (this) {
+ Set<Equivalence.Wrapper<AdInfo>> oldAdInfos;
+ CacheEntry oldEntry = cacheByDeviceId.remove(deviceId);
+ if (oldEntry != null) {
+ cacheByStamp.remove(oldEntry.stamp);
+ oldAdInfos = oldEntry.adInfos;
+ } else {
+ oldAdInfos = ImmutableSet.of();
+ }
+
+ Set<Equivalence.Wrapper<AdInfo>> removed = Sets.difference(oldAdInfos, newAdInfos);
+ for (Equivalence.Wrapper<AdInfo> wrapped : removed) {
+ AdInfo adInfo = wrapped.get();
+ adInfosByInterfaceName.remove(adInfo.getAd().getInterfaceName(), wrapped);
+ adInfo.setLost(true);
+ handleUpdate(adInfo);
+ }
+ Set<Equivalence.Wrapper<AdInfo>> added = Sets.difference(newAdInfos, oldAdInfos);
+ for (Equivalence.Wrapper<AdInfo> wrapped : added) {
+ AdInfo adInfo = wrapped.get();
+ adInfosByInterfaceName.put(adInfo.getAd().getInterfaceName(), wrapped);
+ handleUpdate(adInfo);
+ }
+ cacheByStamp.put(stamp, entry);
+ cacheByDeviceId.put(deviceId, entry);
+ }
+ }
+
+ private void handleUpdate(AdInfo adinfo) {
+ Set<Scanner> scanners = scannersByInterfaceName.get("");
+ if (scanners != null) {
+ for (Scanner scanner : scanners) {
+ scanner.handler.handleUpdate(adinfo);
+ }
+ }
+ scanners = scannersByInterfaceName.get(adinfo.getAd().getInterfaceName());
+ if (scanners != null) {
+ for (Scanner scanner : scanners) {
+ scanner.handler.handleUpdate(adinfo);
+ }
+ }
+ }
+
+ /**
+ * Adds a scan handler for advertisements that match {@link interfaceName}.
+ * <p>
+ * If {@link handler} already exists, the old handler is replaced.
+ */
+ void addScanner(String interfaceName, Plugin.ScanHandler handler) {
+ Scanner scanner = new Scanner(interfaceName, handler);
+ synchronized (this) {
+ scannersByHandler.put(handler, scanner);
+ scannersByInterfaceName.put(interfaceName, scanner);
+
+ Iterable<Equivalence.Wrapper<AdInfo>> adinfos;
+ if (interfaceName.isEmpty()) {
+ adinfos = adInfosByInterfaceName.values();
+ } else {
+ adinfos = adInfosByInterfaceName.get(interfaceName);
+ }
+ if (adinfos != null) {
+ for (Equivalence.Wrapper<AdInfo> wrapper : adinfos) {
+ scanner.handler.handleUpdate(wrapper.get());
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes the scan handler.
+ */
+ void removeScanner(Plugin.ScanHandler handler) {
+ synchronized (this) {
+ Scanner scanner = scannersByHandler.remove(handler);
+ if (scanner != null) {
+ scannersByInterfaceName.remove(scanner.interfaceName, scanner);
+ }
+ }
+ }
+}
diff --git a/android-lib/src/main/java/io/v/android/libs/discovery/ble/BlePlugin.java b/android-lib/src/main/java/io/v/android/libs/discovery/ble/BlePlugin.java
deleted file mode 100644
index f85de26..0000000
--- a/android-lib/src/main/java/io/v/android/libs/discovery/ble/BlePlugin.java
+++ /dev/null
@@ -1,395 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.android.libs.discovery.ble;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattServer;
-import android.bluetooth.BluetoothGattServerCallback;
-import android.bluetooth.BluetoothGattService;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.le.AdvertiseCallback;
-import android.bluetooth.le.AdvertiseData;
-import android.bluetooth.le.AdvertiseSettings;
-import android.bluetooth.le.BluetoothLeAdvertiser;
-import android.bluetooth.le.BluetoothLeScanner;
-import android.bluetooth.le.ScanCallback;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanResult;
-import android.bluetooth.le.ScanRecord;
-import android.bluetooth.le.ScanSettings;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.util.Log;
-import android.support.v4.content.ContextCompat;
-import android.Manifest;
-
-import org.joda.time.Duration;
-
-import java.io.IOException;
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-
-import io.v.impl.google.lib.discovery.DeviceCache;
-import io.v.impl.google.lib.discovery.UUIDUtil;
-import io.v.impl.google.lib.discovery.VScanner;
-import io.v.impl.google.lib.discovery.ble.BleAdvertisementConverter;
-import io.v.v23.context.VContext;
-import io.v.impl.google.lib.discovery.ScanHandler;
-import io.v.v23.verror.VException;
-import io.v.x.ref.lib.discovery.Advertisement;
-
-import static io.v.v23.VFutures.sync;
-/**
- * The discovery plugin interface for Bluetooth.
- */
-public class BlePlugin {
- private static final String TAG = "BlePlugin";
-
- // We are using a constant for the MTU because Android and paypal/gatt don't get along
- // when the paypal gatt client sends a setMTU message. The Android server seems to send
- // a malformed L2CAP message.
- private static final int MTU = 23;
-
- // Object used to lock advertisement objects.
- private final Object advertisementLock = new Object();
-
- // Random generator for stamp.
- private SecureRandom random = new SecureRandom();
-
- // The id to assign to the next advertisment.
- private int nextAdv;
- // A map of advertisement ids to the advertisement that corresponds to them.
- private final Map<Integer, BluetoothGattService> advertisements = new HashMap<>();
- // A map of advertisement ids to the thread waiting for cancellation of the context.
- private final Map<Integer, Thread> advCancellationThreads = new HashMap<>();
-
- // Object used to lock scanner objects
- private final Object scannerLock = new Object();
- // A map of scanner ids to the thread waiting for cancellation of the context.
- private final Map<Integer, Thread> scanCancellationThreads = new HashMap<>();
- private final DeviceCache cachedDevices;
- // Used to track the set of devices we currently talking to.
- private final Set<String> pendingCalls = new HashSet<>();
-
- // Set of Ble objects that will be interacted with to perform operations.
- private BluetoothLeAdvertiser bluetoothLeAdvertise;
- private BluetoothLeScanner bluetoothLeScanner;
- private BluetoothGattServer bluetoothGattServer;
-
- // We need to hold onto the callbacks for scan an advertise because that is what is used
- // to stop the operation.
- private ScanCallback scanCallback;
- private AdvertiseCallback advertiseCallback;
-
- private boolean isScanning;
-
- private final Context androidContext;
-
- // If isEnabled is false, then all operations on the ble plugin are no-oped. This wil only
- // be false if the ble hardware is inaccessible.
- private boolean isEnabled = false;
-
- // A thread to wait for the cancellation of a particular advertisement.
- // TODO(spetrovic): remove this thread and replace with a callback on ctx.onDone().
- private class AdvertisementCancellationRunner implements Runnable{
- private final VContext ctx;
-
- private final int id;
- AdvertisementCancellationRunner(VContext ctx, int id) {
- this.id = id;
- this.ctx = ctx;
- }
-
- @Override
- public void run() {
- try {
- sync(ctx.onDone());
- } catch (VException e) {
- Log.e(TAG, "Error waiting for context to be done: " + e);
- }
- finally {
- BlePlugin.this.removeAdvertisement(id);
- }
- }
- }
-
- // Similar to AdvertisementCancellationRunner except for scanning.
- // TODO(spetrovic): Remove this thread and replace with a callback on ctx.onDone().
- private class ScannerCancellationRunner implements Runnable{
- private VContext ctx;
-
- private int id;
- ScannerCancellationRunner(VContext ctx, int id) {
- this.id = id;
- this.ctx = ctx;
- }
-
- @Override
- public void run() {
- try {
- sync(ctx.onDone());
- } catch (VException e) {
- Log.e(TAG, "Error waiting for context to be done: " + e);
- }
- finally {
- BlePlugin.this.removeScanner(id);
- }
- }
- }
-
- private boolean hasPermission(String perm) {
- return ContextCompat.checkSelfPermission(androidContext, perm) ==
- PackageManager.PERMISSION_GRANTED;
- }
- public BlePlugin(Context androidContext) {
- this.androidContext = androidContext;
- cachedDevices = new DeviceCache(Duration.standardMinutes(1));
- BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
- return;
- }
-
- if (!hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION) &&
- !hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
- return;
- }
- isEnabled = true;
- bluetoothLeAdvertise = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
- bluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
- BluetoothManager manager = (BluetoothManager) androidContext.getSystemService(
- Context.BLUETOOTH_SERVICE);
- bluetoothGattServer = manager.openGattServer(androidContext,
- new BluetoothGattServerCallback() {
- @Override
- public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
- super.onConnectionStateChange(device, status, newState);
- }
-
- @Override
- public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
- int offset,
- BluetoothGattCharacteristic characteristic) {
- super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
- byte[] total =characteristic.getValue();
- byte[] res = {};
- // Only send MTU - 1 bytes. The first byte of all packets is the op code.
- if (offset < total.length) {
- int finalByte = offset + MTU - 1;
- if (finalByte > total.length) {
- finalByte = total.length;
- }
- res = Arrays.copyOfRange(total, offset, finalByte);
- bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, res);
- } else {
- // This should probably be an error, but a bug in the paypal/gatt code causes an
- // infinite loop if this returns an error rather than the empty value.
- bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, res);
- }
- }
- });
- }
-
-
- // Converts a Vanadium Advertisement to a Bluetooth gatt service.
- private BluetoothGattService convertToService(Advertisement adv) throws IOException {
- Map<UUID, byte[]> attributes = BleAdvertisementConverter.vAdvertismentToBleAttr(adv);
- BluetoothGattService service = new BluetoothGattService(
- UUIDUtil.UUIDForInterfaceName(adv.getService().getInterfaceName()),
- BluetoothGattService.SERVICE_TYPE_PRIMARY);
- for (Map.Entry<UUID, byte[]> entry : attributes.entrySet()) {
- BluetoothGattCharacteristic ch = new BluetoothGattCharacteristic(
- entry.getKey(),
- BluetoothGattCharacteristic.PROPERTY_READ,
- BluetoothGattCharacteristic.PERMISSION_READ);
- ch.setValue(entry.getValue());
- service.addCharacteristic(ch);
- }
- return service;
- }
-
- public void addAdvertisement(VContext ctx, Advertisement advertisement) throws IOException {
- if (!isEnabled) {
- return;
- }
- BluetoothGattService service = convertToService(advertisement);
- synchronized (advertisementLock) {
- int currentId = nextAdv++;
- advertisements.put(currentId, service);
- Thread t = new Thread(new AdvertisementCancellationRunner(ctx, currentId));
- t.start();
- advCancellationThreads.put(currentId, t);
- bluetoothGattServer.addService(service);
- readvertise();
- }
- }
-
- private void removeAdvertisement(int id) {
- synchronized (advertisements) {
- BluetoothGattService s = advertisements.get(id);
- if (s != null) {
- bluetoothGattServer.removeService(s);
- }
- advertisements.remove(id);
- advCancellationThreads.remove(id);
- readvertise();
- }
- }
-
- public void addScanner(VContext ctx, String interfaceName, ScanHandler handler) {
- if (!isEnabled) {
- return;
- }
- VScanner scanner = new VScanner(interfaceName, handler);
- int currentId = cachedDevices.addScanner(scanner);
- synchronized (scannerLock) {
- Thread t = new Thread(new ScannerCancellationRunner(ctx, currentId));
- t.start();
- scanCancellationThreads.put(currentId, t);
- updateScanning();
- }
- }
-
- private void removeScanner(int id) {
- cachedDevices.removeScanner(id);
- synchronized (scannerLock) {
- scanCancellationThreads.remove(id);
- updateScanning();
- }
- }
-
- private void updateScanning() {
- if (isScanning && scanCancellationThreads.size() == 0) {
- isScanning = false;
- bluetoothLeScanner.stopScan(scanCallback);
- return;
- }
-
- if (!isScanning && scanCancellationThreads.size() > 0) {
- isScanning = true;
- ScanFilter.Builder builder = new ScanFilter.Builder();
- byte[] manufacturerData = {};
- byte[] manufacturerMask = {};
-
- builder.setManufacturerData(1001, manufacturerData, manufacturerMask);
- final List<ScanFilter> scanFilter = new ArrayList<>();
- scanFilter.add(builder.build());
-
-
- scanCallback = new ScanCallback() {
- @Override
- public void onScanResult(int callbackType, ScanResult result) {
- // in L the only value for callbackType is CALLBACK_TYPE_ALL_MATCHES, so
- // we don't look at its value.
- ScanRecord record = result.getScanRecord();
- // Use 1001 to denote that this is a Vanadium device. We picked an id that is
- // currently not in use.
- byte[] data = record.getManufacturerSpecificData(1001);
- ByteBuffer buffer = ByteBuffer.wrap(data);
- final long stamp = buffer.getLong();
- final String deviceId = result.getDevice().getAddress();
- if (cachedDevices.haveSeenStamp(stamp, deviceId)) {
- return;
- }
- synchronized (scannerLock) {
- if (pendingCalls.contains(deviceId)) {
- Log.d("vanadium", "not connecting to " + deviceId + " because of pending connection");
- return;
- }
- pendingCalls.add(deviceId);
- }
- BluetoothGattClientCallback.Callback ccb = new BluetoothGattClientCallback.Callback() {
- @Override
- public void handle(Map<UUID, Map<UUID, byte[]>> services) {
- Set<Advertisement> advs = new HashSet<>();
- for (Map.Entry<UUID, Map<UUID, byte[]>> entry : services.entrySet()) {
- try {
- Advertisement adv =
- BleAdvertisementConverter.
- bleAttrToVAdvertisement(entry.getValue());
- advs.add(adv);
- } catch (IOException e) {
- Log.e("vanadium","Failed to convert advertisement" + e);
- }
- }
- cachedDevices.saveDevice(stamp, advs, deviceId);
- synchronized (scannerLock) {
- pendingCalls.remove(deviceId);
- }
- bluetoothLeScanner.startScan(scanFilter, new ScanSettings.Builder().
- setScanMode(ScanSettings.SCAN_MODE_BALANCED).build(), scanCallback);
- }
- };
- BluetoothGattClientCallback cb = new BluetoothGattClientCallback(ccb);
- bluetoothLeScanner.stopScan(scanCallback);
- Log.d("vanadium", "connecting to " + result.getDevice());
- result.getDevice().connectGatt(androidContext, false, cb);
- }
-
- @Override
- public void onBatchScanResults(List<ScanResult> results) {
- }
-
- @Override
- public void onScanFailed(int errorCode) {
- }
- };
- bluetoothLeScanner.startScan(scanFilter, new ScanSettings.Builder().
- setScanMode(ScanSettings.SCAN_MODE_BALANCED).build(), scanCallback);
- }
- }
-
- private long genStamp() {
- // We use 8-byte stamp to reflect the current services of the current device.
- //
- // TODO(bjornick): 8-byte random number might not be good enough for
- // global uniqueness. We might want to consider a better way to generate
- // stamp like using a unique device id with sequence number.
- return new BigInteger(64, random).longValue();
- }
-
- private void readvertise() {
- if (advertiseCallback != null) {
- bluetoothLeAdvertise.stopAdvertising(advertiseCallback);
- advertiseCallback = null;
- }
- if (advertisements.size() == 0) {
- return;
- }
-
- AdvertiseData.Builder builder = new AdvertiseData.Builder();
- ByteBuffer buf = ByteBuffer.allocate(9);
- buf.put((byte)8);
- buf.putLong(genStamp());
- builder.addManufacturerData(1001, buf.array());
- AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder();
- settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY);
- settingsBuilder.setConnectable(true);
- advertiseCallback = new AdvertiseCallback() {
- @Override
- public void onStartSuccess(AdvertiseSettings settingsInEffect) {
- Log.i("vanadium", "Successfully started " + settingsInEffect);
- }
-
- @Override
- public void onStartFailure(int errorCode) {
- Log.i("vanadium", "Failed to start advertising " + errorCode);
- }
- };
- bluetoothLeAdvertise.startAdvertising(settingsBuilder.build(), builder.build(),
- advertiseCallback);
- }
-}
diff --git a/android-lib/src/main/java/io/v/android/libs/discovery/ble/BluetoothGattClientCallback.java b/android-lib/src/main/java/io/v/android/libs/discovery/ble/BluetoothGattClientCallback.java
deleted file mode 100644
index 7b438cf..0000000
--- a/android-lib/src/main/java/io/v/android/libs/discovery/ble/BluetoothGattClientCallback.java
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.android.libs.discovery.ble;
-
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCallback;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattService;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-/**
- * A handler for responses from a GattServer.
- */
-public class BluetoothGattClientCallback extends BluetoothGattCallback {
- /**
- * A handler that will get called when all the services from a gatt service
- * are read.
- */
- public interface Callback {
- /**
- * Called with the map of service ids to their attributes.
- * @param services A map from service id to (characteristics uuid to values).
- */
- void handle(Map<UUID, Map<UUID, byte[]>> services);
- }
- // We want to ignore the GATT and GAP services, which are 1800 and 1801 respectively.
- static final String GATT_AND_GAP_PREFIX = "0000180";
-
- private final Callback callback;
-
- private final Map<UUID, Map<UUID, byte[]>> services = new HashMap<>();
-
- private BluetoothGatt gatt;
-
- private final List<BluetoothGattCharacteristic> chars = new ArrayList<>();
- private int pos;
-
- BluetoothGattClientCallback(Callback cb) {
- callback = cb;
- }
-
- @Override
- public void onServicesDiscovered(BluetoothGatt gatt, int status) {
- for (BluetoothGattService service : gatt.getServices()) {
- Log.d("vanadium", "Saw service" + service.getUuid().toString());
- // Skip the GATT AND GAP Services.
- if (service.getUuid().toString().startsWith(GATT_AND_GAP_PREFIX)) {
- continue;
- }
- services.put(service.getUuid(), new HashMap<UUID, byte[]>());
- // We only keep track of the characteristics that can be read.
- for (BluetoothGattCharacteristic ch : service.getCharacteristics()) {
- if ((ch.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) != 0) {
- chars.add(ch);
- } else {
- Log.d("vanadium", "skipping non read property");
- }
- }
- }
- pos = 0;
- maybeReadNextCharacteristic();
- }
-
- // Reads the next characteristic if there is one. Otherwise calls callback and
- // closes the gatt connection.
- private void maybeReadNextCharacteristic() {
- if (pos >= chars.size()) {
- gatt.disconnect();
- gatt.close();
- callback.handle(services);
- return;
- }
- BluetoothGattCharacteristic c = chars.get(pos++);
- if (!gatt.readCharacteristic(c)) {
- Log.d("vanadium", "Failed to read characteristic " + c.getUuid());
- maybeReadNextCharacteristic();
- }
- }
-
- @Override
- public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
- int status) {
- UUID serviceUUID = characteristic.getService().getUuid();
- Log.d("vanadium", "Got characteristic [" + serviceUUID + "]"
- + characteristic.getUuid() + "=" + characteristic.getValue());
- services.get(serviceUUID).put(characteristic.getUuid(), characteristic.getValue());
- maybeReadNextCharacteristic();
- }
-
- @Override
- public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
- Log.d("vanadium", "new connections state is " + newState);
-
- this.gatt = gatt;
- if (status != BluetoothGatt.GATT_SUCCESS || newState != BluetoothGatt.STATE_CONNECTED) {
- Log.d("vanadium", "failed to connect with status " + status + " state" + newState);
- gatt.close();
- callback.handle(null);
- return;
- }
- gatt.discoverServices();
- }
-}
diff --git a/android-lib/src/main/java/io/v/android/libs/discovery/ble/NativeScanHandler.java b/android-lib/src/main/java/io/v/android/libs/discovery/ble/NativeScanHandler.java
deleted file mode 100644
index e3995dc..0000000
--- a/android-lib/src/main/java/io/v/android/libs/discovery/ble/NativeScanHandler.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.android.libs.discovery.ble;
-
-import io.v.impl.google.lib.discovery.ScanHandler;
-import io.v.x.ref.lib.discovery.Advertisement;
-
-/**
- * An implementation of the ScanHandler for use by the discovery framework. This handler is used
- * to pass results from the BlePlugin to the go wrapper to passed on to the discovery instance.
- */
-class NativeScanHandler implements ScanHandler{
- /**
- * A pointer to the the native channel.
- */
- private long nativeChan;
-
- NativeScanHandler(long nativeChan) {
- this.nativeChan = nativeChan;
- }
-
- private native void nativeHandleUpdate(Advertisement adv, long chan);
- private native void nativeFinalize(long chan);
-
- @Override
- public void handleUpdate(Advertisement advertisement) {
- nativeHandleUpdate(advertisement, nativeChan);
- }
-
- @Override
- protected void finalize() throws Throwable {
- nativeFinalize(nativeChan);
- }
-}
diff --git a/android-lib/src/test/java/io/v/android/impl/google/discovery/plugins/ble/ConvertUtilTest.java b/android-lib/src/test/java/io/v/android/impl/google/discovery/plugins/ble/ConvertUtilTest.java
new file mode 100644
index 0000000..d814455
--- /dev/null
+++ b/android-lib/src/test/java/io/v/android/impl/google/discovery/plugins/ble/ConvertUtilTest.java
@@ -0,0 +1,56 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.android.impl.google.discovery.plugins.ble;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import io.v.v23.V;
+
+import io.v.x.ref.lib.discovery.AdInfo;
+import io.v.x.ref.lib.discovery.plugins.ble.testdata.AdConversionTestCase;
+import io.v.x.ref.lib.discovery.plugins.ble.testdata.Constants;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static io.v.impl.google.lib.discovery.DiscoveryTestUtil.assertThat;
+
+/**
+ * Tests for {@link ConvertUtil}
+ */
+public class ConvertUtilTest extends TestCase {
+ protected void setUp() {
+ V.init(); // V.init() sets up the jni bindings.
+ }
+
+ public void testToGattAttrs() throws IOException {
+ for (AdConversionTestCase test : Constants.CONVERSION_TEST_DATA) {
+ Map<UUID, byte[]> got = ConvertUtil.toGattAttrs(test.getAdInfo());
+ Map<String, byte[]> want = test.getGattAttrs();
+
+ assertThat(got.size()).isEqualTo(want.size());
+ for (Map.Entry<UUID, byte[]> entry : got.entrySet()) {
+ String uuid = entry.getKey().toString();
+ assertWithMessage(uuid).that(entry.getValue()).isEqualTo(want.get(uuid));
+ }
+ }
+ }
+
+ public void testToAdInfo() throws IOException {
+ for (AdConversionTestCase test : Constants.CONVERSION_TEST_DATA) {
+ Map<UUID, byte[]> attrs = new HashMap<>();
+ for (Map.Entry<String, byte[]> entry : test.getGattAttrs().entrySet()) {
+ attrs.put(UUID.fromString(entry.getKey()), entry.getValue());
+ }
+
+ AdInfo got = ConvertUtil.toAdInfo(attrs);
+ assertThat(got).isEqualTo(test.getAdInfo());
+ }
+ }
+}
diff --git a/android-lib/src/test/java/io/v/android/impl/google/discovery/plugins/ble/DeviceCacheTest.java b/android-lib/src/test/java/io/v/android/impl/google/discovery/plugins/ble/DeviceCacheTest.java
new file mode 100644
index 0000000..77683c0
--- /dev/null
+++ b/android-lib/src/test/java/io/v/android/impl/google/discovery/plugins/ble/DeviceCacheTest.java
@@ -0,0 +1,263 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.android.impl.google.discovery.plugins.ble;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+import org.joda.time.Duration;
+
+import org.junit.Test;
+
+import io.v.v23.discovery.AdId;
+import io.v.v23.discovery.Advertisement;
+import io.v.v23.discovery.Attributes;
+import io.v.v23.discovery.Attachments;
+
+import io.v.x.ref.lib.discovery.AdInfo;
+import io.v.x.ref.lib.discovery.AdHash;
+import io.v.x.ref.lib.discovery.EncryptionAlgorithm;
+import io.v.x.ref.lib.discovery.EncryptionKey;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.v.impl.google.lib.discovery.DiscoveryTestUtil.assertThat;
+
+/**
+ * Tests for {@link DeviceCache}.
+ */
+public class DeviceCacheTest extends TestCase {
+ private static Random rand = new Random();
+
+ private static byte[] randBytes(int size) {
+ byte[] bytes = new byte[size];
+ rand.nextBytes(bytes);
+ return bytes;
+ }
+
+ private static AdInfo newAdInfo(String interfaceName) {
+ return new AdInfo(
+ new Advertisement(
+ new AdId(randBytes(AdId.VDL_TYPE.getLength())),
+ interfaceName,
+ ImmutableList.<String>of(),
+ new Attributes(),
+ new Attachments()),
+ new EncryptionAlgorithm(),
+ ImmutableList.<EncryptionKey>of(),
+ new AdHash(randBytes(AdHash.VDL_TYPE.getLength())),
+ ImmutableList.<String>of(),
+ false);
+ }
+
+ private static AdInfo copyAdInfo(AdInfo adinfo) {
+ return new AdInfo(
+ new Advertisement(
+ adinfo.getAd().getId(),
+ adinfo.getAd().getInterfaceName(),
+ adinfo.getAd().getAddresses(),
+ adinfo.getAd().getAttributes(),
+ adinfo.getAd().getAttachments()),
+ adinfo.getEncryptionAlgorithm(),
+ adinfo.getEncryptionKeys(),
+ adinfo.getHash(),
+ adinfo.getDirAddrs(),
+ adinfo.getLost());
+ }
+
+ private static List<AdInfo> newAdInfoList(AdInfo... adinfos) {
+ return Lists.transform(
+ ImmutableList.copyOf(adinfos),
+ new Function<AdInfo, AdInfo>() {
+ @Override
+ public AdInfo apply(AdInfo adinfo) {
+ return copyAdInfo(adinfo);
+ }
+ });
+ }
+
+ private static class MockHandler implements Plugin.ScanHandler {
+ private List<AdInfo> updates = new ArrayList<>();
+
+ @Override
+ public synchronized void handleUpdate(AdInfo adinfo) {
+ updates.add(copyAdInfo(adinfo));
+ notifyAll();
+ }
+ }
+
+ public void testSaveDeveice() {
+ DeviceCache cache = new DeviceCache(Duration.standardMinutes(10));
+ // The advertisements here are not relevant since we are just checking
+ // the seen stamp function.
+ long stamp = 10001;
+ assertThat(cache.haveSeenStamp(stamp, "device")).isFalse();
+ cache.saveDevice(stamp, "device", ImmutableList.<AdInfo>of());
+ assertThat(cache.haveSeenStamp(stamp, "device")).isTrue();
+ cache.shutdownCache();
+ }
+
+ public void testSaveDeviceWithDifferentStampCode() {
+ DeviceCache cache = new DeviceCache(Duration.standardMinutes(10));
+ // The advertisements here are not relevant since we are just checking
+ // the seen stamp function.
+ long stamp = 10001;
+ assertThat(cache.haveSeenStamp(stamp, "device")).isFalse();
+ cache.saveDevice(stamp, "device", ImmutableList.<AdInfo>of());
+ assertThat(cache.haveSeenStamp(stamp, "device")).isTrue();
+ cache.saveDevice(stamp + 1, "device", ImmutableList.<AdInfo>of());
+ assertThat(cache.haveSeenStamp(stamp + 1, "device")).isTrue();
+ assertThat(cache.haveSeenStamp(stamp, "device")).isFalse();
+ cache.shutdownCache();
+ }
+
+ public void testAddingScannerBeforeSavingDevice() {
+ DeviceCache cache = new DeviceCache(Duration.standardMinutes(10));
+ long stamp = 10001;
+
+ AdInfo adinfo1 = newAdInfo("interface1");
+ AdInfo adinfo2 = newAdInfo("interface2");
+
+ MockHandler handler1 = new MockHandler();
+ cache.addScanner("interface1", handler1);
+
+ MockHandler handler2 = new MockHandler();
+ cache.addScanner("", handler2);
+
+ cache.saveDevice(stamp, "device", newAdInfoList(adinfo1, adinfo2));
+
+ // Make sure that the handlers are called;
+ assertThat(handler1.updates.size()).isEqualTo(1);
+ assertThat(handler1.updates.get(0)).isEqualTo(adinfo1);
+ assertThat(handler2.updates).isEqualTo(adinfo1, adinfo2);
+ cache.shutdownCache();
+ }
+
+ public void testAddingScannerAfterSavingDevice() {
+ DeviceCache cache = new DeviceCache(Duration.standardMinutes(10));
+ long stamp = 10001;
+
+ AdInfo adinfo1 = newAdInfo("interface1");
+ AdInfo adinfo2 = newAdInfo("interface2");
+
+ cache.saveDevice(stamp, "device", newAdInfoList(adinfo1, adinfo2));
+
+ MockHandler handler1 = new MockHandler();
+ cache.addScanner("interface1", handler1);
+
+ MockHandler handler2 = new MockHandler();
+ cache.addScanner("", handler2);
+
+ // Make sure that the handlers are called;
+ assertThat(handler1.updates.size()).isEqualTo(1);
+ assertThat(handler1.updates.get(0)).isEqualTo(adinfo1);
+ assertThat(handler2.updates).isEqualTo(adinfo1, adinfo2);
+ cache.shutdownCache();
+ }
+
+ public void testRemovingAdvertisement() {
+ DeviceCache cache = new DeviceCache(Duration.standardMinutes(10));
+ long stamp = 10001;
+
+ AdInfo adinfo1 = newAdInfo("interface1");
+ AdInfo adinfo2 = newAdInfo("interface2");
+
+ MockHandler handler = new MockHandler();
+ cache.addScanner("interface1", handler);
+
+ cache.saveDevice(stamp, "device", newAdInfoList(adinfo1, adinfo2));
+ cache.saveDevice(stamp + 1, "device", newAdInfoList(adinfo2));
+
+ // Make sure that the handler is called;
+ assertThat(handler.updates.size()).isEqualTo(2);
+ assertThat(handler.updates.get(0)).isEqualTo(adinfo1, false);
+ assertThat(handler.updates.get(1)).isEqualTo(adinfo1, true);
+ cache.shutdownCache();
+ }
+
+ public void testAddingSameAdvertisement() {
+ DeviceCache cache = new DeviceCache(Duration.standardMinutes(10));
+ long stamp = 10001;
+
+ AdInfo adinfo = newAdInfo("interface1");
+
+ MockHandler handler = new MockHandler();
+ cache.addScanner("interface1", handler);
+
+ cache.saveDevice(stamp, "device", newAdInfoList(adinfo));
+ cache.saveDevice(stamp + 1, "device", newAdInfoList(adinfo));
+
+ // Make sure that the handler is called;
+ assertThat(handler.updates.size()).isEqualTo(1);
+ assertThat(handler.updates.get(0)).isEqualTo(adinfo);
+ cache.shutdownCache();
+ }
+
+ public void testRemovingScanner() {
+ DeviceCache cache = new DeviceCache(Duration.standardMinutes(10));
+ long stamp = 10001;
+
+ AdInfo adinfo1 = newAdInfo("interface1");
+ AdInfo adinfo2 = newAdInfo("interface2");
+
+ MockHandler handler = new MockHandler();
+ cache.addScanner("interface1", handler);
+
+ cache.saveDevice(stamp, "device", newAdInfoList(adinfo1));
+
+ assertThat(handler.updates.size()).isEqualTo(1);
+ assertThat(handler.updates.get(0)).isEqualTo(adinfo1);
+
+ cache.removeScanner(handler);
+
+ cache.saveDevice(stamp, "device", newAdInfoList(adinfo2));
+
+ // Make sure that the handler is not called any more;
+ assertThat(handler.updates.size()).isEqualTo(1);
+ cache.shutdownCache();
+ }
+
+ @Test(timeout = 30000)
+ public void testCacheEviction() {
+ // TODO(jhahn): Use a fake ScheduledExecutorService.
+ DeviceCache cache = new DeviceCache(Duration.millis(2));
+ long stamp = 10001;
+
+ AdInfo adinfo = newAdInfo("interface1");
+
+ MockHandler handler = new MockHandler();
+ cache.addScanner("interface1", handler);
+
+ cache.saveDevice(stamp, "device", newAdInfoList(adinfo));
+
+ synchronized (handler) {
+ try {
+ while (handler.updates.size() < 2) {
+ handler.wait();
+ }
+ } catch (InterruptedException e) {
+ // Keep waiting.
+ }
+ }
+
+ // Make sure that the handler is called;
+ assertThat(handler.updates.size()).isEqualTo(2);
+ assertThat(handler.updates.get(0)).isEqualTo(adinfo, false);
+ assertThat(handler.updates.get(1)).isEqualTo(adinfo, true);
+
+ // Make sure that there is no cached entries.
+ handler = new MockHandler();
+ cache.addScanner("interface1", handler);
+ assertThat(handler.updates.size()).isEqualTo(0);
+
+ cache.shutdownCache();
+ }
+}
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/DeviceCache.java b/lib/src/main/java/io/v/impl/google/lib/discovery/DeviceCache.java
deleted file mode 100644
index b6b7b80..0000000
--- a/lib/src/main/java/io/v/impl/google/lib/discovery/DeviceCache.java
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.impl.google.lib.discovery;
-
-
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.SetMultimap;
-import com.google.common.collect.Sets;
-
-import org.joda.time.Duration;
-import org.joda.time.Instant;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.HashSet;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import io.v.x.ref.lib.discovery.Advertisement;
-
-/**
- * A cache of ble devices that were seen recently. The current Vanadium BLE protocol requires
- * connecting to the advertiser to grab the attributes and the addrs. This can be expensive
- * so we only refetch the data if its stamp changed.
- */
-public class DeviceCache {
- private final Map<Long, CacheEntry> cachedDevices = new HashMap<>();
- private final Map<String, CacheEntry> knownIds = new HashMap<>();
-
- private final AtomicInteger nextScanner = new AtomicInteger(0);
- private final SetMultimap<String, Advertisement> knownServices = HashMultimap.create();
- private final Map<Integer, VScanner> scannersById = new HashMap<>();
- private final SetMultimap<String, VScanner> scannersByInterfaceName = HashMultimap.create();
- ScheduledExecutorService timer;
-
- private final Duration maxAge;
-
-
- public DeviceCache(final Duration maxAge) {
- this.maxAge = maxAge;
- this.timer = Executors.newSingleThreadScheduledExecutor();
- long periodicity = maxAge.getMillis() / 2;
- timer.scheduleAtFixedRate(new Runnable() {
- @Override
- public void run() {
- removeStaleEntries();
- }
- }, periodicity, periodicity, TimeUnit.MILLISECONDS);
- }
-
- void removeStaleEntries() {
- synchronized (this) {
- Iterator<Map.Entry<Long, CacheEntry>> it = cachedDevices.entrySet().iterator();
-
- while (it.hasNext()) {
- Map.Entry<Long, CacheEntry> mapEntry = it.next();
- CacheEntry entry = mapEntry.getValue();
- if (entry.lastSeen.plus(maxAge).isBeforeNow()) {
- it.remove();
- knownIds.remove(entry.deviceId);
- for (Advertisement adv : entry.advertisements) {
- knownServices.remove(adv.getService().getInterfaceName(), adv);
- adv.setLost(true);
- handleUpdate(adv);
- }
- }
- }
- }
- }
-
-
- /**
- * Cleans up the cache's state and shutdowns the eviction thread.
- */
- public void shutdownCache() {
- timer.shutdown();
- }
-
- /**
- * Returns whether this stamp has been seen before.
- *
- * @param stamp the stamp of the advertisement
- * @param deviceId the deviceId of the advertisement (used to handle rotating ids).
- * @return true iff this stamp is in the cache.
- */
- public boolean haveSeenStamp(long stamp, String deviceId) {
- synchronized (this) {
- CacheEntry entry = cachedDevices.get(stamp);
- if (entry != null) {
- entry.lastSeen = new Instant();
- if (!entry.deviceId.equals(deviceId)) {
- // This probably happened becuase a device has changed it's ble mac address.
- // We need to update the mac address for this entry.
- knownIds.remove(entry.deviceId);
- entry.deviceId = deviceId;
- knownIds.put(deviceId, entry);
- }
- }
- return entry != null;
- }
- }
-
- /**
- * Saves the set of advertisements and stamp for this device.
- *
- * @param stamp the stamp provided by the device.
- * @param advs the advertisements exposed by the device.
- * @param deviceId the id of the device.
- */
- public void saveDevice(long stamp, Set<Advertisement> advs, String deviceId) {
- CacheEntry entry = new CacheEntry(advs, stamp, deviceId);
- synchronized (this) {
- CacheEntry oldEntry = knownIds.get(deviceId);
- Set<Advertisement> oldValues = null;
- if (oldEntry != null) {
- cachedDevices.remove(oldEntry.stamp);
- knownIds.remove(oldEntry.deviceId);
- oldValues = oldEntry.advertisements;
- } else {
- oldValues = new HashSet<>();
- }
- Set<Advertisement> removed = Sets.difference(oldValues, advs);
- for (Advertisement adv : removed) {
- knownServices.remove(adv.getService().getInterfaceName(), adv);
- adv.setLost(true);
- handleUpdate(adv);
- }
-
- Set<Advertisement> added = Sets.difference(advs, oldValues);
- for (Advertisement adv: added) {
- knownServices.put(adv.getService().getInterfaceName(), adv);
- handleUpdate(adv);
- }
- cachedDevices.put(stamp, entry);
- CacheEntry oldDeviceEntry = knownIds.get(deviceId);
- if (oldDeviceEntry != null) {
- // Delete the old stamp value.
- cachedDevices.remove(stamp);
- }
- knownIds.put(deviceId, entry);
- }
- }
-
- private void handleUpdate(Advertisement adv) {
- Set<VScanner> scanners = scannersByInterfaceName.get(adv.getService().getInterfaceName());
- if (scanners == null) {
- return;
- }
- for (VScanner scanner : scanners) {
- scanner.getHandler().handleUpdate(adv);
- }
- }
-
- /**
- * Adds a scanner that will be notified when advertisements that match its query have changed.
- *
- * @return the handle of the scanner that can be used to remove the scanner.
- */
- public int addScanner(VScanner scanner) {
- synchronized (this) {
- int id = nextScanner.addAndGet(1);
- scannersById.put(id, scanner);
- scannersByInterfaceName.put(scanner.getInterfaceName(), scanner);
- Set<Advertisement> knownAdvs = knownServices.get(scanner.getInterfaceName());
- if (knownAdvs != null) {
- for (Advertisement adv : knownAdvs) {
- scanner.getHandler().handleUpdate(adv);
- }
- }
- return id;
- }
- }
-
- /**
- * Removes the scanner matching this id. This scanner will stop getting updates.
- */
- public void removeScanner(int id) {
- synchronized (this) {
- VScanner scanner = scannersById.get(id);
- if (scanner != null) {
- scannersByInterfaceName.remove(scanner.getInterfaceName(), scanner);
- scannersById.remove(id);
- }
- }
- }
-
- private class CacheEntry {
- Set<Advertisement> advertisements;
-
- long stamp;
-
- Instant lastSeen;
-
- String deviceId;
-
- CacheEntry(Set<Advertisement> advs, long stamp, String deviceId) {
- advertisements = advs;
- this.stamp = stamp;
- lastSeen = new Instant();
- this.deviceId = deviceId;
- }
- }
-
-}
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/DiscoveryImpl.java b/lib/src/main/java/io/v/impl/google/lib/discovery/DiscoveryImpl.java
new file mode 100644
index 0000000..89d918b
--- /dev/null
+++ b/lib/src/main/java/io/v/impl/google/lib/discovery/DiscoveryImpl.java
@@ -0,0 +1,71 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.impl.google.lib.discovery;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.FutureFallback;
+import com.google.common.util.concurrent.Futures;
+
+import java.util.List;
+import java.util.concurrent.CancellationException;
+
+import io.v.v23.InputChannel;
+import io.v.v23.context.VContext;
+import io.v.v23.discovery.Advertisement;
+import io.v.v23.discovery.Discovery;
+import io.v.v23.discovery.Update;
+import io.v.v23.security.BlessingPattern;
+import io.v.v23.verror.VException;
+
+import io.v.impl.google.ListenableFutureCallback;
+
+class DiscoveryImpl implements Discovery {
+ private final long nativePtr;
+
+ private native void nativeAdvertise(
+ long nativePtr,
+ VContext ctx,
+ Advertisement ad,
+ List<BlessingPattern> visibility,
+ ListenableFutureCallback<Void> cb)
+ throws VException;
+
+ private native InputChannel<Update> nativeScan(long nativePtr, VContext ctx, String query)
+ throws VException;
+
+ private native void nativeFinalize(long nativePtr);
+
+ private DiscoveryImpl(long nativePtr) {
+ this.nativePtr = nativePtr;
+ }
+
+ @Override
+ public ListenableFuture<Void> advertise(
+ VContext ctx, Advertisement ad, List<BlessingPattern> visibility) throws VException {
+ ListenableFutureCallback<Void> cb = new ListenableFutureCallback<>();
+ nativeAdvertise(nativePtr, ctx, ad, visibility, cb);
+ return Futures.withFallback(
+ cb.getFuture(ctx),
+ new FutureFallback<Void>() {
+ public ListenableFuture<Void> create(Throwable t) {
+ if (t instanceof CancellationException) {
+ return Futures.immediateFuture(null);
+ }
+ return Futures.immediateFailedFuture(t);
+ }
+ });
+ }
+
+ @Override
+ public InputChannel<Update> scan(VContext ctx, String query) throws VException {
+ return nativeScan(nativePtr, ctx, query);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ nativeFinalize(nativePtr);
+ }
+}
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/EncodingUtil.java b/lib/src/main/java/io/v/impl/google/lib/discovery/EncodingUtil.java
index 94b9669..34b6c48 100644
--- a/lib/src/main/java/io/v/impl/google/lib/discovery/EncodingUtil.java
+++ b/lib/src/main/java/io/v/impl/google/lib/discovery/EncodingUtil.java
@@ -1,10 +1,9 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.impl.google.lib.discovery;
-
import com.google.common.primitives.Bytes;
import java.io.ByteArrayInputStream;
@@ -17,15 +16,15 @@
import java.util.ArrayList;
import java.util.List;
+import io.v.x.ref.lib.discovery.EncryptionAlgorithm;
import io.v.x.ref.lib.discovery.EncryptionKey;
/**
* A utility to encode and decode fields in io.v.v23.Service fields for use in discovery.
- *
- * TODO(bjornick,jhahn): Consider to share v.io/x/ref/lib/discovery/encoding.go through jni.
*/
public class EncodingUtil {
- static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
+ // We use "ISO8859-1" to preserve data in a string without interpretation.
+ private static final Charset ENC = Charset.forName("ISO8859-1");
private static void writeUint(OutputStream out, int x) throws IOException {
while ((x & 0xffffff80) != 0) {
@@ -36,7 +35,7 @@
}
private static int readUint(InputStream in) throws IOException {
- for (int x = 0, s = 0; ;) {
+ for (int x = 0, s = 0; ; ) {
int b = in.read();
if (b == -1) {
throw new EOFException();
@@ -55,15 +54,15 @@
/**
* Encodes the addresses passed in.
*
- * @param addrs the list of addresses to encode.
- * @return the byte representation of the encoded addresses.
- * @throws IOException if the address can't be encoded.
+ * @param addrs the list of addresses to encode
+ * @return the byte representation of the encoded addresses
+ * @throws IOException if the address can't be encoded
*/
public static byte[] packAddresses(List<String> addrs) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
for (String addr : addrs) {
writeUint(stream, addr.length());
- stream.write(addr.getBytes(UTF8_CHARSET));
+ stream.write(addr.getBytes(ENC));
}
return stream.toByteArray();
}
@@ -71,9 +70,9 @@
/**
* Decodes addresses from a byte array that was encoded by packAddresses
*
- * @param input the byte array toe decode
- * @return the list of addresses.
- * @throws IOException if the addresses can't be decoded.
+ * @param input the byte array to decode
+ * @return the list of addresses.
+ * @throws IOException if the addresses can't be decoded
*/
public static List<String> unpackAddresses(byte[] input) throws IOException {
ByteArrayInputStream stream = new ByteArrayInputStream(input);
@@ -85,24 +84,24 @@
if (read != size) {
throw new EOFException();
}
- output.add(new String(data, UTF8_CHARSET));
+ output.add(new String(data, ENC));
}
return output;
}
/**
- * Encode the encryption keys and algorithm passed in.
+ * Encodes the encryption algorithm and keys passed in.
*
- * @param encryptionAlgorithm the encryption algorithm to use.
- * See io.v.x.ref.lib.discovery.Constants for valid values.
- * @param keys the keys to encode
- * @return the byte array that is the encoded form.
- * @throws IOException if the keys can't be encoded.
+ * @param algo the encryption algorithm to use; See
+ * {@link io.v.x.ref.lib.discovery.Constants} for valid values
+ * @param keys the keys to encode
+ * @return the byte array that is the encoded form
+ * @throws IOException if the keys can't be encoded
*/
- public static byte[] packEncryptionKeys(int encryptionAlgorithm, List<EncryptionKey> keys)
+ public static byte[] packEncryptionKeys(EncryptionAlgorithm algo, List<EncryptionKey> keys)
throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
- writeUint(stream, encryptionAlgorithm);
+ writeUint(stream, algo.getValue());
for (EncryptionKey key : keys) {
byte[] byteKey = Bytes.toArray(key);
writeUint(stream, byteKey.length);
@@ -114,14 +113,15 @@
/**
* Decodes the encryption algorithm and keys that was encoded by packEncryptionKeys.
*
- * @param input the byte array containg the keys.
- * @return the keys and the encryption algorithm in input.
- * @throws IOException if the keys can't be decoded.
+ * @param input the byte array to decode
+ * @param keys the keys where the decoded keys is stored
+ * @return the encryption algorithm
+ * @throws IOException if the keys can't be decoded
*/
- public static KeysAndAlgorithm unpackEncryptionKeys(byte[] input) throws IOException {
+ public static EncryptionAlgorithm unpackEncryptionKeys(byte[] input, List<EncryptionKey> keys)
+ throws IOException {
ByteArrayInputStream stream = new ByteArrayInputStream(input);
int algo = readUint(stream);
- List<EncryptionKey> keys = new ArrayList<>();
while (stream.available() > 0) {
int size = readUint(stream);
byte[] key = new byte[size];
@@ -131,33 +131,6 @@
}
keys.add(new EncryptionKey(Bytes.asList(key)));
}
- return new KeysAndAlgorithm(algo, keys);
- }
-
- /**
- * Stores {@link EncryptionKey}s and the encryption algorithm.
- */
- public static class KeysAndAlgorithm {
- int encryptionAlgorithm;
- List<EncryptionKey> keys;
-
- /**
- * Returns the stored encryption algorithm.
- */
- public int getEncryptionAlgorithm() {
- return encryptionAlgorithm;
- }
-
- /**
- * Returns the stored keys.
- */
- public List<EncryptionKey> getKeys() {
- return keys;
- }
-
- KeysAndAlgorithm(int encryptionAlgo, List<EncryptionKey> keys) {
- encryptionAlgorithm = encryptionAlgo;
- this.keys = keys;
- }
+ return new EncryptionAlgorithm(algo);
}
}
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/Plugin.java b/lib/src/main/java/io/v/impl/google/lib/discovery/Plugin.java
new file mode 100644
index 0000000..93219ab
--- /dev/null
+++ b/lib/src/main/java/io/v/impl/google/lib/discovery/Plugin.java
@@ -0,0 +1,67 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.impl.google.lib.discovery;
+
+import io.v.x.ref.lib.discovery.AdInfo;
+
+/**
+ * An interface for discovery plugins in Java.
+ */
+public interface Plugin {
+ /**
+ * Starts the advertisement of {@link adInfo}.
+ * <p>
+ * The advertisement will not be changed while it is being advertised.
+ * <p>
+ * If the advertisement is too large, the plugin may drop any information except
+ * {@code id}, {@code interfaceName}, {@code hash}, and {@code dirAddrs}.
+ * <p>
+ *
+ * @param adInfo an advertisement to advertises
+ * @throws Exception if advertising couldn't be started
+ */
+ void startAdvertising(AdInfo adInfo) throws Exception;
+
+ /**
+ * Stops the advertisement of {@link adInfo}.
+ *
+ * @param adInfo the advertisement to stop advertising
+ * @throws Exception if advertising couldn't be stopped
+ */
+ void stopAdvertising(AdInfo adInfo) throws Exception;
+
+ /**
+ * An interface for passing scanned advertisements.
+ */
+ public interface ScanHandler {
+ /**
+ * Called with each discovery update.
+ */
+ void handleUpdate(AdInfo adinfo);
+ }
+
+ /**
+ * Starts a scan looking for advertisements that match the interface name.
+ * <p>
+ * An empty interface name means any advertisements.
+ * <p>
+ * Advertisements that are returned through {@link handler} can be changed.
+ * The plugin should not reuse the returned advertisement.
+ * <p>
+ *
+ * @param interfaceName an interface name to scan
+ * @param handler a handler to return updates of matched advertisements.
+ * @throws Exception if scanning couldn't be started
+ */
+ void startScan(String interfaceName, ScanHandler handler) throws Exception;
+
+ /**
+ * Stops the scanning associated with the given handler.
+ *
+ * @param handler the handler to stop scanning for.
+ * @throws Exception if scanning couldn't be started
+ */
+ void stopScan(ScanHandler handler) throws Exception;
+}
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/ScanHandler.java b/lib/src/main/java/io/v/impl/google/lib/discovery/ScanHandler.java
deleted file mode 100644
index ec4268b..0000000
--- a/lib/src/main/java/io/v/impl/google/lib/discovery/ScanHandler.java
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.impl.google.lib.discovery;
-
-import io.v.x.ref.lib.discovery.Advertisement;
-
-/**
- * An interface that is passed into a Vanadium Discovery Scan operation that will handle updates.
- */
-public interface ScanHandler {
- /**
- * Called when there is a new advertisement or an update to and old advertisement
- */
- void handleUpdate(Advertisement advertisement);
-}
-
-
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/UUIDUtil.java b/lib/src/main/java/io/v/impl/google/lib/discovery/UUIDUtil.java
index dce9b76..bd564df 100644
--- a/lib/src/main/java/io/v/impl/google/lib/discovery/UUIDUtil.java
+++ b/lib/src/main/java/io/v/impl/google/lib/discovery/UUIDUtil.java
@@ -1,4 +1,4 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
@@ -7,9 +7,16 @@
import java.util.UUID;
/**
- * Utility functions for generating UUIDs from interface names and attribute keys.
+ * Utility functions for generating UUIDs from interface names and attribute names.
*/
public class UUIDUtil {
- public static native UUID UUIDForInterfaceName(String name);
- public static native UUID UUIDForAttributeKey(String key);
+ /*
+ * Returns a version 5 UUID for the given interface name.
+ */
+ public static native UUID serviceUUID(String interfaceName);
+
+ /*
+ * returns a version 5 UUID for the given attribute name.
+ */
+ public static native UUID attributeUUID(String name);
}
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/UpdateImpl.java b/lib/src/main/java/io/v/impl/google/lib/discovery/UpdateImpl.java
new file mode 100644
index 0000000..be4d8ad
--- /dev/null
+++ b/lib/src/main/java/io/v/impl/google/lib/discovery/UpdateImpl.java
@@ -0,0 +1,145 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.impl.google.lib.discovery;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.Arrays;
+import java.util.List;
+
+import io.v.v23.VFutures;
+import io.v.v23.context.VContext;
+import io.v.v23.discovery.AdId;
+import io.v.v23.discovery.Advertisement;
+import io.v.v23.discovery.Attachments;
+import io.v.v23.discovery.Attributes;
+import io.v.v23.discovery.Update;
+import io.v.v23.verror.VException;
+import io.v.v23.vom.VomUtil;
+
+import io.v.impl.google.ListenableFutureCallback;
+
+class UpdateImpl implements Update {
+ private final long nativePtr;
+
+ private boolean lost;
+ private Advertisement ad;
+
+ private native void nativeAttachment(
+ long nativePtr, VContext ctx, String name, ListenableFutureCallback<byte[]> callback)
+ throws VException;
+
+ private native void nativeFinalize(long nativePtr);
+
+ private UpdateImpl(long nativePtr, boolean lost, Advertisement ad) {
+ this.nativePtr = nativePtr;
+ this.lost = lost;
+ this.ad = ad;
+ }
+
+ @Override
+ public boolean isLost() {
+ return lost;
+ }
+
+ @Override
+ public AdId getId() {
+ return ad.getId();
+ }
+
+ @Override
+ public String getInterfaceName() {
+ return ad.getInterfaceName();
+ }
+
+ @Override
+ public List<String> getAddresses() {
+ return ImmutableList.copyOf(ad.getAddresses());
+ }
+
+ @Override
+ public String getAttribute(String name) {
+ Attributes attributes = ad.getAttributes();
+ if (attributes != null) {
+ return attributes.get(name);
+ }
+ return null;
+ }
+
+ @Override
+ public ListenableFuture<byte[]> getAttachment(VContext ctx, final String name)
+ throws VException {
+ synchronized (ad) {
+ Attachments attachments = ad.getAttachments();
+ if (attachments != null) {
+ if (attachments.containsKey(name)) {
+ byte[] data = attachments.get(name);
+ return Futures.immediateFuture(Arrays.copyOf(data, data.length));
+ }
+ }
+ }
+
+ ListenableFutureCallback<byte[]> callback = new ListenableFutureCallback<>();
+ nativeAttachment(nativePtr, ctx, name, callback);
+ return VFutures.withUserLandChecks(
+ ctx,
+ Futures.transform(
+ callback.getVanillaFuture(),
+ new Function<byte[], byte[]>() {
+ @Override
+ public byte[] apply(byte[] data) {
+ synchronized (ad) {
+ Attachments attachments = ad.getAttachments();
+ if (attachments == null) {
+ attachments = new Attachments();
+ ad.setAttachments(attachments);
+ }
+ attachments.put(name, data);
+ return Arrays.copyOf(data, data.length);
+ }
+ }
+ }));
+ }
+
+ @Override
+ public Advertisement getAdvertisement() {
+ return new Advertisement(
+ ad.getId(),
+ ad.getInterfaceName(),
+ ImmutableList.copyOf(ad.getAddresses()),
+ new Attributes(ImmutableMap.copyOf(ad.getAttributes())),
+ new Attachments(
+ Maps.transformValues(
+ ad.getAttachments(),
+ new Function<byte[], byte[]>() {
+ @Override
+ public byte[] apply(byte[] data) {
+ return Arrays.copyOf(data, data.length);
+ }
+ })));
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "{%b %s %s %s %s}",
+ lost,
+ VomUtil.bytesToHexString(ad.getId().toPrimitiveArray()),
+ ad.getInterfaceName(),
+ ad.getAddresses(),
+ ad.getAttributes());
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ nativeFinalize(nativePtr);
+ }
+}
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/VDiscoveryImpl.java b/lib/src/main/java/io/v/impl/google/lib/discovery/VDiscoveryImpl.java
deleted file mode 100644
index e0b71fb..0000000
--- a/lib/src/main/java/io/v/impl/google/lib/discovery/VDiscoveryImpl.java
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.impl.google.lib.discovery;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-
-import io.v.impl.google.ListenableFutureCallback;
-import io.v.v23.InputChannel;
-import io.v.v23.context.VContext;
-import io.v.v23.discovery.Service;
-import io.v.v23.discovery.Update;
-import io.v.v23.discovery.VDiscovery;
-import io.v.v23.security.BlessingPattern;
-import io.v.v23.verror.VException;
-
-class VDiscoveryImpl implements VDiscovery {
- private long nativeDiscoveryPtr;
- private long nativeTriggerPtr;
-
- private native void nativeAdvertise(
- long nativeDiscoveryPtr, long nativeTriggerPtr, VContext ctx, Service service,
- List<BlessingPattern> visibility,
- ListenableFutureCallback<ListenableFuture<Void>> startCallback,
- ListenableFutureCallback<Void> doneCallback);
- private native InputChannel<Update> nativeScan(
- long nativeDiscoveryPtr, VContext ctx, String query) throws VException;
- private native void nativeFinalize(long nativeDiscoveryPtr, long nativeTriggerPtr);
-
- private VDiscoveryImpl(long nativeDiscoveryPtr, long nativeTriggerPtr) {
- this.nativeDiscoveryPtr = nativeDiscoveryPtr;
- this.nativeTriggerPtr = nativeTriggerPtr;
- }
- @Override
- public ListenableFuture<ListenableFuture<Void>> advertise(VContext ctx, Service service,
- List<BlessingPattern> visibility) {
- ListenableFutureCallback<ListenableFuture<Void>> startCallback = new ListenableFutureCallback<>();
- ListenableFutureCallback<Void> doneCallback = new ListenableFutureCallback<>();
- nativeAdvertise(nativeDiscoveryPtr, nativeTriggerPtr, ctx, service, visibility,
- startCallback, doneCallback);
- return startCallback.getFuture(ctx);
- }
- @Override
- public InputChannel<Update> scan(VContext ctx, String query) {
- try {
- return nativeScan(nativeDiscoveryPtr, ctx, query);
- } catch (VException e) {
- throw new RuntimeException("Couldn't start discovery scan()", e);
- }
- }
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- nativeFinalize(nativeDiscoveryPtr, nativeTriggerPtr);
- }
-}
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/VScanner.java b/lib/src/main/java/io/v/impl/google/lib/discovery/VScanner.java
deleted file mode 100644
index ab5ac8f..0000000
--- a/lib/src/main/java/io/v/impl/google/lib/discovery/VScanner.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.impl.google.lib.discovery;
-
-/**
- * Stores an interfance name and a {@link ScanHandler}.
- */
-public class VScanner {
- private String interfaceName;
- private ScanHandler handler;
-
- public VScanner(String interfaceName, ScanHandler handler) {
- this.interfaceName = interfaceName;
- this.handler = handler;
- }
-
- /** Returns the interface name */
- public String getInterfaceName() {
- return interfaceName;
- }
-
- /** Returns the {@link ScanHandler} */
- public ScanHandler getHandler() {
- return handler;
- }
-}
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/ble/BleAdvertisementConverter.java b/lib/src/main/java/io/v/impl/google/lib/discovery/ble/BleAdvertisementConverter.java
deleted file mode 100644
index 745e64c..0000000
--- a/lib/src/main/java/io/v/impl/google/lib/discovery/ble/BleAdvertisementConverter.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.impl.google.lib.discovery.ble;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.logging.Logger;
-import java.util.Map;
-import java.util.UUID;
-
-import io.v.impl.google.lib.discovery.EncodingUtil;
-import io.v.impl.google.lib.discovery.UUIDUtil;
-import io.v.x.ref.lib.discovery.Advertisement;
-import io.v.v23.discovery.Attachments;
-import io.v.v23.discovery.Attributes;
-import io.v.v23.discovery.Service;
-import io.v.x.ref.lib.discovery.EncryptionAlgorithm;
-import io.v.x.ref.lib.discovery.EncryptionKey;
-import io.v.x.ref.lib.discovery.plugins.ble.Constants;
-
-/**
- * Converts from {@link Advertisement} to the gatt services and vice-versa.
- */
-public class BleAdvertisementConverter {
- private static final Logger logger = Logger.getLogger(BleAdvertisementConverter.class.getName());
- private static final Charset enc = Charset.forName("UTF-8");
-
- /**
- * Converts from {@link Advertisement} to the ble representation.
- *
- * @return map of Characteristic UUIDs to their values.
- * @throws IOException
- */
- public static Map<UUID, byte[]> vAdvertismentToBleAttr(Advertisement adv)
- throws IOException {
- Map<UUID, byte[]> bleAttr = new HashMap<>();
- Service service = adv.getService();
- bleAttr.put(UUID.fromString(Constants.INSTANCE_ID_UUID),
- service.getInstanceId().getBytes(enc));
- bleAttr.put(UUID.fromString(Constants.INTERFACE_NAME_UUID),
- service.getInterfaceName().getBytes(enc));
- bleAttr.put(UUID.fromString(Constants.ADDRS_UUID),
- EncodingUtil.packAddresses(service.getAddrs()));
- bleAttr.put(UUID.fromString(Constants.HASH_UUID), adv.getHash());
-
- String instanceName = service.getInstanceName();
- if (instanceName != null && !instanceName.isEmpty()) {
- bleAttr.put(UUID.fromString(Constants.INSTANCE_NAME_UUID),
- instanceName.getBytes(enc));
- }
- for (Map.Entry<String, String> entry : service.getAttrs().entrySet()) {
- String key = entry.getKey();
- String data = key + "=" + entry.getValue();
- bleAttr.put(UUIDUtil.UUIDForAttributeKey(key), data.getBytes(enc));
- }
- for (Map.Entry<String, byte[]> entry : service.getAttachments().entrySet()) {
- String key = Constants.ATTACHMENT_NAME_PREFIX + entry.getKey();
- byte[] keyInBytes = key.getBytes(enc);
- byte[] value = entry.getValue();
- ByteArrayOutputStream buf =
- new ByteArrayOutputStream(keyInBytes.length + 1 + value.length);
- buf.write(keyInBytes);
- buf.write((byte)'=');
- buf.write(value);
- bleAttr.put(UUIDUtil.UUIDForAttributeKey(key), buf.toByteArray());
- }
- if (adv.getEncryptionAlgorithm().getValue() != 0) {
- bleAttr.put(UUID.fromString(Constants.ENCRYPTION_UUID),
- EncodingUtil.packEncryptionKeys(adv.getEncryptionAlgorithm().getValue(),
- adv.getEncryptionKeys()));
- }
- List<String> dirAddrs = adv.getDirAddrs();
- if (dirAddrs != null && !dirAddrs.isEmpty()) {
- bleAttr.put(UUID.fromString(Constants.DIR_ADDRS_UUID),
- EncodingUtil.packAddresses(dirAddrs));
- }
- return bleAttr;
- }
-
- /**
- * Converts from map of characteristic {@link UUID}s -> values to an {@link Advertisement}.
- *
- * @param bleAttr map of characteristic uuids to their values
- * @return Vanadium advertisement based on characteristics
- * @throws IOException
- */
- public static Advertisement bleAttrToVAdvertisement(Map<UUID, byte[]> bleAttr)
- throws IOException {
- String instanceId = null;
- String instanceName = null;
- String interfaceName = null;
- List<String> addrs = null;
- Map<String, String> attrs = new HashMap<String, String>();
- Map<String, byte[]> attachments = new HashMap<String, byte[]>();
- int encryptionAlgo = 0;
- List<EncryptionKey> encryptionKeys = null;
- byte[] hash = null;
- List<String> dirAddrs = null;
-
- for (Map.Entry<UUID, byte[]> entry : bleAttr.entrySet()) {
- String uuidKey = entry.getKey().toString();
- byte[] data = entry.getValue();
- if (uuidKey.equals(Constants.INSTANCE_ID_UUID)) {
- instanceId = new String(data, enc);
- } else if (uuidKey.equals(Constants.INSTANCE_NAME_UUID)) {
- instanceName = new String(data, enc);
- } else if (uuidKey.equals(Constants.INTERFACE_NAME_UUID)) {
- interfaceName = new String(data, enc);
- } else if (uuidKey.equals(Constants.ADDRS_UUID)) {
- addrs = EncodingUtil.unpackAddresses(data);
- } else if (uuidKey.equals(Constants.ENCRYPTION_UUID)) {
- EncodingUtil.KeysAndAlgorithm res = EncodingUtil.unpackEncryptionKeys(data);
- encryptionAlgo = res.getEncryptionAlgorithm();
- encryptionKeys = res.getKeys();
- } else if (uuidKey.equals(Constants.HASH_UUID)) {
- hash = data;
- } else if (uuidKey.equals(Constants.DIR_ADDRS_UUID)) {
- dirAddrs = EncodingUtil.unpackAddresses(data);
- } else {
- int index = -1;
- for (int i = 0; i < data.length; i++) {
- if (data[i] == (byte)'=') {
- index = i;
- break;
- }
- }
- if (index < 0) {
- logger.severe("Failed to parse data for " + uuidKey);
- continue;
- }
- String key = new String(data, 0, index, enc);
- if (key.startsWith(Constants.ATTACHMENT_NAME_PREFIX)) {
- key = key.substring(Constants.ATTACHMENT_NAME_PREFIX.length());
- byte[] value = Arrays.copyOfRange(data, index + 1, data.length);
- attachments.put(key, value);
- } else {
- String value = new String(data, index + 1, data.length - index - 1, enc);
- attrs.put(key, value);
- }
- }
- }
- return new Advertisement(
- new Service(instanceId,
- instanceName,
- interfaceName,
- new Attributes(attrs),
- addrs,
- new Attachments(attachments)),
- new EncryptionAlgorithm(encryptionAlgo),
- encryptionKeys,
- hash,
- dirAddrs,
- false);
- }
-}
diff --git a/lib/src/main/java/io/v/impl/google/rt/VRuntimeImpl.java b/lib/src/main/java/io/v/impl/google/rt/VRuntimeImpl.java
index 0a40acf..eaf229f 100644
--- a/lib/src/main/java/io/v/impl/google/rt/VRuntimeImpl.java
+++ b/lib/src/main/java/io/v/impl/google/rt/VRuntimeImpl.java
@@ -18,7 +18,7 @@
import io.v.v23.Options;
import io.v.v23.VRuntime;
import io.v.v23.context.VContext;
-import io.v.v23.discovery.VDiscovery;
+import io.v.v23.discovery.Discovery;
import io.v.v23.namespace.Namespace;
import io.v.v23.rpc.Callback;
import io.v.v23.rpc.Client;
@@ -56,7 +56,7 @@
throws VException;
private static native ListenSpec nativeGetListenSpec(VContext ctx) throws VException;
- private static native VDiscovery nativeNewDiscovery(VContext ctx) throws VException;
+ private static native Discovery nativeNewDiscovery(VContext ctx) throws VException;
// Attaches a server to the given context. Used by this class and other classes
// that natively create a server.
@@ -161,7 +161,7 @@
}
}
@Override
- public VDiscovery newDiscovery(VContext ctx) throws VException {
+ public Discovery newDiscovery(VContext ctx) throws VException {
return nativeNewDiscovery(ctx);
}
@Override
diff --git a/lib/src/main/java/io/v/v23/V.java b/lib/src/main/java/io/v/v23/V.java
index 9dce6b1..2f5d40a 100644
--- a/lib/src/main/java/io/v/v23/V.java
+++ b/lib/src/main/java/io/v/v23/V.java
@@ -18,7 +18,7 @@
import io.v.impl.google.rt.VRuntimeImpl;
import io.v.v23.context.VContext;
-import io.v.v23.discovery.VDiscovery;
+import io.v.v23.discovery.Discovery;
import io.v.v23.namespace.Namespace;
import io.v.v23.rpc.Client;
import io.v.v23.rpc.Dispatcher;
@@ -450,12 +450,12 @@
}
/**
- * Returns a new {@link VDiscovery} instance.
+ * Returns a new {@link Discovery} instance.
*
* @param ctx current context
* @throws VException if a new discovery instance cannot be created
*/
- public static VDiscovery newDiscovery(VContext ctx) throws VException {
+ public static Discovery newDiscovery(VContext ctx) throws VException {
return getRuntime(ctx).newDiscovery(ctx);
}
diff --git a/lib/src/main/java/io/v/v23/VRuntime.java b/lib/src/main/java/io/v/v23/VRuntime.java
index 19f425a..76a4494 100644
--- a/lib/src/main/java/io/v/v23/VRuntime.java
+++ b/lib/src/main/java/io/v/v23/VRuntime.java
@@ -5,7 +5,7 @@
package io.v.v23;
import io.v.v23.context.VContext;
-import io.v.v23.discovery.VDiscovery;
+import io.v.v23.discovery.Discovery;
import io.v.v23.namespace.Namespace;
import io.v.v23.rpc.Client;
import io.v.v23.rpc.Dispatcher;
@@ -198,10 +198,10 @@
VContext getContext();
/**
- * Returns a new {@code VDiscovery} instance.
+ * Returns a new {@code Discovery} instance.
*
* @param ctx current context
* @throws VException if a new discovery instance cannot be created
*/
- VDiscovery newDiscovery(VContext ctx) throws VException;
+ Discovery newDiscovery(VContext ctx) throws VException;
}
diff --git a/lib/src/main/java/io/v/v23/discovery/Discovery.java b/lib/src/main/java/io/v/v23/discovery/Discovery.java
new file mode 100644
index 0000000..a6f846e
--- /dev/null
+++ b/lib/src/main/java/io/v/v23/discovery/Discovery.java
@@ -0,0 +1,82 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.v23.discovery;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+
+import javax.annotation.CheckReturnValue;
+
+import io.v.v23.InputChannel;
+import io.v.v23.context.VContext;
+import io.v.v23.security.BlessingPattern;
+import io.v.v23.verror.VException;
+
+/**
+ * An interface for discovery operations; it is the client-side library for the discovery service.
+ */
+public interface Discovery {
+ /**
+ * Broadcasts the advertisement to be discovered by {@link #scan scan} operations.
+ * <p>
+ * Visibility is used to limit the principals that can see the advertisement. An
+ * empty list means that there are no restrictions on visibility (i.e, equivalent
+ * to {@link io.v.v23.security.Constants#ALL_PRINCIPALS}).
+ * <p>
+ * If {@link Advertisement#id} is not specified, a random unique a random unique
+ * identifier will be assigned. Any change to service will not be applied after
+ * advertising starts.
+ * <p>
+ * It is an error to have simultaneously active advertisements for two identical
+ * instances (i.e., {@link Advertisement#id}s).
+ * <p>
+ * Advertising will continue until the context is canceled or exceeds its deadline
+ * and the returned {@link ListenableFuture} will complete once it stops.
+ * <p>
+ * The returned future is guaranteed to be executed on an {@link java.util.concurrent.Executor}
+ * specified in {@code context} (see {@link io.v.v23.V#withExecutor}).
+ * <p>
+ *
+ * @param context a context that will be used to stop advertising
+ * @param ad an advertisement to advertises; this may be update with a random unique
+ * identifier if ad.id is not specified.
+ * @param visibility a set of blessing patterns for whom this advertisement is meant; any entity
+ * not matching a pattern here won't know what the advertisement is
+ * @return a new {@link ListenableFuture} that completes once advertising stops
+ * @throws VException if advertising couldn't be started
+ */
+ @CheckReturnValue
+ ListenableFuture<Void> advertise(
+ VContext context, Advertisement ad, List<BlessingPattern> visibility) throws VException;
+
+ /**
+ * Scans advertisements that match the query and returns an {@link InputChannel} of updates.
+ * <p>
+ * Scan excludes the advertisements that are advertised from the same discovery instance.
+ * <p>
+ * The query is a {@code WHERE} expression of a {@code syncQL} query against advertisements,
+ * where keys are {@link Advertisement#id}s and values are {@link Advertisement}s.
+ * <p>
+ * Examples:
+ * <p><blockquote><pre>
+ * v.InstanceName = "v.io/i"
+ * v.InstanceName = "v.io/i" AND v.Attributes["a"] = "v"
+ * v.Attributes["a"] = "v1" OR v.Attributes["a"] = "v2"
+ * </pre></blockquote><p>
+ * You can find the {@code SyncQL} tutorial at:
+ * https://vanadium.github.io/tutorials/syncbase/syncql-tutorial.html
+ * <p>
+ * Scanning will continue until the context is canceled or exceeds its deadline. Note that
+ * to avoid memory leaks, the caller should drain the channel after cancelling the context.
+ *
+ * @param context a context that will be used to stop scanning
+ * @param query a WHERE expression of {@code syncQL query} against scanned advertisements
+ * @return a (potentially-infite) {@link InputChannel} of updates
+ * @throws VException if scanning couldn't be started
+ */
+ @CheckReturnValue
+ InputChannel<Update> scan(VContext context, String query) throws VException;
+}
diff --git a/lib/src/main/java/io/v/v23/discovery/Update.java b/lib/src/main/java/io/v/v23/discovery/Update.java
new file mode 100644
index 0000000..fa29e8a
--- /dev/null
+++ b/lib/src/main/java/io/v/v23/discovery/Update.java
@@ -0,0 +1,82 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.v23.discovery;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+
+import javax.annotation.CheckReturnValue;
+
+import io.v.v23.context.VContext;
+import io.v.v23.verror.VException;
+
+/**
+ * Update is the interface for an update from Vanadium discovery scanning.
+ */
+public interface Update {
+ /**
+ * Returns true if this update represents a service that is lost during scan.
+ */
+ boolean isLost();
+
+ /**
+ * Returns the universal unique identifier of the discovered advertisement.
+ */
+ AdId getId();
+
+ /**
+ * Returns the interface name of the service.
+ */
+ String getInterfaceName();
+
+ /**
+ * Returns the addresses (vanadium object names) that the service is served on.
+ */
+ List<String> getAddresses();
+
+ /**
+ * Returns the named attribute of the service.
+ * <p>
+ * Returns null if the named attribute does not exist.
+ *
+ * @param name the attachment name
+ * @return the data of the named attribute
+ */
+ String getAttribute(String name);
+
+ /**
+ * Returns a new {@link ListenableFuture} whose result is the data for the named attachment.
+ * <p>
+ * The returned future completes immediately if the named attachment is already available;
+ * otherwise it completes once attachment fetching over RPC is done.
+ * <p>
+ * The result of future will be null if the named attachment does not exist.
+ * <p>
+ * The returned future is guaranteed to be executed on an {@link java.util.concurrent.Executor}
+ * specified in {@code context} (see {@link io.v.v23.V#withExecutor}).
+ * <p>
+ * The returned future will fail if the row doesn't exist.
+ * <p>
+ * The returned {@link ListenableFuture} will fail if there was an error fetching the
+ * attachments or {@code context} gets canceled.
+ *
+ * @param context a context that will be used to stop fetching; the fetching will end
+ * when the context is cancelled or timed out
+ * @param name the attachment name
+ * @return a new {@link ListenableFuture} whose result is the data for the named
+ * attachment
+ * @throws VException if attachment couldn't be fetched
+ */
+ @CheckReturnValue
+ ListenableFuture<byte[]> getAttachment(VContext context, String name) throws VException;
+
+ /**
+ * Returns the advertisement that this update corresponds to.
+ * <p>
+ * The returned advertisement may not include all attachments.
+ */
+ Advertisement getAdvertisement();
+}
diff --git a/lib/src/main/java/io/v/v23/discovery/VDiscovery.java b/lib/src/main/java/io/v/v23/discovery/VDiscovery.java
deleted file mode 100644
index 234e952..0000000
--- a/lib/src/main/java/io/v/v23/discovery/VDiscovery.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.v23.discovery;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-
-import javax.annotation.CheckReturnValue;
-
-import io.v.v23.InputChannel;
-import io.v.v23.context.VContext;
-import io.v.v23.security.BlessingPattern;
-
-/**
- * An interface for discovery operations; it is the client-side library for the discovery service.
- */
-public interface VDiscovery {
- /**
- * Advertises the service to be discovered by {@link #scan scan} implementations.
- * <p>
- * Returns a new {@link ListenableFuture} that completes once advertising starts. The result
- * of this future is a new {@link ListenableFuture} that completes once advertising stops.
- * Once successfully started, advertising will continue until the context is canceled or
- * exceeds its deadline. Note that the future signaling a completion of advertising can
- * never fail.
- * <p>
- * Visibility is used to limit the principals that can see the advertisement. An
- * empty list means that there are no restrictions on visibility (i.e, equivalent
- * to {@link io.v.v23.security.Constants#ALL_PRINCIPALS}).
- * <p>
- * If {@link Service#instanceId} is not specified, a random 128 bit (16 byte) {@code UUID} will
- * be assigned to it once advertising starts. Any change to service will not be applied after
- * advertising starts.
- * <p>
- * It is an error to have simultaneously active advertisements for two identical
- * instances (i.e., {@link Service#instanceId}s).
- * <p>
- * The returned future is guaranteed to be executed on an {@link java.util.concurrent.Executor}
- * specified in {@code context} (see {@link io.v.v23.V#withExecutor}).
- * <p>
- * The returned future will fail with {@link java.util.concurrent.CancellationException} if
- * {@code context} gets canceled.
- *
- * @param context a context that will be used to stop the advertisement; the advertisement
- * will end when the context is cancelled or timed out
- * @param service the service with the attributes to advertises; this may be update with
- * a random unique identifier if service.instanceId is not specified.
- * @param visibility a set of blessing patterns for whom this advertisement is meant; any entity
- * not matching a pattern here won't know what the advertisement is
- * @return a new {@link ListenableFuture} that completes once advertising starts;
- * the result of this future is a second {@link ListenableFuture} that
- * completes once advertising stops
- */
- @CheckReturnValue
- ListenableFuture<ListenableFuture<Void>> advertise(
- VContext context, Service service, List<BlessingPattern> visibility);
-
- /**
- * Scans services that match the query and returns an {@link InputChannel} of updates.
- * <p>
- * Scanning will continue until the context is canceled or exceeds its deadline.
- * <p>
- * Scanning will exclude the services that are advertised from the same VDiscovery instance.
- * <p>
- * The query is a {@code WHERE} expression of a {@code syncQL} query against advertised services,
- * where keys are {@link Service#instanceId}s and values are {@link Service}s.
- * <p>
- * Examples:
- * <p><blockquote><pre>
- * v.InstanceName = "v.io/i"
- * v.InstanceName = "v.io/i" AND v.Attrs["a"] = "v"
- * v.Attrs["a"] = "v1" OR v.Attrs["a"] = "v2"
- * </pre></blockquote><p>
- * You can find the {@code SyncQL} tutorial at:
- * https://vanadium.github.io/tutorials/syncbase/syncql-tutorial.html
- *
- * @param context a context that will be used to stop the scan; scan will end when the context
- * is cancelled or timed out
- * @param query a WHERE expression of {@code syncQL query} against scanned services
- * @return a (potentially-infite) {@link InputChannel} of updates
- */
- @CheckReturnValue
- InputChannel<Update> scan(VContext context, String query);
-}
diff --git a/lib/src/test/java/io/v/impl/google/lib/discovery/DeviceCacheTest.java b/lib/src/test/java/io/v/impl/google/lib/discovery/DeviceCacheTest.java
deleted file mode 100644
index ac53f6d..0000000
--- a/lib/src/test/java/io/v/impl/google/lib/discovery/DeviceCacheTest.java
+++ /dev/null
@@ -1,285 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.impl.google.lib.discovery;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-import junit.framework.TestCase;
-
-import org.joda.time.DateTimeUtils;
-import org.joda.time.Duration;
-
-import io.v.v23.discovery.Service;
-
-import io.v.x.ref.lib.discovery.Advertisement;
-import io.v.x.ref.lib.discovery.EncryptionAlgorithm;
-
-/**
- * Tests for {@link DeviceCache}.
- */
-public class DeviceCacheTest extends TestCase {
- private abstract class CountingHandler implements ScanHandler {
- protected int mNumCalls = 0;
-
- @Override
- public void handleUpdate(Advertisement adv) {}
- }
-
- public void tearDown() {
- DateTimeUtils.setCurrentMillisSystem();
- }
-
- public void testSaveDeveice() {
- DeviceCache cache = new DeviceCache(new Duration(1000 * 60 * 60));
- // The advertisements here are not relevant since we are just checking
- // the seen stamp function.
- Set<Advertisement> advs = new HashSet<>();
- long stamp = 10001;
- assertFalse(cache.haveSeenStamp(stamp, "newDevice"));
- cache.saveDevice(stamp, advs, "newDevice");
- assertTrue(cache.haveSeenStamp(stamp, "newDevice"));
- cache.shutdownCache();
- }
-
- public void testSaveDeviceWithDifferentStampCode() {
- DeviceCache cache = new DeviceCache(new Duration(1000 * 60 * 60));
- // The advertisements here are not relevant since we are just checking
- // the seen stamp function.
- Set<Advertisement> advs = new HashSet<>();
- long stamp = 10001;
- assertFalse(cache.haveSeenStamp(stamp, "newDevice"));
- cache.saveDevice(stamp, advs, "newDevice");
- assertTrue(cache.haveSeenStamp(stamp, "newDevice"));
- cache.saveDevice(stamp + 1, advs, "newDevice");
- assertTrue(cache.haveSeenStamp(stamp + 1, "newDevice"));
- assertFalse(cache.haveSeenStamp(stamp, "newDevice"));
- cache.shutdownCache();
- }
-
- public void testAddingScannerBeforeSavingDevice() {
- DeviceCache cache = new DeviceCache(new Duration(1000 * 60 * 60));
- Set<Advertisement> advs = new HashSet<>();
- long stamp = 10001;
- assertFalse(cache.haveSeenStamp(stamp, "newDevice"));
-
- Service service1 = new Service();
- service1.setInterfaceName("randomInterface");
- final Advertisement adv1 = new Advertisement(
- service1, new EncryptionAlgorithm(0), null,
- new byte[]{1, 2, 3}, Arrays.asList("dir1", "dir2"), false);
- advs.add(adv1);
-
- CountingHandler handler = new CountingHandler() {
- @Override
- public void handleUpdate(Advertisement advertisement) {
- assertEquals(adv1, advertisement);
- assertEquals(mNumCalls, 0);
- mNumCalls++;
- }
- };
-
- cache.addScanner(new VScanner(service1.getInterfaceName(), handler));
-
- Service service2 = new Service();
- service2.setInterfaceName("randomInterface2");
- Advertisement adv2 = new Advertisement(service2, new EncryptionAlgorithm(0), null, null, null, false);
- advs.add(adv2);
-
- cache.saveDevice(stamp, advs, "newDevice");
-
- // Make sure that the handler is called;
- assertEquals(1, handler.mNumCalls);
- cache.shutdownCache();
- }
-
- public void testAddingScannerAfterSavingDevice() {
- DeviceCache cache = new DeviceCache(new Duration(1000 * 60 * 60));
- Set<Advertisement> advs = new HashSet<>();
- long stamp = 10001;
- assertFalse(cache.haveSeenStamp(stamp, "newDevice"));
-
- Service service1 = new Service();
- service1.setInterfaceName("randomInterface");
- final Advertisement adv1 = new Advertisement(service1, new EncryptionAlgorithm(0), null, null, null, false);
-
- advs.add(adv1);
-
- CountingHandler handler = new CountingHandler() {
- @Override
- public void handleUpdate(Advertisement advertisement) {
- assertEquals(adv1, advertisement);
- assertEquals(mNumCalls, 0);
- mNumCalls++;
- }
- };
-
- Service service2 = new Service();
- service1.setInterfaceName("randomInterface2");
- Advertisement adv2 = new Advertisement(service2, new EncryptionAlgorithm(0), null, null, null, false);
- advs.add(adv2);
- cache.saveDevice(stamp, advs, "newDevice");
-
- cache.addScanner(new VScanner(service1.getInterfaceName(), handler));
-
- // Make sure that the handler is called;
- assertEquals(1, handler.mNumCalls);
- cache.shutdownCache();
- }
-
- public void testRemovingAnAdvertisementCallsHandler() {
- DeviceCache cache = new DeviceCache(new Duration(1000 * 60 * 60));
- Set<Advertisement> advs = new HashSet<>();
- long stamp = 10001;
- assertFalse(cache.haveSeenStamp(stamp, "newDevice"));
-
- Service service1 = new Service();
- service1.setInterfaceName("randomInterface");
- final Advertisement adv1 = new Advertisement(service1, new EncryptionAlgorithm(0), null, null, null, false);
- advs.add(adv1);
-
- CountingHandler handler = new CountingHandler() {
- @Override
- public void handleUpdate(Advertisement advertisement) {
- // The first call should be an add and the second call should be
- // a remove.
- if (mNumCalls == 0) {
- assertEquals(adv1, advertisement);
- } else {
- Advertisement removed = new Advertisement(
- adv1.getService(), adv1.getEncryptionAlgorithm(), adv1.getEncryptionKeys(),
- adv1.getHash(), adv1.getDirAddrs(), true);
- assertEquals(removed, advertisement);
- }
- mNumCalls++;
- }
- };
-
- cache.addScanner(new VScanner(service1.getInterfaceName(), handler));
-
- Service service2 = new Service();
- service2.setInterfaceName("randomInterface2");
- Advertisement adv2 = new Advertisement(service2, new EncryptionAlgorithm(0), null, null, null, false);
- advs.add(adv2);
-
- cache.saveDevice(stamp, advs, "newDevice");
-
- Set<Advertisement> newAdvs = new HashSet<>();
- newAdvs.add(adv2);
-
- cache.saveDevice(10002, newAdvs, "newDevice");
-
- // Make sure that the handler is called;
- assertEquals(2, handler.mNumCalls);
- cache.shutdownCache();
- }
-
- public void testAddingtheSameAdvertisementDoesNotCallsHandler() {
- DeviceCache cache = new DeviceCache(new Duration(1000 * 60 * 60));
- Set<Advertisement> advs = new HashSet<>();
- long stamp = 10001;
- assertFalse(cache.haveSeenStamp(stamp, "newDevice"));
-
- Service service1 = new Service();
- service1.setInterfaceName("randomInterface");
- final Advertisement adv1 = new Advertisement(service1, new EncryptionAlgorithm(0), null, null, null, false);
- advs.add(adv1);
-
- CountingHandler handler = new CountingHandler() {
- @Override
- public void handleUpdate(Advertisement advertisement) {
- assertEquals(adv1, advertisement);
- mNumCalls++;
- }
- };
-
- cache.addScanner(new VScanner(service1.getInterfaceName(), handler));
-
- Service service2 = new Service();
- service2.setInterfaceName("randomInterface2");
- Advertisement adv2 = new Advertisement(service2, new EncryptionAlgorithm(0), null, null, null, false);
- advs.add(adv2);
-
- cache.saveDevice(stamp, advs, "newDevice");
-
- Set<Advertisement> advs2 = new HashSet<>(advs);
- cache.saveDevice(10002, advs2, "newDevice");
-
- // Make sure that the handler is called;
- assertEquals(1, handler.mNumCalls);
- cache.shutdownCache();
- }
-
- public void testCacheEvictionCallsHandler() {
- DeviceCache cache = new DeviceCache(new Duration(1000 * 60 * 60));
- Set<Advertisement> advs = new HashSet<>();
- long stamp = 10001;
- assertFalse(cache.haveSeenStamp(stamp, "newDevice"));
-
- Service service1 = new Service();
- service1.setInterfaceName("randomInterface");
- final Advertisement adv1 = new Advertisement(service1, new EncryptionAlgorithm(0), null, null, null, false);
- advs.add(adv1);
-
- CountingHandler handler = new CountingHandler() {
- @Override
- public void handleUpdate(Advertisement advertisement) {
- // The first call should be an add and the second call should be
- // a remove.
- if (mNumCalls == 0) {
- assertEquals(adv1, advertisement);
- } else {
- Advertisement removed = new Advertisement(
- adv1.getService(), adv1.getEncryptionAlgorithm(), adv1.getEncryptionKeys(),
- adv1.getHash(), adv1.getDirAddrs(), true);
- assertEquals(removed, advertisement);
- }
- mNumCalls++;
- }
- };
-
- long cacheTime = DateTimeUtils.currentTimeMillis();
- cache.saveDevice(stamp, advs, "newDevice");
- cache.addScanner(new VScanner(service1.getInterfaceName(), handler));
-
- DateTimeUtils.setCurrentMillisFixed(cacheTime + 1000 * 60 * 61);
- cache.removeStaleEntries();
- // Make sure that the handler is called;
- assertEquals(2, handler.mNumCalls);
- cache.shutdownCache();
- }
-
- public void testCacheEvictionClearsAllState() {
- DeviceCache cache = new DeviceCache(new Duration(1000 * 60 * 60));
- Set<Advertisement> advs = new HashSet<>();
- long stamp = 10001;
- assertFalse(cache.haveSeenStamp(stamp, "newDevice"));
-
- Service service1 = new Service();
- service1.setInterfaceName("randomInterface");
- final Advertisement adv1 = new Advertisement(service1, new EncryptionAlgorithm(0), null, null, null, false);
- advs.add(adv1);
-
- CountingHandler handler = new CountingHandler() {
- @Override
- public void handleUpdate(Advertisement advertisement) {
- mNumCalls++;
- }
- };
-
- long cacheTime = DateTimeUtils.currentTimeMillis();
- cache.saveDevice(stamp, advs, "newDevice");
-
- DateTimeUtils.setCurrentMillisFixed(cacheTime + 1000 * 60 * 61);
- cache.removeStaleEntries();
-
- cache.addScanner(new VScanner(service1.getInterfaceName(), handler));
-
- // Make sure that the handler is never called.
- assertEquals(0, handler.mNumCalls);
- cache.shutdownCache();
- }
-}
diff --git a/lib/src/test/java/io/v/impl/google/lib/discovery/DiscoveryTest.java b/lib/src/test/java/io/v/impl/google/lib/discovery/DiscoveryTest.java
new file mode 100644
index 0000000..ef853c1
--- /dev/null
+++ b/lib/src/test/java/io/v/impl/google/lib/discovery/DiscoveryTest.java
@@ -0,0 +1,70 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.impl.google.lib.discovery;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import junit.framework.TestCase;
+
+import io.v.v23.V;
+import io.v.v23.context.VContext;
+import io.v.v23.discovery.Discovery;
+import io.v.v23.discovery.Advertisement;
+import io.v.v23.discovery.Attributes;
+import io.v.v23.discovery.Attachments;
+import io.v.v23.discovery.Update;
+import io.v.v23.VFutures;
+import io.v.v23.verror.VException;
+import io.v.v23.InputChannel;
+import io.v.v23.InputChannels;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.v.impl.google.lib.discovery.DiscoveryTestUtil.assertThat;
+
+/**
+ * Tests for {@link Discovery} implementation.
+ */
+public class DiscoveryTest extends TestCase {
+ public void testBasicTest() throws VException {
+ VContext ctx = DiscoveryTestUtil.withMockDiscovery();
+ Discovery d1 = V.newDiscovery(ctx);
+
+ Advertisement ad = new Advertisement();
+ ad.setInterfaceName("v.io/v23/a");
+ ad.setAddresses(Arrays.asList("/h1:123/x"));
+ ad.setAttributes(new Attributes(ImmutableMap.of("a", "v")));
+ ad.setAttachments(new Attachments(ImmutableMap.of("a", new byte[] {1, 2, 3})));
+
+ VContext advCtx = ctx.withCancel();
+ ListenableFuture<Void> advFuture = d1.advertise(advCtx, ad, null);
+
+ Discovery d2 = V.newDiscovery(ctx);
+ VContext scanCtx = ctx.withCancel();
+
+ InputChannel<Update> updateCh = d2.scan(scanCtx, "");
+ Iterator<Update> it = InputChannels.asIterable(updateCh).iterator();
+
+ assertThat(it.hasNext()).isTrue();
+ Update update = it.next();
+ assertThat(update.isLost()).isFalse();
+ assertThat(update).isEqualTo(ctx, ad);
+
+ advCtx.cancel();
+ VFutures.sync(advFuture);
+
+ assertThat(it.hasNext()).isTrue();
+ update = it.next();
+ assertThat(update.isLost()).isTrue();
+ assertThat(update).isEqualTo(ctx, ad);
+ assertThat(update.getAdvertisement()).isEqualTo(ad);
+
+ scanCtx.cancel();
+ assertThat(it.hasNext()).isFalse();
+ }
+}
diff --git a/lib/src/test/java/io/v/impl/google/lib/discovery/DiscoveryTestUtil.java b/lib/src/test/java/io/v/impl/google/lib/discovery/DiscoveryTestUtil.java
new file mode 100644
index 0000000..3355097
--- /dev/null
+++ b/lib/src/test/java/io/v/impl/google/lib/discovery/DiscoveryTestUtil.java
@@ -0,0 +1,257 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.impl.google.lib.discovery;
+
+import com.google.common.base.Equivalence;
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMultiset;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Maps;
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.Subject;
+import com.google.common.truth.SubjectFactory;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import io.v.v23.V;
+import io.v.v23.VFutures;
+import io.v.v23.context.VContext;
+import io.v.v23.discovery.Advertisement;
+import io.v.v23.discovery.Update;
+import io.v.v23.verror.VException;
+
+import io.v.x.ref.lib.discovery.AdInfo;
+
+/**
+ * Various test utilities for discovery.
+ */
+public class DiscoveryTestUtil {
+ /**
+ * Allows a runtime to use a mock discovery instance.
+ * <p>
+ * This should be called before V.newDiscovery() is called.
+ */
+ private static native void injectMockDiscovery(VContext ctx) throws VException;
+
+ /**
+ * Initializes a runtime with a mock discovery instance.
+ */
+ public static VContext withMockDiscovery() throws VException {
+ VContext ctx = V.init();
+ injectMockDiscovery(ctx);
+ return ctx;
+ }
+
+ private static final Equivalence<byte[]> BYTE_ARRAY_EQUIVALENCE =
+ new Equivalence<byte[]>() {
+ @Override
+ protected boolean doEquivalent(byte[] a, byte[] b) {
+ return Arrays.equals(a, b);
+ }
+
+ @Override
+ protected int doHash(byte[] bytes) {
+ return Arrays.hashCode(bytes);
+ }
+ };
+
+ private static final Equivalence<Advertisement> ADVERTISEMENT_EQUIVALENCE =
+ new Equivalence<Advertisement>() {
+ @Override
+ protected boolean doEquivalent(Advertisement a, Advertisement b) {
+ return a.getId().equals(b.getId())
+ && a.getInterfaceName().equals(b.getInterfaceName())
+ && a.getAddresses().equals(b.getAddresses())
+ && a.getAttributes().equals(b.getAttributes())
+ && Maps.difference(
+ a.getAttachments(),
+ b.getAttachments(),
+ BYTE_ARRAY_EQUIVALENCE)
+ .areEqual();
+ }
+
+ @Override
+ protected int doHash(Advertisement ad) {
+ return Objects.hashCode(ad.getId());
+ }
+ };
+
+ private static final Equivalence<AdInfo> ADINFO_EQUIVALENCE =
+ new Equivalence<AdInfo>() {
+ @Override
+ protected boolean doEquivalent(AdInfo a, AdInfo b) {
+ return ADVERTISEMENT_EQUIVALENCE.equivalent(a.getAd(), b.getAd())
+ && a.getEncryptionAlgorithm().equals(b.getEncryptionAlgorithm())
+ && a.getEncryptionKeys().equals(b.getEncryptionKeys())
+ && a.getHash().equals(b.getHash())
+ && a.getDirAddrs().equals(b.getDirAddrs());
+ }
+
+ @Override
+ protected int doHash(AdInfo adInfo) {
+ return Objects.hashCode(adInfo.getAd().getId(), adInfo.getHash());
+ }
+ };
+
+ private static final Function<AdInfo, Equivalence.Wrapper<AdInfo>> ADINFO_WRAPPER =
+ new Function<AdInfo, Equivalence.Wrapper<AdInfo>>() {
+ @Override
+ public Equivalence.Wrapper<AdInfo> apply(AdInfo adinfo) {
+ return ADINFO_EQUIVALENCE.wrap(adinfo);
+ }
+ };
+
+ /**
+ * {@link SubjectFactory} for {@link Advertisement}.
+ */
+ public static class AdvertisementSubject extends Subject<AdvertisementSubject, Advertisement> {
+ public AdvertisementSubject(FailureStrategy fs, Advertisement subject) {
+ super(fs, subject);
+ }
+
+ public void isEqualTo(Advertisement expected) {
+ if (!ADVERTISEMENT_EQUIVALENCE.equivalent(getSubject(), expected)) {
+ fail("is equal to", expected);
+ }
+ }
+ }
+
+ private static final SubjectFactory<AdvertisementSubject, Advertisement> ADVERTISEMENT_SF =
+ new SubjectFactory<AdvertisementSubject, Advertisement>() {
+ @Override
+ public AdvertisementSubject getSubject(FailureStrategy fs, Advertisement target) {
+ return new AdvertisementSubject(fs, target);
+ }
+ };
+
+ public static AdvertisementSubject assertThat(Advertisement actual) {
+ return Truth.assertAbout(ADVERTISEMENT_SF).that(actual);
+ }
+
+ /**
+ * {@link SubjectFactory} for {@link AdInfo}.
+ */
+ public static class AdInfoSubject extends Subject<AdInfoSubject, AdInfo> {
+ public AdInfoSubject(FailureStrategy fs, AdInfo subject) {
+ super(fs, subject);
+ }
+
+ public void isEqualTo(AdInfo expected) {
+ if (!ADINFO_EQUIVALENCE.equivalent(getSubject(), expected)
+ || getSubject().getLost() != expected.getLost()) {
+ fail("is equal to", expected);
+ }
+ }
+
+ public void isEqualTo(AdInfo expected, boolean lost) {
+ if (!ADINFO_EQUIVALENCE.equivalent(getSubject(), expected)) {
+ fail("is equal to", expected, lost);
+ }
+ if (getSubject().getLost() != lost) {
+ failWithCustomSubject("is equal to", getSubject().getLost(), lost);
+ }
+ }
+ }
+
+ private static final SubjectFactory<AdInfoSubject, AdInfo> ADINFO_SF =
+ new SubjectFactory<AdInfoSubject, AdInfo>() {
+ @Override
+ public AdInfoSubject getSubject(FailureStrategy fs, AdInfo target) {
+ return new AdInfoSubject(fs, target);
+ }
+ };
+
+ public static AdInfoSubject assertThat(AdInfo actual) {
+ return Truth.assertAbout(ADINFO_SF).that(actual);
+ }
+
+ /**
+ * {@link SubjectFactory} for {@link List<AdInfo>}.
+ */
+ public static class AdInfoListSubject extends Subject<AdInfoListSubject, List<AdInfo>> {
+ public AdInfoListSubject(FailureStrategy fs, List<AdInfo> subject) {
+ super(fs, subject);
+ }
+
+ public void isEqualTo(AdInfo... expected) {
+ List<Equivalence.Wrapper<AdInfo>> actualWrapped =
+ FluentIterable.of(expected).transform(ADINFO_WRAPPER).toList();
+ List<Equivalence.Wrapper<AdInfo>> expectedWrapped =
+ FluentIterable.from(getSubject()).transform(ADINFO_WRAPPER).toList();
+ if (!ImmutableMultiset.copyOf(actualWrapped)
+ .equals(ImmutableMultiset.copyOf(expectedWrapped))) {
+ fail("is equal to", FluentIterable.of(expected));
+ }
+ }
+ }
+
+ private static final SubjectFactory<AdInfoListSubject, List<AdInfo>> ADINFO_LIST_SF =
+ new SubjectFactory<AdInfoListSubject, List<AdInfo>>() {
+ @Override
+ public AdInfoListSubject getSubject(FailureStrategy fs, List<AdInfo> target) {
+ return new AdInfoListSubject(fs, target);
+ }
+ };
+
+ public static AdInfoListSubject assertThat(List<AdInfo> actual) {
+ return Truth.assertAbout(ADINFO_LIST_SF).that(actual);
+ }
+
+ /**
+ * {@link SubjectFactory} for {@link Update}.
+ */
+ public static class UpdateSubject extends Subject<UpdateSubject, Update> {
+ public UpdateSubject(FailureStrategy fs, Update subject) {
+ super(fs, subject);
+ }
+
+ public void isEqualTo(VContext ctx, Advertisement expected) throws VException {
+ if (!equivalent(ctx, getSubject(), expected)) {
+ fail("is equal to", expected);
+ }
+ }
+
+ private static boolean equivalent(VContext ctx, Update update, Advertisement ad)
+ throws VException {
+ if (!update.getId().equals(ad.getId())) {
+ return false;
+ }
+ if (!update.getInterfaceName().equals(ad.getInterfaceName())) {
+ return false;
+ }
+ if (!update.getAddresses().equals(ad.getAddresses())) {
+ return false;
+ }
+ for (Map.Entry<String, String> entry : ad.getAttributes().entrySet()) {
+ if (!entry.getValue().equals(update.getAttribute(entry.getKey()))) {
+ return false;
+ }
+ }
+ for (Map.Entry<String, byte[]> e : ad.getAttachments().entrySet()) {
+ byte[] data = VFutures.sync(update.getAttachment(ctx, e.getKey()));
+ if (!Arrays.equals(e.getValue(), data)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private static final SubjectFactory<UpdateSubject, Update> UPDATE_SF =
+ new SubjectFactory<UpdateSubject, Update>() {
+ @Override
+ public UpdateSubject getSubject(FailureStrategy fs, Update target) {
+ return new UpdateSubject(fs, target);
+ }
+ };
+
+ public static UpdateSubject assertThat(Update actual) {
+ return Truth.assertAbout(UPDATE_SF).that(actual);
+ }
+}
diff --git a/lib/src/test/java/io/v/impl/google/lib/discovery/EncodingUtilTest.java b/lib/src/test/java/io/v/impl/google/lib/discovery/EncodingUtilTest.java
index 0c135ca..3e7f2c2 100644
--- a/lib/src/test/java/io/v/impl/google/lib/discovery/EncodingUtilTest.java
+++ b/lib/src/test/java/io/v/impl/google/lib/discovery/EncodingUtilTest.java
@@ -1,52 +1,52 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.impl.google.lib.discovery;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
import junit.framework.TestCase;
-import java.io.IOException;
-
-import java.util.Arrays;
-
import io.v.x.ref.lib.discovery.EncryptionAlgorithm;
-import io.v.x.ref.lib.discovery.testdata.PackAddressTest;
+import io.v.x.ref.lib.discovery.EncryptionKey;
import io.v.x.ref.lib.discovery.testdata.Constants;
+import io.v.x.ref.lib.discovery.testdata.PackAddressTest;
import io.v.x.ref.lib.discovery.testdata.PackEncryptionKeysTest;
+import static com.google.common.truth.Truth.assertThat;
+
/**
* Tests for {@link EncodingUtil}.
*/
public class EncodingUtilTest extends TestCase {
public void testPackAddresses() throws IOException {
for (PackAddressTest test : Constants.PACK_ADDRESS_TEST_DATA) {
- assertEquals(Arrays.toString(test.getPacked()),
- Arrays.toString(EncodingUtil.packAddresses(test.getIn())));
+ assertThat(EncodingUtil.packAddresses(test.getIn())).isEqualTo(test.getPacked());
}
}
public void testUnpackAddresses() throws IOException {
- for (PackAddressTest test : Constants.PACK_ADDRESS_TEST_DATA) {
- assertEquals(test.getIn(),
- EncodingUtil.unpackAddresses(test.getPacked()));
+ for (PackAddressTest test : Constants.PACK_ADDRESS_TEST_DATA) {
+ assertThat(EncodingUtil.unpackAddresses(test.getPacked())).isEqualTo(test.getIn());
}
}
public void testPackEncryptionKeys() throws IOException {
for (PackEncryptionKeysTest test : Constants.PACK_ENCRYPTION_KEYS_TEST_DATA) {
- byte[] res = EncodingUtil.packEncryptionKeys(test.getAlgo().getValue(),
- test.getKeys());
- assertEquals(Arrays.toString(test.getPacked()), Arrays.toString(res));
+ byte[] packed = EncodingUtil.packEncryptionKeys(test.getAlgo(), test.getKeys());
+ assertThat(packed).isEqualTo(test.getPacked());
}
}
public void testUnpackEncryptionKeys() throws IOException {
- for (PackEncryptionKeysTest test : Constants.PACK_ENCRYPTION_KEYS_TEST_DATA) {
- EncodingUtil.KeysAndAlgorithm res = EncodingUtil.unpackEncryptionKeys(
- test.getPacked());
- assertEquals(test.getAlgo(), new EncryptionAlgorithm(res.getEncryptionAlgorithm()));
- assertEquals(test.getKeys(), res.getKeys());
+ for (PackEncryptionKeysTest test : Constants.PACK_ENCRYPTION_KEYS_TEST_DATA) {
+ List<EncryptionKey> keys = new ArrayList<>();
+ EncryptionAlgorithm algo = EncodingUtil.unpackEncryptionKeys(test.getPacked(), keys);
+ assertThat(algo).isEqualTo(test.getAlgo());
+ assertThat(keys).isEqualTo(test.getKeys());
}
}
}
diff --git a/lib/src/test/java/io/v/impl/google/lib/discovery/UUIDUtilTest.java b/lib/src/test/java/io/v/impl/google/lib/discovery/UUIDUtilTest.java
index f28ea48..dfca526 100644
--- a/lib/src/test/java/io/v/impl/google/lib/discovery/UUIDUtilTest.java
+++ b/lib/src/test/java/io/v/impl/google/lib/discovery/UUIDUtilTest.java
@@ -1,27 +1,38 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.impl.google.lib.discovery;
-import junit.framework.TestCase;
-
import java.util.UUID;
+import junit.framework.TestCase;
+
import io.v.v23.V;
import io.v.v23.context.VContext;
+
import io.v.x.ref.lib.discovery.testdata.Constants;
import io.v.x.ref.lib.discovery.testdata.UuidTestData;
+import static com.google.common.truth.Truth.assertThat;
+
/**
* Tests for {@link UUIDUtil}.
*/
public class UUIDUtilTest extends TestCase {
- public void testInterfaceNameUUID() {
+ public void testServiceUuidTest() {
VContext ctx = V.init();
- for (UuidTestData test: Constants.INTERFACE_NAME_TEST) {
- UUID id = UUIDUtil.UUIDForInterfaceName(test.getIn());
- assertEquals(test.getWant(), id.toString());
+ for (UuidTestData test : Constants.SERVICE_UUID_TEST) {
+ UUID uuid = UUIDUtil.serviceUUID(test.getIn());
+ assertThat(uuid.toString()).isEqualTo(test.getWant());
+ }
+ }
+
+ public void testAttributeUuidTest() {
+ VContext ctx = V.init();
+ for (UuidTestData test : Constants.ATTRIBUTE_UUID_TEST) {
+ UUID uuid = UUIDUtil.attributeUUID(test.getIn());
+ assertThat(uuid.toString()).isEqualTo(test.getWant());
}
}
}
diff --git a/lib/src/test/java/io/v/impl/google/lib/discovery/ble/BleAdvertisementConverterTest.java b/lib/src/test/java/io/v/impl/google/lib/discovery/ble/BleAdvertisementConverterTest.java
deleted file mode 100644
index dfcd44b..0000000
--- a/lib/src/test/java/io/v/impl/google/lib/discovery/ble/BleAdvertisementConverterTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.impl.google.lib.discovery.ble;
-
-import junit.framework.TestCase;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
-import io.v.v23.V;
-import io.v.v23.vom.VomUtil;
-import io.v.v23.verror.VException;
-
-import io.v.x.ref.lib.discovery.Advertisement;
-import io.v.x.ref.lib.discovery.plugins.ble.testdata.AdvertisementConversionTestCase;
-import io.v.x.ref.lib.discovery.plugins.ble.testdata.Constants;
-
-/**
- * Tests for {@link BleAdvertisementConverter}
- */
-public class BleAdvertisementConverterTest extends TestCase {
- private static Map<UUID, byte[]> convertBAdv(Map<String, byte[]> in) {
- Map<UUID, byte[]> res = new HashMap<>(in.size());
- for (Map.Entry<String, byte[]> entry : in.entrySet()) {
- res.put(UUID.fromString(entry.getKey()), entry.getValue());
- }
- return res;
- }
-
- public void testConversionToBle() throws IOException {
- // V.init() sets up the jni bindings.
- V.init();
- for (AdvertisementConversionTestCase test : Constants.CONVERSION_TEST_DATA) {
- Map<UUID, byte[]> res = BleAdvertisementConverter.vAdvertismentToBleAttr(
- test.getAdvertisement());
- Map<String, byte[]> want = test.getBleAdvertisement();
- assertEquals(want.size(), res.size());
- for (Map.Entry<UUID, byte[]> entry : res.entrySet()) {
- String stringKey = entry.getKey().toString();
- assertTrue(stringKey + " not matched",
- Arrays.equals(want.get(stringKey), entry.getValue()));
- }
- }
- }
-
- public void testConversionFromBle() throws IOException {
- // V.init() sets up the jni bindings.
- V.init();
- for (AdvertisementConversionTestCase test : Constants.CONVERSION_TEST_DATA) {
- Map<UUID, byte[]> bleAdv = convertBAdv(test.getBleAdvertisement());
- Advertisement res = BleAdvertisementConverter.bleAttrToVAdvertisement(bleAdv);
- // We can't use assertEquals here since we need a deep comparison.
- // We compare them by serializing to vom.
- try {
- byte[] wantVom = VomUtil.encode(test.getAdvertisement(), test.getAdvertisement().VDL_TYPE);
- byte[] resVom = VomUtil.encode(res, res.VDL_TYPE);
- assertTrue("expected:<" + test.getAdvertisement() + "> but was:<" + res + ">",
- Arrays.equals(wantVom, resVom));
- } catch (VException e) {
- fail(e.toString());
- }
- }
- }
-}