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
32pub type Result<T> = core::result::Result<T, Error>;
34
35#[derive(thiserror::Error, Debug)]
37pub enum Error {
38 #[error("Input/Output Error: {0}")]
40 Io(#[from] std::io::Error),
41 #[error("Bluetooth error: {0}")]
43 Bt(#[from] BluetoothError),
44 #[error("OTS core error: {0}")]
46 Core(#[from] CoreError),
47 #[error("Not supported")]
55 NotSupported,
56 #[error("Not found")]
58 NotFound,
59 #[error("No response")]
61 NoResponse,
62 #[error("Invalid response")]
64 BadResponse,
65 #[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#[derive(Debug, Clone, Default)]
84pub struct ClientConfig {
85 pub privileged: bool,
89
90 pub security: Option<Security>,
92}
93
94pub 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 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 pub fn action_features(&self) -> &ActionFeature {
333 &self.action_features
334 }
335
336 pub fn list_features(&self) -> &ListFeature {
338 &self.list_features
339 }
340
341 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub async fn read_stream(&self, offset: usize, length: Option<usize>) -> Result<Stream> {
536 let size = self.size().await?.current;
537
538 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 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 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 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 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 pub create: Create { size: usize, type_: Uuid } => None [Create],
693 pub delete: Delete => None [Delete],
695 pub check_sum: CheckSum { offset: usize, length: usize } => CheckSum { value: u32 } [CheckSum],
697 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 pub abort: Abort => None [Abort],
703 }
704 list_request: ListReq => ListRes [olcp_chr: Option, list_features: ListFeature] {
705 pub first: First => None,
707 pub last: Last => None,
709 do_previous: Previous => None,
710 do_next: Next => None,
711 do_go_to: GoTo { id: u64 } => None [GoTo],
712 pub order: Order { order: SortOrder } => None [Order],
714 pub number_of: NumberOf => NumberOf { count: u32 } [NumberOf],
716 pub clear_mark: ClearMark => None [ClearMark],
718 }
719 }
720}