Auto merge of #14277 - szeged:service-and-manufacturer-data, r=jdm

serviceData and manufacturerData support

<!-- Please describe your changes on the following line: -->
Allow requesting for BluetoothDevices with service and manufacturer specific data.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors

<!-- Either: -->
- [x] There are tests for these changes

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/14277)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-11-23 01:25:39 -08:00 committed by GitHub
commit 5946f756d7
19 changed files with 830 additions and 83 deletions

View file

@ -130,26 +130,63 @@ fn matches_filter(device: &BluetoothDevice, filter: &BluetoothScanfilter) -> boo
}
}
// Step 4.
// TODO: Implement get_manufacturer_data in device crate.
// if let Some(manufacturer_id) = filter.get_manufacturer_id() {
// if !device.get_manufacturer_data().contains_key(manufacturer_id) {
// return false;
// }
// }
//
// Step 5.
// TODO: Implement get_device_data in device crate.
// if !filter.get_service_data_uuid().is_empty() {
// if !device.get_service_data().contains_key(filter.get_service_data_uuid()) {
// return false;
// }
// }
// Step 4.
if let Some(ref manufacturer_data) = filter.get_manufacturer_data() {
let advertised_manufacturer_data = match device.get_manufacturer_data() {
Ok(data) => data,
Err(_) => return false,
};
for (ref id, &(ref prefix, ref mask)) in manufacturer_data.iter() {
if let Some(advertised_data) = advertised_manufacturer_data.get(id) {
if !data_filter_matches(advertised_data, prefix, mask) {
return false;
}
} else {
return false;
}
}
}
// Step 5.
if let Some(ref service_data) = filter.get_service_data() {
let advertised_service_data = match device.get_service_data() {
Ok(data) => data,
Err(_) => return false,
};
for (uuid, &(ref prefix, ref mask)) in service_data.iter() {
if let Some(advertised_data) = advertised_service_data.get(uuid.as_str()) {
if !data_filter_matches(advertised_data, prefix, mask) {
return false;
}
} else {
return false;
}
}
}
// Step 6.
true
}
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothdatafilterinit-matches
fn data_filter_matches(data: &[u8], prefix: &[u8], mask: &[u8]) -> bool {
// Step 1-2: No need to copy the bytes here.
// Step 3.
if data.len() < prefix.len() {
return false;
}
// Step 4.
for ((data, mask), prefix) in data.iter().zip(mask.iter()).zip(prefix.iter()) {
if data & mask != prefix & mask {
return false;
}
}
// Step 5.
true
}
fn matches_filters(device: &BluetoothDevice, filters: &BluetoothScanfilterSequence) -> bool {
if filters.has_empty_or_invalid_filter() {
return false;

View file

@ -7,7 +7,7 @@ use device::bluetooth::{BluetoothAdapter, BluetoothDevice};
use device::bluetooth::{BluetoothGATTCharacteristic, BluetoothGATTDescriptor, BluetoothGATTService};
use std::borrow::ToOwned;
use std::cell::RefCell;
use std::collections::HashSet;
use std::collections::{HashSet, HashMap};
use std::error::Error;
use std::string::String;
use uuid::Uuid;
@ -417,6 +417,29 @@ fn create_blocklisted_device(adapter: &BluetoothAdapter) -> Result<(), Box<Error
Ok(())
}
fn create_glucose_heart_rate_devices(adapter: &BluetoothAdapter) -> Result<(), Box<Error>> {
let glucose_devie = try!(create_device_with_uuids(adapter,
GLUCOSE_DEVICE_NAME.to_owned(),
GLUCOSE_DEVICE_ADDRESS.to_owned(),
vec![GLUCOSE_SERVICE_UUID.to_owned(),
TX_POWER_SERVICE_UUID.to_owned()]));
let heart_rate_device_empty = try!(create_heart_rate_device(adapter, true));
let mut manufacturer_dta = HashMap::new();
manufacturer_dta.insert(17, vec![1, 2, 3]);
try!(glucose_devie.set_manufacturer_data(manufacturer_dta));
let mut service_data = HashMap::new();
service_data.insert(GLUCOSE_SERVICE_UUID.to_owned(), vec![1, 2, 3]);
try!(glucose_devie.set_service_data(service_data));
service_data = HashMap::new();
service_data.insert(HEART_RATE_SERVICE_UUID.to_owned(), vec![1, 2, 3]);
try!(heart_rate_device_empty.set_service_data(service_data));
Ok(())
}
pub fn test(manager: &mut BluetoothManager, data_set_name: String) -> Result<(), Box<Error>> {
let may_existing_adapter = manager.get_or_create_adapter();
let adapter = match may_existing_adapter.as_ref() {
@ -437,14 +460,7 @@ pub fn test(manager: &mut BluetoothManager, data_set_name: String) -> Result<(),
},
GLUCOSE_HEART_RATE_ADAPTER => {
try!(set_adapter(adapter, GLUCOSE_HEART_RATE_ADAPTER.to_owned()));
let _glucose_devie = try!(create_device_with_uuids(adapter,
GLUCOSE_DEVICE_NAME.to_owned(),
GLUCOSE_DEVICE_ADDRESS.to_owned(),
vec![GLUCOSE_SERVICE_UUID.to_owned(),
TX_POWER_SERVICE_UUID.to_owned()]));
let _heart_rate_device_empty = try!(create_heart_rate_device(adapter, true));
let _ = try!(create_glucose_heart_rate_devices(adapter));
},
UNICODE_DEVICE_ADAPTER => {
try!(set_adapter(adapter, UNICODE_DEVICE_ADAPTER.to_owned()));

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::slice::Iter;
// A device name can never be longer than 29 bytes. An adv packet is at most
@ -23,28 +23,31 @@ impl ServiceUUIDSequence {
}
}
type ManufacturerData = HashMap<u16, (Vec<u8>, Vec<u8>)>;
type ServiceData = HashMap<String, (Vec<u8>, Vec<u8>)>;
#[derive(Deserialize, Serialize)]
pub struct BluetoothScanfilter {
name: Option<String>,
name_prefix: String,
services: ServiceUUIDSequence,
manufacturer_id: Option<u16>,
service_data_uuid: String,
manufacturer_data: Option<ManufacturerData>,
service_data: Option<ServiceData>,
}
impl BluetoothScanfilter {
pub fn new(name: Option<String>,
name_prefix: String,
services: Vec<String>,
manufacturer_id: Option<u16>,
service_data_uuid: String)
manufacturer_data: Option<ManufacturerData>,
service_data: Option<ServiceData>)
-> BluetoothScanfilter {
BluetoothScanfilter {
name: name,
name_prefix: name_prefix,
services: ServiceUUIDSequence::new(services),
manufacturer_id: manufacturer_id,
service_data_uuid: service_data_uuid,
manufacturer_data: manufacturer_data,
service_data: service_data,
}
}
@ -60,20 +63,20 @@ impl BluetoothScanfilter {
&self.services.0
}
pub fn get_manufacturer_id(&self) -> Option<u16> {
self.manufacturer_id
pub fn get_manufacturer_data(&self) -> Option<&ManufacturerData> {
self.manufacturer_data.as_ref()
}
pub fn get_service_data_uuid(&self) -> &str {
&self.service_data_uuid
pub fn get_service_data(&self) -> Option<&ServiceData> {
self.service_data.as_ref()
}
pub fn is_empty_or_invalid(&self) -> bool {
(self.name.is_none() &&
self.name_prefix.is_empty() &&
self.get_services().is_empty() &&
self.manufacturer_id.is_none() &&
self.service_data_uuid.is_empty()) ||
self.manufacturer_data.is_none() &&
self.service_data.is_none()) ||
self.get_name().unwrap_or("").len() > MAX_NAME_LENGTH ||
self.name_prefix.len() > MAX_NAME_LENGTH
}

View file

@ -9,8 +9,9 @@ use bluetooth_traits::scanfilter::{BluetoothScanfilter, BluetoothScanfilterSeque
use bluetooth_traits::scanfilter::{RequestDeviceoptions, ServiceUUIDSequence};
use core::clone::Clone;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::BluetoothBinding::{self, BluetoothMethods, BluetoothRequestDeviceFilter};
use dom::bindings::codegen::Bindings::BluetoothBinding::RequestDeviceOptions;
use dom::bindings::codegen::Bindings::BluetoothBinding::{self, BluetoothDataFilterInit, BluetoothLEScanFilterInit};
use dom::bindings::codegen::Bindings::BluetoothBinding::{BluetoothMethods, RequestDeviceOptions};
use dom::bindings::codegen::UnionTypes::StringOrUnsignedLong;
use dom::bindings::error::Error::{self, NotFound, Security, Type};
use dom::bindings::error::Fallible;
use dom::bindings::js::{JS, MutHeap, Root};
@ -31,10 +32,14 @@ use js::jsapi::{JSAutoCompartment, JSContext};
use network_listener::{NetworkListener, PreInvoke};
use std::collections::HashMap;
use std::rc::Rc;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
const KEY_CONVERSION_ERROR: &'static str = "This `manufacturerData` key can not be parsed as unsigned short:";
const FILTER_EMPTY_ERROR: &'static str = "'filters' member, if present, must be nonempty to find any devices.";
const FILTER_ERROR: &'static str = "A filter must restrict the devices in some way.";
const MANUFACTURER_DATA_ERROR: &'static str = "'manufacturerData', if present, must be non-empty to filter devices.";
const MASK_LENGTH_ERROR: &'static str = "`mask`, if present, must have the same length as `dataPrefix`.";
// 248 is the maximum number of UTF-8 code units in a Bluetooth Device Name.
const MAX_DEVICE_NAME_LENGTH: usize = 248;
// A device name can never be longer than 29 bytes.
@ -44,6 +49,7 @@ const MAX_DEVICE_NAME_LENGTH: usize = 248;
const MAX_FILTER_NAME_LENGTH: usize = 29;
const NAME_PREFIX_ERROR: &'static str = "'namePrefix', if present, must be nonempty.";
const NAME_TOO_LONG_ERROR: &'static str = "A device name can't be longer than 248 bytes.";
const SERVICE_DATA_ERROR: &'static str = "'serviceData', if present, must be non-empty to filter devices.";
const SERVICE_ERROR: &'static str = "'services', if present, must contain at least one service.";
const OPTIONS_ERROR: &'static str = "Fields of 'options' conflict with each other.
Either 'acceptAllDevices' member must be true, or 'filters' member must be set to a value.";
@ -122,7 +128,7 @@ impl Bluetooth {
// https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices
fn request_bluetooth_devices(&self,
p: &Rc<Promise>,
filters: &Option<Vec<BluetoothRequestDeviceFilter>>,
filters: &Option<Vec<BluetoothLEScanFilterInit>>,
optional_services: &Option<Vec<BluetoothServiceUUID>>) {
// TODO: Step 1: Triggered by user activation.
@ -165,7 +171,7 @@ pub fn response_async<T: AsyncBluetoothListener + Reflectable + 'static>(
}
// https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices
fn convert_request_device_options(filters: &Option<Vec<BluetoothRequestDeviceFilter>>,
fn convert_request_device_options(filters: &Option<Vec<BluetoothLEScanFilterInit>>,
optional_services: &Option<Vec<BluetoothServiceUUID>>)
-> Fallible<RequestDeviceoptions> {
// Step 2.2: There is no requiredServiceUUIDS, we scan for all devices.
@ -206,13 +212,13 @@ fn convert_request_device_options(filters: &Option<Vec<BluetoothRequestDeviceFil
}
// https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices
fn canonicalize_filter(filter: &BluetoothRequestDeviceFilter) -> Fallible<BluetoothScanfilter> {
fn canonicalize_filter(filter: &BluetoothLEScanFilterInit) -> Fallible<BluetoothScanfilter> {
// Step 2.4.1.
if filter.services.is_none() &&
filter.name.is_none() &&
filter.namePrefix.is_none() &&
filter.manufacturerId.is_none() &&
filter.serviceDataUUID.is_none() {
filter.manufacturerData.is_none() &&
filter.serviceData.is_none() {
return Err(Type(FILTER_ERROR.to_owned()));
}
@ -285,31 +291,66 @@ fn canonicalize_filter(filter: &BluetoothRequestDeviceFilter) -> Fallible<Blueto
None => String::new(),
};
// Step 2.4.6.
let manufacturer_id = filter.manufacturerId;
// Step 2.4.7.
let service_data_uuid = match filter.serviceDataUUID {
Some(ref service_data_uuid) => {
// Step 2.4.7.1 - 2.4.7.2.
let uuid = try!(BluetoothUUID::service(service_data_uuid.clone())).to_string();
// Step 2.4.7.3.
if uuid_is_blocklisted(uuid.as_ref(), Blocklist::All) {
return Err(Security)
// Step 2.4.6 - 2.4.7
let manufacturer_data = match filter.manufacturerData {
Some(ref manufacturer_data_map) => {
if manufacturer_data_map.is_empty() {
return Err(Type(MANUFACTURER_DATA_ERROR.to_owned()));
}
// Step 2.4.7.4.
uuid
let mut map = HashMap::new();
for (key, bdfi) in manufacturer_data_map.iter() {
let manufacturer_id = match u16::from_str(key.as_ref()) {
Ok(id) => id,
Err(err) => return Err(Type(format!("{} {} {}", KEY_CONVERSION_ERROR, key, err))),
};
map.insert(manufacturer_id, try!(canonicalize_bluetooth_data_filter_init(bdfi)));
}
Some(map)
},
None => String::new(),
None => None,
};
Ok(BluetoothScanfilter::new(name,
name_prefix,
services_vec,
manufacturer_id,
service_data_uuid))
// Step 2.4.8 -2.4.9
let service_data = match filter.serviceData {
Some(ref service_data_map) => {
if service_data_map.is_empty() {
return Err(Type(SERVICE_DATA_ERROR.to_owned()));
}
let mut map = HashMap::new();
for (key, bdfi) in service_data_map.iter() {
let service_name = match u32::from_str(key.as_ref()) {
Ok(number) => StringOrUnsignedLong::UnsignedLong(number),
_ => StringOrUnsignedLong::String(key.clone())
};
let service = try!(BluetoothUUID::service(service_name)).to_string();
if uuid_is_blocklisted(service.as_ref(), Blocklist::All) {
return Err(Security);
}
map.insert(service, try!(canonicalize_bluetooth_data_filter_init(bdfi)));
}
Some(map)
},
None => None,
};
// Step 10.
Ok(BluetoothScanfilter::new(name, name_prefix, services_vec, manufacturer_data, service_data))
}
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothdatafilterinit-canonicalizing
fn canonicalize_bluetooth_data_filter_init(bdfi: &BluetoothDataFilterInit) -> Fallible<(Vec<u8>, Vec<u8>)> {
// Step 1.
let data_prefix = bdfi.dataPrefix.clone().unwrap_or(vec![]);
// Step 2.
// If no mask present, mask will be a sequence of 0xFF bytes the same length as dataPrefix.
// Masking dataPrefix with this, leaves dataPrefix untouched.
let mask = bdfi.mask.clone().unwrap_or(vec![0xFF; data_prefix.len()]);
// Step 3.
if mask.len() != data_prefix.len() {
return Err(Type(MASK_LENGTH_ERROR.to_owned()));
}
// Step 4.
Ok((data_prefix, mask))
}
impl From<BluetoothError> for Error {

View file

@ -4,16 +4,25 @@
// https://webbluetoothcg.github.io/web-bluetooth/#bluetooth
dictionary BluetoothRequestDeviceFilter {
dictionary BluetoothDataFilterInit {
// BufferSource dataPrefix;
sequence<octet> dataPrefix;
// BufferSource mask;
sequence<octet> mask;
};
dictionary BluetoothLEScanFilterInit {
sequence<BluetoothServiceUUID> services;
DOMString name;
DOMString namePrefix;
unsigned short manufacturerId;
BluetoothServiceUUID serviceDataUUID;
// Maps unsigned shorts to BluetoothDataFilters.
MozMap<BluetoothDataFilterInit> manufacturerData;
// Maps BluetoothServiceUUIDs to BluetoothDataFilters.
MozMap<BluetoothDataFilterInit> serviceData;
};
dictionary RequestDeviceOptions {
sequence<BluetoothRequestDeviceFilter> filters;
sequence<BluetoothLEScanFilterInit> filters;
sequence<BluetoothServiceUUID> optionalServices /*= []*/;
boolean acceptAllDevices = false;
};
@ -23,6 +32,12 @@ interface Bluetooth {
// [SecureContext]
// readonly attribute BluetoothDevice? referringDevice;
// [SecureContext]
// Promise<boolean> getAvailability();
// [SecureContext]
// attribute EventHandler onavailabilitychanged;
// [SecureContext]
// readonly attribute BluetoothDevice? referringDevice;
[SecureContext]
Promise<BluetoothDevice> requestDevice(optional RequestDeviceOptions options);
};