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