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 dom_struct::dom_struct;
6use js::context::JSContext;
7use js::realm::CurrentRealm;
8use script_bindings::cell::DomRefCell;
9use script_bindings::reflector::reflect_dom_object;
10use servo_base::generic_channel::GenericSender;
11use servo_bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted};
12use servo_bluetooth_traits::{BluetoothRequest, BluetoothResponse, GATTType};
13
14use crate::dom::bindings::buffer_source::get_buffer_source_copy;
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, RootedPromise};
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 JSContext,
69        global: &GlobalScope,
70        service: &BluetoothRemoteGATTService,
71        uuid: DOMString,
72        properties: &BluetoothCharacteristicProperties,
73        instance_id: String,
74    ) -> DomRoot<BluetoothRemoteGATTCharacteristic> {
75        reflect_dom_object(
76            cx,
77            Box::new(BluetoothRemoteGATTCharacteristic::new_inherited(
78                service,
79                uuid,
80                properties,
81                instance_id,
82            )),
83            global,
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    ) -> RootedPromise {
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    ) -> RootedPromise {
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) -> RootedPromise {
159        let p = Promise::new_in_realm_rooted(cx);
160
161        // Step 1.
162        if uuid_is_blocklisted(&self.uuid.str(), Blocklist::Reads) {
163            p.reject_error(cx, Security(None));
164            return p;
165        }
166
167        // Step 2.
168        if !self.Service().Device().get_gatt(cx).Connected() {
169            p.reject_error(cx, Network(None));
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(cx, NotSupported(None));
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    ) -> RootedPromise {
196        let p = Promise::new_in_realm_rooted(cx);
197
198        // Step 1.
199        if uuid_is_blocklisted(&self.uuid.str(), Blocklist::Writes) {
200            p.reject_error(cx, Security(None));
201            return p;
202        }
203
204        // Step 2 - 3.
205        let vec = get_buffer_source_copy((&value).into());
206
207        if vec.len() > MAXIMUM_ATTRIBUTE_LENGTH {
208            p.reject_error(cx, InvalidModification(None));
209            return p;
210        }
211
212        // Step 4.
213        if !self.Service().Device().get_gatt(cx).Connected() {
214            p.reject_error(cx, Network(None));
215            return p;
216        }
217
218        // TODO: Step 7: Implement the `connection-checking-wrapper` algorithm for BluetoothRemoteGATTServer.
219
220        // Step 7.1.
221        if !(self.Properties().Write() ||
222            self.Properties().WriteWithoutResponse() ||
223            self.Properties().AuthenticatedSignedWrites())
224        {
225            p.reject_error(cx, NotSupported(None));
226            return p;
227        }
228
229        // Note: Steps 5 - 6 and the remaining substeps of Step 7 are implemented in components/bluetooth/lib.rs
230        // in writeValue function and in handle_response function.
231        let sender = response_async(&p, self);
232        self.get_bluetooth_thread()
233            .send(BluetoothRequest::WriteValue(
234                self.get_instance_id(),
235                vec,
236                sender,
237            ))
238            .unwrap();
239        p
240    }
241
242    /// <https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-startnotifications>
243    fn StartNotifications(&self, cx: &mut CurrentRealm) -> RootedPromise {
244        let p = Promise::new_in_realm_rooted(cx);
245
246        // Step 1.
247        if uuid_is_blocklisted(&self.uuid.str(), Blocklist::Reads) {
248            p.reject_error(cx, Security(None));
249            return p;
250        }
251
252        // Step 2.
253        if !self.Service().Device().get_gatt(cx).Connected() {
254            p.reject_error(cx, Network(None));
255            return p;
256        }
257
258        // Step 5.
259        if !(self.Properties().Notify() || self.Properties().Indicate()) {
260            p.reject_error(cx, NotSupported(None));
261            return p;
262        }
263
264        // TODO: Step 6: Implement `active notification context set` for BluetoothRemoteGATTCharacteristic.
265
266        // Note: Steps 3 - 4, 7 - 11 are implemented in components/bluetooth/lib.rs in enable_notification function
267        // and in handle_response function.
268        let sender = response_async(&p, self);
269        self.get_bluetooth_thread()
270            .send(BluetoothRequest::EnableNotification(
271                self.get_instance_id(),
272                true,
273                sender,
274            ))
275            .unwrap();
276        p
277    }
278
279    /// <https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-stopnotifications>
280    fn StopNotifications(&self, cx: &mut CurrentRealm) -> RootedPromise {
281        let p = Promise::new_in_realm_rooted(cx);
282        let sender = response_async(&p, self);
283
284        // TODO: Step 3 - 4: Implement `active notification context set` for BluetoothRemoteGATTCharacteristic,
285
286        // Note: Steps 1 - 2, and part of Step 4 and Step 5 are implemented in components/bluetooth/lib.rs
287        // in enable_notification function and in handle_response function.
288        self.get_bluetooth_thread()
289            .send(BluetoothRequest::EnableNotification(
290                self.get_instance_id(),
291                false,
292                sender,
293            ))
294            .unwrap();
295        p
296    }
297
298    // https://webbluetoothcg.github.io/web-bluetooth/#dom-characteristiceventhandlers-oncharacteristicvaluechanged
299    event_handler!(
300        characteristicvaluechanged,
301        GetOncharacteristicvaluechanged,
302        SetOncharacteristicvaluechanged
303    );
304}
305
306impl AsyncBluetoothListener for BluetoothRemoteGATTCharacteristic {
307    fn handle_response(
308        &self,
309        cx: &mut JSContext,
310        response: BluetoothResponse,
311        promise: &RootedPromise,
312    ) {
313        let device = self.Service().Device();
314        match response {
315            // https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
316            // Step 7.
317            BluetoothResponse::GetDescriptors(descriptors_vec, single) => {
318                if single {
319                    let descriptor = device.get_or_create_descriptor(cx, &descriptors_vec[0], self);
320                    promise.resolve_native(cx, &descriptor);
321                    return;
322                }
323                let mut descriptors = vec![];
324                for descriptor in descriptors_vec {
325                    let bt_descriptor = device.get_or_create_descriptor(cx, &descriptor, self);
326                    descriptors.push(bt_descriptor);
327                }
328                promise.resolve_native(cx, &descriptors);
329            },
330            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-readvalue
331            BluetoothResponse::ReadValue(result) => {
332                // TODO: Step 5.5.1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
333
334                // Step 5.5.2.
335                // TODO(#5014): Replace ByteString with ArrayBuffer when it is implemented.
336                let value = ByteString::new(result);
337                *self.value.safe_borrow_mut(cx.no_gc()) = Some(value.clone());
338
339                // Step 5.5.3.
340                self.upcast::<EventTarget>()
341                    .fire_bubbling_event(cx, atom!("characteristicvaluechanged"));
342
343                // Step 5.5.4.
344                promise.resolve_native(cx, &value);
345            },
346            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-writevalue
347            BluetoothResponse::WriteValue(result) => {
348                // TODO: Step 7.5.1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
349
350                // Step 7.5.2.
351                // TODO(#5014): Replace ByteString with an ArrayBuffer wrapped in a DataView.
352                *self.value.safe_borrow_mut(cx.no_gc()) = Some(ByteString::new(result));
353
354                // Step 7.5.3.
355                promise.resolve_native(cx, &());
356            },
357            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-startnotifications
358            // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-stopnotifications
359            BluetoothResponse::EnableNotification(_result) => {
360                // (StartNotification) TODO: Step 10:  Implement `active notification context set`
361                // for BluetoothRemoteGATTCharacteristic.
362
363                // (StartNotification) Step 11.
364                // (StopNotification)  Step 5.
365                promise.resolve_native(cx, self);
366            },
367            _ => promise.reject_error(cx, Error::Type(c"Something went wrong...".to_owned())),
368        }
369    }
370}