Skip to main content

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