script/dom/bluetooth/
bluetoothremotegattcharacteristic.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::rc::Rc;
6
7use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted};
8use bluetooth_traits::{BluetoothRequest, BluetoothResponse, GATTType};
9use dom_struct::dom_struct;
10use ipc_channel::ipc::IpcSender;
11
12use crate::dom::bindings::cell::DomRefCell;
13use crate::dom::bindings::codegen::Bindings::BluetoothCharacteristicPropertiesBinding::BluetoothCharacteristicPropertiesMethods;
14use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding::BluetoothRemoteGATTCharacteristicMethods;
15use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
16use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods;
17use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
18use crate::dom::bindings::error::Error::{
19    self, InvalidModification, Network, NotSupported, Security,
20};
21use crate::dom::bindings::inheritance::Castable;
22use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
23use crate::dom::bindings::root::{Dom, DomRoot};
24use crate::dom::bindings::str::{ByteString, DOMString};
25use crate::dom::bluetooth::{AsyncBluetoothListener, get_gatt_children, response_async};
26use crate::dom::bluetoothcharacteristicproperties::BluetoothCharacteristicProperties;
27use crate::dom::bluetoothremotegattservice::BluetoothRemoteGATTService;
28use crate::dom::bluetoothuuid::{BluetoothDescriptorUUID, BluetoothUUID};
29use crate::dom::eventtarget::EventTarget;
30use crate::dom::globalscope::GlobalScope;
31use crate::dom::promise::Promise;
32use crate::realms::InRealm;
33use crate::script_runtime::CanGc;
34
35// Maximum length of an attribute value.
36// https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=286439 (Vol. 3, page 2169)
37pub(crate) const MAXIMUM_ATTRIBUTE_LENGTH: usize = 512;
38
39// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattcharacteristic
40#[dom_struct]
41pub(crate) struct BluetoothRemoteGATTCharacteristic {
42    eventtarget: EventTarget,
43    service: Dom<BluetoothRemoteGATTService>,
44    uuid: DOMString,
45    properties: Dom<BluetoothCharacteristicProperties>,
46    value: DomRefCell<Option<ByteString>>,
47    instance_id: String,
48}
49
50impl BluetoothRemoteGATTCharacteristic {
51    pub(crate) fn new_inherited(
52        service: &BluetoothRemoteGATTService,
53        uuid: DOMString,
54        properties: &BluetoothCharacteristicProperties,
55        instance_id: String,
56    ) -> BluetoothRemoteGATTCharacteristic {
57        BluetoothRemoteGATTCharacteristic {
58            eventtarget: EventTarget::new_inherited(),
59            service: Dom::from_ref(service),
60            uuid,
61            properties: Dom::from_ref(properties),
62            value: DomRefCell::new(None),
63            instance_id,
64        }
65    }
66
67    pub(crate) fn new(
68        global: &GlobalScope,
69        service: &BluetoothRemoteGATTService,
70        uuid: DOMString,
71        properties: &BluetoothCharacteristicProperties,
72        instance_id: String,
73        can_gc: CanGc,
74    ) -> DomRoot<BluetoothRemoteGATTCharacteristic> {
75        reflect_dom_object(
76            Box::new(BluetoothRemoteGATTCharacteristic::new_inherited(
77                service,
78                uuid,
79                properties,
80                instance_id,
81            )),
82            global,
83            can_gc,
84        )
85    }
86
87    fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
88        self.global().as_window().bluetooth_thread()
89    }
90
91    fn get_instance_id(&self) -> String {
92        self.instance_id.clone()
93    }
94}
95
96impl BluetoothRemoteGATTCharacteristicMethods<crate::DomTypeHolder>
97    for BluetoothRemoteGATTCharacteristic
98{
99    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-properties
100    fn Properties(&self) -> DomRoot<BluetoothCharacteristicProperties> {
101        DomRoot::from_ref(&self.properties)
102    }
103
104    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-service
105    fn Service(&self) -> DomRoot<BluetoothRemoteGATTService> {
106        DomRoot::from_ref(&self.service)
107    }
108
109    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-uuid
110    fn Uuid(&self) -> DOMString {
111        self.uuid.clone()
112    }
113
114    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-getdescriptor
115    fn GetDescriptor(&self, descriptor: BluetoothDescriptorUUID, can_gc: CanGc) -> Rc<Promise> {
116        get_gatt_children(
117            self,
118            true,
119            BluetoothUUID::descriptor,
120            Some(descriptor),
121            self.get_instance_id(),
122            self.Service().Device().get_gatt().Connected(),
123            GATTType::Descriptor,
124            can_gc,
125        )
126    }
127
128    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-getdescriptors
129    fn GetDescriptors(
130        &self,
131        descriptor: Option<BluetoothDescriptorUUID>,
132        can_gc: CanGc,
133    ) -> Rc<Promise> {
134        get_gatt_children(
135            self,
136            false,
137            BluetoothUUID::descriptor,
138            descriptor,
139            self.get_instance_id(),
140            self.Service().Device().get_gatt().Connected(),
141            GATTType::Descriptor,
142            can_gc,
143        )
144    }
145
146    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-value
147    fn GetValue(&self) -> Option<ByteString> {
148        self.value.borrow().clone()
149    }
150
151    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-readvalue
152    fn ReadValue(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
153        let p = Promise::new_in_current_realm(comp, can_gc);
154
155        // Step 1.
156        if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Reads) {
157            p.reject_error(Security, can_gc);
158            return p;
159        }
160
161        // Step 2.
162        if !self.Service().Device().get_gatt().Connected() {
163            p.reject_error(Network, can_gc);
164            return p;
165        }
166
167        // TODO: Step 5: Implement the `connection-checking-wrapper` algorithm for BluetoothRemoteGATTServer.
168
169        // Step 5.1.
170        if !self.Properties().Read() {
171            p.reject_error(NotSupported, can_gc);
172            return p;
173        }
174
175        // Note: Steps 3 - 4 and the remaining substeps of Step 5 are implemented in components/bluetooth/lib.rs
176        // in readValue function and in handle_response function.
177        let sender = response_async(&p, self);
178        self.get_bluetooth_thread()
179            .send(BluetoothRequest::ReadValue(self.get_instance_id(), sender))
180            .unwrap();
181        p
182    }
183
184    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-writevalue
185    fn WriteValue(
186        &self,
187        value: ArrayBufferViewOrArrayBuffer,
188        comp: InRealm,
189        can_gc: CanGc,
190    ) -> Rc<Promise> {
191        let p = Promise::new_in_current_realm(comp, can_gc);
192
193        // Step 1.
194        if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Writes) {
195            p.reject_error(Security, can_gc);
196            return p;
197        }
198
199        // Step 2 - 3.
200        let vec = match value {
201            ArrayBufferViewOrArrayBuffer::ArrayBufferView(avb) => avb.to_vec(),
202            ArrayBufferViewOrArrayBuffer::ArrayBuffer(ab) => ab.to_vec(),
203        };
204
205        if vec.len() > MAXIMUM_ATTRIBUTE_LENGTH {
206            p.reject_error(InvalidModification, can_gc);
207            return p;
208        }
209
210        // Step 4.
211        if !self.Service().Device().get_gatt().Connected() {
212            p.reject_error(Network, can_gc);
213            return p;
214        }
215
216        // TODO: Step 7: Implement the `connection-checking-wrapper` algorithm for BluetoothRemoteGATTServer.
217
218        // Step 7.1.
219        if !(self.Properties().Write() ||
220            self.Properties().WriteWithoutResponse() ||
221            self.Properties().AuthenticatedSignedWrites())
222        {
223            p.reject_error(NotSupported, can_gc);
224            return p;
225        }
226
227        // Note: Steps 5 - 6 and the remaining substeps of Step 7 are implemented in components/bluetooth/lib.rs
228        // in writeValue function and in handle_response function.
229        let sender = response_async(&p, self);
230        self.get_bluetooth_thread()
231            .send(BluetoothRequest::WriteValue(
232                self.get_instance_id(),
233                vec,
234                sender,
235            ))
236            .unwrap();
237        p
238    }
239
240    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-startnotifications
241    fn StartNotifications(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
242        let p = Promise::new_in_current_realm(comp, can_gc);
243
244        // Step 1.
245        if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Reads) {
246            p.reject_error(Security, can_gc);
247            return p;
248        }
249
250        // Step 2.
251        if !self.Service().Device().get_gatt().Connected() {
252            p.reject_error(Network, can_gc);
253            return p;
254        }
255
256        // Step 5.
257        if !(self.Properties().Notify() || self.Properties().Indicate()) {
258            p.reject_error(NotSupported, can_gc);
259            return p;
260        }
261
262        // TODO: Step 6: Implement `active notification context set` for BluetoothRemoteGATTCharacteristic.
263
264        // Note: Steps 3 - 4, 7 - 11 are implemented in components/bluetooth/lib.rs in enable_notification function
265        // and in handle_response function.
266        let sender = response_async(&p, self);
267        self.get_bluetooth_thread()
268            .send(BluetoothRequest::EnableNotification(
269                self.get_instance_id(),
270                true,
271                sender,
272            ))
273            .unwrap();
274        p
275    }
276
277    // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-stopnotifications
278    fn StopNotifications(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
279        let p = Promise::new_in_current_realm(comp, can_gc);
280        let sender = response_async(&p, self);
281
282        // TODO: Step 3 - 4: Implement `active notification context set` for BluetoothRemoteGATTCharacteristic,
283
284        // Note: Steps 1 - 2, and part of Step 4 and Step 5 are implemented in components/bluetooth/lib.rs
285        // in enable_notification function and in handle_response function.
286        self.get_bluetooth_thread()
287            .send(BluetoothRequest::EnableNotification(
288                self.get_instance_id(),
289                false,
290                sender,
291            ))
292            .unwrap();
293        p
294    }
295
296    // https://webbluetoothcg.github.io/web-bluetooth/#dom-characteristiceventhandlers-oncharacteristicvaluechanged
297    event_handler!(
298        characteristicvaluechanged,
299        GetOncharacteristicvaluechanged,
300        SetOncharacteristicvaluechanged
301    );
302}
303
304impl AsyncBluetoothListener for BluetoothRemoteGATTCharacteristic {
305    fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, can_gc: CanGc) {
306        let device = self.Service().Device();
307        match response {
308            // https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
309            // Step 7.
310            BluetoothResponse::GetDescriptors(descriptors_vec, single) => {
311                if single {
312                    promise.resolve_native(
313                        &device.get_or_create_descriptor(&descriptors_vec[0], self, can_gc),
314                        can_gc,
315                    );
316                    return;
317                }
318                let mut descriptors = vec![];
319                for descriptor in descriptors_vec {
320                    let bt_descriptor = device.get_or_create_descriptor(&descriptor, self, can_gc);
321                    descriptors.push(bt_descriptor);
322                }
323                promise.resolve_native(&descriptors, can_gc);
324            },
325            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-readvalue
326            BluetoothResponse::ReadValue(result) => {
327                // TODO: Step 5.5.1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
328
329                // Step 5.5.2.
330                // TODO(#5014): Replace ByteString with ArrayBuffer when it is implemented.
331                let value = ByteString::new(result);
332                *self.value.borrow_mut() = Some(value.clone());
333
334                // Step 5.5.3.
335                self.upcast::<EventTarget>()
336                    .fire_bubbling_event(atom!("characteristicvaluechanged"), can_gc);
337
338                // Step 5.5.4.
339                promise.resolve_native(&value, can_gc);
340            },
341            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-writevalue
342            BluetoothResponse::WriteValue(result) => {
343                // TODO: Step 7.5.1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
344
345                // Step 7.5.2.
346                // TODO(#5014): Replace ByteString with an ArrayBuffer wrapped in a DataView.
347                *self.value.borrow_mut() = Some(ByteString::new(result));
348
349                // Step 7.5.3.
350                promise.resolve_native(&(), can_gc);
351            },
352            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-startnotifications
353            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-stopnotifications
354            BluetoothResponse::EnableNotification(_result) => {
355                // (StartNotification) TODO: Step 10:  Implement `active notification context set`
356                // for BluetoothRemoteGATTCharacteristic.
357
358                // (StartNotification) Step 11.
359                // (StopNotification)  Step 5.
360                promise.resolve_native(self, can_gc);
361            },
362            _ => promise.reject_error(Error::Type("Something went wrong...".to_owned()), can_gc),
363        }
364    }
365}