servo_bluetooth/
bluetooth.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 http://mozilla.org/MPL/2.0/. */
4
5use std::collections::HashMap;
6use std::error::Error;
7use std::sync::Arc;
8
9#[cfg(feature = "bluetooth-test")]
10use blurmock::fake_characteristic::FakeBluetoothGATTCharacteristic;
11#[cfg(feature = "bluetooth-test")]
12use blurmock::fake_descriptor::FakeBluetoothGATTDescriptor;
13#[cfg(feature = "bluetooth-test")]
14use blurmock::fake_device::FakeBluetoothDevice;
15#[cfg(feature = "bluetooth-test")]
16use blurmock::fake_discovery_session::FakeBluetoothDiscoverySession;
17#[cfg(feature = "bluetooth-test")]
18use blurmock::fake_service::FakeBluetoothGATTService;
19#[cfg(feature = "native-bluetooth")]
20use btleplug::api::{Central, CharPropFlags, Peripheral, ScanFilter, WriteType};
21#[cfg(feature = "native-bluetooth")]
22use btleplug::platform::{Adapter, Peripheral as PlatformPeripheral};
23
24pub use super::adapter::BluetoothAdapter;
25use crate::macros::get_inner_and_call_test_func;
26
27#[cfg(feature = "native-bluetooth")]
28#[derive(Clone, Debug)]
29pub struct BtleplugDiscoverySession {
30    pub(crate) adapter: Adapter,
31}
32
33#[cfg(feature = "native-bluetooth")]
34impl BtleplugDiscoverySession {
35    pub async fn start_discovery(&self) -> Result<(), Box<dyn Error>> {
36        Ok(self.adapter.start_scan(ScanFilter::default()).await?)
37    }
38
39    pub async fn stop_discovery(&self) -> Result<(), Box<dyn Error>> {
40        Ok(self.adapter.stop_scan().await?)
41    }
42}
43
44#[cfg(feature = "native-bluetooth")]
45#[derive(Clone, Debug)]
46pub struct BtleplugDevice {
47    pub(crate) peripheral: PlatformPeripheral,
48}
49
50#[cfg(feature = "native-bluetooth")]
51impl BtleplugDevice {
52    async fn properties(&self) -> Result<btleplug::api::PeripheralProperties, Box<dyn Error>> {
53        self.peripheral
54            .properties()
55            .await?
56            .ok_or_else(|| Box::from("Device properties not available"))
57    }
58
59    pub fn get_id(&self) -> String {
60        self.peripheral.id().to_string()
61    }
62
63    pub fn get_address(&self) -> Result<String, Box<dyn Error>> {
64        // On macOS, CoreBluetooth doesn't expose real MAC addresses but always returns 00:00:00:00:00:00.
65        // Use the peripheral ID (a UUID) as a unique identifier instead.
66        Ok(self.peripheral.id().to_string())
67    }
68
69    pub async fn get_name(&self) -> Result<String, Box<dyn Error>> {
70        let props = self.properties().await?;
71        if let Some(name) = props.local_name {
72            return Ok(name);
73        }
74        if let Some(name) = props.advertisement_name {
75            return Ok(name);
76        }
77        Err(Box::from("Device name not available"))
78    }
79
80    pub async fn get_uuids(&self) -> Result<Vec<String>, Box<dyn Error>> {
81        Ok(self
82            .properties()
83            .await?
84            .services
85            .into_iter()
86            .map(|uuid| uuid.to_string())
87            .collect())
88    }
89
90    pub async fn is_connected(&self) -> Result<bool, Box<dyn Error>> {
91        Ok(self.peripheral.is_connected().await.map_err(Box::new)?)
92    }
93
94    pub async fn connect(&self) -> Result<(), Box<dyn Error>> {
95        Ok(self.peripheral.connect().await.map_err(Box::new)?)
96    }
97
98    pub async fn disconnect(&self) -> Result<(), Box<dyn Error>> {
99        Ok(self.peripheral.disconnect().await.map_err(Box::new)?)
100    }
101
102    pub async fn get_manufacturer_data(&self) -> Result<HashMap<u16, Vec<u8>>, Box<dyn Error>> {
103        Ok(self.properties().await?.manufacturer_data)
104    }
105
106    pub async fn get_service_data(&self) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>> {
107        Ok(self
108            .properties()
109            .await?
110            .service_data
111            .into_iter()
112            .map(|(uuid, data)| (uuid.to_string(), data))
113            .collect())
114    }
115
116    pub async fn discover_services(&self) -> Result<Vec<BluetoothGATTService>, Box<dyn Error>> {
117        self.peripheral
118            .discover_services()
119            .await
120            .map_err(Box::new)?;
121
122        let device_id = self.peripheral.id().to_string();
123        let services = self.peripheral.services();
124        let mut result = Vec::new();
125        let mut uuid_counts: HashMap<String, usize> = HashMap::new();
126
127        for service in services {
128            let uuid_str = service.uuid.to_string();
129            let idx = uuid_counts.entry(uuid_str.clone()).or_default();
130            let instance_id = format!("{device_id}/svc/{uuid_str}/{idx}");
131            *idx += 1;
132            result.push(BluetoothGATTService::Btleplug(BtleplugGATTService {
133                instance_id,
134                service,
135                peripheral: self.peripheral.clone(),
136            }));
137        }
138        Ok(result)
139    }
140}
141
142#[cfg(feature = "native-bluetooth")]
143#[derive(Clone, Debug)]
144pub struct BtleplugGATTService {
145    pub(crate) instance_id: String,
146    pub(crate) service: btleplug::api::Service,
147    pub(crate) peripheral: PlatformPeripheral,
148}
149
150#[cfg(feature = "native-bluetooth")]
151impl BtleplugGATTService {
152    pub fn get_id(&self) -> String {
153        self.instance_id.clone()
154    }
155
156    pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> {
157        Ok(self.service.uuid.to_string())
158    }
159
160    pub fn is_primary(&self) -> Result<bool, Box<dyn Error>> {
161        Ok(self.service.primary)
162    }
163
164    pub fn get_includes(&self) -> Result<Vec<String>, Box<dyn Error>> {
165        // TODO: btleplug does not support included services yet.
166        //       Add support in btleplug upstream.
167        Ok(vec![])
168    }
169
170    pub fn get_gatt_characteristics(&self) -> Vec<BluetoothGATTCharacteristic> {
171        let mut result = Vec::new();
172        let mut uuid_counts: HashMap<String, usize> = HashMap::new();
173
174        for characteristic in &self.service.characteristics {
175            let uuid_str = characteristic.uuid.to_string();
176            let idx = uuid_counts.entry(uuid_str.clone()).or_default();
177            let instance_id = format!("{}/char/{uuid_str}/{idx}", self.instance_id);
178            *idx += 1;
179            result.push(BluetoothGATTCharacteristic::Btleplug(
180                BtleplugGATTCharacteristic {
181                    instance_id,
182                    characteristic: characteristic.clone(),
183                    peripheral: self.peripheral.clone(),
184                },
185            ));
186        }
187        result
188    }
189}
190
191#[cfg(feature = "native-bluetooth")]
192#[derive(Clone, Debug)]
193pub struct BtleplugGATTCharacteristic {
194    pub(crate) instance_id: String,
195    pub(crate) characteristic: btleplug::api::Characteristic,
196    pub(crate) peripheral: PlatformPeripheral,
197}
198
199#[cfg(feature = "native-bluetooth")]
200impl BtleplugGATTCharacteristic {
201    pub fn get_id(&self) -> String {
202        self.instance_id.clone()
203    }
204
205    pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> {
206        Ok(self.characteristic.uuid.to_string())
207    }
208
209    pub fn get_flags(&self) -> Result<Vec<String>, Box<dyn Error>> {
210        let props = self.characteristic.properties;
211        let mut flags = Vec::new();
212
213        if props.contains(CharPropFlags::BROADCAST) {
214            flags.push("broadcast".to_string());
215        }
216        if props.contains(CharPropFlags::READ) {
217            flags.push("read".to_string());
218        }
219        if props.contains(CharPropFlags::WRITE_WITHOUT_RESPONSE) {
220            flags.push("write-without-response".to_string());
221        }
222        if props.contains(CharPropFlags::WRITE) {
223            flags.push("write".to_string());
224        }
225        if props.contains(CharPropFlags::NOTIFY) {
226            flags.push("notify".to_string());
227        }
228        if props.contains(CharPropFlags::INDICATE) {
229            flags.push("indicate".to_string());
230        }
231        if props.contains(CharPropFlags::AUTHENTICATED_SIGNED_WRITES) {
232            flags.push("authenticated-signed-writes".to_string());
233        }
234        Ok(flags)
235    }
236
237    pub fn get_gatt_descriptors(&self) -> Vec<BluetoothGATTDescriptor> {
238        let mut result = Vec::new();
239        let mut uuid_counts: HashMap<String, usize> = HashMap::new();
240
241        for descriptor in &self.characteristic.descriptors {
242            let uuid_str = descriptor.uuid.to_string();
243            let idx = uuid_counts.entry(uuid_str.clone()).or_default();
244            let instance_id = format!("{}/desc/{uuid_str}/{idx}", self.instance_id);
245            *idx += 1;
246            result.push(BluetoothGATTDescriptor::Btleplug(BtleplugGATTDescriptor {
247                instance_id,
248                descriptor: descriptor.clone(),
249                peripheral: self.peripheral.clone(),
250            }));
251        }
252        result
253    }
254
255    pub async fn read_value(&self) -> Result<Vec<u8>, Box<dyn Error>> {
256        Ok(self
257            .peripheral
258            .read(&self.characteristic)
259            .await
260            .map_err(Box::new)?)
261    }
262
263    pub async fn write_value(&self, values: Vec<u8>) -> Result<(), Box<dyn Error>> {
264        let write_type = if self
265            .characteristic
266            .properties
267            .contains(CharPropFlags::WRITE)
268        {
269            WriteType::WithResponse
270        } else {
271            WriteType::WithoutResponse
272        };
273        Ok(self
274            .peripheral
275            .write(&self.characteristic, &values, write_type)
276            .await
277            .map_err(Box::new)?)
278    }
279
280    pub async fn start_notify(&self) -> Result<(), Box<dyn Error>> {
281        Ok(self
282            .peripheral
283            .subscribe(&self.characteristic)
284            .await
285            .map_err(Box::new)?)
286    }
287
288    pub async fn stop_notify(&self) -> Result<(), Box<dyn Error>> {
289        Ok(self
290            .peripheral
291            .unsubscribe(&self.characteristic)
292            .await
293            .map_err(Box::new)?)
294    }
295}
296
297#[cfg(feature = "native-bluetooth")]
298#[derive(Clone, Debug)]
299pub struct BtleplugGATTDescriptor {
300    pub(crate) instance_id: String,
301    pub(crate) descriptor: btleplug::api::Descriptor,
302    pub(crate) peripheral: PlatformPeripheral,
303}
304
305#[cfg(feature = "native-bluetooth")]
306impl BtleplugGATTDescriptor {
307    pub fn get_id(&self) -> String {
308        self.instance_id.clone()
309    }
310
311    pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> {
312        Ok(self.descriptor.uuid.to_string())
313    }
314
315    pub fn get_flags(&self) -> Result<Vec<String>, Box<dyn Error>> {
316        Ok(vec![])
317    }
318
319    pub async fn read_value(&self) -> Result<Vec<u8>, Box<dyn Error>> {
320        Ok(self
321            .peripheral
322            .read_descriptor(&self.descriptor)
323            .await
324            .map_err(Box::new)?)
325    }
326
327    pub async fn write_value(&self, values: Vec<u8>) -> Result<(), Box<dyn Error>> {
328        Ok(self
329            .peripheral
330            .write_descriptor(&self.descriptor, &values)
331            .await
332            .map_err(Box::new)?)
333    }
334}
335
336#[derive(Debug)]
337pub enum BluetoothDiscoverySession {
338    #[cfg(feature = "native-bluetooth")]
339    Btleplug(BtleplugDiscoverySession),
340    #[cfg(feature = "bluetooth-test")]
341    Mock(Arc<FakeBluetoothDiscoverySession>),
342}
343
344impl BluetoothDiscoverySession {
345    pub async fn start_discovery(&self) -> Result<(), Box<dyn Error>> {
346        match self {
347            #[cfg(feature = "native-bluetooth")]
348            Self::Btleplug(inner) => inner.start_discovery().await,
349            #[cfg(feature = "bluetooth-test")]
350            Self::Mock(inner) => inner.start_discovery(),
351        }
352    }
353
354    pub async fn stop_discovery(&self) -> Result<(), Box<dyn Error>> {
355        match self {
356            #[cfg(feature = "native-bluetooth")]
357            Self::Btleplug(inner) => inner.stop_discovery().await,
358            #[cfg(feature = "bluetooth-test")]
359            Self::Mock(inner) => inner.stop_discovery(),
360        }
361    }
362}
363
364#[derive(Clone, Debug)]
365pub enum BluetoothDevice {
366    #[cfg(feature = "native-bluetooth")]
367    Btleplug(BtleplugDevice),
368    #[cfg(feature = "bluetooth-test")]
369    Mock(Arc<FakeBluetoothDevice>),
370}
371
372impl BluetoothDevice {
373    pub fn get_id(&self) -> String {
374        match self {
375            #[cfg(feature = "native-bluetooth")]
376            Self::Btleplug(inner) => inner.get_id(),
377            #[cfg(feature = "bluetooth-test")]
378            Self::Mock(inner) => inner.get_id(),
379        }
380    }
381
382    pub fn get_address(&self) -> Result<String, Box<dyn Error>> {
383        match self {
384            #[cfg(feature = "native-bluetooth")]
385            Self::Btleplug(inner) => inner.get_address(),
386            #[cfg(feature = "bluetooth-test")]
387            Self::Mock(inner) => inner.get_address(),
388        }
389    }
390
391    pub async fn get_name(&self) -> Result<String, Box<dyn Error>> {
392        match self {
393            #[cfg(feature = "native-bluetooth")]
394            Self::Btleplug(inner) => inner.get_name().await,
395            #[cfg(feature = "bluetooth-test")]
396            Self::Mock(inner) => inner.get_name(),
397        }
398    }
399
400    pub async fn get_uuids(&self) -> Result<Vec<String>, Box<dyn Error>> {
401        match self {
402            #[cfg(feature = "native-bluetooth")]
403            Self::Btleplug(inner) => inner.get_uuids().await,
404            #[cfg(feature = "bluetooth-test")]
405            Self::Mock(inner) => inner.get_uuids(),
406        }
407    }
408
409    pub async fn is_connected(&self) -> Result<bool, Box<dyn Error>> {
410        match self {
411            #[cfg(feature = "native-bluetooth")]
412            Self::Btleplug(inner) => inner.is_connected().await,
413            #[cfg(feature = "bluetooth-test")]
414            Self::Mock(inner) => inner.is_connected(),
415        }
416    }
417
418    pub async fn connect(&self) -> Result<(), Box<dyn Error>> {
419        match self {
420            #[cfg(feature = "native-bluetooth")]
421            Self::Btleplug(inner) => inner.connect().await,
422            #[cfg(feature = "bluetooth-test")]
423            Self::Mock(inner) => inner.connect(),
424        }
425    }
426
427    pub async fn disconnect(&self) -> Result<(), Box<dyn Error>> {
428        match self {
429            #[cfg(feature = "native-bluetooth")]
430            Self::Btleplug(inner) => inner.disconnect().await,
431            #[cfg(feature = "bluetooth-test")]
432            Self::Mock(inner) => inner.disconnect(),
433        }
434    }
435
436    pub async fn get_manufacturer_data(&self) -> Result<HashMap<u16, Vec<u8>>, Box<dyn Error>> {
437        match self {
438            #[cfg(feature = "native-bluetooth")]
439            Self::Btleplug(inner) => inner.get_manufacturer_data().await,
440            #[cfg(feature = "bluetooth-test")]
441            Self::Mock(inner) => inner.get_manufacturer_data(),
442        }
443    }
444
445    pub async fn get_service_data(&self) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>> {
446        match self {
447            #[cfg(feature = "native-bluetooth")]
448            Self::Btleplug(inner) => inner.get_service_data().await,
449            #[cfg(feature = "bluetooth-test")]
450            Self::Mock(inner) => inner.get_service_data(),
451        }
452    }
453
454    pub async fn get_gatt_services(&self) -> Result<Vec<BluetoothGATTService>, Box<dyn Error>> {
455        match self {
456            #[cfg(feature = "native-bluetooth")]
457            Self::Btleplug(inner) => inner.discover_services().await,
458            #[cfg(feature = "bluetooth-test")]
459            Self::Mock(inner) => {
460                let services = inner.get_gatt_services()?;
461                Ok(services
462                    .into_iter()
463                    .map(|service| {
464                        BluetoothGATTService::Mock(FakeBluetoothGATTService::new_empty(
465                            inner.clone(),
466                            service,
467                        ))
468                    })
469                    .collect())
470            },
471        }
472    }
473
474    #[cfg(feature = "bluetooth-test")]
475    pub fn set_id(&self, id: String) {
476        #[allow(irrefutable_let_patterns)]
477        let Self::Mock(inner) = self else {
478            return;
479        };
480        inner.set_id(id);
481    }
482
483    #[cfg(feature = "bluetooth-test")]
484    pub fn set_address(&self, address: String) -> Result<(), Box<dyn Error>> {
485        get_inner_and_call_test_func!(self, BluetoothDevice, set_address, address)
486    }
487
488    #[cfg(feature = "bluetooth-test")]
489    pub fn set_name(&self, name: Option<String>) -> Result<(), Box<dyn Error>> {
490        get_inner_and_call_test_func!(self, BluetoothDevice, set_name, name)
491    }
492
493    #[cfg(feature = "bluetooth-test")]
494    pub fn set_uuids(&self, uuids: Vec<String>) -> Result<(), Box<dyn Error>> {
495        get_inner_and_call_test_func!(self, BluetoothDevice, set_uuids, uuids)
496    }
497
498    #[cfg(feature = "bluetooth-test")]
499    pub fn set_connectable(&self, connectable: bool) -> Result<(), Box<dyn Error>> {
500        get_inner_and_call_test_func!(self, BluetoothDevice, set_connectable, connectable)
501    }
502
503    #[cfg(feature = "bluetooth-test")]
504    pub fn set_connected(&self, connected: bool) -> Result<(), Box<dyn Error>> {
505        get_inner_and_call_test_func!(self, BluetoothDevice, set_connected, connected)
506    }
507
508    #[cfg(feature = "bluetooth-test")]
509    pub fn set_manufacturer_data(
510        &self,
511        manufacturer_data: HashMap<u16, Vec<u8>>,
512    ) -> Result<(), Box<dyn Error>> {
513        get_inner_and_call_test_func!(
514            self,
515            BluetoothDevice,
516            set_manufacturer_data,
517            Some(manufacturer_data)
518        )
519    }
520
521    #[cfg(feature = "bluetooth-test")]
522    pub fn set_service_data(
523        &self,
524        service_data: HashMap<String, Vec<u8>>,
525    ) -> Result<(), Box<dyn Error>> {
526        get_inner_and_call_test_func!(self, BluetoothDevice, set_service_data, Some(service_data))
527    }
528}
529
530#[derive(Clone, Debug)]
531pub enum BluetoothGATTService {
532    #[cfg(feature = "native-bluetooth")]
533    Btleplug(BtleplugGATTService),
534    #[cfg(feature = "bluetooth-test")]
535    Mock(Arc<FakeBluetoothGATTService>),
536}
537
538impl BluetoothGATTService {
539    fn create_service(device: BluetoothDevice, service: String) -> BluetoothGATTService {
540        match device {
541            #[cfg(feature = "native-bluetooth")]
542            BluetoothDevice::Btleplug(_) => {
543                unreachable!("btleplug services are created directly, not via create_service")
544            },
545            #[cfg(feature = "bluetooth-test")]
546            BluetoothDevice::Mock(fake_device) => BluetoothGATTService::Mock(
547                FakeBluetoothGATTService::new_empty(fake_device, service),
548            ),
549        }
550    }
551
552    #[cfg(feature = "bluetooth-test")]
553    pub fn create_mock_service(
554        device: BluetoothDevice,
555        service: String,
556    ) -> Result<BluetoothGATTService, Box<dyn Error>> {
557        match device {
558            BluetoothDevice::Mock(fake_device) => Ok(BluetoothGATTService::Mock(
559                FakeBluetoothGATTService::new_empty(fake_device, service),
560            )),
561            #[cfg(feature = "native-bluetooth")]
562            _ => Err(Box::from("The first parameter must be a mock structure")),
563        }
564    }
565
566    pub fn get_id(&self) -> String {
567        match self {
568            #[cfg(feature = "native-bluetooth")]
569            Self::Btleplug(inner) => inner.get_id(),
570            #[cfg(feature = "bluetooth-test")]
571            Self::Mock(inner) => inner.get_id(),
572        }
573    }
574
575    pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> {
576        match self {
577            #[cfg(feature = "native-bluetooth")]
578            Self::Btleplug(inner) => inner.get_uuid(),
579            #[cfg(feature = "bluetooth-test")]
580            Self::Mock(inner) => inner.get_uuid(),
581        }
582    }
583
584    pub fn is_primary(&self) -> Result<bool, Box<dyn Error>> {
585        match self {
586            #[cfg(feature = "native-bluetooth")]
587            Self::Btleplug(inner) => inner.is_primary(),
588            #[cfg(feature = "bluetooth-test")]
589            Self::Mock(inner) => inner.is_primary(),
590        }
591    }
592
593    pub fn get_includes(
594        &self,
595        device: BluetoothDevice,
596    ) -> Result<Vec<BluetoothGATTService>, Box<dyn Error>> {
597        let services = match self {
598            #[cfg(feature = "native-bluetooth")]
599            Self::Btleplug(inner) => inner.get_includes()?,
600            #[cfg(feature = "bluetooth-test")]
601            Self::Mock(inner) => inner.get_includes()?,
602        };
603        Ok(services
604            .into_iter()
605            .map(|service| BluetoothGATTService::create_service(device.clone(), service))
606            .collect())
607    }
608
609    pub fn get_gatt_characteristics(
610        &self,
611    ) -> Result<Vec<BluetoothGATTCharacteristic>, Box<dyn Error>> {
612        match self {
613            #[cfg(feature = "native-bluetooth")]
614            Self::Btleplug(inner) => Ok(inner.get_gatt_characteristics()),
615            #[cfg(feature = "bluetooth-test")]
616            Self::Mock(inner) => {
617                let characteristics = inner.get_gatt_characteristics()?;
618                Ok(characteristics
619                    .into_iter()
620                    .map(|characteristic| {
621                        BluetoothGATTCharacteristic::Mock(
622                            FakeBluetoothGATTCharacteristic::new_empty(
623                                inner.clone(),
624                                characteristic,
625                            ),
626                        )
627                    })
628                    .collect())
629            },
630        }
631    }
632
633    #[cfg(feature = "bluetooth-test")]
634    pub fn set_id(&self, id: String) {
635        #[allow(irrefutable_let_patterns)]
636        let Self::Mock(inner) = self else {
637            return;
638        };
639        inner.set_id(id);
640    }
641
642    #[cfg(feature = "bluetooth-test")]
643    pub fn set_uuid(&self, uuid: String) -> Result<(), Box<dyn Error>> {
644        get_inner_and_call_test_func!(self, BluetoothGATTService, set_uuid, uuid)
645    }
646
647    #[cfg(feature = "bluetooth-test")]
648    pub fn set_primary(&self, primary: bool) -> Result<(), Box<dyn Error>> {
649        get_inner_and_call_test_func!(self, BluetoothGATTService, set_is_primary, primary)
650    }
651}
652
653#[derive(Clone, Debug)]
654pub enum BluetoothGATTCharacteristic {
655    #[cfg(feature = "native-bluetooth")]
656    Btleplug(BtleplugGATTCharacteristic),
657    #[cfg(feature = "bluetooth-test")]
658    Mock(Arc<FakeBluetoothGATTCharacteristic>),
659}
660
661impl BluetoothGATTCharacteristic {
662    #[cfg(feature = "bluetooth-test")]
663    pub fn create_mock_characteristic(
664        service: BluetoothGATTService,
665        characteristic: String,
666    ) -> Result<BluetoothGATTCharacteristic, Box<dyn Error>> {
667        match service {
668            BluetoothGATTService::Mock(fake_service) => Ok(BluetoothGATTCharacteristic::Mock(
669                FakeBluetoothGATTCharacteristic::new_empty(fake_service, characteristic),
670            )),
671            #[cfg(feature = "native-bluetooth")]
672            _ => Err(Box::from("The first parameter must be a mock structure")),
673        }
674    }
675
676    pub fn get_id(&self) -> String {
677        match self {
678            #[cfg(feature = "native-bluetooth")]
679            Self::Btleplug(inner) => inner.get_id(),
680            #[cfg(feature = "bluetooth-test")]
681            Self::Mock(inner) => inner.get_id(),
682        }
683    }
684
685    pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> {
686        match self {
687            #[cfg(feature = "native-bluetooth")]
688            Self::Btleplug(inner) => inner.get_uuid(),
689            #[cfg(feature = "bluetooth-test")]
690            Self::Mock(inner) => inner.get_uuid(),
691        }
692    }
693
694    pub fn get_flags(&self) -> Result<Vec<String>, Box<dyn Error>> {
695        match self {
696            #[cfg(feature = "native-bluetooth")]
697            Self::Btleplug(inner) => inner.get_flags(),
698            #[cfg(feature = "bluetooth-test")]
699            Self::Mock(inner) => inner.get_flags(),
700        }
701    }
702
703    pub fn get_gatt_descriptors(&self) -> Result<Vec<BluetoothGATTDescriptor>, Box<dyn Error>> {
704        match self {
705            #[cfg(feature = "native-bluetooth")]
706            Self::Btleplug(inner) => Ok(inner.get_gatt_descriptors()),
707            #[cfg(feature = "bluetooth-test")]
708            Self::Mock(inner) => {
709                let descriptors = inner.get_gatt_descriptors()?;
710                Ok(descriptors
711                    .into_iter()
712                    .map(|descriptor| {
713                        BluetoothGATTDescriptor::Mock(FakeBluetoothGATTDescriptor::new_empty(
714                            inner.clone(),
715                            descriptor,
716                        ))
717                    })
718                    .collect())
719            },
720        }
721    }
722
723    pub async fn read_value(&self) -> Result<Vec<u8>, Box<dyn Error>> {
724        match self {
725            #[cfg(feature = "native-bluetooth")]
726            Self::Btleplug(inner) => inner.read_value().await,
727            #[cfg(feature = "bluetooth-test")]
728            Self::Mock(inner) => inner.read_value(),
729        }
730    }
731
732    pub async fn write_value(&self, values: Vec<u8>) -> Result<(), Box<dyn Error>> {
733        match self {
734            #[cfg(feature = "native-bluetooth")]
735            Self::Btleplug(inner) => inner.write_value(values).await,
736            #[cfg(feature = "bluetooth-test")]
737            Self::Mock(inner) => inner.write_value(values),
738        }
739    }
740
741    pub async fn start_notify(&self) -> Result<(), Box<dyn Error>> {
742        match self {
743            #[cfg(feature = "native-bluetooth")]
744            Self::Btleplug(inner) => inner.start_notify().await,
745            #[cfg(feature = "bluetooth-test")]
746            Self::Mock(inner) => inner.start_notify(),
747        }
748    }
749
750    pub async fn stop_notify(&self) -> Result<(), Box<dyn Error>> {
751        match self {
752            #[cfg(feature = "native-bluetooth")]
753            Self::Btleplug(inner) => inner.stop_notify().await,
754            #[cfg(feature = "bluetooth-test")]
755            Self::Mock(inner) => inner.stop_notify(),
756        }
757    }
758
759    #[cfg(feature = "bluetooth-test")]
760    pub fn set_id(&self, id: String) {
761        #[allow(irrefutable_let_patterns)]
762        let Self::Mock(inner) = self else {
763            return;
764        };
765        inner.set_id(id);
766    }
767
768    #[cfg(feature = "bluetooth-test")]
769    pub fn set_uuid(&self, uuid: String) -> Result<(), Box<dyn Error>> {
770        get_inner_and_call_test_func!(self, BluetoothGATTCharacteristic, set_uuid, uuid)
771    }
772
773    #[cfg(feature = "bluetooth-test")]
774    pub fn set_value(&self, value: Vec<u8>) -> Result<(), Box<dyn Error>> {
775        get_inner_and_call_test_func!(self, BluetoothGATTCharacteristic, set_value, Some(value))
776    }
777
778    #[cfg(feature = "bluetooth-test")]
779    pub fn set_flags(&self, flags: Vec<String>) -> Result<(), Box<dyn Error>> {
780        get_inner_and_call_test_func!(self, BluetoothGATTCharacteristic, set_flags, flags)
781    }
782}
783
784#[derive(Clone, Debug)]
785pub enum BluetoothGATTDescriptor {
786    #[cfg(feature = "native-bluetooth")]
787    Btleplug(BtleplugGATTDescriptor),
788    #[cfg(feature = "bluetooth-test")]
789    Mock(Arc<FakeBluetoothGATTDescriptor>),
790}
791
792impl BluetoothGATTDescriptor {
793    #[cfg(feature = "bluetooth-test")]
794    pub fn create_mock_descriptor(
795        characteristic: BluetoothGATTCharacteristic,
796        descriptor: String,
797    ) -> Result<BluetoothGATTDescriptor, Box<dyn Error>> {
798        #[allow(unreachable_patterns)]
799        match characteristic {
800            BluetoothGATTCharacteristic::Mock(fake_characteristic) => {
801                Ok(BluetoothGATTDescriptor::Mock(
802                    FakeBluetoothGATTDescriptor::new_empty(fake_characteristic, descriptor),
803                ))
804            },
805            _ => Err(Box::from("The first parameter must be a mock structure")),
806        }
807    }
808
809    pub fn get_id(&self) -> String {
810        match self {
811            #[cfg(feature = "native-bluetooth")]
812            Self::Btleplug(inner) => inner.get_id(),
813            #[cfg(feature = "bluetooth-test")]
814            Self::Mock(inner) => inner.get_id(),
815        }
816    }
817
818    pub fn get_uuid(&self) -> Result<String, Box<dyn Error>> {
819        match self {
820            #[cfg(feature = "native-bluetooth")]
821            Self::Btleplug(inner) => inner.get_uuid(),
822            #[cfg(feature = "bluetooth-test")]
823            Self::Mock(inner) => inner.get_uuid(),
824        }
825    }
826
827    pub async fn read_value(&self) -> Result<Vec<u8>, Box<dyn Error>> {
828        match self {
829            #[cfg(feature = "native-bluetooth")]
830            Self::Btleplug(inner) => inner.read_value().await,
831            #[cfg(feature = "bluetooth-test")]
832            Self::Mock(inner) => inner.read_value(),
833        }
834    }
835
836    pub async fn write_value(&self, values: Vec<u8>) -> Result<(), Box<dyn Error>> {
837        match self {
838            #[cfg(feature = "native-bluetooth")]
839            Self::Btleplug(inner) => inner.write_value(values).await,
840            #[cfg(feature = "bluetooth-test")]
841            Self::Mock(inner) => inner.write_value(values),
842        }
843    }
844
845    #[cfg(feature = "bluetooth-test")]
846    pub fn set_id(&self, id: String) {
847        #[allow(irrefutable_let_patterns)]
848        let Self::Mock(inner) = self else {
849            return;
850        };
851        inner.set_id(id);
852    }
853
854    #[cfg(feature = "bluetooth-test")]
855    pub fn set_uuid(&self, uuid: String) -> Result<(), Box<dyn Error>> {
856        get_inner_and_call_test_func!(self, BluetoothGATTDescriptor, set_uuid, uuid)
857    }
858
859    #[cfg(feature = "bluetooth-test")]
860    pub fn set_value(&self, value: Vec<u8>) -> Result<(), Box<dyn Error>> {
861        get_inner_and_call_test_func!(self, BluetoothGATTDescriptor, set_value, Some(value))
862    }
863
864    #[cfg(feature = "bluetooth-test")]
865    pub fn set_flags(&self, flags: Vec<String>) -> Result<(), Box<dyn Error>> {
866        get_inner_and_call_test_func!(self, BluetoothGATTDescriptor, set_flags, flags)
867    }
868}