blob: 5ba958b14a3e14275abc49ec626e86768719c6b2 [file] [log] [blame]
// 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;
}
}