mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Make WebBluetooth an optional feature. (#35479)
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
parent
32f19c1eae
commit
1d606bb85c
42 changed files with 124 additions and 37 deletions
788
components/script/dom/bluetooth/bluetooth.rs
Normal file
788
components/script/dom/bluetooth/bluetooth.rs
Normal file
|
@ -0,0 +1,788 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use bluetooth_traits::{BluetoothError, BluetoothRequest, GATTType};
|
||||
use bluetooth_traits::{BluetoothResponse, BluetoothResponseResult};
|
||||
use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted};
|
||||
use bluetooth_traits::scanfilter::{BluetoothScanfilter, BluetoothScanfilterSequence};
|
||||
use bluetooth_traits::scanfilter::{RequestDeviceoptions, ServiceUUIDSequence};
|
||||
use crate::realms::{AlreadyInRealm, InRealm};
|
||||
use crate::conversions::Convert;
|
||||
use crate::dom::bindings::cell::{DomRefCell, Ref};
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothBinding::BluetoothDataFilterInit;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothBinding::{BluetoothMethods, RequestDeviceOptions};
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothBinding::BluetoothLEScanFilterInit;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothPermissionResultBinding::BluetoothPermissionDescriptor;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServer_Binding::
|
||||
BluetoothRemoteGATTServerMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{PermissionName, PermissionState};
|
||||
use crate::dom::bindings::codegen::UnionTypes::{ArrayBufferViewOrArrayBuffer, StringOrUnsignedLong};
|
||||
use crate::dom::bindings::error::Error::{self, Network, Security, Type};
|
||||
use crate::dom::bindings::error::Fallible;
|
||||
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
|
||||
use crate::dom::bindings::reflector::{DomGlobal, DomObject, reflect_dom_object};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::bluetoothdevice::BluetoothDevice;
|
||||
use crate::dom::bluetoothpermissionresult::BluetoothPermissionResult;
|
||||
use crate::dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID, UUID};
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::permissions::{descriptor_permission_state, PermissionAlgorithm};
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::script_runtime::{CanGc, JSContext};
|
||||
use crate::task::TaskOnce;
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::ipc::{self, IpcSender};
|
||||
use ipc_channel::router::ROUTER;
|
||||
use js::conversions::ConversionResult;
|
||||
use js::jsapi::JSObject;
|
||||
use js::jsval::{ObjectValue, UndefinedValue};
|
||||
use profile_traits::ipc as ProfiledIpc;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
const KEY_CONVERSION_ERROR: &str =
|
||||
"This `manufacturerData` key can not be parsed as unsigned short:";
|
||||
const FILTER_EMPTY_ERROR: &str =
|
||||
"'filters' member, if present, must be nonempty to find any devices.";
|
||||
const FILTER_ERROR: &str = "A filter must restrict the devices in some way.";
|
||||
const MANUFACTURER_DATA_ERROR: &str =
|
||||
"'manufacturerData', if present, must be non-empty to filter devices.";
|
||||
const MASK_LENGTH_ERROR: &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;
|
||||
const NAME_PREFIX_ERROR: &str = "'namePrefix', if present, must be nonempty.";
|
||||
const NAME_TOO_LONG_ERROR: &str = "A device name can't be longer than 248 bytes.";
|
||||
const SERVICE_DATA_ERROR: &str = "'serviceData', if present, must be non-empty to filter devices.";
|
||||
const SERVICE_ERROR: &str = "'services', if present, must contain at least one service.";
|
||||
const OPTIONS_ERROR: &str = "Fields of 'options' conflict with each other.
|
||||
Either 'acceptAllDevices' member must be true, or 'filters' member must be set to a value.";
|
||||
const BT_DESC_CONVERSION_ERROR: &str =
|
||||
"Can't convert to an IDL value of type BluetoothPermissionDescriptor";
|
||||
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) struct AllowedBluetoothDevice {
|
||||
pub(crate) deviceId: DOMString,
|
||||
pub(crate) mayUseGATT: bool,
|
||||
}
|
||||
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
pub(crate) struct BluetoothExtraPermissionData {
|
||||
allowed_devices: DomRefCell<Vec<AllowedBluetoothDevice>>,
|
||||
}
|
||||
|
||||
impl BluetoothExtraPermissionData {
|
||||
pub(crate) fn new() -> BluetoothExtraPermissionData {
|
||||
BluetoothExtraPermissionData {
|
||||
allowed_devices: DomRefCell::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_new_allowed_device(&self, allowed_device: AllowedBluetoothDevice) {
|
||||
self.allowed_devices.borrow_mut().push(allowed_device);
|
||||
}
|
||||
|
||||
fn get_allowed_devices(&self) -> Ref<Vec<AllowedBluetoothDevice>> {
|
||||
self.allowed_devices.borrow()
|
||||
}
|
||||
|
||||
pub(crate) fn allowed_devices_contains_id(&self, id: DOMString) -> bool {
|
||||
self.allowed_devices
|
||||
.borrow()
|
||||
.iter()
|
||||
.any(|d| d.deviceId == id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BluetoothExtraPermissionData {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
struct BluetoothContext<T: AsyncBluetoothListener + DomObject> {
|
||||
promise: Option<TrustedPromise>,
|
||||
receiver: Trusted<T>,
|
||||
}
|
||||
|
||||
pub(crate) trait AsyncBluetoothListener {
|
||||
fn handle_response(&self, result: BluetoothResponse, promise: &Rc<Promise>, can_gc: CanGc);
|
||||
}
|
||||
|
||||
impl<T> BluetoothContext<T>
|
||||
where
|
||||
T: AsyncBluetoothListener + DomObject,
|
||||
{
|
||||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||||
fn response(&mut self, response: BluetoothResponseResult, can_gc: CanGc) {
|
||||
let promise = self.promise.take().expect("bt promise is missing").root();
|
||||
|
||||
// JSAutoRealm needs to be manually made.
|
||||
// Otherwise, Servo will crash.
|
||||
match response {
|
||||
Ok(response) => self
|
||||
.receiver
|
||||
.root()
|
||||
.handle_response(response, &promise, can_gc),
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice
|
||||
// Step 3 - 4.
|
||||
Err(error) => promise.reject_error(error.convert()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#bluetooth
|
||||
#[dom_struct]
|
||||
pub(crate) struct Bluetooth {
|
||||
eventtarget: EventTarget,
|
||||
device_instance_map: DomRefCell<HashMap<String, Dom<BluetoothDevice>>>,
|
||||
}
|
||||
|
||||
impl Bluetooth {
|
||||
pub(crate) fn new_inherited() -> Bluetooth {
|
||||
Bluetooth {
|
||||
eventtarget: EventTarget::new_inherited(),
|
||||
device_instance_map: DomRefCell::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(global: &GlobalScope) -> DomRoot<Bluetooth> {
|
||||
reflect_dom_object(Box::new(Bluetooth::new_inherited()), global, CanGc::note())
|
||||
}
|
||||
|
||||
fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
|
||||
self.global().as_window().bluetooth_thread()
|
||||
}
|
||||
|
||||
pub(crate) fn get_device_map(&self) -> &DomRefCell<HashMap<String, Dom<BluetoothDevice>>> {
|
||||
&self.device_instance_map
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices
|
||||
fn request_bluetooth_devices(
|
||||
&self,
|
||||
p: &Rc<Promise>,
|
||||
filters: &Option<Vec<BluetoothLEScanFilterInit>>,
|
||||
optional_services: &[BluetoothServiceUUID],
|
||||
sender: IpcSender<BluetoothResponseResult>,
|
||||
) {
|
||||
// TODO: Step 1: Triggered by user activation.
|
||||
|
||||
// Step 2.2: There are no requiredServiceUUIDS, we scan for all devices.
|
||||
let mut uuid_filters = vec![];
|
||||
|
||||
if let Some(filters) = filters {
|
||||
// Step 2.1.
|
||||
if filters.is_empty() {
|
||||
p.reject_error(Type(FILTER_EMPTY_ERROR.to_owned()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2.3: There are no requiredServiceUUIDS, we scan for all devices.
|
||||
|
||||
// Step 2.4.
|
||||
for filter in filters {
|
||||
// Step 2.4.1.
|
||||
match canonicalize_filter(filter) {
|
||||
// Step 2.4.2.
|
||||
Ok(f) => uuid_filters.push(f),
|
||||
Err(e) => {
|
||||
p.reject_error(e);
|
||||
return;
|
||||
},
|
||||
}
|
||||
// Step 2.4.3: There are no requiredServiceUUIDS, we scan for all devices.
|
||||
}
|
||||
}
|
||||
|
||||
let mut optional_services_uuids = vec![];
|
||||
for opt_service in optional_services {
|
||||
// Step 2.5 - 2.6.
|
||||
let uuid = match BluetoothUUID::service(opt_service.clone()) {
|
||||
Ok(u) => u.to_string(),
|
||||
Err(e) => {
|
||||
p.reject_error(e);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
// Step 2.7.
|
||||
// Note: What we are doing here, is adding the not blocklisted UUIDs to the result vector,
|
||||
// instead of removing them from an already filled vector.
|
||||
if !uuid_is_blocklisted(uuid.as_ref(), Blocklist::All) {
|
||||
optional_services_uuids.push(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
let option = RequestDeviceoptions::new(
|
||||
self.global().as_window().webview_id(),
|
||||
BluetoothScanfilterSequence::new(uuid_filters),
|
||||
ServiceUUIDSequence::new(optional_services_uuids),
|
||||
);
|
||||
|
||||
// Step 4 - 5.
|
||||
if let PermissionState::Denied =
|
||||
descriptor_permission_state(PermissionName::Bluetooth, None)
|
||||
{
|
||||
return p.reject_error(Error::NotFound);
|
||||
}
|
||||
|
||||
// Note: Step 3, 6 - 8 are implemented in
|
||||
// components/net/bluetooth_thread.rs in request_device function.
|
||||
self.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::RequestDevice(option, sender))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn response_async<T: AsyncBluetoothListener + DomObject + 'static>(
|
||||
promise: &Rc<Promise>,
|
||||
receiver: &T,
|
||||
) -> IpcSender<BluetoothResponseResult> {
|
||||
let (action_sender, action_receiver) = ipc::channel().unwrap();
|
||||
let task_source = receiver
|
||||
.global()
|
||||
.task_manager()
|
||||
.networking_task_source()
|
||||
.to_sendable();
|
||||
let context = Arc::new(Mutex::new(BluetoothContext {
|
||||
promise: Some(TrustedPromise::new(promise.clone())),
|
||||
receiver: Trusted::new(receiver),
|
||||
}));
|
||||
ROUTER.add_typed_route(
|
||||
action_receiver,
|
||||
Box::new(move |message| {
|
||||
struct ListenerTask<T: AsyncBluetoothListener + DomObject> {
|
||||
context: Arc<Mutex<BluetoothContext<T>>>,
|
||||
action: BluetoothResponseResult,
|
||||
}
|
||||
|
||||
impl<T> TaskOnce for ListenerTask<T>
|
||||
where
|
||||
T: AsyncBluetoothListener + DomObject,
|
||||
{
|
||||
fn run_once(self) {
|
||||
let mut context = self.context.lock().unwrap();
|
||||
context.response(self.action, CanGc::note());
|
||||
}
|
||||
}
|
||||
|
||||
let task = ListenerTask {
|
||||
context: context.clone(),
|
||||
action: message.unwrap(),
|
||||
};
|
||||
|
||||
task_source.queue_unconditionally(task);
|
||||
}),
|
||||
);
|
||||
action_sender
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn get_gatt_children<T, F>(
|
||||
attribute: &T,
|
||||
single: bool,
|
||||
uuid_canonicalizer: F,
|
||||
uuid: Option<StringOrUnsignedLong>,
|
||||
instance_id: String,
|
||||
connected: bool,
|
||||
child_type: GATTType,
|
||||
can_gc: CanGc,
|
||||
) -> Rc<Promise>
|
||||
where
|
||||
T: AsyncBluetoothListener + DomObject + 'static,
|
||||
F: FnOnce(StringOrUnsignedLong) -> Fallible<UUID>,
|
||||
{
|
||||
let in_realm_proof = AlreadyInRealm::assert();
|
||||
let p = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
|
||||
|
||||
let result_uuid = if let Some(u) = uuid {
|
||||
// Step 1.
|
||||
let canonicalized = match uuid_canonicalizer(u) {
|
||||
Ok(canonicalized_uuid) => canonicalized_uuid.to_string(),
|
||||
Err(e) => {
|
||||
p.reject_error(e);
|
||||
return p;
|
||||
},
|
||||
};
|
||||
// Step 2.
|
||||
if uuid_is_blocklisted(canonicalized.as_ref(), Blocklist::All) {
|
||||
p.reject_error(Security);
|
||||
return p;
|
||||
}
|
||||
Some(canonicalized)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Step 3 - 4.
|
||||
if !connected {
|
||||
p.reject_error(Network);
|
||||
return p;
|
||||
}
|
||||
|
||||
// TODO: Step 5: Implement representedDevice internal slot for BluetoothDevice.
|
||||
|
||||
// Note: Steps 6 - 7 are implemented in components/bluetooth/lib.rs in get_descriptor function
|
||||
// and in handle_response function.
|
||||
let sender = response_async(&p, attribute);
|
||||
attribute
|
||||
.global()
|
||||
.as_window()
|
||||
.bluetooth_thread()
|
||||
.send(BluetoothRequest::GetGATTChildren(
|
||||
instance_id,
|
||||
result_uuid,
|
||||
single,
|
||||
child_type,
|
||||
sender,
|
||||
))
|
||||
.unwrap();
|
||||
p
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothlescanfilterinit-canonicalizing
|
||||
fn canonicalize_filter(filter: &BluetoothLEScanFilterInit) -> Fallible<BluetoothScanfilter> {
|
||||
// Step 1.
|
||||
if filter.services.is_none() &&
|
||||
filter.name.is_none() &&
|
||||
filter.namePrefix.is_none() &&
|
||||
filter.manufacturerData.is_none() &&
|
||||
filter.serviceData.is_none()
|
||||
{
|
||||
return Err(Type(FILTER_ERROR.to_owned()));
|
||||
}
|
||||
|
||||
// Step 2: There is no empty canonicalizedFilter member,
|
||||
// we create a BluetoothScanfilter instance at the end of the function.
|
||||
|
||||
// Step 3.
|
||||
let services_vec = match filter.services {
|
||||
Some(ref services) => {
|
||||
// Step 3.1.
|
||||
if services.is_empty() {
|
||||
return Err(Type(SERVICE_ERROR.to_owned()));
|
||||
}
|
||||
|
||||
let mut services_vec = vec![];
|
||||
|
||||
for service in services {
|
||||
// Step 3.2 - 3.3.
|
||||
let uuid = BluetoothUUID::service(service.clone())?.to_string();
|
||||
|
||||
// Step 3.4.
|
||||
if uuid_is_blocklisted(uuid.as_ref(), Blocklist::All) {
|
||||
return Err(Security);
|
||||
}
|
||||
|
||||
services_vec.push(uuid);
|
||||
}
|
||||
// Step 3.5.
|
||||
services_vec
|
||||
},
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
// Step 4.
|
||||
let name = match filter.name {
|
||||
Some(ref name) => {
|
||||
// Step 4.1.
|
||||
// Note: DOMString::len() gives back the size in bytes.
|
||||
if name.len() > MAX_DEVICE_NAME_LENGTH {
|
||||
return Err(Type(NAME_TOO_LONG_ERROR.to_owned()));
|
||||
}
|
||||
|
||||
// Step 4.2.
|
||||
Some(name.to_string())
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
// Step 5.
|
||||
let name_prefix = match filter.namePrefix {
|
||||
Some(ref name_prefix) => {
|
||||
// Step 5.1.
|
||||
if name_prefix.is_empty() {
|
||||
return Err(Type(NAME_PREFIX_ERROR.to_owned()));
|
||||
}
|
||||
if name_prefix.len() > MAX_DEVICE_NAME_LENGTH {
|
||||
return Err(Type(NAME_TOO_LONG_ERROR.to_owned()));
|
||||
}
|
||||
|
||||
// Step 5.2.
|
||||
name_prefix.to_string()
|
||||
},
|
||||
None => String::new(),
|
||||
};
|
||||
|
||||
// Step 6 - 7.
|
||||
let manufacturer_data = match filter.manufacturerData {
|
||||
Some(ref manufacturer_data_map) => {
|
||||
// Note: If manufacturer_data_map is empty, that means there are no key values in it.
|
||||
if manufacturer_data_map.is_empty() {
|
||||
return Err(Type(MANUFACTURER_DATA_ERROR.to_owned()));
|
||||
}
|
||||
let mut map = HashMap::new();
|
||||
for (key, bdfi) in manufacturer_data_map.iter() {
|
||||
// Step 7.1 - 7.2.
|
||||
let manufacturer_id = match u16::from_str(key.as_ref()) {
|
||||
Ok(id) => id,
|
||||
Err(err) => {
|
||||
return Err(Type(format!("{} {} {}", KEY_CONVERSION_ERROR, key, err)));
|
||||
},
|
||||
};
|
||||
|
||||
// Step 7.3: No need to convert to IDL values since this is only used by native code.
|
||||
|
||||
// Step 7.4 - 7.5.
|
||||
map.insert(
|
||||
manufacturer_id,
|
||||
canonicalize_bluetooth_data_filter_init(bdfi)?,
|
||||
);
|
||||
}
|
||||
Some(map)
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
// Step 8 - 9.
|
||||
let service_data = match filter.serviceData {
|
||||
Some(ref service_data_map) => {
|
||||
// Note: If service_data_map is empty, that means there are no key values in it.
|
||||
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()) {
|
||||
// Step 9.1.
|
||||
Ok(number) => StringOrUnsignedLong::UnsignedLong(number),
|
||||
// Step 9.2.
|
||||
_ => StringOrUnsignedLong::String(key.clone()),
|
||||
};
|
||||
|
||||
// Step 9.3 - 9.4.
|
||||
let service = BluetoothUUID::service(service_name)?.to_string();
|
||||
|
||||
// Step 9.5.
|
||||
if uuid_is_blocklisted(service.as_ref(), Blocklist::All) {
|
||||
return Err(Security);
|
||||
}
|
||||
|
||||
// Step 9.6: No need to convert to IDL values since this is only used by native code.
|
||||
|
||||
// Step 9.7 - 9.8.
|
||||
map.insert(service, 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 = match bdfi.dataPrefix {
|
||||
Some(ArrayBufferViewOrArrayBuffer::ArrayBufferView(ref avb)) => avb.to_vec(),
|
||||
Some(ArrayBufferViewOrArrayBuffer::ArrayBuffer(ref ab)) => ab.to_vec(),
|
||||
None => 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 = match bdfi.mask {
|
||||
Some(ArrayBufferViewOrArrayBuffer::ArrayBufferView(ref avb)) => avb.to_vec(),
|
||||
Some(ArrayBufferViewOrArrayBuffer::ArrayBuffer(ref ab)) => ab.to_vec(),
|
||||
None => 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 Convert<Error> for BluetoothError {
|
||||
fn convert(self) -> Error {
|
||||
match self {
|
||||
BluetoothError::Type(message) => Error::Type(message),
|
||||
BluetoothError::Network => Error::Network,
|
||||
BluetoothError::NotFound => Error::NotFound,
|
||||
BluetoothError::NotSupported => Error::NotSupported,
|
||||
BluetoothError::Security => Error::Security,
|
||||
BluetoothError::InvalidState => Error::InvalidState,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BluetoothMethods<crate::DomTypeHolder> for Bluetooth {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice
|
||||
fn RequestDevice(
|
||||
&self,
|
||||
option: &RequestDeviceOptions,
|
||||
comp: InRealm,
|
||||
can_gc: CanGc,
|
||||
) -> Rc<Promise> {
|
||||
let p = Promise::new_in_current_realm(comp, can_gc);
|
||||
// Step 1.
|
||||
if (option.filters.is_some() && option.acceptAllDevices) ||
|
||||
(option.filters.is_none() && !option.acceptAllDevices)
|
||||
{
|
||||
p.reject_error(Error::Type(OPTIONS_ERROR.to_owned()));
|
||||
return p;
|
||||
}
|
||||
|
||||
// Step 2.
|
||||
let sender = response_async(&p, self);
|
||||
self.request_bluetooth_devices(&p, &option.filters, &option.optionalServices, sender);
|
||||
//Note: Step 3 - 4. in response function, Step 5. in handle_response function.
|
||||
p
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-getavailability
|
||||
fn GetAvailability(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
|
||||
let p = Promise::new_in_current_realm(comp, can_gc);
|
||||
// Step 1. We did not override the method
|
||||
// Step 2 - 3. in handle_response
|
||||
let sender = response_async(&p, self);
|
||||
self.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::GetAvailability(sender))
|
||||
.unwrap();
|
||||
p
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-onavailabilitychanged
|
||||
event_handler!(
|
||||
availabilitychanged,
|
||||
GetOnavailabilitychanged,
|
||||
SetOnavailabilitychanged
|
||||
);
|
||||
}
|
||||
|
||||
impl AsyncBluetoothListener for Bluetooth {
|
||||
fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, _can_gc: CanGc) {
|
||||
match response {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices
|
||||
// Step 11, 13 - 14.
|
||||
BluetoothResponse::RequestDevice(device) => {
|
||||
let mut device_instance_map = self.device_instance_map.borrow_mut();
|
||||
if let Some(existing_device) = device_instance_map.get(&device.id.clone()) {
|
||||
return promise.resolve_native(&**existing_device);
|
||||
}
|
||||
let bt_device = BluetoothDevice::new(
|
||||
&self.global(),
|
||||
DOMString::from(device.id.clone()),
|
||||
device.name.map(DOMString::from),
|
||||
self,
|
||||
);
|
||||
device_instance_map.insert(device.id.clone(), Dom::from_ref(&bt_device));
|
||||
|
||||
self.global()
|
||||
.as_window()
|
||||
.bluetooth_extra_permission_data()
|
||||
.add_new_allowed_device(AllowedBluetoothDevice {
|
||||
deviceId: DOMString::from(device.id),
|
||||
mayUseGATT: true,
|
||||
});
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice
|
||||
// Step 5.
|
||||
promise.resolve_native(&bt_device);
|
||||
},
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-getavailability
|
||||
// Step 2 - 3.
|
||||
BluetoothResponse::GetAvailability(is_available) => {
|
||||
promise.resolve_native(&is_available);
|
||||
},
|
||||
_ => promise.reject_error(Error::Type("Something went wrong...".to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PermissionAlgorithm for Bluetooth {
|
||||
type Descriptor = BluetoothPermissionDescriptor;
|
||||
type Status = BluetoothPermissionResult;
|
||||
|
||||
fn create_descriptor(
|
||||
cx: JSContext,
|
||||
permission_descriptor_obj: *mut JSObject,
|
||||
) -> Result<BluetoothPermissionDescriptor, Error> {
|
||||
rooted!(in(*cx) let mut property = UndefinedValue());
|
||||
property
|
||||
.handle_mut()
|
||||
.set(ObjectValue(permission_descriptor_obj));
|
||||
match BluetoothPermissionDescriptor::new(cx, property.handle()) {
|
||||
Ok(ConversionResult::Success(descriptor)) => Ok(descriptor),
|
||||
Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())),
|
||||
Err(_) => Err(Error::Type(String::from(BT_DESC_CONVERSION_ERROR))),
|
||||
}
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#query-the-bluetooth-permission
|
||||
fn permission_query(
|
||||
_cx: JSContext,
|
||||
promise: &Rc<Promise>,
|
||||
descriptor: &BluetoothPermissionDescriptor,
|
||||
status: &BluetoothPermissionResult,
|
||||
) {
|
||||
// Step 1: We are not using the `global` variable.
|
||||
|
||||
// Step 2.
|
||||
status.set_state(descriptor_permission_state(status.get_query(), None));
|
||||
|
||||
// Step 3.
|
||||
if let PermissionState::Denied = status.get_state() {
|
||||
status.set_devices(Vec::new());
|
||||
return promise.resolve_native(status);
|
||||
}
|
||||
|
||||
// Step 4.
|
||||
rooted_vec!(let mut matching_devices);
|
||||
|
||||
// Step 5.
|
||||
let global = status.global();
|
||||
let allowed_devices = global
|
||||
.as_window()
|
||||
.bluetooth_extra_permission_data()
|
||||
.get_allowed_devices();
|
||||
|
||||
let bluetooth = status.get_bluetooth();
|
||||
let device_map = bluetooth.get_device_map().borrow();
|
||||
|
||||
// Step 6.
|
||||
for allowed_device in allowed_devices.iter() {
|
||||
// Step 6.1.
|
||||
if let Some(ref id) = descriptor.deviceId {
|
||||
if &allowed_device.deviceId != id {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let device_id = String::from(allowed_device.deviceId.as_ref());
|
||||
|
||||
// Step 6.2.
|
||||
if let Some(ref filters) = descriptor.filters {
|
||||
let mut scan_filters: Vec<BluetoothScanfilter> = Vec::new();
|
||||
|
||||
// Step 6.2.1.
|
||||
for filter in filters {
|
||||
match canonicalize_filter(filter) {
|
||||
Ok(f) => scan_filters.push(f),
|
||||
Err(error) => return promise.reject_error(error),
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6.2.2.
|
||||
// Instead of creating an internal slot we send an ipc message to the Bluetooth thread
|
||||
// to check if one of the filters matches.
|
||||
let (sender, receiver) =
|
||||
ProfiledIpc::channel(global.time_profiler_chan().clone()).unwrap();
|
||||
status
|
||||
.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::MatchesFilter(
|
||||
device_id.clone(),
|
||||
BluetoothScanfilterSequence::new(scan_filters),
|
||||
sender,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
match receiver.recv().unwrap() {
|
||||
Ok(true) => (),
|
||||
Ok(false) => continue,
|
||||
Err(error) => return promise.reject_error(error.convert()),
|
||||
};
|
||||
}
|
||||
|
||||
// Step 6.3.
|
||||
// TODO: Implement this correctly, not just using device ids here.
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#get-the-bluetoothdevice-representing
|
||||
if let Some(device) = device_map.get(&device_id) {
|
||||
matching_devices.push(Dom::from_ref(&**device));
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7.
|
||||
status.set_devices(matching_devices.drain(..).collect());
|
||||
|
||||
// https://w3c.github.io/permissions/#dom-permissions-query
|
||||
// Step 7.
|
||||
promise.resolve_native(status);
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#request-the-bluetooth-permission
|
||||
fn permission_request(
|
||||
_cx: JSContext,
|
||||
promise: &Rc<Promise>,
|
||||
descriptor: &BluetoothPermissionDescriptor,
|
||||
status: &BluetoothPermissionResult,
|
||||
) {
|
||||
// Step 1.
|
||||
if descriptor.filters.is_some() == descriptor.acceptAllDevices {
|
||||
return promise.reject_error(Error::Type(OPTIONS_ERROR.to_owned()));
|
||||
}
|
||||
|
||||
// Step 2.
|
||||
let sender = response_async(promise, status);
|
||||
let bluetooth = status.get_bluetooth();
|
||||
bluetooth.request_bluetooth_devices(
|
||||
promise,
|
||||
&descriptor.filters,
|
||||
&descriptor.optionalServices,
|
||||
sender,
|
||||
);
|
||||
|
||||
// NOTE: Step 3. is in BluetoothPermissionResult's `handle_response` function.
|
||||
}
|
||||
|
||||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#revoke-bluetooth-access
|
||||
fn permission_revoke(
|
||||
_descriptor: &BluetoothPermissionDescriptor,
|
||||
status: &BluetoothPermissionResult,
|
||||
can_gc: CanGc,
|
||||
) {
|
||||
// Step 1.
|
||||
let global = status.global();
|
||||
let allowed_devices = global
|
||||
.as_window()
|
||||
.bluetooth_extra_permission_data()
|
||||
.get_allowed_devices();
|
||||
// Step 2.
|
||||
let bluetooth = status.get_bluetooth();
|
||||
let device_map = bluetooth.get_device_map().borrow();
|
||||
for (id, device) in device_map.iter() {
|
||||
let id = DOMString::from(id.clone());
|
||||
// Step 2.1.
|
||||
if allowed_devices.iter().any(|d| d.deviceId == id) &&
|
||||
!device.is_represented_device_null()
|
||||
{
|
||||
// Note: We don't need to update the allowed_services,
|
||||
// because we store it in the lower level
|
||||
// where it is already up-to-date
|
||||
continue;
|
||||
}
|
||||
// Step 2.2 - 2.4
|
||||
let _ = device.get_gatt().Disconnect(can_gc);
|
||||
}
|
||||
}
|
||||
}
|
144
components/script/dom/bluetooth/bluetoothadvertisingevent.rs
Normal file
144
components/script/dom/bluetooth/bluetoothadvertisingevent.rs
Normal file
|
@ -0,0 +1,144 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::HandleObject;
|
||||
use servo_atoms::Atom;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothAdvertisingEventBinding::{
|
||||
BluetoothAdvertisingEventInit, BluetoothAdvertisingEventMethods,
|
||||
};
|
||||
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
|
||||
use crate::dom::bindings::error::Fallible;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::bluetoothdevice::BluetoothDevice;
|
||||
use crate::dom::event::{Event, EventBubbles, EventCancelable};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::window::Window;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothadvertisingevent
|
||||
#[dom_struct]
|
||||
pub(crate) struct BluetoothAdvertisingEvent {
|
||||
event: Event,
|
||||
device: Dom<BluetoothDevice>,
|
||||
name: Option<DOMString>,
|
||||
appearance: Option<u16>,
|
||||
tx_power: Option<i8>,
|
||||
rssi: Option<i8>,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl BluetoothAdvertisingEvent {
|
||||
pub(crate) fn new_inherited(
|
||||
device: &BluetoothDevice,
|
||||
name: Option<DOMString>,
|
||||
appearance: Option<u16>,
|
||||
tx_power: Option<i8>,
|
||||
rssi: Option<i8>,
|
||||
) -> BluetoothAdvertisingEvent {
|
||||
BluetoothAdvertisingEvent {
|
||||
event: Event::new_inherited(),
|
||||
device: Dom::from_ref(device),
|
||||
name,
|
||||
appearance,
|
||||
tx_power,
|
||||
rssi,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
type_: Atom,
|
||||
bubbles: EventBubbles,
|
||||
cancelable: EventCancelable,
|
||||
device: &BluetoothDevice,
|
||||
name: Option<DOMString>,
|
||||
appearance: Option<u16>,
|
||||
txPower: Option<i8>,
|
||||
rssi: Option<i8>,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<BluetoothAdvertisingEvent> {
|
||||
let ev = reflect_dom_object_with_proto(
|
||||
Box::new(BluetoothAdvertisingEvent::new_inherited(
|
||||
device, name, appearance, txPower, rssi,
|
||||
)),
|
||||
global,
|
||||
proto,
|
||||
can_gc,
|
||||
);
|
||||
{
|
||||
let event = ev.upcast::<Event>();
|
||||
event.init_event(type_, bool::from(bubbles), bool::from(cancelable));
|
||||
}
|
||||
ev
|
||||
}
|
||||
}
|
||||
|
||||
impl BluetoothAdvertisingEventMethods<crate::DomTypeHolder> for BluetoothAdvertisingEvent {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-bluetoothadvertisingevent
|
||||
#[allow(non_snake_case)]
|
||||
fn Constructor(
|
||||
window: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
can_gc: CanGc,
|
||||
type_: DOMString,
|
||||
init: &BluetoothAdvertisingEventInit,
|
||||
) -> Fallible<DomRoot<BluetoothAdvertisingEvent>> {
|
||||
let name = init.name.clone();
|
||||
let appearance = init.appearance;
|
||||
let txPower = init.txPower;
|
||||
let rssi = init.rssi;
|
||||
let bubbles = EventBubbles::from(init.parent.bubbles);
|
||||
let cancelable = EventCancelable::from(init.parent.cancelable);
|
||||
Ok(BluetoothAdvertisingEvent::new(
|
||||
window.as_global_scope(),
|
||||
proto,
|
||||
Atom::from(type_),
|
||||
bubbles,
|
||||
cancelable,
|
||||
&init.device,
|
||||
name,
|
||||
appearance,
|
||||
txPower,
|
||||
rssi,
|
||||
can_gc,
|
||||
))
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-device
|
||||
fn Device(&self) -> DomRoot<BluetoothDevice> {
|
||||
DomRoot::from_ref(&*self.device)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-name
|
||||
fn GetName(&self) -> Option<DOMString> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-appearance
|
||||
fn GetAppearance(&self) -> Option<u16> {
|
||||
self.appearance
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-txpower
|
||||
fn GetTxPower(&self) -> Option<i8> {
|
||||
self.tx_power
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-rssi
|
||||
fn GetRssi(&self) -> Option<i8> {
|
||||
self.rssi
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-event-istrusted
|
||||
fn IsTrusted(&self) -> bool {
|
||||
self.event.IsTrusted()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothCharacteristicPropertiesBinding::BluetoothCharacteristicPropertiesMethods;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#characteristicproperties
|
||||
#[dom_struct]
|
||||
pub(crate) struct BluetoothCharacteristicProperties {
|
||||
reflector_: Reflector,
|
||||
broadcast: bool,
|
||||
read: bool,
|
||||
write_without_response: bool,
|
||||
write: bool,
|
||||
notify: bool,
|
||||
indicate: bool,
|
||||
authenticated_signed_writes: bool,
|
||||
reliable_write: bool,
|
||||
writable_auxiliaries: bool,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl BluetoothCharacteristicProperties {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new_inherited(
|
||||
broadcast: bool,
|
||||
read: bool,
|
||||
write_without_response: bool,
|
||||
write: bool,
|
||||
notify: bool,
|
||||
indicate: bool,
|
||||
authenticated_signed_writes: bool,
|
||||
reliable_write: bool,
|
||||
writable_auxiliaries: bool,
|
||||
) -> BluetoothCharacteristicProperties {
|
||||
BluetoothCharacteristicProperties {
|
||||
reflector_: Reflector::new(),
|
||||
broadcast,
|
||||
read,
|
||||
write_without_response,
|
||||
write,
|
||||
notify,
|
||||
indicate,
|
||||
authenticated_signed_writes,
|
||||
reliable_write,
|
||||
writable_auxiliaries,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
global: &GlobalScope,
|
||||
broadcast: bool,
|
||||
read: bool,
|
||||
writeWithoutResponse: bool,
|
||||
write: bool,
|
||||
notify: bool,
|
||||
indicate: bool,
|
||||
authenticatedSignedWrites: bool,
|
||||
reliableWrite: bool,
|
||||
writableAuxiliaries: bool,
|
||||
) -> DomRoot<BluetoothCharacteristicProperties> {
|
||||
reflect_dom_object(
|
||||
Box::new(BluetoothCharacteristicProperties::new_inherited(
|
||||
broadcast,
|
||||
read,
|
||||
writeWithoutResponse,
|
||||
write,
|
||||
notify,
|
||||
indicate,
|
||||
authenticatedSignedWrites,
|
||||
reliableWrite,
|
||||
writableAuxiliaries,
|
||||
)),
|
||||
global,
|
||||
CanGc::note(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl BluetoothCharacteristicPropertiesMethods<crate::DomTypeHolder>
|
||||
for BluetoothCharacteristicProperties
|
||||
{
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-broadcast
|
||||
fn Broadcast(&self) -> bool {
|
||||
self.broadcast
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-read
|
||||
fn Read(&self) -> bool {
|
||||
self.read
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-writewithoutresponse
|
||||
fn WriteWithoutResponse(&self) -> bool {
|
||||
self.write_without_response
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-write
|
||||
fn Write(&self) -> bool {
|
||||
self.write
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-notify
|
||||
fn Notify(&self) -> bool {
|
||||
self.notify
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-indicate
|
||||
fn Indicate(&self) -> bool {
|
||||
self.indicate
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-authenticatedsignedwrites
|
||||
fn AuthenticatedSignedWrites(&self) -> bool {
|
||||
self.authenticated_signed_writes
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-reliablewrite
|
||||
fn ReliableWrite(&self) -> bool {
|
||||
self.reliable_write
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-writableauxiliaries
|
||||
fn WritableAuxiliaries(&self) -> bool {
|
||||
self.writable_auxiliaries
|
||||
}
|
||||
}
|
334
components/script/dom/bluetooth/bluetoothdevice.rs
Normal file
334
components/script/dom/bluetooth/bluetoothdevice.rs
Normal file
|
@ -0,0 +1,334 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use bluetooth_traits::{
|
||||
BluetoothCharacteristicMsg, BluetoothDescriptorMsg, BluetoothRequest, BluetoothResponse,
|
||||
BluetoothServiceMsg,
|
||||
};
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use profile_traits::ipc;
|
||||
|
||||
use crate::conversions::Convert;
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
|
||||
use crate::dom::bindings::error::{Error, ErrorResult};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::bluetooth::{response_async, AsyncBluetoothListener, Bluetooth};
|
||||
use crate::dom::bluetoothcharacteristicproperties::BluetoothCharacteristicProperties;
|
||||
use crate::dom::bluetoothremotegattcharacteristic::BluetoothRemoteGATTCharacteristic;
|
||||
use crate::dom::bluetoothremotegattdescriptor::BluetoothRemoteGATTDescriptor;
|
||||
use crate::dom::bluetoothremotegattserver::BluetoothRemoteGATTServer;
|
||||
use crate::dom::bluetoothremotegattservice::BluetoothRemoteGATTService;
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::realms::InRealm;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
struct AttributeInstanceMap {
|
||||
service_map: DomRefCell<HashMap<String, Dom<BluetoothRemoteGATTService>>>,
|
||||
characteristic_map: DomRefCell<HashMap<String, Dom<BluetoothRemoteGATTCharacteristic>>>,
|
||||
descriptor_map: DomRefCell<HashMap<String, Dom<BluetoothRemoteGATTDescriptor>>>,
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothdevice
|
||||
#[dom_struct]
|
||||
pub(crate) struct BluetoothDevice {
|
||||
eventtarget: EventTarget,
|
||||
id: DOMString,
|
||||
name: Option<DOMString>,
|
||||
gatt: MutNullableDom<BluetoothRemoteGATTServer>,
|
||||
context: Dom<Bluetooth>,
|
||||
attribute_instance_map: AttributeInstanceMap,
|
||||
watching_advertisements: Cell<bool>,
|
||||
}
|
||||
|
||||
impl BluetoothDevice {
|
||||
pub(crate) fn new_inherited(
|
||||
id: DOMString,
|
||||
name: Option<DOMString>,
|
||||
context: &Bluetooth,
|
||||
) -> BluetoothDevice {
|
||||
BluetoothDevice {
|
||||
eventtarget: EventTarget::new_inherited(),
|
||||
id,
|
||||
name,
|
||||
gatt: Default::default(),
|
||||
context: Dom::from_ref(context),
|
||||
attribute_instance_map: AttributeInstanceMap {
|
||||
service_map: DomRefCell::new(HashMap::new()),
|
||||
characteristic_map: DomRefCell::new(HashMap::new()),
|
||||
descriptor_map: DomRefCell::new(HashMap::new()),
|
||||
},
|
||||
watching_advertisements: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
global: &GlobalScope,
|
||||
id: DOMString,
|
||||
name: Option<DOMString>,
|
||||
context: &Bluetooth,
|
||||
) -> DomRoot<BluetoothDevice> {
|
||||
reflect_dom_object(
|
||||
Box::new(BluetoothDevice::new_inherited(id, name, context)),
|
||||
global,
|
||||
CanGc::note(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn get_gatt(&self) -> DomRoot<BluetoothRemoteGATTServer> {
|
||||
self.gatt
|
||||
.or_init(|| BluetoothRemoteGATTServer::new(&self.global(), self))
|
||||
}
|
||||
|
||||
fn get_context(&self) -> DomRoot<Bluetooth> {
|
||||
DomRoot::from_ref(&self.context)
|
||||
}
|
||||
|
||||
pub(crate) fn get_or_create_service(
|
||||
&self,
|
||||
service: &BluetoothServiceMsg,
|
||||
server: &BluetoothRemoteGATTServer,
|
||||
) -> DomRoot<BluetoothRemoteGATTService> {
|
||||
let service_map_ref = &self.attribute_instance_map.service_map;
|
||||
let mut service_map = service_map_ref.borrow_mut();
|
||||
if let Some(existing_service) = service_map.get(&service.instance_id) {
|
||||
return DomRoot::from_ref(existing_service);
|
||||
}
|
||||
let bt_service = BluetoothRemoteGATTService::new(
|
||||
&server.global(),
|
||||
&server.Device(),
|
||||
DOMString::from(service.uuid.clone()),
|
||||
service.is_primary,
|
||||
service.instance_id.clone(),
|
||||
);
|
||||
service_map.insert(service.instance_id.clone(), Dom::from_ref(&bt_service));
|
||||
bt_service
|
||||
}
|
||||
|
||||
pub(crate) fn get_or_create_characteristic(
|
||||
&self,
|
||||
characteristic: &BluetoothCharacteristicMsg,
|
||||
service: &BluetoothRemoteGATTService,
|
||||
) -> DomRoot<BluetoothRemoteGATTCharacteristic> {
|
||||
let characteristic_map_ref = &self.attribute_instance_map.characteristic_map;
|
||||
let mut characteristic_map = characteristic_map_ref.borrow_mut();
|
||||
if let Some(existing_characteristic) = characteristic_map.get(&characteristic.instance_id) {
|
||||
return DomRoot::from_ref(existing_characteristic);
|
||||
}
|
||||
let properties = BluetoothCharacteristicProperties::new(
|
||||
&service.global(),
|
||||
characteristic.broadcast,
|
||||
characteristic.read,
|
||||
characteristic.write_without_response,
|
||||
characteristic.write,
|
||||
characteristic.notify,
|
||||
characteristic.indicate,
|
||||
characteristic.authenticated_signed_writes,
|
||||
characteristic.reliable_write,
|
||||
characteristic.writable_auxiliaries,
|
||||
);
|
||||
let bt_characteristic = BluetoothRemoteGATTCharacteristic::new(
|
||||
&service.global(),
|
||||
service,
|
||||
DOMString::from(characteristic.uuid.clone()),
|
||||
&properties,
|
||||
characteristic.instance_id.clone(),
|
||||
);
|
||||
characteristic_map.insert(
|
||||
characteristic.instance_id.clone(),
|
||||
Dom::from_ref(&bt_characteristic),
|
||||
);
|
||||
bt_characteristic
|
||||
}
|
||||
|
||||
pub(crate) fn is_represented_device_null(&self) -> bool {
|
||||
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
|
||||
self.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::IsRepresentedDeviceNull(
|
||||
self.Id().to_string(),
|
||||
sender,
|
||||
))
|
||||
.unwrap();
|
||||
receiver.recv().unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn get_or_create_descriptor(
|
||||
&self,
|
||||
descriptor: &BluetoothDescriptorMsg,
|
||||
characteristic: &BluetoothRemoteGATTCharacteristic,
|
||||
) -> DomRoot<BluetoothRemoteGATTDescriptor> {
|
||||
let descriptor_map_ref = &self.attribute_instance_map.descriptor_map;
|
||||
let mut descriptor_map = descriptor_map_ref.borrow_mut();
|
||||
if let Some(existing_descriptor) = descriptor_map.get(&descriptor.instance_id) {
|
||||
return DomRoot::from_ref(existing_descriptor);
|
||||
}
|
||||
let bt_descriptor = BluetoothRemoteGATTDescriptor::new(
|
||||
&characteristic.global(),
|
||||
characteristic,
|
||||
DOMString::from(descriptor.uuid.clone()),
|
||||
descriptor.instance_id.clone(),
|
||||
);
|
||||
descriptor_map.insert(
|
||||
descriptor.instance_id.clone(),
|
||||
Dom::from_ref(&bt_descriptor),
|
||||
);
|
||||
bt_descriptor
|
||||
}
|
||||
|
||||
fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
|
||||
self.global().as_window().bluetooth_thread()
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#clean-up-the-disconnected-device
|
||||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||||
pub(crate) fn clean_up_disconnected_device(&self, can_gc: CanGc) {
|
||||
// Step 1.
|
||||
self.get_gatt().set_connected(false);
|
||||
|
||||
// TODO: Step 2: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
|
||||
|
||||
// Step 3: We don't need `context`, we get the attributeInstanceMap from the device.
|
||||
// https://github.com/WebBluetoothCG/web-bluetooth/issues/330
|
||||
|
||||
// Step 4.
|
||||
let mut service_map = self.attribute_instance_map.service_map.borrow_mut();
|
||||
let service_ids = service_map.drain().map(|(id, _)| id).collect();
|
||||
|
||||
let mut characteristic_map = self.attribute_instance_map.characteristic_map.borrow_mut();
|
||||
let characteristic_ids = characteristic_map.drain().map(|(id, _)| id).collect();
|
||||
|
||||
let mut descriptor_map = self.attribute_instance_map.descriptor_map.borrow_mut();
|
||||
let descriptor_ids = descriptor_map.drain().map(|(id, _)| id).collect();
|
||||
|
||||
// Step 5, 6.4, 7.
|
||||
// TODO: Step 6: Implement `active notification context set` for BluetoothRemoteGATTCharacteristic.
|
||||
let _ = self
|
||||
.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::SetRepresentedToNull(
|
||||
service_ids,
|
||||
characteristic_ids,
|
||||
descriptor_ids,
|
||||
));
|
||||
|
||||
// Step 8.
|
||||
self.upcast::<EventTarget>()
|
||||
.fire_bubbling_event(atom!("gattserverdisconnected"), can_gc);
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#garbage-collect-the-connection
|
||||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||||
pub(crate) fn garbage_collect_the_connection(&self) -> ErrorResult {
|
||||
// Step 1: TODO: Check if other systems using this device.
|
||||
|
||||
// Step 2.
|
||||
let context = self.get_context();
|
||||
for (id, device) in context.get_device_map().borrow().iter() {
|
||||
// Step 2.1 - 2.2.
|
||||
if id == &self.Id().to_string() && device.get_gatt().Connected() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3.
|
||||
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
|
||||
self.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::GATTServerDisconnect(
|
||||
String::from(self.Id()),
|
||||
sender,
|
||||
))
|
||||
.unwrap();
|
||||
receiver.recv().unwrap().map_err(Convert::convert)
|
||||
}
|
||||
}
|
||||
|
||||
impl BluetoothDeviceMethods<crate::DomTypeHolder> for BluetoothDevice {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-id
|
||||
fn Id(&self) -> DOMString {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-name
|
||||
fn GetName(&self) -> Option<DOMString> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-gatt
|
||||
fn GetGatt(&self) -> Option<DomRoot<BluetoothRemoteGATTServer>> {
|
||||
// Step 1.
|
||||
if self
|
||||
.global()
|
||||
.as_window()
|
||||
.bluetooth_extra_permission_data()
|
||||
.allowed_devices_contains_id(self.id.clone()) &&
|
||||
!self.is_represented_device_null()
|
||||
{
|
||||
return Some(self.get_gatt());
|
||||
}
|
||||
// Step 2.
|
||||
None
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-watchadvertisements
|
||||
fn WatchAdvertisements(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
|
||||
let p = Promise::new_in_current_realm(comp, can_gc);
|
||||
let sender = response_async(&p, self);
|
||||
// TODO: Step 1.
|
||||
// Note: Steps 2 - 3 are implemented in components/bluetooth/lib.rs in watch_advertisements function
|
||||
// and in handle_response function.
|
||||
self.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::WatchAdvertisements(
|
||||
String::from(self.Id()),
|
||||
sender,
|
||||
))
|
||||
.unwrap();
|
||||
p
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-unwatchadvertisements
|
||||
fn UnwatchAdvertisements(&self) {
|
||||
// Step 1.
|
||||
self.watching_advertisements.set(false)
|
||||
// TODO: Step 2.
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-watchingadvertisements
|
||||
fn WatchingAdvertisements(&self) -> bool {
|
||||
self.watching_advertisements.get()
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdeviceeventhandlers-ongattserverdisconnected
|
||||
event_handler!(
|
||||
gattserverdisconnected,
|
||||
GetOngattserverdisconnected,
|
||||
SetOngattserverdisconnected
|
||||
);
|
||||
}
|
||||
|
||||
impl AsyncBluetoothListener for BluetoothDevice {
|
||||
fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, _can_gc: CanGc) {
|
||||
match response {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-unwatchadvertisements
|
||||
BluetoothResponse::WatchAdvertisements(_result) => {
|
||||
// Step 3.1.
|
||||
self.watching_advertisements.set(true);
|
||||
// Step 3.2.
|
||||
promise.resolve_native(&());
|
||||
},
|
||||
_ => promise.reject_error(Error::Type("Something went wrong...".to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
141
components/script/dom/bluetooth/bluetoothpermissionresult.rs
Normal file
141
components/script/dom/bluetooth/bluetoothpermissionresult.rs
Normal file
|
@ -0,0 +1,141 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use bluetooth_traits::{BluetoothRequest, BluetoothResponse};
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothPermissionResultBinding::BluetoothPermissionResultMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::NavigatorBinding::Navigator_Binding::NavigatorMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatus_Binding::PermissionStatusMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
|
||||
PermissionName, PermissionState,
|
||||
};
|
||||
use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
|
||||
use crate::dom::bindings::error::Error;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::bluetooth::{AllowedBluetoothDevice, AsyncBluetoothListener, Bluetooth};
|
||||
use crate::dom::bluetoothdevice::BluetoothDevice;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::permissionstatus::PermissionStatus;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothpermissionresult
|
||||
#[dom_struct]
|
||||
pub(crate) struct BluetoothPermissionResult {
|
||||
status: PermissionStatus,
|
||||
devices: DomRefCell<Vec<Dom<BluetoothDevice>>>,
|
||||
}
|
||||
|
||||
impl BluetoothPermissionResult {
|
||||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||||
fn new_inherited(status: &PermissionStatus) -> BluetoothPermissionResult {
|
||||
let result = BluetoothPermissionResult {
|
||||
status: PermissionStatus::new_inherited(status.get_query()),
|
||||
devices: DomRefCell::new(Vec::new()),
|
||||
};
|
||||
result.status.set_state(status.State());
|
||||
result
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
global: &GlobalScope,
|
||||
status: &PermissionStatus,
|
||||
) -> DomRoot<BluetoothPermissionResult> {
|
||||
reflect_dom_object(
|
||||
Box::new(BluetoothPermissionResult::new_inherited(status)),
|
||||
global,
|
||||
CanGc::note(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn get_bluetooth(&self) -> DomRoot<Bluetooth> {
|
||||
self.global().as_window().Navigator().Bluetooth()
|
||||
}
|
||||
|
||||
pub(crate) fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
|
||||
self.global().as_window().bluetooth_thread()
|
||||
}
|
||||
|
||||
pub(crate) fn get_query(&self) -> PermissionName {
|
||||
self.status.get_query()
|
||||
}
|
||||
|
||||
pub(crate) fn set_state(&self, state: PermissionState) {
|
||||
self.status.set_state(state)
|
||||
}
|
||||
|
||||
pub(crate) fn get_state(&self) -> PermissionState {
|
||||
self.status.State()
|
||||
}
|
||||
|
||||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||||
pub(crate) fn set_devices(&self, devices: Vec<Dom<BluetoothDevice>>) {
|
||||
*self.devices.borrow_mut() = devices;
|
||||
}
|
||||
}
|
||||
|
||||
impl BluetoothPermissionResultMethods<crate::DomTypeHolder> for BluetoothPermissionResult {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothpermissionresult-devices
|
||||
fn Devices(&self) -> Vec<DomRoot<BluetoothDevice>> {
|
||||
let device_vec: Vec<DomRoot<BluetoothDevice>> = self
|
||||
.devices
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|d| DomRoot::from_ref(&**d))
|
||||
.collect();
|
||||
device_vec
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncBluetoothListener for BluetoothPermissionResult {
|
||||
fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, _can_gc: CanGc) {
|
||||
match response {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices
|
||||
// Step 3, 11, 13 - 14.
|
||||
BluetoothResponse::RequestDevice(device) => {
|
||||
self.set_state(PermissionState::Granted);
|
||||
let bluetooth = self.get_bluetooth();
|
||||
let mut device_instance_map = bluetooth.get_device_map().borrow_mut();
|
||||
if let Some(existing_device) = device_instance_map.get(&device.id) {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#request-the-bluetooth-permission
|
||||
// Step 3.
|
||||
self.set_devices(vec![Dom::from_ref(existing_device)]);
|
||||
|
||||
// https://w3c.github.io/permissions/#dom-permissions-request
|
||||
// Step 8.
|
||||
return promise.resolve_native(self);
|
||||
}
|
||||
let bt_device = BluetoothDevice::new(
|
||||
&self.global(),
|
||||
DOMString::from(device.id.clone()),
|
||||
device.name.map(DOMString::from),
|
||||
&bluetooth,
|
||||
);
|
||||
device_instance_map.insert(device.id.clone(), Dom::from_ref(&bt_device));
|
||||
self.global()
|
||||
.as_window()
|
||||
.bluetooth_extra_permission_data()
|
||||
.add_new_allowed_device(AllowedBluetoothDevice {
|
||||
deviceId: DOMString::from(device.id),
|
||||
mayUseGATT: true,
|
||||
});
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#request-the-bluetooth-permission
|
||||
// Step 3.
|
||||
self.set_devices(vec![Dom::from_ref(&bt_device)]);
|
||||
|
||||
// https://w3c.github.io/permissions/#dom-permissions-request
|
||||
// Step 8.
|
||||
promise.resolve_native(self);
|
||||
},
|
||||
_ => promise.reject_error(Error::Type("Something went wrong...".to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,363 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use bluetooth_traits::blocklist::{uuid_is_blocklisted, Blocklist};
|
||||
use bluetooth_traits::{BluetoothRequest, BluetoothResponse, GATTType};
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothCharacteristicPropertiesBinding::BluetoothCharacteristicPropertiesMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding::BluetoothRemoteGATTCharacteristicMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods;
|
||||
use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
|
||||
use crate::dom::bindings::error::Error::{
|
||||
self, InvalidModification, Network, NotSupported, Security,
|
||||
};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::{ByteString, DOMString};
|
||||
use crate::dom::bluetooth::{get_gatt_children, response_async, AsyncBluetoothListener};
|
||||
use crate::dom::bluetoothcharacteristicproperties::BluetoothCharacteristicProperties;
|
||||
use crate::dom::bluetoothremotegattservice::BluetoothRemoteGATTService;
|
||||
use crate::dom::bluetoothuuid::{BluetoothDescriptorUUID, BluetoothUUID};
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::realms::InRealm;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
// Maximum length of an attribute value.
|
||||
// https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=286439 (Vol. 3, page 2169)
|
||||
pub(crate) const MAXIMUM_ATTRIBUTE_LENGTH: usize = 512;
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattcharacteristic
|
||||
#[dom_struct]
|
||||
pub(crate) struct BluetoothRemoteGATTCharacteristic {
|
||||
eventtarget: EventTarget,
|
||||
service: Dom<BluetoothRemoteGATTService>,
|
||||
uuid: DOMString,
|
||||
properties: Dom<BluetoothCharacteristicProperties>,
|
||||
value: DomRefCell<Option<ByteString>>,
|
||||
instance_id: String,
|
||||
}
|
||||
|
||||
impl BluetoothRemoteGATTCharacteristic {
|
||||
pub(crate) fn new_inherited(
|
||||
service: &BluetoothRemoteGATTService,
|
||||
uuid: DOMString,
|
||||
properties: &BluetoothCharacteristicProperties,
|
||||
instance_id: String,
|
||||
) -> BluetoothRemoteGATTCharacteristic {
|
||||
BluetoothRemoteGATTCharacteristic {
|
||||
eventtarget: EventTarget::new_inherited(),
|
||||
service: Dom::from_ref(service),
|
||||
uuid,
|
||||
properties: Dom::from_ref(properties),
|
||||
value: DomRefCell::new(None),
|
||||
instance_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
global: &GlobalScope,
|
||||
service: &BluetoothRemoteGATTService,
|
||||
uuid: DOMString,
|
||||
properties: &BluetoothCharacteristicProperties,
|
||||
instance_id: String,
|
||||
) -> DomRoot<BluetoothRemoteGATTCharacteristic> {
|
||||
reflect_dom_object(
|
||||
Box::new(BluetoothRemoteGATTCharacteristic::new_inherited(
|
||||
service,
|
||||
uuid,
|
||||
properties,
|
||||
instance_id,
|
||||
)),
|
||||
global,
|
||||
CanGc::note(),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
|
||||
self.global().as_window().bluetooth_thread()
|
||||
}
|
||||
|
||||
fn get_instance_id(&self) -> String {
|
||||
self.instance_id.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl BluetoothRemoteGATTCharacteristicMethods<crate::DomTypeHolder>
|
||||
for BluetoothRemoteGATTCharacteristic
|
||||
{
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-properties
|
||||
fn Properties(&self) -> DomRoot<BluetoothCharacteristicProperties> {
|
||||
DomRoot::from_ref(&self.properties)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-service
|
||||
fn Service(&self) -> DomRoot<BluetoothRemoteGATTService> {
|
||||
DomRoot::from_ref(&self.service)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-uuid
|
||||
fn Uuid(&self) -> DOMString {
|
||||
self.uuid.clone()
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-getdescriptor
|
||||
fn GetDescriptor(&self, descriptor: BluetoothDescriptorUUID, can_gc: CanGc) -> Rc<Promise> {
|
||||
get_gatt_children(
|
||||
self,
|
||||
true,
|
||||
BluetoothUUID::descriptor,
|
||||
Some(descriptor),
|
||||
self.get_instance_id(),
|
||||
self.Service().Device().get_gatt().Connected(),
|
||||
GATTType::Descriptor,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-getdescriptors
|
||||
fn GetDescriptors(
|
||||
&self,
|
||||
descriptor: Option<BluetoothDescriptorUUID>,
|
||||
can_gc: CanGc,
|
||||
) -> Rc<Promise> {
|
||||
get_gatt_children(
|
||||
self,
|
||||
false,
|
||||
BluetoothUUID::descriptor,
|
||||
descriptor,
|
||||
self.get_instance_id(),
|
||||
self.Service().Device().get_gatt().Connected(),
|
||||
GATTType::Descriptor,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-value
|
||||
fn GetValue(&self) -> Option<ByteString> {
|
||||
self.value.borrow().clone()
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-readvalue
|
||||
fn ReadValue(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
|
||||
let p = Promise::new_in_current_realm(comp, can_gc);
|
||||
|
||||
// Step 1.
|
||||
if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Reads) {
|
||||
p.reject_error(Security);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Step 2.
|
||||
if !self.Service().Device().get_gatt().Connected() {
|
||||
p.reject_error(Network);
|
||||
return p;
|
||||
}
|
||||
|
||||
// TODO: Step 5: Implement the `connection-checking-wrapper` algorithm for BluetoothRemoteGATTServer.
|
||||
|
||||
// Step 5.1.
|
||||
if !self.Properties().Read() {
|
||||
p.reject_error(NotSupported);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Note: Steps 3 - 4 and the remaining substeps of Step 5 are implemented in components/bluetooth/lib.rs
|
||||
// in readValue function and in handle_response function.
|
||||
let sender = response_async(&p, self);
|
||||
self.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::ReadValue(self.get_instance_id(), sender))
|
||||
.unwrap();
|
||||
p
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-writevalue
|
||||
fn WriteValue(
|
||||
&self,
|
||||
value: ArrayBufferViewOrArrayBuffer,
|
||||
comp: InRealm,
|
||||
can_gc: CanGc,
|
||||
) -> Rc<Promise> {
|
||||
let p = Promise::new_in_current_realm(comp, can_gc);
|
||||
|
||||
// Step 1.
|
||||
if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Writes) {
|
||||
p.reject_error(Security);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Step 2 - 3.
|
||||
let vec = match value {
|
||||
ArrayBufferViewOrArrayBuffer::ArrayBufferView(avb) => avb.to_vec(),
|
||||
ArrayBufferViewOrArrayBuffer::ArrayBuffer(ab) => ab.to_vec(),
|
||||
};
|
||||
|
||||
if vec.len() > MAXIMUM_ATTRIBUTE_LENGTH {
|
||||
p.reject_error(InvalidModification);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Step 4.
|
||||
if !self.Service().Device().get_gatt().Connected() {
|
||||
p.reject_error(Network);
|
||||
return p;
|
||||
}
|
||||
|
||||
// TODO: Step 7: Implement the `connection-checking-wrapper` algorithm for BluetoothRemoteGATTServer.
|
||||
|
||||
// Step 7.1.
|
||||
if !(self.Properties().Write() ||
|
||||
self.Properties().WriteWithoutResponse() ||
|
||||
self.Properties().AuthenticatedSignedWrites())
|
||||
{
|
||||
p.reject_error(NotSupported);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Note: Steps 5 - 6 and the remaining substeps of Step 7 are implemented in components/bluetooth/lib.rs
|
||||
// in writeValue function and in handle_response function.
|
||||
let sender = response_async(&p, self);
|
||||
self.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::WriteValue(
|
||||
self.get_instance_id(),
|
||||
vec,
|
||||
sender,
|
||||
))
|
||||
.unwrap();
|
||||
p
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-startnotifications
|
||||
fn StartNotifications(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
|
||||
let p = Promise::new_in_current_realm(comp, can_gc);
|
||||
|
||||
// Step 1.
|
||||
if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Reads) {
|
||||
p.reject_error(Security);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Step 2.
|
||||
if !self.Service().Device().get_gatt().Connected() {
|
||||
p.reject_error(Network);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Step 5.
|
||||
if !(self.Properties().Notify() || self.Properties().Indicate()) {
|
||||
p.reject_error(NotSupported);
|
||||
return p;
|
||||
}
|
||||
|
||||
// TODO: Step 6: Implement `active notification context set` for BluetoothRemoteGATTCharacteristic.
|
||||
|
||||
// Note: Steps 3 - 4, 7 - 11 are implemented in components/bluetooth/lib.rs in enable_notification function
|
||||
// and in handle_response function.
|
||||
let sender = response_async(&p, self);
|
||||
self.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::EnableNotification(
|
||||
self.get_instance_id(),
|
||||
true,
|
||||
sender,
|
||||
))
|
||||
.unwrap();
|
||||
p
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-stopnotifications
|
||||
fn StopNotifications(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
|
||||
let p = Promise::new_in_current_realm(comp, can_gc);
|
||||
let sender = response_async(&p, self);
|
||||
|
||||
// TODO: Step 3 - 4: Implement `active notification context set` for BluetoothRemoteGATTCharacteristic,
|
||||
|
||||
// Note: Steps 1 - 2, and part of Step 4 and Step 5 are implemented in components/bluetooth/lib.rs
|
||||
// in enable_notification function and in handle_response function.
|
||||
self.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::EnableNotification(
|
||||
self.get_instance_id(),
|
||||
false,
|
||||
sender,
|
||||
))
|
||||
.unwrap();
|
||||
p
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-characteristiceventhandlers-oncharacteristicvaluechanged
|
||||
event_handler!(
|
||||
characteristicvaluechanged,
|
||||
GetOncharacteristicvaluechanged,
|
||||
SetOncharacteristicvaluechanged
|
||||
);
|
||||
}
|
||||
|
||||
impl AsyncBluetoothListener for BluetoothRemoteGATTCharacteristic {
|
||||
fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, can_gc: CanGc) {
|
||||
let device = self.Service().Device();
|
||||
match response {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
|
||||
// Step 7.
|
||||
BluetoothResponse::GetDescriptors(descriptors_vec, single) => {
|
||||
if single {
|
||||
promise.resolve_native(
|
||||
&device.get_or_create_descriptor(&descriptors_vec[0], self),
|
||||
);
|
||||
return;
|
||||
}
|
||||
let mut descriptors = vec![];
|
||||
for descriptor in descriptors_vec {
|
||||
let bt_descriptor = device.get_or_create_descriptor(&descriptor, self);
|
||||
descriptors.push(bt_descriptor);
|
||||
}
|
||||
promise.resolve_native(&descriptors);
|
||||
},
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-readvalue
|
||||
BluetoothResponse::ReadValue(result) => {
|
||||
// TODO: Step 5.5.1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
|
||||
|
||||
// Step 5.5.2.
|
||||
// TODO(#5014): Replace ByteString with ArrayBuffer when it is implemented.
|
||||
let value = ByteString::new(result);
|
||||
*self.value.borrow_mut() = Some(value.clone());
|
||||
|
||||
// Step 5.5.3.
|
||||
self.upcast::<EventTarget>()
|
||||
.fire_bubbling_event(atom!("characteristicvaluechanged"), can_gc);
|
||||
|
||||
// Step 5.5.4.
|
||||
promise.resolve_native(&value);
|
||||
},
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-writevalue
|
||||
BluetoothResponse::WriteValue(result) => {
|
||||
// TODO: Step 7.5.1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
|
||||
|
||||
// Step 7.5.2.
|
||||
// TODO(#5014): Replace ByteString with an ArrayBuffer wrapped in a DataView.
|
||||
*self.value.borrow_mut() = Some(ByteString::new(result));
|
||||
|
||||
// Step 7.5.3.
|
||||
promise.resolve_native(&());
|
||||
},
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-startnotifications
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-stopnotifications
|
||||
BluetoothResponse::EnableNotification(_result) => {
|
||||
// (StartNotification) TODO: Step 10: Implement `active notification context set`
|
||||
// for BluetoothRemoteGATTCharacteristic.
|
||||
|
||||
// (StartNotification) Step 11.
|
||||
// (StopNotification) Step 5.
|
||||
promise.resolve_native(self);
|
||||
},
|
||||
_ => promise.reject_error(Error::Type("Something went wrong...".to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
212
components/script/dom/bluetooth/bluetoothremotegattdescriptor.rs
Normal file
212
components/script/dom/bluetooth/bluetoothremotegattdescriptor.rs
Normal file
|
@ -0,0 +1,212 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use bluetooth_traits::blocklist::{uuid_is_blocklisted, Blocklist};
|
||||
use bluetooth_traits::{BluetoothRequest, BluetoothResponse};
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding::BluetoothRemoteGATTCharacteristicMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTDescriptorBinding::BluetoothRemoteGATTDescriptorMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods;
|
||||
use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
|
||||
use crate::dom::bindings::error::Error::{self, InvalidModification, Network, Security};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::{ByteString, DOMString};
|
||||
use crate::dom::bluetooth::{response_async, AsyncBluetoothListener};
|
||||
use crate::dom::bluetoothremotegattcharacteristic::{
|
||||
BluetoothRemoteGATTCharacteristic, MAXIMUM_ATTRIBUTE_LENGTH,
|
||||
};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::realms::InRealm;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
// http://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattdescriptor
|
||||
#[dom_struct]
|
||||
pub(crate) struct BluetoothRemoteGATTDescriptor {
|
||||
reflector_: Reflector,
|
||||
characteristic: Dom<BluetoothRemoteGATTCharacteristic>,
|
||||
uuid: DOMString,
|
||||
value: DomRefCell<Option<ByteString>>,
|
||||
instance_id: String,
|
||||
}
|
||||
|
||||
impl BluetoothRemoteGATTDescriptor {
|
||||
pub(crate) fn new_inherited(
|
||||
characteristic: &BluetoothRemoteGATTCharacteristic,
|
||||
uuid: DOMString,
|
||||
instance_id: String,
|
||||
) -> BluetoothRemoteGATTDescriptor {
|
||||
BluetoothRemoteGATTDescriptor {
|
||||
reflector_: Reflector::new(),
|
||||
characteristic: Dom::from_ref(characteristic),
|
||||
uuid,
|
||||
value: DomRefCell::new(None),
|
||||
instance_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
global: &GlobalScope,
|
||||
characteristic: &BluetoothRemoteGATTCharacteristic,
|
||||
uuid: DOMString,
|
||||
instance_id: String,
|
||||
) -> DomRoot<BluetoothRemoteGATTDescriptor> {
|
||||
reflect_dom_object(
|
||||
Box::new(BluetoothRemoteGATTDescriptor::new_inherited(
|
||||
characteristic,
|
||||
uuid,
|
||||
instance_id,
|
||||
)),
|
||||
global,
|
||||
CanGc::note(),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
|
||||
self.global().as_window().bluetooth_thread()
|
||||
}
|
||||
|
||||
fn get_instance_id(&self) -> String {
|
||||
self.instance_id.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl BluetoothRemoteGATTDescriptorMethods<crate::DomTypeHolder> for BluetoothRemoteGATTDescriptor {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-characteristic
|
||||
fn Characteristic(&self) -> DomRoot<BluetoothRemoteGATTCharacteristic> {
|
||||
DomRoot::from_ref(&self.characteristic)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-uuid
|
||||
fn Uuid(&self) -> DOMString {
|
||||
self.uuid.clone()
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-value
|
||||
fn GetValue(&self) -> Option<ByteString> {
|
||||
self.value.borrow().clone()
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-readvalue
|
||||
fn ReadValue(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
|
||||
let p = Promise::new_in_current_realm(comp, can_gc);
|
||||
|
||||
// Step 1.
|
||||
if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Reads) {
|
||||
p.reject_error(Security);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Step 2.
|
||||
if !self
|
||||
.Characteristic()
|
||||
.Service()
|
||||
.Device()
|
||||
.get_gatt()
|
||||
.Connected()
|
||||
{
|
||||
p.reject_error(Network);
|
||||
return p;
|
||||
}
|
||||
|
||||
// TODO: Step 5: Implement the `connection-checking-wrapper` algorithm for BluetoothRemoteGATTServer.
|
||||
// Note: Steps 3 - 4 and substeps of Step 5 are implemented in components/bluetooth/lib.rs
|
||||
// in readValue function and in handle_response function.
|
||||
let sender = response_async(&p, self);
|
||||
self.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::ReadValue(self.get_instance_id(), sender))
|
||||
.unwrap();
|
||||
p
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-writevalue
|
||||
fn WriteValue(
|
||||
&self,
|
||||
value: ArrayBufferViewOrArrayBuffer,
|
||||
comp: InRealm,
|
||||
can_gc: CanGc,
|
||||
) -> Rc<Promise> {
|
||||
let p = Promise::new_in_current_realm(comp, can_gc);
|
||||
|
||||
// Step 1.
|
||||
if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Writes) {
|
||||
p.reject_error(Security);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Step 2 - 3.
|
||||
let vec = match value {
|
||||
ArrayBufferViewOrArrayBuffer::ArrayBufferView(avb) => avb.to_vec(),
|
||||
ArrayBufferViewOrArrayBuffer::ArrayBuffer(ab) => ab.to_vec(),
|
||||
};
|
||||
if vec.len() > MAXIMUM_ATTRIBUTE_LENGTH {
|
||||
p.reject_error(InvalidModification);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Step 4.
|
||||
if !self
|
||||
.Characteristic()
|
||||
.Service()
|
||||
.Device()
|
||||
.get_gatt()
|
||||
.Connected()
|
||||
{
|
||||
p.reject_error(Network);
|
||||
return p;
|
||||
}
|
||||
|
||||
// TODO: Step 7: Implement the `connection-checking-wrapper` algorithm for BluetoothRemoteGATTServer.
|
||||
// Note: Steps 5 - 6 and substeps of Step 7 are implemented in components/bluetooth/lib.rs
|
||||
// in writeValue function and in handle_response function.
|
||||
let sender = response_async(&p, self);
|
||||
self.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::WriteValue(
|
||||
self.get_instance_id(),
|
||||
vec,
|
||||
sender,
|
||||
))
|
||||
.unwrap();
|
||||
p
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncBluetoothListener for BluetoothRemoteGATTDescriptor {
|
||||
fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, _can_gc: CanGc) {
|
||||
match response {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-readvalue
|
||||
BluetoothResponse::ReadValue(result) => {
|
||||
// TODO: Step 5.4.1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
|
||||
|
||||
// Step 5.4.2.
|
||||
// TODO(#5014): Replace ByteString with ArrayBuffer when it is implemented.
|
||||
let value = ByteString::new(result);
|
||||
*self.value.borrow_mut() = Some(value.clone());
|
||||
|
||||
// Step 5.4.3.
|
||||
promise.resolve_native(&value);
|
||||
},
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-writevalue
|
||||
BluetoothResponse::WriteValue(result) => {
|
||||
// TODO: Step 7.4.1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
|
||||
|
||||
// Step 7.4.2.
|
||||
// TODO(#5014): Replace ByteString with an ArrayBuffer wrapped in a DataView.
|
||||
*self.value.borrow_mut() = Some(ByteString::new(result));
|
||||
|
||||
// Step 7.4.3.
|
||||
// TODO: Resolve promise with undefined instead of a value.
|
||||
promise.resolve_native(&());
|
||||
},
|
||||
_ => promise.reject_error(Error::Type("Something went wrong...".to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
186
components/script/dom/bluetooth/bluetoothremotegattserver.rs
Normal file
186
components/script/dom/bluetooth/bluetoothremotegattserver.rs
Normal file
|
@ -0,0 +1,186 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use bluetooth_traits::{BluetoothRequest, BluetoothResponse, GATTType};
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
|
||||
use crate::dom::bindings::error::{Error, ErrorResult};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal, Reflector};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bluetooth::{get_gatt_children, response_async, AsyncBluetoothListener};
|
||||
use crate::dom::bluetoothdevice::BluetoothDevice;
|
||||
use crate::dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::realms::InRealm;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattserver
|
||||
#[dom_struct]
|
||||
pub(crate) struct BluetoothRemoteGATTServer {
|
||||
reflector_: Reflector,
|
||||
device: Dom<BluetoothDevice>,
|
||||
connected: Cell<bool>,
|
||||
}
|
||||
|
||||
impl BluetoothRemoteGATTServer {
|
||||
pub(crate) fn new_inherited(device: &BluetoothDevice) -> BluetoothRemoteGATTServer {
|
||||
BluetoothRemoteGATTServer {
|
||||
reflector_: Reflector::new(),
|
||||
device: Dom::from_ref(device),
|
||||
connected: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
global: &GlobalScope,
|
||||
device: &BluetoothDevice,
|
||||
) -> DomRoot<BluetoothRemoteGATTServer> {
|
||||
reflect_dom_object(
|
||||
Box::new(BluetoothRemoteGATTServer::new_inherited(device)),
|
||||
global,
|
||||
CanGc::note(),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
|
||||
self.global().as_window().bluetooth_thread()
|
||||
}
|
||||
|
||||
pub(crate) fn set_connected(&self, connected: bool) {
|
||||
self.connected.set(connected);
|
||||
}
|
||||
}
|
||||
|
||||
impl BluetoothRemoteGATTServerMethods<crate::DomTypeHolder> for BluetoothRemoteGATTServer {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-device
|
||||
fn Device(&self) -> DomRoot<BluetoothDevice> {
|
||||
DomRoot::from_ref(&self.device)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connected
|
||||
fn Connected(&self) -> bool {
|
||||
self.connected.get()
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connect
|
||||
#[allow(unsafe_code)]
|
||||
fn Connect(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
|
||||
// Step 1.
|
||||
let p = Promise::new_in_current_realm(comp, can_gc);
|
||||
let sender = response_async(&p, self);
|
||||
|
||||
// TODO: Step 3: Check if the UA is currently using the Bluetooth system.
|
||||
|
||||
// TODO: Step 4: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
|
||||
|
||||
// TODO: Step 5.1 - 5.2: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
|
||||
|
||||
// Note: Steps 2, 5.1.1 and 5.1.3 are in components/bluetooth/lib.rs in the gatt_server_connect function.
|
||||
// Steps 5.2.3 - 5.2.5 are in response function.
|
||||
self.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::GATTServerConnect(
|
||||
String::from(self.Device().Id()),
|
||||
sender,
|
||||
))
|
||||
.unwrap();
|
||||
// Step 5: return promise.
|
||||
p
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-disconnect
|
||||
fn Disconnect(&self, can_gc: CanGc) -> ErrorResult {
|
||||
// TODO: Step 1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
|
||||
|
||||
// Step 2.
|
||||
if !self.Connected() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Step 3.
|
||||
self.Device().clean_up_disconnected_device(can_gc);
|
||||
|
||||
// Step 4 - 5:
|
||||
self.Device().garbage_collect_the_connection()
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservice
|
||||
fn GetPrimaryService(&self, service: BluetoothServiceUUID, can_gc: CanGc) -> Rc<Promise> {
|
||||
// Step 1 - 2.
|
||||
get_gatt_children(
|
||||
self,
|
||||
true,
|
||||
BluetoothUUID::service,
|
||||
Some(service),
|
||||
String::from(self.Device().Id()),
|
||||
self.Device().get_gatt().Connected(),
|
||||
GATTType::PrimaryService,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservices
|
||||
fn GetPrimaryServices(
|
||||
&self,
|
||||
service: Option<BluetoothServiceUUID>,
|
||||
can_gc: CanGc,
|
||||
) -> Rc<Promise> {
|
||||
// Step 1 - 2.
|
||||
get_gatt_children(
|
||||
self,
|
||||
false,
|
||||
BluetoothUUID::service,
|
||||
service,
|
||||
String::from(self.Device().Id()),
|
||||
self.Connected(),
|
||||
GATTType::PrimaryService,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncBluetoothListener for BluetoothRemoteGATTServer {
|
||||
fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, _can_gc: CanGc) {
|
||||
match response {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connect
|
||||
BluetoothResponse::GATTServerConnect(connected) => {
|
||||
// Step 5.2.3
|
||||
if self.Device().is_represented_device_null() {
|
||||
if let Err(e) = self.Device().garbage_collect_the_connection() {
|
||||
return promise.reject_error(e);
|
||||
}
|
||||
return promise.reject_error(Error::Network);
|
||||
}
|
||||
|
||||
// Step 5.2.4.
|
||||
self.connected.set(connected);
|
||||
|
||||
// Step 5.2.5.
|
||||
promise.resolve_native(self);
|
||||
},
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
|
||||
// Step 7.
|
||||
BluetoothResponse::GetPrimaryServices(services_vec, single) => {
|
||||
let device = self.Device();
|
||||
if single {
|
||||
promise.resolve_native(&device.get_or_create_service(&services_vec[0], self));
|
||||
return;
|
||||
}
|
||||
let mut services = vec![];
|
||||
for service in services_vec {
|
||||
let bt_service = device.get_or_create_service(&service, self);
|
||||
services.push(bt_service);
|
||||
}
|
||||
promise.resolve_native(&services);
|
||||
},
|
||||
_ => promise.reject_error(Error::Type("Something went wrong...".to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
205
components/script/dom/bluetooth/bluetoothremotegattservice.rs
Normal file
205
components/script/dom/bluetooth/bluetoothremotegattservice.rs
Normal file
|
@ -0,0 +1,205 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use bluetooth_traits::{BluetoothResponse, GATTType};
|
||||
use dom_struct::dom_struct;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods;
|
||||
use crate::dom::bindings::error::Error;
|
||||
use crate::dom::bindings::reflector::reflect_dom_object;
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::bluetooth::{get_gatt_children, AsyncBluetoothListener};
|
||||
use crate::dom::bluetoothdevice::BluetoothDevice;
|
||||
use crate::dom::bluetoothuuid::{BluetoothCharacteristicUUID, BluetoothServiceUUID, BluetoothUUID};
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattservice
|
||||
#[dom_struct]
|
||||
pub(crate) struct BluetoothRemoteGATTService {
|
||||
eventtarget: EventTarget,
|
||||
device: Dom<BluetoothDevice>,
|
||||
uuid: DOMString,
|
||||
is_primary: bool,
|
||||
instance_id: String,
|
||||
}
|
||||
|
||||
impl BluetoothRemoteGATTService {
|
||||
pub(crate) fn new_inherited(
|
||||
device: &BluetoothDevice,
|
||||
uuid: DOMString,
|
||||
is_primary: bool,
|
||||
instance_id: String,
|
||||
) -> BluetoothRemoteGATTService {
|
||||
BluetoothRemoteGATTService {
|
||||
eventtarget: EventTarget::new_inherited(),
|
||||
device: Dom::from_ref(device),
|
||||
uuid,
|
||||
is_primary,
|
||||
instance_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn new(
|
||||
global: &GlobalScope,
|
||||
device: &BluetoothDevice,
|
||||
uuid: DOMString,
|
||||
isPrimary: bool,
|
||||
instanceID: String,
|
||||
) -> DomRoot<BluetoothRemoteGATTService> {
|
||||
reflect_dom_object(
|
||||
Box::new(BluetoothRemoteGATTService::new_inherited(
|
||||
device, uuid, isPrimary, instanceID,
|
||||
)),
|
||||
global,
|
||||
CanGc::note(),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_instance_id(&self) -> String {
|
||||
self.instance_id.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl BluetoothRemoteGATTServiceMethods<crate::DomTypeHolder> for BluetoothRemoteGATTService {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-device
|
||||
fn Device(&self) -> DomRoot<BluetoothDevice> {
|
||||
DomRoot::from_ref(&self.device)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-isprimary
|
||||
fn IsPrimary(&self) -> bool {
|
||||
self.is_primary
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-uuid
|
||||
fn Uuid(&self) -> DOMString {
|
||||
self.uuid.clone()
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getcharacteristic
|
||||
fn GetCharacteristic(
|
||||
&self,
|
||||
characteristic: BluetoothCharacteristicUUID,
|
||||
can_gc: CanGc,
|
||||
) -> Rc<Promise> {
|
||||
get_gatt_children(
|
||||
self,
|
||||
true,
|
||||
BluetoothUUID::characteristic,
|
||||
Some(characteristic),
|
||||
self.get_instance_id(),
|
||||
self.Device().get_gatt().Connected(),
|
||||
GATTType::Characteristic,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getcharacteristics
|
||||
fn GetCharacteristics(
|
||||
&self,
|
||||
characteristic: Option<BluetoothCharacteristicUUID>,
|
||||
can_gc: CanGc,
|
||||
) -> Rc<Promise> {
|
||||
get_gatt_children(
|
||||
self,
|
||||
false,
|
||||
BluetoothUUID::characteristic,
|
||||
characteristic,
|
||||
self.get_instance_id(),
|
||||
self.Device().get_gatt().Connected(),
|
||||
GATTType::Characteristic,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getincludedservice
|
||||
fn GetIncludedService(&self, service: BluetoothServiceUUID, can_gc: CanGc) -> Rc<Promise> {
|
||||
get_gatt_children(
|
||||
self,
|
||||
false,
|
||||
BluetoothUUID::service,
|
||||
Some(service),
|
||||
self.get_instance_id(),
|
||||
self.Device().get_gatt().Connected(),
|
||||
GATTType::IncludedService,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getincludedservices
|
||||
fn GetIncludedServices(
|
||||
&self,
|
||||
service: Option<BluetoothServiceUUID>,
|
||||
can_gc: CanGc,
|
||||
) -> Rc<Promise> {
|
||||
get_gatt_children(
|
||||
self,
|
||||
false,
|
||||
BluetoothUUID::service,
|
||||
service,
|
||||
self.get_instance_id(),
|
||||
self.Device().get_gatt().Connected(),
|
||||
GATTType::IncludedService,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-serviceeventhandlers-onserviceadded
|
||||
event_handler!(serviceadded, GetOnserviceadded, SetOnserviceadded);
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-serviceeventhandlers-onservicechanged
|
||||
event_handler!(servicechanged, GetOnservicechanged, SetOnservicechanged);
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-serviceeventhandlers-onserviceremoved
|
||||
event_handler!(serviceremoved, GetOnserviceremoved, SetOnserviceremoved);
|
||||
}
|
||||
|
||||
impl AsyncBluetoothListener for BluetoothRemoteGATTService {
|
||||
fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, _can_gc: CanGc) {
|
||||
let device = self.Device();
|
||||
match response {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
|
||||
// Step 7.
|
||||
BluetoothResponse::GetCharacteristics(characteristics_vec, single) => {
|
||||
if single {
|
||||
promise.resolve_native(
|
||||
&device.get_or_create_characteristic(&characteristics_vec[0], self),
|
||||
);
|
||||
return;
|
||||
}
|
||||
let mut characteristics = vec![];
|
||||
for characteristic in characteristics_vec {
|
||||
let bt_characteristic =
|
||||
device.get_or_create_characteristic(&characteristic, self);
|
||||
characteristics.push(bt_characteristic);
|
||||
}
|
||||
promise.resolve_native(&characteristics);
|
||||
},
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
|
||||
// Step 7.
|
||||
BluetoothResponse::GetIncludedServices(services_vec, single) => {
|
||||
if single {
|
||||
return promise.resolve_native(
|
||||
&device.get_or_create_service(&services_vec[0], &device.get_gatt()),
|
||||
);
|
||||
}
|
||||
let mut services = vec![];
|
||||
for service in services_vec {
|
||||
let bt_service = device.get_or_create_service(&service, &device.get_gatt());
|
||||
services.push(bt_service);
|
||||
}
|
||||
promise.resolve_native(&services);
|
||||
},
|
||||
_ => promise.reject_error(Error::Type("Something went wrong...".to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
670
components/script/dom/bluetooth/bluetoothuuid.rs
Normal file
670
components/script/dom/bluetooth/bluetoothuuid.rs
Normal file
|
@ -0,0 +1,670 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::BluetoothUUIDBinding::BluetoothUUIDMethods;
|
||||
use crate::dom::bindings::codegen::UnionTypes::StringOrUnsignedLong;
|
||||
use crate::dom::bindings::error::Error::Type;
|
||||
use crate::dom::bindings::error::Fallible;
|
||||
use crate::dom::bindings::reflector::Reflector;
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::window::Window;
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub(crate) type UUID = DOMString;
|
||||
pub(crate) type BluetoothServiceUUID = StringOrUnsignedLong;
|
||||
pub(crate) type BluetoothCharacteristicUUID = StringOrUnsignedLong;
|
||||
pub(crate) type BluetoothDescriptorUUID = StringOrUnsignedLong;
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothuuid
|
||||
#[dom_struct]
|
||||
pub(crate) struct BluetoothUUID {
|
||||
reflector_: Reflector,
|
||||
}
|
||||
|
||||
//https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx
|
||||
const BLUETOOTH_ASSIGNED_SERVICES: &[(&str, u32)] = &[
|
||||
("org.bluetooth.service.alert_notification", 0x1811_u32),
|
||||
("org.bluetooth.service.automation_io", 0x1815_u32),
|
||||
("org.bluetooth.service.battery_service", 0x180f_u32),
|
||||
("org.bluetooth.service.blood_pressure", 0x1810_u32),
|
||||
("org.bluetooth.service.body_composition", 0x181b_u32),
|
||||
("org.bluetooth.service.bond_management", 0x181e_u32),
|
||||
(
|
||||
"org.bluetooth.service.continuous_glucose_monitoring",
|
||||
0x181f_u32,
|
||||
),
|
||||
("org.bluetooth.service.current_time", 0x1805_u32),
|
||||
("org.bluetooth.service.cycling_power", 0x1818_u32),
|
||||
(
|
||||
"org.bluetooth.service.cycling_speed_and_cadence",
|
||||
0x1816_u32,
|
||||
),
|
||||
("org.bluetooth.service.device_information", 0x180a_u32),
|
||||
("org.bluetooth.service.environmental_sensing", 0x181a_u32),
|
||||
("org.bluetooth.service.generic_access", 0x1800_u32),
|
||||
("org.bluetooth.service.generic_attribute", 0x1801_u32),
|
||||
("org.bluetooth.service.glucose", 0x1808_u32),
|
||||
("org.bluetooth.service.health_thermometer", 0x1809_u32),
|
||||
("org.bluetooth.service.heart_rate", 0x180d_u32),
|
||||
("org.bluetooth.service.http_proxy", 0x1823_u32),
|
||||
("org.bluetooth.service.human_interface_device", 0x1812_u32),
|
||||
("org.bluetooth.service.immediate_alert", 0x1802_u32),
|
||||
("org.bluetooth.service.indoor_positioning", 0x1821_u32),
|
||||
(
|
||||
"org.bluetooth.service.internet_protocol_support",
|
||||
0x1820_u32,
|
||||
),
|
||||
("org.bluetooth.service.link_loss", 0x1803_u32),
|
||||
("org.bluetooth.service.location_and_navigation", 0x1819_u32),
|
||||
("org.bluetooth.service.next_dst_change", 0x1807_u32),
|
||||
("org.bluetooth.service.object_transfer", 0x1825_u32),
|
||||
("org.bluetooth.service.phone_alert_status", 0x180e_u32),
|
||||
("org.bluetooth.service.pulse_oximeter", 0x1822_u32),
|
||||
("org.bluetooth.service.reference_time_update", 0x1806_u32),
|
||||
(
|
||||
"org.bluetooth.service.running_speed_and_cadence",
|
||||
0x1814_u32,
|
||||
),
|
||||
("org.bluetooth.service.scan_parameters", 0x1813_u32),
|
||||
("org.bluetooth.service.transport_discovery", 0x1824),
|
||||
("org.bluetooth.service.tx_power", 0x1804_u32),
|
||||
("org.bluetooth.service.user_data", 0x181c_u32),
|
||||
("org.bluetooth.service.weight_scale", 0x181d_u32),
|
||||
];
|
||||
|
||||
//https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx
|
||||
const BLUETOOTH_ASSIGNED_CHARCTERISTICS: &[(&str, u32)] = &[
|
||||
(
|
||||
"org.bluetooth.characteristic.aerobic_heart_rate_lower_limit",
|
||||
0x2a7e_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.aerobic_heart_rate_upper_limit",
|
||||
0x2a84_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.aerobic_threshold", 0x2a7f_u32),
|
||||
("org.bluetooth.characteristic.age", 0x2a80_u32),
|
||||
("org.bluetooth.characteristic.aggregate", 0x2a5a_u32),
|
||||
("org.bluetooth.characteristic.alert_category_id", 0x2a43_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.alert_category_id_bit_mask",
|
||||
0x2a42_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.alert_level", 0x2a06_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.alert_notification_control_point",
|
||||
0x2a44_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.alert_status", 0x2a3f_u32),
|
||||
("org.bluetooth.characteristic.altitude", 0x2ab3_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.anaerobic_heart_rate_lower_limit",
|
||||
0x2a81_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.anaerobic_heart_rate_upper_limit",
|
||||
0x2a82_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.anaerobic_threshold",
|
||||
0x2a83_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.analog", 0x2a58_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.apparent_wind_direction",
|
||||
0x2a73_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.apparent_wind_speed",
|
||||
0x2a72_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.gap.appearance", 0x2a01_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.barometric_pressure_trend",
|
||||
0x2aa3_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.battery_level", 0x2a19_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.blood_pressure_feature",
|
||||
0x2a49_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.blood_pressure_measurement",
|
||||
0x2a35_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.body_composition_feature",
|
||||
0x2a9b_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.body_composition_measurement",
|
||||
0x2a9c_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.body_sensor_location",
|
||||
0x2a38_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.bond_management_control_point",
|
||||
0x2aa4_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.bond_management_feature",
|
||||
0x2aa5_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.boot_keyboard_input_report",
|
||||
0x2a22_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.boot_keyboard_output_report",
|
||||
0x2a32_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.boot_mouse_input_report",
|
||||
0x2a33_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.gap.central_address_resolution_support",
|
||||
0x2aa6_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.cgm_feature", 0x2aa8_u32),
|
||||
("org.bluetooth.characteristic.cgm_measurement", 0x2aa7_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.cgm_session_run_time",
|
||||
0x2aab_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.cgm_session_start_time",
|
||||
0x2aaa_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.cgm_specific_ops_control_point",
|
||||
0x2aac_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.cgm_status", 0x2aa9_u32),
|
||||
("org.bluetooth.characteristic.csc_feature", 0x2a5c_u32),
|
||||
("org.bluetooth.characteristic.csc_measurement", 0x2a5b_u32),
|
||||
("org.bluetooth.characteristic.current_time", 0x2a2b_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.cycling_power_control_point",
|
||||
0x2a66_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.cycling_power_feature",
|
||||
0x2a65_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.cycling_power_measurement",
|
||||
0x2a63_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.cycling_power_vector",
|
||||
0x2a64_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.database_change_increment",
|
||||
0x2a99_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.date_of_birth", 0x2a85_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.date_of_threshold_assessment",
|
||||
0x2a86_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.date_time", 0x2a08_u32),
|
||||
("org.bluetooth.characteristic.day_date_time", 0x2a0a_u32),
|
||||
("org.bluetooth.characteristic.day_of_week", 0x2a09_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.descriptor_value_changed",
|
||||
0x2a7d_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.gap.device_name", 0x2a00_u32),
|
||||
("org.bluetooth.characteristic.dew_point", 0x2a7b_u32),
|
||||
("org.bluetooth.characteristic.digital", 0x2a56_u32),
|
||||
("org.bluetooth.characteristic.dst_offset", 0x2a0d_u32),
|
||||
("org.bluetooth.characteristic.elevation", 0x2a6c_u32),
|
||||
("org.bluetooth.characteristic.email_address", 0x2a87_u32),
|
||||
("org.bluetooth.characteristic.exact_time_256", 0x2a0c_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.fat_burn_heart_rate_lower_limit",
|
||||
0x2a88_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.fat_burn_heart_rate_upper_limit",
|
||||
0x2a89_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.firmware_revision_string",
|
||||
0x2a26_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.first_name", 0x2a8a_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.five_zone_heart_rate_limits",
|
||||
0x2a8b_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.floor_number", 0x2ab2_u32),
|
||||
("org.bluetooth.characteristic.gender", 0x2a8c_u32),
|
||||
("org.bluetooth.characteristic.glucose_feature", 0x2a51_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.glucose_measurement",
|
||||
0x2a18_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.glucose_measurement_context",
|
||||
0x2a34_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.gust_factor", 0x2a74_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.hardware_revision_string",
|
||||
0x2a27_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.heart_rate_control_point",
|
||||
0x2a39_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.heart_rate_max", 0x2a8d_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.heart_rate_measurement",
|
||||
0x2a37_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.heat_index", 0x2a7a_u32),
|
||||
("org.bluetooth.characteristic.height", 0x2a8e_u32),
|
||||
("org.bluetooth.characteristic.hid_control_point", 0x2a4c_u32),
|
||||
("org.bluetooth.characteristic.hid_information", 0x2a4a_u32),
|
||||
("org.bluetooth.characteristic.hip_circumference", 0x2a8f_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.http_control_point",
|
||||
0x2aba_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.http_entity_body", 0x2ab9_u32),
|
||||
("org.bluetooth.characteristic.http_headers", 0x2ab7_u32),
|
||||
("org.bluetooth.characteristic.http_status_code", 0x2ab8_u32),
|
||||
("org.bluetooth.characteristic.https_security", 0x2abb_u32),
|
||||
("org.bluetooth.characteristic.humidity", 0x2a6f_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.ieee_11073-20601_regulatory_certification_data_list",
|
||||
0x2a2a_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.indoor_positioning_configuration",
|
||||
0x2aad_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.intermediate_cuff_pressure",
|
||||
0x2a36_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.intermediate_temperature",
|
||||
0x2a1e_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.irradiance", 0x2a77_u32),
|
||||
("org.bluetooth.characteristic.language", 0x2aa2_u32),
|
||||
("org.bluetooth.characteristic.last_name", 0x2a90_u32),
|
||||
("org.bluetooth.characteristic.latitude", 0x2aae_u32),
|
||||
("org.bluetooth.characteristic.ln_control_point", 0x2a6b_u32),
|
||||
("org.bluetooth.characteristic.ln_feature", 0x2a6a_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.local_east_coordinate.xml",
|
||||
0x2ab1_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.local_north_coordinate",
|
||||
0x2ab0_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.local_time_information",
|
||||
0x2a0f_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.location_and_speed",
|
||||
0x2a67_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.location_name", 0x2ab5_u32),
|
||||
("org.bluetooth.characteristic.longitude", 0x2aaf_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.magnetic_declination",
|
||||
0x2a2c_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.magnetic_flux_density_2d",
|
||||
0x2aa0_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.magnetic_flux_density_3d",
|
||||
0x2aa1_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.manufacturer_name_string",
|
||||
0x2a29_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.maximum_recommended_heart_rate",
|
||||
0x2a91_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.measurement_interval",
|
||||
0x2a21_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.model_number_string",
|
||||
0x2a24_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.navigation", 0x2a68_u32),
|
||||
("org.bluetooth.characteristic.new_alert", 0x2a46_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.object_action_control_point",
|
||||
0x2ac5_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.object_changed", 0x2ac8_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.object_first_created",
|
||||
0x2ac1_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.object_id", 0x2ac3_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.object_last_modified",
|
||||
0x2ac2_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.object_list_control_point",
|
||||
0x2ac6_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.object_list_filter",
|
||||
0x2ac7_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.object_name", 0x2abe_u32),
|
||||
("org.bluetooth.characteristic.object_properties", 0x2ac4_u32),
|
||||
("org.bluetooth.characteristic.object_size", 0x2ac0_u32),
|
||||
("org.bluetooth.characteristic.object_type", 0x2abf_u32),
|
||||
("org.bluetooth.characteristic.ots_feature", 0x2abd_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.gap.peripheral_preferred_connection_parameters",
|
||||
0x2a04_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.gap.peripheral_privacy_flag",
|
||||
0x2a02_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.plx_continuous_measurement",
|
||||
0x2a5f_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.plx_features", 0x2a60_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.plx_spot_check_measurement",
|
||||
0x2a5e_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.pnp_id", 0x2a50_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.pollen_concentration",
|
||||
0x2a75_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.position_quality", 0x2a69_u32),
|
||||
("org.bluetooth.characteristic.pressure", 0x2a6d_u32),
|
||||
("org.bluetooth.characteristic.protocol_mode", 0x2a4e_u32),
|
||||
("org.bluetooth.characteristic.rainfall", 0x2a78_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.gap.reconnection_address",
|
||||
0x2a03_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.record_access_control_point",
|
||||
0x2a52_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.reference_time_information",
|
||||
0x2a14_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.report", 0x2a4d_u32),
|
||||
("org.bluetooth.characteristic.report_map", 0x2a4b_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.resting_heart_rate",
|
||||
0x2a92_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.ringer_control_point",
|
||||
0x2a40_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.ringer_setting", 0x2a41_u32),
|
||||
("org.bluetooth.characteristic.rsc_feature", 0x2a54_u32),
|
||||
("org.bluetooth.characteristic.rsc_measurement", 0x2a53_u32),
|
||||
("org.bluetooth.characteristic.sc_control_point", 0x2a55_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.scan_interval_window",
|
||||
0x2a4f_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.scan_refresh", 0x2a31_u32),
|
||||
("org.bluetooth.characteristic.sensor_location", 0x2a5d_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.serial_number_string",
|
||||
0x2a25_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.gatt.service_changed",
|
||||
0x2a05_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.software_revision_string",
|
||||
0x2a28_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.sport_type_for_aerobic_and_anaerobic_thresholds",
|
||||
0x2a93_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.supported_new_alert_category",
|
||||
0x2a47_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.supported_unread_alert_category",
|
||||
0x2a48_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.system_id", 0x2a23_u32),
|
||||
("org.bluetooth.characteristic.tds_control_point", 0x2abc_u32),
|
||||
("org.bluetooth.characteristic.temperature", 0x2a6e_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.temperature_measurement",
|
||||
0x2a1c_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.temperature_type", 0x2a1d_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.three_zone_heart_rate_limits",
|
||||
0x2a94_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.time_accuracy", 0x2a12_u32),
|
||||
("org.bluetooth.characteristic.time_source", 0x2a13_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.time_update_control_point",
|
||||
0x2a16_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.time_update_state", 0x2a17_u32),
|
||||
("org.bluetooth.characteristic.time_with_dst", 0x2a11_u32),
|
||||
("org.bluetooth.characteristic.time_zone", 0x2a0e_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.true_wind_direction",
|
||||
0x2a71_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.true_wind_speed", 0x2a70_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.two_zone_heart_rate_limit",
|
||||
0x2a95_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.tx_power_level", 0x2a07_u32),
|
||||
("org.bluetooth.characteristic.uncertainty", 0x2ab4_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.unread_alert_status",
|
||||
0x2a45_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.uri", 0x2ab6_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.user_control_point",
|
||||
0x2a9f_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.user_index", 0x2a9a_u32),
|
||||
("org.bluetooth.characteristic.uv_index", 0x2a76_u32),
|
||||
("org.bluetooth.characteristic.vo2_max", 0x2a96_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.waist_circumference",
|
||||
0x2a97_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.weight", 0x2a98_u32),
|
||||
(
|
||||
"org.bluetooth.characteristic.weight_measurement",
|
||||
0x2a9d_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.characteristic.weight_scale_feature",
|
||||
0x2a9e_u32,
|
||||
),
|
||||
("org.bluetooth.characteristic.wind_chill", 0x2a79_u32),
|
||||
];
|
||||
|
||||
//https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx
|
||||
const BLUETOOTH_ASSIGNED_DESCRIPTORS: &[(&str, u32)] = &[
|
||||
(
|
||||
"org.bluetooth.descriptor.gatt.characteristic_extended_properties",
|
||||
0x2900_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.descriptor.gatt.characteristic_user_description",
|
||||
0x2901_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.descriptor.gatt.client_characteristic_configuration",
|
||||
0x2902_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.descriptor.gatt.server_characteristic_configuration",
|
||||
0x2903_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.descriptor.gatt.characteristic_presentation_format",
|
||||
0x2904_u32,
|
||||
),
|
||||
(
|
||||
"org.bluetooth.descriptor.gatt.characteristic_aggregate_format",
|
||||
0x2905_u32,
|
||||
),
|
||||
("org.bluetooth.descriptor.valid_range", 0x2906_u32),
|
||||
(
|
||||
"org.bluetooth.descriptor.external_report_reference",
|
||||
0x2907_u32,
|
||||
),
|
||||
("org.bluetooth.descriptor.report_reference", 0x2908_u32),
|
||||
("org.bluetooth.descriptor.number_of_digitals", 0x2909_u32),
|
||||
("org.bluetooth.descriptor.value_trigger_setting", 0x290a_u32),
|
||||
("org.bluetooth.descriptor.es_configuration", 0x290b_u32),
|
||||
("org.bluetooth.descriptor.es_measurement", 0x290c_u32),
|
||||
("org.bluetooth.descriptor.es_trigger_setting", 0x290d_u32),
|
||||
("org.bluetooth.descriptor.time_trigger_setting", 0x290e_u32),
|
||||
];
|
||||
|
||||
const BASE_UUID: &str = "-0000-1000-8000-00805f9b34fb";
|
||||
const SERVICE_PREFIX: &str = "org.bluetooth.service";
|
||||
const CHARACTERISTIC_PREFIX: &str = "org.bluetooth.characteristic";
|
||||
const DESCRIPTOR_PREFIX: &str = "org.bluetooth.descriptor";
|
||||
const VALID_UUID_REGEX: &str = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$";
|
||||
// https://cs.chromium.org/chromium/src/third_party/WebKit/Source/modules/bluetooth/BluetoothUUID.cpp?l=314
|
||||
const UUID_ERROR_MESSAGE: &str = "It must be a valid UUID alias (e.g. 0x1234), \
|
||||
UUID (lowercase hex characters e.g. '00001234-0000-1000-8000-00805f9b34fb'),\nor recognized standard name from";
|
||||
// https://cs.chromium.org/chromium/src/third_party/WebKit/Source/modules/bluetooth/BluetoothUUID.cpp?l=321
|
||||
const SERVICES_ERROR_MESSAGE: &str =
|
||||
"https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx\
|
||||
\ne.g. 'alert_notification'.";
|
||||
// https://cs.chromium.org/chromium/src/third_party/WebKit/Source/modules/bluetooth/BluetoothUUID.cpp?l=327
|
||||
const CHARACTERISTIC_ERROR_MESSAGE: &str =
|
||||
"https://developer.bluetooth.org/gatt/characteristics/Pages/\
|
||||
CharacteristicsHome.aspx\ne.g. 'aerobic_heart_rate_lower_limit'.";
|
||||
// https://cs.chromium.org/chromium/src/third_party/WebKit/Source/modules/bluetooth/BluetoothUUID.cpp?l=333
|
||||
const DESCRIPTOR_ERROR_MESSAGE: &str = "https://developer.bluetooth.org/gatt/descriptors/Pages/\
|
||||
DescriptorsHomePage.aspx\ne.g. 'gatt.characteristic_presentation_format'.";
|
||||
|
||||
impl BluetoothUUIDMethods<crate::DomTypeHolder> for BluetoothUUID {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-canonicaluuid
|
||||
fn CanonicalUUID(_: &Window, alias: u32) -> UUID {
|
||||
canonical_uuid(alias)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-getservice
|
||||
fn GetService(_: &Window, name: BluetoothServiceUUID) -> Fallible<UUID> {
|
||||
Self::service(name)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-getcharacteristic
|
||||
fn GetCharacteristic(_: &Window, name: BluetoothCharacteristicUUID) -> Fallible<UUID> {
|
||||
Self::characteristic(name)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-getdescriptor
|
||||
fn GetDescriptor(_: &Window, name: BluetoothDescriptorUUID) -> Fallible<UUID> {
|
||||
Self::descriptor(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl BluetoothUUID {
|
||||
pub(crate) fn service(name: BluetoothServiceUUID) -> Fallible<UUID> {
|
||||
resolve_uuid_name(name, BLUETOOTH_ASSIGNED_SERVICES, SERVICE_PREFIX)
|
||||
}
|
||||
|
||||
pub(crate) fn characteristic(name: BluetoothCharacteristicUUID) -> Fallible<UUID> {
|
||||
resolve_uuid_name(
|
||||
name,
|
||||
BLUETOOTH_ASSIGNED_CHARCTERISTICS,
|
||||
CHARACTERISTIC_PREFIX,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn descriptor(name: BluetoothDescriptorUUID) -> Fallible<UUID> {
|
||||
resolve_uuid_name(name, BLUETOOTH_ASSIGNED_DESCRIPTORS, DESCRIPTOR_PREFIX)
|
||||
}
|
||||
}
|
||||
|
||||
fn canonical_uuid(alias: u32) -> UUID {
|
||||
UUID::from(format!("{:08x}", &alias) + BASE_UUID)
|
||||
}
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/#resolveuuidname
|
||||
fn resolve_uuid_name(
|
||||
name: StringOrUnsignedLong,
|
||||
assigned_numbers_table: &'static [(&'static str, u32)],
|
||||
prefix: &str,
|
||||
) -> Fallible<DOMString> {
|
||||
match name {
|
||||
// Step 1.
|
||||
StringOrUnsignedLong::UnsignedLong(unsigned32) => Ok(canonical_uuid(unsigned32)),
|
||||
StringOrUnsignedLong::String(dstring) => {
|
||||
// Step 2.
|
||||
let regex = Regex::new(VALID_UUID_REGEX).unwrap();
|
||||
if regex.is_match(&dstring) {
|
||||
Ok(dstring)
|
||||
} else {
|
||||
// Step 3.
|
||||
let concatenated = format!("{}.{}", prefix, dstring);
|
||||
let is_in_table = assigned_numbers_table.iter().find(|p| p.0 == concatenated);
|
||||
match is_in_table {
|
||||
Some(&(_, alias)) => Ok(canonical_uuid(alias)),
|
||||
None => {
|
||||
let (attribute_type, error_url_message) = match prefix {
|
||||
SERVICE_PREFIX => ("Service", SERVICES_ERROR_MESSAGE),
|
||||
CHARACTERISTIC_PREFIX => {
|
||||
("Characteristic", CHARACTERISTIC_ERROR_MESSAGE)
|
||||
},
|
||||
DESCRIPTOR_PREFIX => ("Descriptor", DESCRIPTOR_ERROR_MESSAGE),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
// Step 4.
|
||||
Err(Type(format!(
|
||||
"Invalid {} name : '{}'.\n{} {}",
|
||||
attribute_type, dstring, UUID_ERROR_MESSAGE, error_url_message
|
||||
)))
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
16
components/script/dom/bluetooth/mod.rs
Normal file
16
components/script/dom/bluetooth/mod.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub(crate) use self::bluetooth::*;
|
||||
pub(crate) mod bluetooth;
|
||||
pub(crate) mod bluetoothadvertisingevent;
|
||||
pub(crate) mod bluetoothcharacteristicproperties;
|
||||
pub(crate) mod bluetoothdevice;
|
||||
pub(crate) mod bluetoothpermissionresult;
|
||||
pub(crate) mod bluetoothremotegattcharacteristic;
|
||||
pub(crate) mod bluetoothremotegattdescriptor;
|
||||
pub(crate) mod bluetoothremotegattserver;
|
||||
pub(crate) mod bluetoothremotegattservice;
|
||||
pub(crate) mod bluetoothuuid;
|
||||
pub(crate) mod testrunner;
|
54
components/script/dom/bluetooth/testrunner.rs
Normal file
54
components/script/dom/bluetooth/testrunner.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use bluetooth_traits::BluetoothRequest;
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use profile_traits::ipc;
|
||||
|
||||
use crate::conversions::Convert;
|
||||
use crate::dom::bindings::codegen::Bindings::TestRunnerBinding::TestRunnerMethods;
|
||||
use crate::dom::bindings::error::ErrorResult;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal, Reflector};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/tests#test-runner
|
||||
#[dom_struct]
|
||||
pub(crate) struct TestRunner {
|
||||
reflector_: Reflector,
|
||||
}
|
||||
|
||||
impl TestRunner {
|
||||
pub(crate) fn new_inherited() -> TestRunner {
|
||||
TestRunner {
|
||||
reflector_: Reflector::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(global: &GlobalScope) -> DomRoot<TestRunner> {
|
||||
reflect_dom_object(Box::new(TestRunner::new_inherited()), global, CanGc::note())
|
||||
}
|
||||
|
||||
fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
|
||||
self.global().as_window().bluetooth_thread()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestRunnerMethods<crate::DomTypeHolder> for TestRunner {
|
||||
// https://webbluetoothcg.github.io/web-bluetooth/tests#setBluetoothMockDataSet
|
||||
#[allow(non_snake_case)]
|
||||
fn SetBluetoothMockDataSet(&self, dataSetName: DOMString) -> ErrorResult {
|
||||
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
|
||||
self.get_bluetooth_thread()
|
||||
.send(BluetoothRequest::Test(String::from(dataSetName), sender))
|
||||
.unwrap();
|
||||
match receiver.recv().unwrap() {
|
||||
Ok(()) => Ok(()),
|
||||
Err(error) => Err(error.convert()),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue