1use crate::broadcast_state::BleBroadcastState;
5use crate::db;
6use crate::error::{BleError, Result};
7use crate::gatt::{GattConnection, GattService};
8use crate::pairing;
9use crate::pdu::{self, OpCode};
10use crate::session::BleSession;
11use hap_crypto::{AccessoryPairing, ControllerKeypair};
12use hap_model::format::{CharFormat, CharValue};
13use hap_model::tree::Accessory;
14use hap_model::{CharacteristicType, ServiceType};
15use std::collections::HashMap;
16use std::sync::Arc;
17use tokio::sync::Mutex;
18use tokio_stream::StreamExt as _;
19
20fn gsn_is_newer(new: u16, last: u16) -> bool {
24 let diff = new.wrapping_sub(last);
25 diff != 0 && diff < 0x8000
26}
27
28fn parse_device_id(s: &str) -> Option<[u8; 6]> {
34 let mut out = [0u8; 6];
35 let mut parts = s.split(':');
36 for slot in &mut out {
37 let p = parts.next()?;
38 if p.len() != 2 {
39 return None;
40 }
41 *slot = u8::from_str_radix(p, 16).ok()?;
42 }
43 parts.next().is_none().then_some(out)
44}
45
46const MAX_REVIVE_RETRIES: u32 = 3;
49
50const ENABLE_BROADCAST_BODY: [u8; 7] = [0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01];
53
54#[derive(Debug, Clone, PartialEq)]
56pub struct CharacteristicEvent {
57 pub aid: u64,
59 pub iid: u64,
61 pub value: CharValue,
63}
64
65struct Secure {
68 session: BleSession,
69 tid: u8,
70 generation: u64,
74}
75
76struct Reviver {
79 keypair: ControllerKeypair,
80 pairing: AccessoryPairing,
81 verify_char: String,
82 verify_iid: u16,
83 frag_size: usize,
84}
85
86pub(crate) struct SecureContext {
91 pub session: BleSession,
93 pub session_generation: u64,
95 pub keypair: ControllerKeypair,
97 pub pairing: AccessoryPairing,
99 pub verify_char: String,
101 pub verify_iid: u16,
102 pub pairings_char: String,
104 pub pairings_iid: u16,
105 pub broadcast_key: hap_crypto::BroadcastKey,
107 pub initial_gsn: u16,
109}
110
111async fn revive_if_stale(
115 gatt: &dyn GattConnection,
116 s: &mut Secure,
117 reviver: &Reviver,
118) -> Result<()> {
119 if gatt.generation().await <= s.generation {
120 return Ok(());
121 }
122 let (session, _bkey) = pairing::pair_verify(
123 gatt,
124 &reviver.verify_char,
125 reviver.verify_iid,
126 &reviver.keypair,
127 &reviver.pairing,
128 reviver.frag_size,
129 )
130 .await?;
131 s.session = session;
132 s.tid = 0;
133 s.generation = gatt.generation().await;
136 Ok(())
137}
138
139mod pairings_tlv {
141 pub(super) const STATE: u8 = 0x06;
142 pub(super) const METHOD: u8 = 0x00;
143 pub(super) const IDENTIFIER: u8 = 0x01;
144 pub(super) const ERROR: u8 = 0x07;
145 pub(super) const STATE_M1: u8 = 0x01;
146 pub(super) const STATE_M2: u8 = 0x02;
147 pub(super) const METHOD_REMOVE: u8 = 0x04;
148}
149
150fn encode_remove_pairing(controller_id: &str) -> Vec<u8> {
153 let mut out = Vec::new();
154 let mut w = hap_tlv8::Tlv8Writer::new(&mut out);
155 w.push_u8(pairings_tlv::STATE, pairings_tlv::STATE_M1);
156 w.push_u8(pairings_tlv::METHOD, pairings_tlv::METHOD_REMOVE);
157 w.push(pairings_tlv::IDENTIFIER, controller_id.as_bytes());
158 out
159}
160
161fn expect_remove_m2(tlv: &[u8]) -> Result<()> {
164 let map = hap_tlv8::Tlv8Map::parse(tlv)?;
165 if let Some(err) = map.get(pairings_tlv::ERROR) {
166 return Err(BleError::PairingRejected(err.first().copied().unwrap_or(1)));
167 }
168 match map
169 .get(pairings_tlv::STATE)
170 .and_then(|s| s.first().copied())
171 {
172 Some(pairings_tlv::STATE_M2) => Ok(()),
173 _ => Err(BleError::MalformedPdu("remove-pairing reply not state M2")),
174 }
175}
176
177async fn dedup_should_emit(emitted: &Mutex<HashMap<u64, u16>>, iid: u64, gsn: u16) -> bool {
186 let mut e = emitted.lock().await;
187 let prev = e.get(&iid).copied();
188 if prev == Some(gsn) {
189 return false;
190 }
191 if prev.is_none_or(|p| gsn_is_newer(gsn, p)) {
192 e.insert(iid, gsn);
193 }
194 true
195}
196
197async fn read_char_raw(
202 gatt: &dyn GattConnection,
203 secure: &Mutex<Secure>,
204 reviver: &Reviver,
205 uuid: &str,
206 iid: u64,
207 frag_size: usize,
208) -> Result<Vec<u8>> {
209 let iid16 = u16::try_from(iid).map_err(|_| BleError::CharacteristicNotFound { aid: 0, iid })?;
210 let mut s = secure.lock().await;
211 let mut attempts = 0;
212 loop {
213 revive_if_stale(gatt, &mut s, reviver).await?;
214 s.tid = s.tid.wrapping_add(1);
215 let tid = s.tid;
216 match pdu::request_secure(
217 gatt,
218 &mut s.session,
219 uuid,
220 OpCode::CharacteristicRead,
221 tid,
222 iid16,
223 &[],
224 frag_size,
225 )
226 .await
227 {
228 Ok(resp) => return pdu::value_param(&resp.body),
229 Err(e) => {
233 attempts += 1;
234 if attempts < MAX_REVIVE_RETRIES && gatt.generation().await > s.generation {
235 continue;
236 }
237 return Err(e);
238 }
239 }
240 }
241}
242
243async fn write_char_raw(
250 gatt: &dyn GattConnection,
251 secure: &Mutex<Secure>,
252 reviver: &Reviver,
253 uuid: &str,
254 iid: u64,
255 value_bytes: &[u8],
256 frag_size: usize,
257) -> Result<()> {
258 let iid16 = u16::try_from(iid).map_err(|_| BleError::CharacteristicNotFound { aid: 0, iid })?;
259 let body = pdu::encode_write_body(value_bytes);
260 let mut s = secure.lock().await;
261 let mut attempts = 0;
262 loop {
263 revive_if_stale(gatt, &mut s, reviver).await?;
264 s.tid = s.tid.wrapping_add(1);
265 let tid = s.tid;
266 match pdu::request_secure(
267 gatt,
268 &mut s.session,
269 uuid,
270 OpCode::CharacteristicWrite,
271 tid,
272 iid16,
273 &body,
274 frag_size,
275 )
276 .await
277 {
278 Ok(resp) if resp.status != 0 => return Err(BleError::RequestRejected(resp.status)),
279 Ok(_) => return Ok(()),
280 Err(e) => {
281 attempts += 1;
282 if attempts < MAX_REVIVE_RETRIES && gatt.generation().await > s.generation {
283 continue;
284 }
285 return Err(e);
286 }
287 }
288 }
289}
290
291pub struct BleAccessory {
295 gatt: Arc<dyn GattConnection>,
296 secure: Arc<Mutex<Secure>>,
297 reviver: Arc<Reviver>,
298 pairings: (String, u16),
300 frag_size: usize,
301 accessories: Vec<Accessory>,
302 chars: HashMap<(u64, u64), (String, CharFormat)>,
304 events_tx: tokio::sync::broadcast::Sender<CharacteristicEvent>,
305 tasks: Vec<tokio::task::JoinHandle<()>>,
307 last_gsn: Arc<Mutex<u16>>,
309 emitted: Arc<Mutex<HashMap<u64, u16>>>,
312 broadcast_key: hap_crypto::BroadcastKey,
314 advert_source: Option<Arc<dyn crate::gatt::AdvertSource>>,
318}
319
320impl Drop for BleAccessory {
321 fn drop(&mut self) {
322 for task in &self.tasks {
323 task.abort();
324 }
325 }
326}
327
328impl BleAccessory {
329 pub(crate) fn new(
336 gatt: Arc<dyn GattConnection>,
337 ctx: SecureContext,
338 frag_size: usize,
339 gatt_services: &[GattService],
340 accessories: Vec<Accessory>,
341 ) -> Self {
342 let (events_tx, _) = tokio::sync::broadcast::channel(64);
343 let mut uuid_by_iid: HashMap<u64, String> = HashMap::new();
347 for gs in gatt_services {
348 for gc in &gs.characteristics {
349 uuid_by_iid.insert(u64::from(gc.iid), gc.uuid.clone());
350 }
351 }
352 let mut chars = HashMap::new();
353 for acc in &accessories {
354 for svc in &acc.services {
355 for ch in &svc.characteristics {
356 if let Some(uuid) = uuid_by_iid.get(&ch.iid) {
357 chars.insert((acc.aid, ch.iid), (uuid.clone(), ch.format));
358 }
359 }
360 }
361 }
362 Self {
363 gatt,
364 secure: Arc::new(Mutex::new(Secure {
365 session: ctx.session,
366 tid: 0,
367 generation: ctx.session_generation,
368 })),
369 reviver: Arc::new(Reviver {
370 keypair: ctx.keypair,
371 pairing: ctx.pairing,
372 verify_char: ctx.verify_char,
373 verify_iid: ctx.verify_iid,
374 frag_size,
375 }),
376 pairings: (ctx.pairings_char, ctx.pairings_iid),
377 frag_size,
378 accessories,
379 chars,
380 events_tx,
381 tasks: Vec::new(),
382 last_gsn: Arc::new(Mutex::new(ctx.initial_gsn)),
383 emitted: Arc::new(Mutex::new(HashMap::new())),
384 broadcast_key: ctx.broadcast_key,
385 advert_source: None,
386 }
387 }
388
389 pub fn accessories(&self) -> &[Accessory] {
391 &self.accessories
392 }
393
394 pub async fn broadcast_state(&self) -> BleBroadcastState {
397 BleBroadcastState {
398 key: self.broadcast_key.clone(),
399 gsn: *self.last_gsn.lock().await,
400 }
401 }
402
403 #[allow(clippy::needless_pass_by_value)]
411 pub fn find(&self, svc: ServiceType, chr: CharacteristicType) -> Result<(u64, u64)> {
412 for acc in &self.accessories {
413 for service in &acc.services {
414 if service.service_type == svc {
415 for ch in &service.characteristics {
416 if ch.char_type == chr {
417 return Ok((acc.aid, ch.iid));
418 }
419 }
420 }
421 }
422 }
423 Err(BleError::CharacteristicNotFound { aid: 0, iid: 0 })
424 }
425
426 pub async fn read(&mut self, aid: u64, iid: u64) -> Result<CharValue> {
431 let (uuid, format) = self
432 .chars
433 .get(&(aid, iid))
434 .cloned()
435 .ok_or(BleError::CharacteristicNotFound { aid, iid })?;
436 let raw = read_char_raw(
437 self.gatt.as_ref(),
438 &self.secure,
439 &self.reviver,
440 &uuid,
441 iid,
442 self.frag_size,
443 )
444 .await?;
445 db::decode_value(format, &raw)
446 }
447
448 pub async fn remove_pairing(&mut self, controller_id: &str) -> Result<()> {
469 let (uuid, iid) = self.pairings.clone();
470 let removing_self = controller_id == self.reviver.keypair.id;
471 let tlv = encode_remove_pairing(controller_id);
472 let body = pdu::encode_write_body(&tlv);
473 let mut s = self.secure.lock().await;
474 revive_if_stale(self.gatt.as_ref(), &mut s, &self.reviver).await?;
475 s.tid = s.tid.wrapping_add(1);
476 let tid = s.tid;
477 let result = pdu::request_secure(
478 self.gatt.as_ref(),
479 &mut s.session,
480 &uuid,
481 OpCode::CharacteristicWrite,
482 tid,
483 iid,
484 &body,
485 self.frag_size,
486 )
487 .await;
488 match result {
489 Ok(resp) if resp.status != 0 => Err(BleError::PairingRejected(resp.status)),
490 Ok(resp) => expect_remove_m2(&pdu::value_param(&resp.body)?),
491 Err(BleError::Disconnected | BleError::Crypto(_)) if removing_self => Ok(()),
495 Err(e) => Err(e),
496 }
497 }
498
499 pub async fn write(&mut self, aid: u64, iid: u64, value: CharValue) -> Result<()> {
508 let (uuid, format) = self
509 .chars
510 .get(&(aid, iid))
511 .cloned()
512 .ok_or(BleError::CharacteristicNotFound { aid, iid })?;
513 let bytes = db::encode_value(format, &value)?;
514 write_char_raw(
515 self.gatt.as_ref(),
516 &self.secure,
517 &self.reviver,
518 &uuid,
519 iid,
520 &bytes,
521 self.frag_size,
522 )
523 .await
524 }
525
526 pub async fn thread_provision(&mut self, dataset: &crate::thread::ThreadDataset) -> Result<()> {
547 let (uuid, iid) = self.thread_control_point()?;
548
549 write_char_raw(
551 self.gatt.as_ref(),
552 &self.secure,
553 &self.reviver,
554 &uuid,
555 iid,
556 &crate::thread::encode_query(),
557 self.frag_size,
558 )
559 .await?;
560
561 let provision = crate::thread::encode_provision(dataset);
564 match write_char_raw(
565 self.gatt.as_ref(),
566 &self.secure,
567 &self.reviver,
568 &uuid,
569 iid,
570 &provision,
571 self.frag_size,
572 )
573 .await
574 {
575 Ok(()) => tracing::info!("thread provision write acknowledged"),
576 Err(e) => tracing::info!(
577 error = %e,
578 "thread provision write errored — expected as the accessory joins Thread"
579 ),
580 }
581 Ok(())
582 }
583
584 fn thread_control_point(&self) -> Result<(String, u64)> {
587 self.chars
588 .iter()
589 .find(|(_, (uuid, _))| {
590 uuid.eq_ignore_ascii_case(crate::thread::THREAD_CONTROL_POINT_UUID)
591 })
592 .map(|(&(_, iid), (uuid, _))| (uuid.clone(), iid))
593 .ok_or(BleError::CharacteristicNotFound { aid: 0, iid: 0 })
594 }
595
596 #[must_use]
598 pub fn pairing_id(&self) -> &str {
599 &self.reviver.pairing.pairing_id
600 }
601
602 pub async fn enable_broadcasts(&mut self, iids: &[u64]) -> Result<()> {
613 let mut s = self.secure.lock().await;
614 for &iid in iids {
615 let Some((uuid, _)) = self.chars.get(&(1, iid)).cloned() else {
616 tracing::debug!(
617 iid,
618 "enable_broadcasts: characteristic not on this accessory — skipped"
619 );
620 continue;
621 };
622 let Ok(iid16) = u16::try_from(iid) else {
623 tracing::debug!(iid, "enable_broadcasts: iid exceeds u16 — skipped");
624 continue;
625 };
626 revive_if_stale(self.gatt.as_ref(), &mut s, &self.reviver).await?;
627 s.tid = s.tid.wrapping_add(1);
628 let tid = s.tid;
629 match pdu::request_secure(
635 self.gatt.as_ref(),
636 &mut s.session,
637 &uuid,
638 OpCode::CharacteristicConfig,
639 tid,
640 iid16,
641 &ENABLE_BROADCAST_BODY,
642 self.frag_size,
643 )
644 .await
645 {
646 Ok(r) if r.status == 0 => {
647 tracing::debug!(iid, "enable_broadcasts: accepted by accessory");
648 }
649 Ok(r) => {
650 tracing::debug!(
651 iid,
652 status = r.status,
653 "enable_broadcasts: rejected by accessory (non-zero HAP status)"
654 );
655 }
656 Err(e) => {
657 tracing::debug!(iid, error = %e, "enable_broadcasts: write failed");
658 }
659 }
660 }
661 Ok(())
662 }
663
664 pub async fn disconnect(&self) {
667 self.gatt.disconnect().await;
668 }
669
670 pub async fn subscribe(&mut self, aid: u64, iid: u64) -> Result<()> {
683 let (uuid, format) = self
684 .chars
685 .get(&(aid, iid))
686 .cloned()
687 .ok_or(BleError::CharacteristicNotFound { aid, iid })?;
688 let mut rx = self.gatt.subscribe(&uuid).await?;
689 let tx = self.events_tx.clone();
690 let gatt = self.gatt.clone();
691 let secure = self.secure.clone();
692 let reviver = self.reviver.clone();
693 let frag_size = self.frag_size;
694 let task = tokio::spawn(async move {
695 while rx.recv().await.is_some() {
697 if let Ok(raw) =
698 read_char_raw(gatt.as_ref(), &secure, &reviver, &uuid, iid, frag_size).await
699 {
700 if let Ok(value) = db::decode_value(format, &raw) {
701 let _ = tx.send(CharacteristicEvent { aid, iid, value });
702 }
703 }
704 }
705 });
706 self.tasks.push(task);
707 Ok(())
708 }
709
710 #[allow(clippy::too_many_lines)]
726 pub async fn watch_sleepy_events_with_source(
727 &mut self,
728 advert_source: Arc<dyn crate::gatt::AdvertSource>,
729 device_id: [u8; 6],
730 poll_iids: Vec<(u64, u64)>,
731 ) -> Result<()> {
732 let mut targets = Vec::new();
735 for (aid, iid) in poll_iids {
736 if let Some((uuid, format)) = self.chars.get(&(aid, iid)).cloned() {
737 targets.push((aid, iid, uuid, format));
738 }
739 }
740 let formats: std::collections::HashMap<u64, CharFormat> = self
742 .chars
743 .iter()
744 .map(|((_, iid), (_, f))| (*iid, *f))
745 .collect();
746 let broadcast_key = self.broadcast_key.clone();
747
748 let mut adverts = advert_source.watch_adverts().await?;
749
750 let (poll_tx, mut poll_rx) = tokio::sync::watch::channel(0u16);
755 if !targets.is_empty() {
756 let gatt = self.gatt.clone();
757 let secure = self.secure.clone();
758 let reviver = self.reviver.clone();
759 let frag = self.frag_size;
760 let poll_events = self.events_tx.clone();
761 let poll_emitted = self.emitted.clone();
762 let poll_task = tokio::spawn(async move {
763 while poll_rx.changed().await.is_ok() {
764 let gsn = *poll_rx.borrow_and_update();
765 tracing::debug!(
766 gsn,
767 targets = targets.len(),
768 "catch-up poll firing — reconnecting to read"
769 );
770 for (aid, iid, uuid, format) in &targets {
771 match read_char_raw(gatt.as_ref(), &secure, &reviver, uuid, *iid, frag)
772 .await
773 {
774 Ok(raw_val) => {
775 if let Ok(value) = db::decode_value(*format, &raw_val) {
776 if dedup_should_emit(&poll_emitted, *iid, gsn).await {
777 tracing::debug!(
778 aid = *aid,
779 iid = *iid,
780 gsn,
781 "catch-up poll emitting event"
782 );
783 let _ = poll_events.send(CharacteristicEvent {
784 aid: *aid,
785 iid: *iid,
786 value,
787 });
788 } else {
789 tracing::debug!(iid = *iid, gsn, "catch-up poll read ok but (iid,gsn) already emitted — deduped");
790 }
791 }
792 }
793 Err(e) => {
794 tracing::debug!(iid = *iid, error = %e, "catch-up poll read failed");
795 }
796 }
797 }
798 }
799 });
800 self.tasks.push(poll_task);
801 }
802
803 let tx = self.events_tx.clone();
804 let last_gsn = self.last_gsn.clone();
805 let emitted = self.emitted.clone();
806 let advert_task = tokio::spawn(async move {
807 tracing::debug!(target = ?device_id, "sleepy advert watch armed");
808 while let Some(raw) = adverts.recv().await {
809 tracing::trace!(
810 len = raw.manufacturer_data.len(),
811 first = ?raw.manufacturer_data.first(),
812 "advert frame reached the sleepy loop"
813 );
814 match crate::advert::HapAdvert::parse(&raw.manufacturer_data) {
815 Some(crate::advert::HapAdvert::Regular {
816 device_id: d, gsn, ..
817 }) => {
818 if d != device_id {
819 tracing::trace!(saw = ?d, target = ?device_id, "0x06 advert: device-id mismatch, ignoring");
820 continue;
821 }
822 {
823 let mut lg = last_gsn.lock().await;
824 if !gsn_is_newer(gsn, *lg) {
825 tracing::debug!(
826 gsn,
827 last_gsn = *lg,
828 "0x06 advert for our device: gsn NOT newer — poll suppressed"
829 );
830 continue;
831 }
832 tracing::debug!(
833 gsn,
834 prev = *lg,
835 "0x06 advert for our device: gsn bump — triggering poll"
836 );
837 *lg = gsn;
838 }
839 let _ = poll_tx.send(gsn);
842 }
843 Some(crate::advert::HapAdvert::EncryptedNotification {
844 advertising_id,
845 payload,
846 }) => {
847 if advertising_id != device_id {
848 tracing::trace!(saw = ?advertising_id, target = ?device_id, "0x11 broadcast: advertising-id mismatch, ignoring");
849 continue;
850 }
851 tracing::debug!(
852 ?advertising_id,
853 "0x11 encrypted broadcast for our device — attempting decrypt"
854 );
855 let start = *last_gsn.lock().await;
856 let candidates = std::iter::once(start.wrapping_add(1))
859 .chain(std::iter::once(start))
860 .chain((2..=100u16).map(|d| start.wrapping_add(d)));
861 for gsn in candidates {
862 let Ok(pt) = broadcast_key.open(gsn, &payload, &advertising_id) else {
863 continue;
864 };
865 if pt.len() < 12 {
866 continue;
867 }
868 if u16::from_le_bytes([pt[0], pt[1]]) != gsn {
869 continue;
870 }
871 if !gsn_is_newer(gsn, start) {
873 break;
874 }
875 let iid = u64::from(u16::from_le_bytes([pt[2], pt[3]]));
876 {
880 let mut lg = last_gsn.lock().await;
881 *lg = gsn;
882 }
883 let Some(format) = formats.get(&iid).copied() else {
884 break;
885 };
886 if let Ok(value) = db::decode_value(format, &pt[4..12]) {
887 if dedup_should_emit(&emitted, iid, gsn).await {
888 let _ = tx.send(CharacteristicEvent { aid: 1, iid, value });
889 }
890 }
891 break;
892 }
893 }
894 _ => {}
895 }
896 }
897 });
898 self.tasks.push(advert_task);
899 Ok(())
900 }
901
902 pub fn set_advert_source(&mut self, src: Arc<dyn crate::gatt::AdvertSource>) {
905 self.advert_source = Some(src);
906 }
907
908 pub async fn watch_sleepy_events(&mut self, poll_iids: Vec<(u64, u64)>) -> Result<()> {
918 let src = self.advert_source.clone().ok_or(BleError::NoAdvertSource)?;
919 let device_id =
920 parse_device_id(self.reviver.pairing.pairing_id.as_str()).ok_or_else(|| {
921 BleError::Backend("malformed pairing id; cannot derive device id".into())
922 })?;
923 self.watch_sleepy_events_with_source(src, device_id, poll_iids)
924 .await
925 }
926
927 pub fn events(&self) -> impl tokio_stream::Stream<Item = CharacteristicEvent> {
930 tokio_stream::wrappers::BroadcastStream::new(self.events_tx.subscribe())
931 .filter_map(std::result::Result::ok)
932 }
933}
934
935#[cfg(test)]
936mod tests {
937 use super::*;
938 use crate::test_support::ble_accessory_with_db;
939
940 #[tokio::test]
941 #[allow(clippy::unwrap_used)]
942 async fn find_locates_characteristic() {
943 let (h, _g) = ble_accessory_with_db().await;
944 let (aid, iid) = h
945 .find(ServiceType::LightBulb, CharacteristicType::On)
946 .unwrap();
947 assert_eq!((aid, iid), (1, 11));
948 }
949
950 #[tokio::test]
951 #[allow(clippy::unwrap_used)]
952 async fn find_missing_errors() {
953 let (h, _g) = ble_accessory_with_db().await;
954 let err = h
955 .find(ServiceType::LightBulb, CharacteristicType::Brightness)
956 .unwrap_err();
957 assert!(matches!(err, BleError::CharacteristicNotFound { .. }));
958 }
959
960 #[test]
961 fn encode_remove_pairing_matches_hap_layout() {
962 let tlv = encode_remove_pairing("c2");
964 assert_eq!(
965 tlv,
966 vec![0x06, 0x01, 0x01, 0x00, 0x01, 0x04, 0x01, 0x02, b'c', b'2']
967 );
968 }
969
970 #[test]
971 fn expect_remove_m2_accepts_m2_and_rejects_error() {
972 assert!(expect_remove_m2(&[0x06, 0x01, 0x02]).is_ok());
973 assert!(matches!(
975 expect_remove_m2(&[0x07, 0x01, 0x02]),
976 Err(BleError::PairingRejected(2))
977 ));
978 assert!(matches!(
980 expect_remove_m2(&[0x06, 0x01, 0x01]),
981 Err(BleError::MalformedPdu(_))
982 ));
983 }
984
985 #[tokio::test]
986 #[allow(clippy::unwrap_used)]
987 async fn remove_pairing_writes_request_and_accepts_m2() {
988 let (mut h, gatt) = ble_accessory_with_db().await;
989
990 let m2 = vec![0x06, 0x01, 0x02];
993 let vbody = crate::pdu::encode_value_param(&m2);
994 let mut plain = vec![0x02, 0x01, 0x00];
995 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
996 plain.extend_from_slice(&vbody);
997 let sealed =
998 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
999 gatt.queue_read("00000050-0000-1000-8000-0026bb765291", sealed);
1000
1001 h.remove_pairing("AE:EC:86:C0:BF:D7").await.unwrap();
1002 }
1003
1004 #[tokio::test]
1005 #[allow(clippy::unwrap_used)]
1006 async fn remove_own_pairing_tolerates_session_teardown() {
1007 let (mut h, gatt) = ble_accessory_with_db().await;
1009 gatt.queue_read("00000050-0000-1000-8000-0026bb765291", vec![0u8; 24]);
1013 h.remove_pairing("test-controller").await.unwrap();
1014 }
1015
1016 #[tokio::test]
1017 #[allow(clippy::unwrap_used)]
1018 async fn remove_other_pairing_propagates_teardown_error() {
1019 let (mut h, gatt) = ble_accessory_with_db().await;
1022 gatt.queue_read("00000050-0000-1000-8000-0026bb765291", vec![0u8; 24]);
1023 let err = h.remove_pairing("some-other-controller").await.unwrap_err();
1024 assert!(matches!(err, BleError::Crypto(_)));
1025 }
1026
1027 #[tokio::test]
1028 #[allow(clippy::unwrap_used)]
1029 async fn subscribe_then_event_decodes_value() {
1030 use tokio_stream::StreamExt as _;
1031 let (mut h, gatt) = ble_accessory_with_db().await;
1032
1033 let mut plain = vec![0x02, 0x01, 0x00];
1037 let vbody = crate::pdu::encode_value_param(&[0x01]); plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
1039 plain.extend_from_slice(&vbody);
1040 let sealed =
1041 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1042 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1043
1044 h.subscribe(1, 11).await.unwrap();
1045 let mut events = h.events();
1046
1047 gatt.notifier("00000025-0000-1000-8000-0026bb765291")
1049 .unwrap()
1050 .send(Vec::new())
1051 .await
1052 .unwrap();
1053
1054 let ev = events.next().await.unwrap();
1055 assert_eq!(ev.iid, 11);
1056 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
1057 }
1058
1059 #[tokio::test]
1060 #[allow(clippy::unwrap_used)]
1061 async fn gsn_bump_triggers_disconnected_event_read() {
1062 use tokio_stream::StreamExt as _;
1063 let (mut h, gatt) = ble_accessory_with_db().await;
1064
1065 let mut plain = vec![0x02, 0x01, 0x00];
1068 let vbody = crate::pdu::encode_value_param(&[0x01]);
1069 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
1070 plain.extend_from_slice(&vbody);
1071 let sealed =
1072 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1073 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1074
1075 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1076 h.watch_sleepy_events_with_source(advert_source, [1, 2, 3, 4, 5, 6], vec![(1, 11)])
1077 .await
1078 .unwrap();
1079 let mut events = h.events();
1080
1081 gatt.advert_sender()
1083 .send(crate::gatt::RawAdvert {
1084 manufacturer_data: vec![
1085 0x06, 0x21, 0x01, 1, 2, 3, 4, 5, 6, 0x01, 0x00, 0x09, 0x00, 0x01, 0x00,
1086 ],
1087 })
1088 .await
1089 .unwrap();
1090
1091 let ev = events.next().await.unwrap();
1092 assert_eq!(ev.iid, 11);
1093 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
1094 }
1095
1096 #[tokio::test]
1097 #[allow(clippy::unwrap_used)]
1098 async fn encrypted_broadcast_0x11_decrypts_and_emits_event() {
1099 use tokio_stream::StreamExt as _;
1100 let (mut h, gatt) = ble_accessory_with_db().await;
1102
1103 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
1106 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1107 let mut pt = Vec::new();
1108 pt.extend_from_slice(&1u16.to_le_bytes()); pt.extend_from_slice(&11u16.to_le_bytes()); pt.extend_from_slice(&[0x01, 0, 0, 0, 0, 0, 0, 0]); let sealed = key.seal(1, &pt, &aid_bytes);
1112
1113 let mut mfg = vec![0x11u8, 0x00];
1115 mfg.extend_from_slice(&aid_bytes);
1116 mfg.extend_from_slice(&sealed);
1117
1118 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1119 h.watch_sleepy_events_with_source(advert_source, aid_bytes, vec![])
1121 .await
1122 .unwrap();
1123 let mut events = h.events();
1124
1125 gatt.advert_sender()
1126 .send(crate::gatt::RawAdvert {
1127 manufacturer_data: mfg,
1128 })
1129 .await
1130 .unwrap();
1131
1132 let ev = events.next().await.unwrap();
1133 assert_eq!(ev.aid, 1);
1134 assert_eq!(ev.iid, 11);
1135 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
1136 }
1137
1138 #[test]
1139 fn gsn_is_newer_handles_wraparound() {
1140 assert!(gsn_is_newer(6, 5));
1141 assert!(!gsn_is_newer(5, 5));
1142 assert!(!gsn_is_newer(4, 5));
1143 assert!(gsn_is_newer(1, 65535)); assert!(!gsn_is_newer(65535, 1)); }
1146
1147 #[tokio::test]
1148 #[allow(clippy::unwrap_used)]
1149 async fn same_change_via_poll_and_broadcast_emits_once() {
1150 use tokio_stream::StreamExt as _;
1151 let (mut h, gatt) = ble_accessory_with_db().await;
1152
1153 let mut plain = vec![0x02, 0x01, 0x00];
1156 let vbody = crate::pdu::encode_value_param(&[0x01]);
1157 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
1158 plain.extend_from_slice(&vbody);
1159 let sealed =
1160 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1161 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1162
1163 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1164 h.watch_sleepy_events_with_source(advert_source, [1, 2, 3, 4, 5, 6], vec![(1, 11)])
1165 .await
1166 .unwrap();
1167 let mut events = h.events();
1168
1169 gatt.advert_sender()
1171 .send(crate::gatt::RawAdvert {
1172 manufacturer_data: vec![
1173 0x06, 0x21, 0x01, 1, 2, 3, 4, 5, 6, 0x01, 0x00, 0x09, 0x00, 0x01, 0x00,
1174 ],
1175 })
1176 .await
1177 .unwrap();
1178
1179 let ev = events.next().await.unwrap();
1181 assert_eq!(ev.iid, 11);
1182 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
1183
1184 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
1186 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1187 let mut pt = Vec::new();
1188 pt.extend_from_slice(&9u16.to_le_bytes()); pt.extend_from_slice(&11u16.to_le_bytes()); pt.extend_from_slice(&[0x01, 0, 0, 0, 0, 0, 0, 0]); let sealed_bc = key.seal(9, &pt, &aid_bytes);
1192
1193 let mut mfg = vec![0x11u8, 0x00];
1194 mfg.extend_from_slice(&aid_bytes);
1195 mfg.extend_from_slice(&sealed_bc);
1196
1197 gatt.advert_sender()
1198 .send(crate::gatt::RawAdvert {
1199 manufacturer_data: mfg,
1200 })
1201 .await
1202 .unwrap();
1203
1204 let timeout_result =
1206 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1207 assert!(
1208 timeout_result.is_err(),
1209 "expected dedup to suppress the duplicate 0x11 broadcast event, but got one"
1210 );
1211 }
1212
1213 #[tokio::test]
1218 #[allow(clippy::unwrap_used)]
1219 async fn broadcast_delivered_while_poll_read_blocked() {
1220 use tokio_stream::StreamExt as _;
1221 let (mut h, gatt) = ble_accessory_with_db().await;
1222
1223 let release = gatt.block_next_read("00000025-0000-1000-8000-0026bb765291");
1226 let mut plain = vec![0x02, 0x01, 0x00];
1227 let vbody = crate::pdu::encode_value_param(&[0x01]);
1228 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
1229 plain.extend_from_slice(&vbody);
1230 let sealed =
1231 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1232 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1233
1234 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1235 h.watch_sleepy_events_with_source(advert_source, [1, 2, 3, 4, 5, 6], vec![(1, 11)])
1236 .await
1237 .unwrap();
1238 let mut events = h.events();
1239
1240 gatt.advert_sender()
1242 .send(crate::gatt::RawAdvert {
1243 manufacturer_data: vec![
1244 0x06, 0x21, 0x01, 1, 2, 3, 4, 5, 6, 0x01, 0x00, 0x09, 0x00, 0x01, 0x00,
1245 ],
1246 })
1247 .await
1248 .unwrap();
1249
1250 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
1253 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1254 let mut pt = Vec::new();
1255 pt.extend_from_slice(&10u16.to_le_bytes()); pt.extend_from_slice(&11u16.to_le_bytes()); pt.extend_from_slice(&[0x00, 0, 0, 0, 0, 0, 0, 0]); let sealed_bc = key.seal(10, &pt, &aid_bytes);
1259 let mut mfg = vec![0x11u8, 0x00];
1260 mfg.extend_from_slice(&aid_bytes);
1261 mfg.extend_from_slice(&sealed_bc);
1262 gatt.advert_sender()
1263 .send(crate::gatt::RawAdvert {
1264 manufacturer_data: mfg,
1265 })
1266 .await
1267 .unwrap();
1268
1269 let ev = tokio::time::timeout(std::time::Duration::from_secs(2), events.next())
1273 .await
1274 .unwrap()
1275 .unwrap();
1276 assert_eq!(ev.iid, 11);
1277 assert_eq!(ev.value, hap_model::format::CharValue::Bool(false));
1278
1279 release.notify_one();
1281 let ev2 = tokio::time::timeout(std::time::Duration::from_secs(2), events.next())
1282 .await
1283 .unwrap()
1284 .unwrap();
1285 assert_eq!(ev2.iid, 11);
1286 assert_eq!(ev2.value, hap_model::format::CharValue::Bool(true));
1287 }
1288
1289 #[tokio::test]
1294 #[allow(clippy::unwrap_used)]
1295 async fn foreign_device_advert_ignored() {
1296 use tokio_stream::StreamExt as _;
1297 let (mut h, gatt) = ble_accessory_with_db().await;
1298
1299 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1300 h.watch_sleepy_events_with_source(advert_source, [1, 2, 3, 4, 5, 6], vec![(1, 11)])
1302 .await
1303 .unwrap();
1304 let mut events = h.events();
1305
1306 gatt.advert_sender()
1308 .send(crate::gatt::RawAdvert {
1309 manufacturer_data: vec![
1310 0x06, 0x21, 0x01, 9, 9, 9, 9, 9, 9, 0x01, 0x00, 0x09, 0x00, 0x01, 0x00,
1311 ],
1312 })
1313 .await
1314 .unwrap();
1315
1316 let timeout_result =
1317 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1318 assert!(
1319 timeout_result.is_err(),
1320 "foreign device advert must not emit an event, but one was received"
1321 );
1322 }
1323
1324 #[tokio::test]
1327 #[allow(clippy::unwrap_used)]
1328 async fn stale_gsn_broadcast_ignored() {
1329 use tokio_stream::StreamExt as _;
1330 let (mut h, gatt) = ble_accessory_with_db().await;
1331
1332 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
1333 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1334
1335 let mut pt = Vec::new();
1338 pt.extend_from_slice(&5u16.to_le_bytes()); pt.extend_from_slice(&11u16.to_le_bytes()); pt.extend_from_slice(&[0x01, 0, 0, 0, 0, 0, 0, 0]); let sealed = key.seal(5, &pt, &aid_bytes);
1342
1343 let mut mfg = vec![0x11u8, 0x00];
1344 mfg.extend_from_slice(&aid_bytes);
1345 mfg.extend_from_slice(&sealed);
1346
1347 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1348 h.watch_sleepy_events_with_source(advert_source, aid_bytes, vec![])
1349 .await
1350 .unwrap();
1351 let mut events = h.events();
1352
1353 gatt.advert_sender()
1355 .send(crate::gatt::RawAdvert {
1356 manufacturer_data: mfg.clone(),
1357 })
1358 .await
1359 .unwrap();
1360
1361 let ev = events.next().await.unwrap();
1362 assert_eq!(ev.iid, 11);
1363 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
1364
1365 gatt.advert_sender()
1367 .send(crate::gatt::RawAdvert {
1368 manufacturer_data: mfg,
1369 })
1370 .await
1371 .unwrap();
1372
1373 let timeout_result =
1374 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1375 assert!(
1376 timeout_result.is_err(),
1377 "duplicate GSN 5 broadcast must not emit a second event"
1378 );
1379 }
1380
1381 #[tokio::test]
1384 #[allow(clippy::unwrap_used)]
1385 async fn wrong_broadcast_key_ignored() {
1386 use tokio_stream::StreamExt as _;
1387 let (mut h, gatt) = ble_accessory_with_db().await;
1389
1390 let wrong_key = hap_crypto::BroadcastKey::from_bytes([0xFF; 32]);
1392 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1393
1394 let mut pt = Vec::new();
1395 pt.extend_from_slice(&1u16.to_le_bytes());
1396 pt.extend_from_slice(&11u16.to_le_bytes());
1397 pt.extend_from_slice(&[0x01, 0, 0, 0, 0, 0, 0, 0]);
1398 let sealed = wrong_key.seal(1, &pt, &aid_bytes);
1399
1400 let mut mfg = vec![0x11u8, 0x00];
1401 mfg.extend_from_slice(&aid_bytes);
1402 mfg.extend_from_slice(&sealed);
1403
1404 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1405 h.watch_sleepy_events_with_source(advert_source, aid_bytes, vec![])
1406 .await
1407 .unwrap();
1408 let mut events = h.events();
1409
1410 gatt.advert_sender()
1411 .send(crate::gatt::RawAdvert {
1412 manufacturer_data: mfg,
1413 })
1414 .await
1415 .unwrap();
1416
1417 let timeout_result =
1418 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1419 assert!(
1420 timeout_result.is_err(),
1421 "wrong-key broadcast must not emit any event (all candidate opens fail)"
1422 );
1423 }
1424
1425 #[tokio::test]
1429 #[allow(clippy::unwrap_used)]
1430 async fn malformed_0x11_advert_ignored() {
1431 use tokio_stream::StreamExt as _;
1432 let (mut h, gatt) = ble_accessory_with_db().await;
1433
1434 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1435 h.watch_sleepy_events_with_source(advert_source, [1, 2, 3, 4, 5, 6], vec![])
1436 .await
1437 .unwrap();
1438 let mut events = h.events();
1439
1440 let manufacturer_data = vec![0x11, 0x00, 1, 2, 3, 4, 5, 6, 0xAA, 0xBB];
1442 gatt.advert_sender()
1443 .send(crate::gatt::RawAdvert { manufacturer_data })
1444 .await
1445 .unwrap();
1446
1447 let timeout_result =
1448 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1449 assert!(
1450 timeout_result.is_err(),
1451 "malformed (too-short payload) 0x11 advert must not emit any event"
1452 );
1453 }
1454
1455 #[tokio::test]
1459 #[allow(clippy::unwrap_used)]
1460 async fn broadcast_value_self_inconsistent_gsn_ignored() {
1461 use tokio_stream::StreamExt as _;
1462 let (mut h, gatt) = ble_accessory_with_db().await;
1463
1464 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
1465 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1466
1467 let mut pt = Vec::new();
1471 pt.extend_from_slice(&3u16.to_le_bytes()); pt.extend_from_slice(&11u16.to_le_bytes()); pt.extend_from_slice(&[0x01, 0, 0, 0, 0, 0, 0, 0]); let sealed = key.seal(7, &pt, &aid_bytes); let mut mfg = vec![0x11u8, 0x00];
1477 mfg.extend_from_slice(&aid_bytes);
1478 mfg.extend_from_slice(&sealed);
1479
1480 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1481 h.watch_sleepy_events_with_source(advert_source, aid_bytes, vec![])
1482 .await
1483 .unwrap();
1484 let mut events = h.events();
1485
1486 gatt.advert_sender()
1487 .send(crate::gatt::RawAdvert {
1488 manufacturer_data: mfg,
1489 })
1490 .await
1491 .unwrap();
1492
1493 let timeout_result =
1494 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1495 assert!(
1496 timeout_result.is_err(),
1497 "self-inconsistent GSN (embedded 3 != nonce 7) must not emit any event"
1498 );
1499 }
1500
1501 #[tokio::test]
1502 #[allow(clippy::unwrap_used)]
1503 async fn read_after_reconnect_re_verifies_before_using_session() {
1504 let (mut h, gatt) = ble_accessory_with_db().await;
1505
1506 let mut plain = vec![0x02, 0x01, 0x00];
1509 let vbody = crate::pdu::encode_value_param(&[0x01]);
1510 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
1511 plain.extend_from_slice(&vbody);
1512 let sealed =
1513 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1514 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1515
1516 gatt.bump_generation();
1521 let err = h.read(1, 11).await.unwrap_err();
1522 assert!(
1523 !matches!(err, BleError::CharacteristicNotFound { .. }),
1524 "expected a verify/transport error from the re-verify attempt, got {err:?}"
1525 );
1526 }
1527
1528 #[tokio::test]
1529 async fn dedup_emits_once_per_gsn_and_never_downgrades() {
1530 let emitted = Mutex::new(HashMap::new());
1531 assert!(dedup_should_emit(&emitted, 11, 9).await);
1533 assert!(!dedup_should_emit(&emitted, 11, 9).await);
1535 assert!(dedup_should_emit(&emitted, 11, 10).await);
1537 assert!(dedup_should_emit(&emitted, 11, 9).await);
1540 assert!(!dedup_should_emit(&emitted, 11, 10).await);
1542 assert!(dedup_should_emit(&emitted, 12, 65535).await);
1544 assert!(dedup_should_emit(&emitted, 12, 1).await);
1545 assert!(!dedup_should_emit(&emitted, 12, 1).await);
1546 }
1547
1548 #[tokio::test]
1549 #[allow(clippy::unwrap_used)]
1550 async fn write_sends_secure_pdu_and_accepts_success() {
1551 let (mut h, gatt) = ble_accessory_with_db().await;
1552 let plain = vec![0x02, 0x01, 0x00];
1554 let sealed =
1555 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1556 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1557 h.write(1, 11, hap_model::format::CharValue::Bool(true))
1558 .await
1559 .unwrap();
1560 }
1561
1562 #[tokio::test]
1563 #[allow(clippy::unwrap_used)]
1564 async fn write_surfaces_nonzero_pdu_status() {
1565 let (mut h, gatt) = ble_accessory_with_db().await;
1566 let plain = vec![0x02, 0x01, 0x06]; let sealed =
1568 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1569 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1570 let err = h
1571 .write(1, 11, hap_model::format::CharValue::Bool(true))
1572 .await
1573 .unwrap_err();
1574 assert!(matches!(err, BleError::RequestRejected(6)));
1575 }
1576
1577 #[tokio::test]
1578 #[allow(clippy::unwrap_used)]
1579 async fn thread_provision_writes_query_then_tolerates_provision_teardown() {
1580 let (mut h, gatt) = ble_accessory_with_db().await;
1581 let cp = "00000704-0000-1000-8000-0026bb765291";
1583 let (_, fmt) = h.chars.get(&(1, 11)).unwrap().clone();
1584 h.chars.insert((1, 99), (cp.to_string(), fmt));
1585
1586 let plain = vec![0x02, 0x01, 0x00];
1590 let sealed =
1591 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1592 gatt.queue_read(cp, sealed);
1593
1594 let dataset = crate::thread::ThreadDataset {
1595 network_name: "OpenThread-89d7".into(),
1596 channel: 24,
1597 pan_id: 0x89d7,
1598 ext_pan_id: [0x78, 0x96, 0x21, 0x7f, 0x78, 0x7f, 0x6e, 0xbe],
1599 network_key: [0u8; 16],
1600 };
1601 h.thread_provision(&dataset).await.unwrap();
1603 }
1604
1605 #[tokio::test]
1606 #[allow(clippy::unwrap_used)]
1607 async fn thread_provision_errors_without_a_control_point() {
1608 let (mut h, _g) = ble_accessory_with_db().await;
1609 let dataset = crate::thread::ThreadDataset {
1610 network_name: "OpenThread-89d7".into(),
1611 channel: 24,
1612 pan_id: 0x89d7,
1613 ext_pan_id: [0u8; 8],
1614 network_key: [0u8; 16],
1615 };
1616 let err = h.thread_provision(&dataset).await.unwrap_err();
1617 assert!(matches!(err, BleError::CharacteristicNotFound { .. }));
1618 }
1619
1620 #[tokio::test]
1621 #[allow(clippy::unwrap_used)]
1622 async fn pairing_id_exposes_the_stored_pairing() {
1623 let (h, _g) = ble_accessory_with_db().await;
1624 assert_eq!(h.pairing_id(), "AE:EC:86:C0:BF:D7");
1625 }
1626
1627 #[tokio::test]
1628 #[allow(clippy::unwrap_used)]
1629 async fn disconnect_is_callable_on_the_accessory() {
1630 let (h, _g) = ble_accessory_with_db().await;
1631 h.disconnect().await; }
1633
1634 #[tokio::test]
1635 #[allow(clippy::unwrap_used)]
1636 async fn self_sourcing_watch_errors_without_source() {
1637 let (mut h, _g) = ble_accessory_with_db().await;
1638 let err = h.watch_sleepy_events(vec![(1, 11)]).await.unwrap_err();
1640 assert!(matches!(err, BleError::NoAdvertSource));
1641 }
1642
1643 #[tokio::test]
1644 #[allow(clippy::unwrap_used)]
1645 async fn self_sourcing_watch_emits_via_set_source() {
1646 use tokio_stream::StreamExt as _;
1647 let (mut h, gatt) = ble_accessory_with_db().await;
1648 h.set_advert_source(gatt.clone() as std::sync::Arc<dyn crate::gatt::AdvertSource>);
1650 let mut plain = vec![0x02, 0x01, 0x00];
1652 let vbody = crate::pdu::encode_value_param(&[0x01]);
1653 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
1654 plain.extend_from_slice(&vbody);
1655 let sealed =
1656 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1657 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1658 h.watch_sleepy_events(vec![(1, 11)]).await.unwrap();
1659 let mut events = h.events();
1660 gatt.advert_sender()
1662 .send(crate::gatt::RawAdvert {
1663 manufacturer_data: vec![
1664 0x06, 0x21, 0x01, 0xAE, 0xEC, 0x86, 0xC0, 0xBF, 0xD7, 0x01, 0x00, 0x09, 0x00,
1665 0x01, 0x00,
1666 ],
1667 })
1668 .await
1669 .unwrap();
1670 let ev = events.next().await.unwrap();
1671 assert_eq!((ev.aid, ev.iid), (1, 11));
1672 }
1673}