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 base::generic_channel::GenericSender;
8use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted};
9use bluetooth_traits::{BluetoothRequest, BluetoothResponse, GATTType};
10use dom_struct::dom_struct;
11use js::realm::CurrentRealm;
12
13use crate::dom::bindings::cell::DomRefCell;
14use crate::dom::bindings::codegen::Bindings::BluetoothCharacteristicPropertiesBinding::BluetoothCharacteristicPropertiesMethods;
15use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding::BluetoothRemoteGATTCharacteristicMethods;
16use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
17use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods;
18use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
19use crate::dom::bindings::error::Error::{
20    self, InvalidModification, Network, NotSupported, Security,
21};
22use crate::dom::bindings::inheritance::Castable;
23use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_cx};
24use crate::dom::bindings::root::{Dom, DomRoot};
25use crate::dom::bindings::str::{ByteString, DOMString};
26use crate::dom::bluetooth::{AsyncBluetoothListener, get_gatt_children, response_async};
27use crate::dom::bluetoothcharacteristicproperties::BluetoothCharacteristicProperties;
28use crate::dom::bluetoothremotegattservice::BluetoothRemoteGATTService;
29use crate::dom::bluetoothuuid::{BluetoothDescriptorUUID, BluetoothUUID};
30use crate::dom::eventtarget::EventTarget;
31use crate::dom::globalscope::GlobalScope;
32use crate::dom::promise::Promise;
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        cx: &mut js::context::JSContext,
69        global: &GlobalScope,
70        service: &BluetoothRemoteGATTService,
71        uuid: DOMString,
72        properties: &BluetoothCharacteristicProperties,
73        instance_id: String,
74    ) -> DomRoot<BluetoothRemoteGATTCharacteristic> {
75        reflect_dom_object_with_cx(
76            Box::new(BluetoothRemoteGATTCharacteristic::new_inherited(
77                service,
78                uuid,
79                properties,
80                instance_id,
81            )),
82            global,
83            cx,
84        )
85    }
86
87    fn get_bluetooth_thread(&self) -> GenericSender<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(
116        &self,
117        cx: &mut CurrentRealm,
118        descriptor: BluetoothDescriptorUUID,
119    ) -> Rc<Promise> {
120        let is_connected = self.Service().Device().get_gatt(cx).Connected();
121        get_gatt_children(
122            cx,
123            self,
124            true,
125            BluetoothUUID::descriptor,
126            Some(descriptor),
127            self.get_instance_id(),
128            is_connected,
129            GATTType::Descriptor,
130        )
131    }
132
133    /// <https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-getdescriptors>
134    fn GetDescriptors(
135        &self,
136        cx: &mut CurrentRealm,
137        descriptor: Option<BluetoothDescriptorUUID>,
138    ) -> Rc<Promise> {
139        let is_connected = self.Service().Device().get_gatt(cx).Connected();
140        get_gatt_children(
141            cx,
142            self,
143            false,
144            BluetoothUUID::descriptor,
145            descriptor,
146            self.get_instance_id(),
147            is_connected,
148            GATTType::Descriptor,
149        )
150    }
151
152    /// <https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-value>
153    fn GetValue(&self) -> Option<ByteString> {
154        self.value.borrow().clone()
155    }
156
157    /// <https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-readvalue>
158    fn ReadValue(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
159        let p = Promise::new_in_realm(cx);
160
161        // Step 1.
162        if uuid_is_blocklisted(&self.uuid.str(), Blocklist::Reads) {
163            p.reject_error(Security(None), CanGc::from_cx(cx));
164            return p;
165        }
166
167        // Step 2.
168        if !self.Service().Device().get_gatt(cx).Connected() {
169            p.reject_error(Network(None), CanGc::from_cx(cx));
170            return p;
171        }
172
173        // TODO: Step 5: Implement the `connection-checking-wrapper` algorithm for BluetoothRemoteGATTServer.
174
175        // Step 5.1.
176        if !self.Properties().Read() {
177            p.reject_error(NotSupported(None), CanGc::from_cx(cx));
178            return p;
179        }
180
181        // Note: Steps 3 - 4 and the remaining substeps of Step 5 are implemented in components/bluetooth/lib.rs
182        // in readValue function and in handle_response function.
183        let sender = response_async(&p, self);
184        self.get_bluetooth_thread()
185            .send(BluetoothRequest::ReadValue(self.get_instance_id(), sender))
186            .unwrap();
187        p
188    }
189
190    /// <https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-writevalue>
191    fn WriteValue(
192        &self,
193        cx: &mut CurrentRealm,
194        value: ArrayBufferViewOrArrayBuffer,
195    ) -> Rc<Promise> {
196        let p = Promise::new_in_realm(cx);
197
198        // Step 1.
199        if uuid_is_blocklisted(&self.uuid.str(), Blocklist::Writes) {
200            p.reject_error(Security(None), CanGc::from_cx(cx));
201            return p;
202        }
203
204        // Step 2 - 3.
205        let vec = match value {
206            ArrayBufferViewOrArrayBuffer::ArrayBufferView(avb) => avb.to_vec(),
207            ArrayBufferViewOrArrayBuffer::ArrayBuffer(ab) => ab.to_vec(),
208        };
209
210        if vec.len() > MAXIMUM_ATTRIBUTE_LENGTH {
211            p.reject_error(InvalidModification(None), CanGc::from_cx(cx));
212            return p;
213        }
214
215        // Step 4.
216        if !self.Service().Device().get_gatt(cx).Connected() {
217            p.reject_error(Network(None), CanGc::from_cx(cx));
218            return p;
219        }
220
221        // TODO: Step 7: Implement the `connection-checking-wrapper` algorithm for BluetoothRemoteGATTServer.
222
223        // Step 7.1.
224        if !(self.Properties().Write() ||
225            self.Properties().WriteWithoutResponse() ||
226            self.Properties().AuthenticatedSignedWrites())
227        {
228            p.reject_error(NotSupported(None), CanGc::from_cx(cx));
229            return p;
230        }
231
232        // Note: Steps 5 - 6 and the remaining substeps of Step 7 are implemented in components/bluetooth/lib.rs
233        // in writeValue function and in handle_response function.
234        let sender = response_async(&p, self);
235        self.get_bluetooth_thread()
236            .send(BluetoothRequest::WriteValue(
237                self.get_instance_id(),
238                vec,
239                sender,
240            ))
241            .unwrap();
242        p
243    }
244
245    /// <https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-startnotifications>
246    fn StartNotifications(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
247        let p = Promise::new_in_realm(cx);
248
249        // Step 1.
250        if uuid_is_blocklisted(&self.uuid.str(), Blocklist::Reads) {
251            p.reject_error(Security(None), CanGc::from_cx(cx));
252            return p;
253        }
254
255        // Step 2.
256        if !self.Service().Device().get_gatt(cx).Connected() {
257            p.reject_error(Network(None), CanGc::from_cx(cx));
258            return p;
259        }
260
261        // Step 5.
262        if !(self.Properties().Notify() || self.Properties().Indicate()) {
263            p.reject_error(NotSupported(None), CanGc::from_cx(cx));
264            return p;
265        }
266
267        // TODO: Step 6: Implement `active notification context set` for BluetoothRemoteGATTCharacteristic.
268
269        // Note: Steps 3 - 4, 7 - 11 are implemented in components/bluetooth/lib.rs in enable_notification function
270        // and in handle_response function.
271        let sender = response_async(&p, self);
272        self.get_bluetooth_thread()
273            .send(BluetoothRequest::EnableNotification(
274                self.get_instance_id(),
275                true,
276                sender,
277            ))
278            .unwrap();
279        p
280    }
281
282    /// <https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-stopnotifications>
283    fn StopNotifications(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
284        let p = Promise::new_in_realm(cx);
285        let sender = response_async(&p, self);
286
287        // TODO: Step 3 - 4: Implement `active notification context set` for BluetoothRemoteGATTCharacteristic,
288
289        // Note: Steps 1 - 2, and part of Step 4 and Step 5 are implemented in components/bluetooth/lib.rs
290        // in enable_notification function and in handle_response function.
291        self.get_bluetooth_thread()
292            .send(BluetoothRequest::EnableNotification(
293                self.get_instance_id(),
294                false,
295                sender,
296            ))
297            .unwrap();
298        p
299    }
300
301    // https://webbluetoothcg.github.io/web-bluetooth/#dom-characteristiceventhandlers-oncharacteristicvaluechanged
302    event_handler!(
303        characteristicvaluechanged,
304        GetOncharacteristicvaluechanged,
305        SetOncharacteristicvaluechanged
306    );
307}
308
309impl AsyncBluetoothListener for BluetoothRemoteGATTCharacteristic {
310    fn handle_response(
311        &self,
312        cx: &mut js::context::JSContext,
313        response: BluetoothResponse,
314        promise: &Rc<Promise>,
315    ) {
316        let device = self.Service().Device();
317        match response {
318            // https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
319            // Step 7.
320            BluetoothResponse::GetDescriptors(descriptors_vec, single) => {
321                if single {
322                    promise.resolve_native(
323                        &device.get_or_create_descriptor(cx, &descriptors_vec[0], self),
324                        CanGc::from_cx(cx),
325                    );
326                    return;
327                }
328                let mut descriptors = vec![];
329                for descriptor in descriptors_vec {
330                    let bt_descriptor = device.get_or_create_descriptor(cx, &descriptor, self);
331                    descriptors.push(bt_descriptor);
332                }
333                promise.resolve_native(&descriptors, CanGc::from_cx(cx));
334            },
335            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-readvalue
336            BluetoothResponse::ReadValue(result) => {
337                // TODO: Step 5.5.1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
338
339                // Step 5.5.2.
340                // TODO(#5014): Replace ByteString with ArrayBuffer when it is implemented.
341                let value = ByteString::new(result);
342                *self.value.borrow_mut() = Some(value.clone());
343
344                // Step 5.5.3.
345                self.upcast::<EventTarget>()
346                    .fire_bubbling_event(atom!("characteristicvaluechanged"), CanGc::from_cx(cx));
347
348                // Step 5.5.4.
349                promise.resolve_native(&value, CanGc::from_cx(cx));
350            },
351            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-writevalue
352            BluetoothResponse::WriteValue(result) => {
353                // TODO: Step 7.5.1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
354
355                // Step 7.5.2.
356                // TODO(#5014): Replace ByteString with an ArrayBuffer wrapped in a DataView.
357                *self.value.borrow_mut() = Some(ByteString::new(result));
358
359                // Step 7.5.3.
360                promise.resolve_native(&(), CanGc::from_cx(cx));
361            },
362            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-startnotifications
363            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-stopnotifications
364            BluetoothResponse::EnableNotification(_result) => {
365                // (StartNotification) TODO: Step 10:  Implement `active notification context set`
366                // for BluetoothRemoteGATTCharacteristic.
367
368                // (StartNotification) Step 11.
369                // (StopNotification)  Step 5.
370                promise.resolve_native(self, CanGc::from_cx(cx));
371            },
372            _ => promise.reject_error(
373                Error::Type(c"Something went wrong...".to_owned()),
374                CanGc::from_cx(cx),
375            ),
376        }
377    }
378}