Skip to main content

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