bluez_async_ots/
lib.rs

1#![forbid(future_incompatible)]
2#![deny(bad_style, missing_docs)]
3#![doc = include_str!("../README.md")]
4
5mod l2cap;
6
7use ots_core::{
8    ids,
9    l2cap::{AddressType, L2capSockAddr as SocketAddr, Psm, SocketType},
10    types, Sizes,
11};
12
13use bluez_async::{
14    AdapterId, BluetoothError, BluetoothEvent, BluetoothSession, CharacteristicEvent,
15    CharacteristicId, DeviceId,
16};
17use futures::stream::StreamExt;
18use uuid::Uuid;
19
20use l2cap::{L2capSocket as Socket, L2capStream as Stream};
21use types::{ActionReq, ActionRes, ListReq, ListRes, Ule48};
22
23pub use ots_core::{
24    l2cap::{Security, SecurityLevel},
25    types::{
26        ActionFeature, ActionRc, DateTime, DirEntries, ListFeature, ListRc, Metadata, Property,
27        SortOrder, WriteMode,
28    },
29    Error as CoreError,
30};
31
32/// OTS client result
33pub type Result<T> = core::result::Result<T, Error>;
34
35/// OTS client error
36#[derive(thiserror::Error, Debug)]
37pub enum Error {
38    /// Input/output error
39    #[error("Input/Output Error: {0}")]
40    Io(#[from] std::io::Error),
41    /// Bluetooth error
42    #[error("Bluetooth error: {0}")]
43    Bt(#[from] BluetoothError),
44    /// Core error
45    #[error("OTS core error: {0}")]
46    Core(#[from] CoreError),
47    //// UTF-8 decoding error
48    //#[error("Invalid UTF8 string: {0}")]
49    //Utf8(#[from] core::str::Utf8Error),
50    //// UUID decoding error
51    //#[error("Invalid UUID: {0}")]
52    //Uuid(#[from] uuid::Error),
53    /// Not supported function requested
54    #[error("Not supported")]
55    NotSupported,
56    /// Object not found
57    #[error("Not found")]
58    NotFound,
59    /// No response received
60    #[error("No response")]
61    NoResponse,
62    /// Invalid response received
63    #[error("Invalid response")]
64    BadResponse,
65    /// Timeout reached
66    #[error("Timeout reached")]
67    Timeout,
68}
69
70impl From<core::str::Utf8Error> for Error {
71    fn from(err: core::str::Utf8Error) -> Self {
72        Self::Core(CoreError::BadUtf8(err))
73    }
74}
75
76impl From<std::string::FromUtf8Error> for Error {
77    fn from(err: std::string::FromUtf8Error) -> Self {
78        Self::Core(CoreError::BadUtf8(err.utf8_error()))
79    }
80}
81
82/// Object Transfer Service (OTS) client configuration
83#[derive(Debug, Clone, Default)]
84pub struct ClientConfig {
85    /// Privileged mode for connections
86    ///
87    /// If `true` the L2CAP sockets will be openned in privileged mode.
88    pub privileged: bool,
89
90    /// L2cap socket security to set
91    pub security: Option<Security>,
92}
93
94/// Object Transfer Service (OTS) client
95pub struct OtsClient {
96    session: BluetoothSession,
97    adapter_id: AdapterId,
98    device_id: DeviceId,
99    adapter_addr: SocketAddr,
100    device_addr: SocketAddr,
101    sock_security: Option<Security>,
102    action_features: ActionFeature,
103    list_features: ListFeature,
104    oacp_chr: CharacteristicId,
105    olcp_chr: Option<CharacteristicId>,
106    id_chr: Option<CharacteristicId>,
107    name_chr: CharacteristicId,
108    type_chr: CharacteristicId,
109    size_chr: CharacteristicId,
110    prop_chr: CharacteristicId,
111    crt_chr: Option<CharacteristicId>,
112    mod_chr: Option<CharacteristicId>,
113}
114
115impl AsRef<BluetoothSession> for OtsClient {
116    fn as_ref(&self) -> &BluetoothSession {
117        &self.session
118    }
119}
120
121impl AsRef<AdapterId> for OtsClient {
122    fn as_ref(&self) -> &AdapterId {
123        &self.adapter_id
124    }
125}
126
127impl AsRef<DeviceId> for OtsClient {
128    fn as_ref(&self) -> &DeviceId {
129        &self.device_id
130    }
131}
132
133impl AsRef<ActionFeature> for OtsClient {
134    fn as_ref(&self) -> &ActionFeature {
135        &self.action_features
136    }
137}
138
139impl AsRef<ListFeature> for OtsClient {
140    fn as_ref(&self) -> &ListFeature {
141        &self.list_features
142    }
143}
144
145impl OtsClient {
146    /// Create new client instance
147    pub async fn new(
148        session: &BluetoothSession,
149        device_id: &DeviceId,
150        config: &ClientConfig,
151    ) -> Result<Self> {
152        let ots_srv = session
153            .get_service_by_uuid(device_id, ids::service::object_transfer)
154            .await?;
155        log::debug!("Service: {ots_srv:#?}");
156
157        let ots_chrs = session.get_characteristics(&ots_srv.id).await?;
158        log::debug!("Characteristics: {ots_chrs:#?}");
159
160        let ots_feature_chr = session
161            .get_characteristic_by_uuid(&ots_srv.id, ids::characteristic::ots_feature)
162            .await?;
163        log::debug!("Feature Char: {ots_feature_chr:#?}");
164
165        let ots_feature_val = session
166            .read_characteristic_value(&ots_feature_chr.id)
167            .await?;
168        log::trace!("Feature Raw: {ots_feature_val:?}");
169
170        let action_features = (&ots_feature_val[0..4]).try_into()?;
171        let list_features = (&ots_feature_val[4..8]).try_into()?;
172        log::info!("OTS Feature: {action_features:?} {list_features:?}");
173
174        let oacp_chr = session
175            .get_characteristic_by_uuid(
176                &ots_srv.id,
177                ids::characteristic::object_action_control_point,
178            )
179            .await?;
180        log::debug!("OACP Char: {oacp_chr:#?}");
181        let oacp_chr = oacp_chr.id;
182
183        let olcp_chr = session
184            .get_characteristic_by_uuid(&ots_srv.id, ids::characteristic::object_list_control_point)
185            .await
186            .map(Some)
187            .or_else(|error| {
188                if matches!(error, BluetoothError::UuidNotFound { .. }) {
189                    Ok(None)
190                } else {
191                    Err(error)
192                }
193            })?;
194        log::debug!("OLCP Char: {olcp_chr:#?}");
195        let olcp_chr = olcp_chr.map(|chr| chr.id);
196
197        let id_chr = session
198            .get_characteristic_by_uuid(&ots_srv.id, ids::characteristic::object_id)
199            .await
200            .map(Some)
201            .or_else(|error| {
202                if matches!(error, BluetoothError::UuidNotFound { .. }) {
203                    Ok(None)
204                } else {
205                    Err(error)
206                }
207            })?;
208        log::debug!("Id Char: {id_chr:#?}");
209        let id_chr = id_chr.map(|chr| chr.id);
210
211        let name_chr = session
212            .get_characteristic_by_uuid(&ots_srv.id, ids::characteristic::object_name)
213            .await?;
214        log::debug!("Name Char: {name_chr:#?}");
215        let name_chr = name_chr.id;
216
217        let type_chr = session
218            .get_characteristic_by_uuid(&ots_srv.id, ids::characteristic::object_type)
219            .await?;
220        log::debug!("Type Char: {type_chr:#?}");
221        let type_chr = type_chr.id;
222
223        let size_chr = session
224            .get_characteristic_by_uuid(&ots_srv.id, ids::characteristic::object_size)
225            .await?;
226        log::debug!("Size Char: {size_chr:#?}");
227        let size_chr = size_chr.id;
228
229        let prop_chr = session
230            .get_characteristic_by_uuid(&ots_srv.id, ids::characteristic::object_properties)
231            .await?;
232        log::debug!("Prop Char: {prop_chr:#?}");
233        let prop_chr = prop_chr.id;
234
235        let crt_chr = session
236            .get_characteristic_by_uuid(&ots_srv.id, ids::characteristic::object_first_created)
237            .await
238            .map(Some)
239            .or_else(|error| {
240                if matches!(error, BluetoothError::UuidNotFound { .. }) {
241                    Ok(None)
242                } else {
243                    Err(error)
244                }
245            })?;
246        log::debug!("Crt Char: {crt_chr:#?}");
247        let crt_chr = crt_chr.map(|chr| chr.id);
248
249        let mod_chr = session
250            .get_characteristic_by_uuid(&ots_srv.id, ids::characteristic::object_last_modified)
251            .await
252            .map(Some)
253            .or_else(|error| {
254                if matches!(error, BluetoothError::UuidNotFound { .. }) {
255                    Ok(None)
256                } else {
257                    Err(error)
258                }
259            })?;
260        log::debug!("Mod Char: {mod_chr:#?}");
261        let mod_chr = mod_chr.map(|chr| chr.id);
262
263        let mut adapter_and_device_info = None;
264        for adapter_info in session.get_adapters().await? {
265            if let Some(device_info) = session
266                .get_devices_on_adapter(&adapter_info.id)
267                .await?
268                .into_iter()
269                .find(|device_info| &device_info.id == device_id)
270            {
271                adapter_and_device_info = Some((adapter_info, device_info));
272            }
273        }
274        let (adapter_info, device_info) = adapter_and_device_info.ok_or_else(|| Error::NotFound)?;
275
276        fn socketaddr_new(
277            mac: bluez_async::MacAddress,
278            type_: bluez_async::AddressType,
279            psm: Psm,
280        ) -> SocketAddr {
281            let mac: [u8; 6] = mac.into();
282            let type_ = match type_ {
283                bluez_async::AddressType::Public => AddressType::Public,
284                bluez_async::AddressType::Random => AddressType::Random,
285            };
286
287            SocketAddr::new(mac.into(), type_, psm)
288        }
289
290        let adapter_addr = if config.privileged {
291            socketaddr_new(
292                [0, 0, 0, 0, 0, 0].into(),
293                bluez_async::AddressType::Random,
294                Psm::L2CapLeCidOts,
295            )
296        } else {
297            socketaddr_new(
298                adapter_info.mac_address,
299                adapter_info.address_type,
300                Psm::L2CapLeDynStart,
301            )
302        };
303
304        let device_addr = socketaddr_new(
305            device_info.mac_address,
306            device_info.address_type,
307            Psm::L2CapLeCidOts,
308        );
309
310        Ok(Self {
311            session: session.clone(),
312            adapter_id: adapter_info.id,
313            device_id: device_id.clone(),
314            adapter_addr,
315            device_addr,
316            sock_security: config.security,
317            action_features,
318            list_features,
319            oacp_chr,
320            olcp_chr,
321            id_chr,
322            name_chr,
323            type_chr,
324            size_chr,
325            prop_chr,
326            crt_chr,
327            mod_chr,
328        })
329    }
330
331    /// Get object action feature flags
332    pub fn action_features(&self) -> &ActionFeature {
333        &self.action_features
334    }
335
336    /// Get object list feature flags
337    pub fn list_features(&self) -> &ListFeature {
338        &self.list_features
339    }
340
341    /// Get current object identifier
342    pub async fn id(&self) -> Result<Option<u64>> {
343        if let Some(chr) = &self.id_chr {
344            let raw = self.session.read_characteristic_value(chr).await?;
345            Ok(Some(Ule48::try_from(&raw[..])?.into()))
346        } else {
347            Ok(None)
348        }
349    }
350
351    /// Get current object name
352    pub async fn name(&self) -> Result<String> {
353        Ok(String::from_utf8(
354            self.session
355                .read_characteristic_value(&self.name_chr)
356                .await?,
357        )?)
358    }
359
360    /// Get current object type
361    pub async fn type_(&self) -> Result<Uuid> {
362        let raw = self
363            .session
364            .read_characteristic_value(&self.type_chr)
365            .await?;
366        Ok(types::uuid_from_raw(&raw[..])?)
367    }
368
369    /// Get sizes of current object
370    pub async fn size(&self) -> Result<Sizes> {
371        let raw = self
372            .session
373            .read_characteristic_value(&self.size_chr)
374            .await?;
375        Ok(raw[..].try_into()?)
376    }
377
378    /// Get first created time for selected object
379    pub async fn first_created(&self) -> Result<Option<DateTime>> {
380        Ok(if let Some(chr) = &self.crt_chr {
381            let raw = self.session.read_characteristic_value(chr).await?;
382            DateTime::try_from(raw.as_slice()).map(Some)?
383        } else {
384            None
385        })
386    }
387
388    /// Get last modified time for selected object
389    pub async fn last_modified(&self) -> Result<Option<DateTime>> {
390        Ok(if let Some(chr) = &self.mod_chr {
391            let raw = self.session.read_characteristic_value(chr).await?;
392            DateTime::try_from(raw.as_slice()).map(Some)?
393        } else {
394            None
395        })
396    }
397
398    /// Get current object properties
399    pub async fn properties(&self) -> Result<Property> {
400        let raw = self
401            .session
402            .read_characteristic_value(&self.prop_chr)
403            .await?;
404        Ok(Property::try_from(&raw[..])?)
405    }
406
407    /// Get current object metadata
408    pub async fn metadata(&self) -> Result<Metadata> {
409        let id = self.id().await?;
410        let name = self.name().await?;
411        let type_ = self.type_().await?;
412        let (current_size, allocated_size) = if let Ok(size) = self.size().await {
413            (Some(size.current), Some(size.allocated))
414        } else {
415            (None, None)
416        };
417        let first_created = self.first_created().await?;
418        let last_modified = self.last_modified().await?;
419        let properties = self.properties().await.unwrap_or_default();
420
421        Ok(Metadata {
422            id,
423            name,
424            type_,
425            current_size,
426            allocated_size,
427            first_created,
428            last_modified,
429            properties,
430        })
431    }
432
433    /// Select previous object
434    ///
435    /// Returns `false` if current object is first.
436    pub async fn previous(&self) -> Result<bool> {
437        match self.do_previous().await {
438            Ok(_) => Ok(true),
439            Err(Error::Core(ots_core::Error::ListError(ListRc::OutOfBounds))) => Ok(false),
440            Err(error) => Err(error),
441        }
442    }
443
444    /// Select next object
445    ///
446    /// Returns `false` if current object is last.
447    pub async fn next(&self) -> Result<bool> {
448        match self.do_next().await {
449            Ok(_) => Ok(true),
450            Err(Error::Core(CoreError::ListError(ListRc::OutOfBounds))) => Ok(false),
451            Err(error) => Err(error),
452        }
453    }
454
455    /// Select object by identifier
456    ///
457    /// Returns `false` if object nor found.
458    pub async fn go_to(&self, id: u64) -> Result<bool> {
459        match self.do_go_to(id).await {
460            Ok(_) => Ok(true),
461            Err(Error::Core(CoreError::ListError(ListRc::ObjectIdNotFound))) => Ok(false),
462            Err(error) => Err(error),
463        }
464    }
465
466    async fn socket(&self) -> Result<Stream> {
467        let socket = Socket::new(SocketType::SEQPACKET)?;
468        if let Some(security) = self.sock_security.as_ref() {
469            socket.set_security(security)?;
470        }
471        log::debug!("{:?}", socket.security()?);
472        log::debug!("Bind to {:?}", self.adapter_addr);
473        socket.bind(&self.adapter_addr)?;
474        log::debug!("Connect to {:?}", self.device_addr);
475        let stream = tokio::time::timeout(
476            core::time::Duration::from_secs(2),
477            socket.connect(&self.device_addr),
478        )
479        .await
480        .map_err(|_| Error::Timeout)??;
481        log::debug!(
482            "Local/Peer Address: {:?}/{:?}",
483            stream.local_addr()?,
484            stream.peer_addr()?
485        );
486        log::debug!(
487            "Send/Recv MTU: {:?}/{}",
488            stream.send_mtu(),
489            stream.recv_mtu()?
490        );
491        log::debug!("Security: {:?}", stream.security()?);
492        Ok(stream)
493    }
494
495    /// Read object data
496    pub async fn read(&self, offset: usize, length: Option<usize>) -> Result<Vec<u8>> {
497        use tokio::io::AsyncReadExt;
498
499        let length = if let Some(length) = length {
500            length
501        } else {
502            self.size().await?.current
503        };
504
505        let mut buffer = Vec::with_capacity(length);
506        #[allow(clippy::uninit_vec)]
507        unsafe {
508            buffer.set_len(length)
509        };
510
511        let mut stm = self.read_base(offset, length).await?;
512
513        stm.read_exact(&mut buffer[..length]).await?;
514
515        Ok(buffer)
516    }
517
518    /// Read object data
519    pub async fn read_to(&self, offset: usize, buffer: &mut [u8]) -> Result<usize> {
520        use tokio::io::AsyncReadExt;
521
522        let size = self.size().await?.current;
523
524        // length cannot exceeds available length from offset to end
525        let length = buffer.len().min(size - offset);
526
527        let mut stm = self.read_base(offset, length).await?;
528
529        stm.read_exact(&mut buffer[..length]).await?;
530
531        Ok(length)
532    }
533
534    /// Read object data
535    pub async fn read_stream(&self, offset: usize, length: Option<usize>) -> Result<Stream> {
536        let size = self.size().await?.current;
537
538        // length cannot exceeds available length from offset to end
539        let length = length.unwrap_or(size).min(size - offset);
540
541        self.read_base(offset, length).await
542    }
543
544    async fn read_base(&self, offset: usize, length: usize) -> Result<Stream> {
545        let stm = self.socket().await?;
546
547        self.do_read(offset, length).await?;
548
549        log::debug!("recv/send mtu: {}/{}", stm.recv_mtu()?, stm.send_mtu()?);
550
551        Ok(stm)
552    }
553
554    /// Write object data
555    pub async fn write(&self, offset: usize, buffer: &[u8], mode: WriteMode) -> Result<usize> {
556        use tokio::io::AsyncWriteExt;
557
558        let size = self.size().await?.allocated;
559
560        // length cannot exceeds available length from offset to end
561        let length = buffer.len().min(size - offset);
562
563        let mut stm = self.write_base(offset, length, mode).await?;
564
565        stm.write_all(&buffer[..length]).await?;
566
567        Ok(length)
568    }
569
570    /// Write object data
571    pub async fn write_stream(
572        &self,
573        offset: usize,
574        length: Option<usize>,
575        mode: WriteMode,
576    ) -> Result<Stream> {
577        let size = self.size().await?.allocated;
578
579        // length cannot exceeds available length from offset to end
580        let length = length.unwrap_or(size).min(size - offset);
581
582        self.write_base(offset, length, mode).await
583    }
584
585    async fn write_base(&self, offset: usize, length: usize, mode: WriteMode) -> Result<Stream> {
586        let stm = self.socket().await?;
587
588        self.do_write(offset, length, mode).await?;
589
590        log::debug!("recv/send mtu: {}/{}", stm.recv_mtu()?, stm.send_mtu()?);
591
592        Ok(stm)
593    }
594
595    async fn request(&self, chr: &CharacteristicId, req: impl Into<Vec<u8>>) -> Result<Vec<u8>> {
596        self.session.start_notify(chr).await?;
597
598        let resps = self
599            .session
600            .device_event_stream(&self.device_id)
601            .await?
602            .filter_map(|event| {
603                log::trace!("Evt: {event:?}");
604                core::future::ready(
605                    if let BluetoothEvent::Characteristic {
606                        id,
607                        event: CharacteristicEvent::Value { value },
608                    } = event
609                    {
610                        if &id == chr {
611                            Some(value)
612                        } else {
613                            None
614                        }
615                    } else {
616                        None
617                    },
618                )
619            })
620            .take(1)
621            .take_until(tokio::time::sleep(core::time::Duration::from_secs(1)));
622        futures::pin_mut!(resps);
623
624        let req = req.into();
625        log::trace!("Req: {req:?}");
626
627        self.session.write_characteristic_value(chr, req).await?;
628
629        let res = resps.next().await.ok_or_else(|| Error::NoResponse)?;
630        {
631            log::trace!("Res: {res:?}");
632        }
633
634        self.session.stop_notify(chr).await?;
635
636        Ok(res)
637    }
638}
639
640macro_rules! impl_fns {
641    ($($req_func:ident: $req_type:ident => $res_type:ident [ $char_field:ident $(: $char_kind:ident)*, $feat_field:ident: $feat_type:ident ] {
642        $($(#[$($meta:meta)*])*
643          $vis:vis $func:ident: $req_name:ident $({ $($req_arg_name:ident: $req_arg_type:ty),* })* => $res_name:ident $({ $($res_arg_name:ident: $res_arg_type:ty),* })* $([ $feat_name:ident ])*,)*
644    })*) => {
645        $(
646            async fn $req_func(&self, req: &$req_type) -> Result<$res_type> {
647                let res = self.request(impl_fns!(# self.$char_field $(: $char_kind)*), req).await?;
648                Ok(res.as_slice().try_into()?)
649            }
650
651            $(
652                $(#[$($meta)*])*
653                $vis async fn $func(&self $($(, $req_arg_name: $req_arg_type)*)*) -> Result<impl_fns!(@ $($($res_arg_type)*)*)> {
654                    $(if !self.$feat_field.contains($feat_type::$feat_name) {
655                        return Err(Error::NotSupported);
656                    })*
657                    if let $res_type::$res_name $({ $($res_arg_name),* })* = self.$req_func(&$req_type::$req_name $({ $($req_arg_name),* })*).await? {
658                        Ok(impl_fns!(@ $($($res_arg_name)*)*))
659                    } else {
660                        Err(Error::BadResponse)
661                    }
662                }
663            )*
664        )*
665    };
666
667    (@ $id:ident) => {
668        $id
669    };
670
671    (@ $type:ty) => {
672        $type
673    };
674
675    (@ ) => {
676        ()
677    };
678
679    (# $self:ident . $char_field:ident) => {
680        &$self.$char_field
681    };
682
683    (# $self:ident . $char_field:ident: Option) => {
684        $self.$char_field.as_ref().ok_or_else(|| Error::NotSupported)?
685    };
686}
687
688impl OtsClient {
689    impl_fns! {
690        action_request: ActionReq => ActionRes [oacp_chr, action_features: ActionFeature] {
691            /// Create new object
692            pub create: Create { size: usize, type_: Uuid } => None [Create],
693            /// Delete selected object
694            pub delete: Delete => None [Delete],
695            /// Calculate checksum using selected object data
696            pub check_sum: CheckSum { offset: usize, length: usize } => CheckSum { value: u32 } [CheckSum],
697            /// Execute selected object
698            pub execute: Execute { param: Vec<u8> } => Execute { param: Vec<u8> } [Execute],
699            do_read: Read { offset: usize, length: usize } => None [Read],
700            do_write: Write { offset: usize, length: usize, mode: WriteMode } => None [Write],
701            /// Abort operation
702            pub abort: Abort => None [Abort],
703        }
704        list_request: ListReq => ListRes [olcp_chr: Option, list_features: ListFeature] {
705            /// Select first object in a list
706            pub first: First => None,
707            /// Select last object in a list
708            pub last: Last => None,
709            do_previous: Previous => None,
710            do_next: Next => None,
711            do_go_to: GoTo { id: u64 } => None [GoTo],
712            /// Change objects order in a list
713            pub order: Order { order: SortOrder } => None [Order],
714            /// Get number of objects in a list
715            pub number_of: NumberOf => NumberOf { count: u32 } [NumberOf],
716            /// Clear objects mark
717            pub clear_mark: ClearMark => None [ClearMark],
718        }
719    }
720}