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
28const MAX_REVIVE_RETRIES: u32 = 3;
31
32const ENABLE_BROADCAST_BODY: [u8; 7] = [0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01];
35
36#[derive(Debug, Clone, PartialEq)]
38pub struct CharacteristicEvent {
39 pub aid: u64,
41 pub iid: u64,
43 pub value: CharValue,
45}
46
47struct Secure {
50 session: BleSession,
51 tid: u8,
52 generation: u64,
56}
57
58struct Reviver {
61 keypair: ControllerKeypair,
62 pairing: AccessoryPairing,
63 verify_char: String,
64 verify_iid: u16,
65 frag_size: usize,
66}
67
68pub(crate) struct SecureContext {
73 pub session: BleSession,
75 pub session_generation: u64,
77 pub keypair: ControllerKeypair,
79 pub pairing: AccessoryPairing,
81 pub verify_char: String,
83 pub verify_iid: u16,
84 pub pairings_char: String,
86 pub pairings_iid: u16,
87 pub broadcast_key: hap_crypto::BroadcastKey,
89 pub initial_gsn: u16,
91}
92
93async fn revive_if_stale(
97 gatt: &dyn GattConnection,
98 s: &mut Secure,
99 reviver: &Reviver,
100) -> Result<()> {
101 if gatt.generation().await <= s.generation {
102 return Ok(());
103 }
104 let (session, _bkey) = pairing::pair_verify(
105 gatt,
106 &reviver.verify_char,
107 reviver.verify_iid,
108 &reviver.keypair,
109 &reviver.pairing,
110 reviver.frag_size,
111 )
112 .await?;
113 s.session = session;
114 s.tid = 0;
115 s.generation = gatt.generation().await;
118 Ok(())
119}
120
121mod pairings_tlv {
123 pub(super) const STATE: u8 = 0x06;
124 pub(super) const METHOD: u8 = 0x00;
125 pub(super) const IDENTIFIER: u8 = 0x01;
126 pub(super) const ERROR: u8 = 0x07;
127 pub(super) const STATE_M1: u8 = 0x01;
128 pub(super) const STATE_M2: u8 = 0x02;
129 pub(super) const METHOD_REMOVE: u8 = 0x04;
130}
131
132fn encode_remove_pairing(controller_id: &str) -> Vec<u8> {
135 let mut out = Vec::new();
136 let mut w = hap_tlv8::Tlv8Writer::new(&mut out);
137 w.push_u8(pairings_tlv::STATE, pairings_tlv::STATE_M1);
138 w.push_u8(pairings_tlv::METHOD, pairings_tlv::METHOD_REMOVE);
139 w.push(pairings_tlv::IDENTIFIER, controller_id.as_bytes());
140 out
141}
142
143fn expect_remove_m2(tlv: &[u8]) -> Result<()> {
146 let map = hap_tlv8::Tlv8Map::parse(tlv)?;
147 if let Some(err) = map.get(pairings_tlv::ERROR) {
148 return Err(BleError::PairingRejected(err.first().copied().unwrap_or(1)));
149 }
150 match map
151 .get(pairings_tlv::STATE)
152 .and_then(|s| s.first().copied())
153 {
154 Some(pairings_tlv::STATE_M2) => Ok(()),
155 _ => Err(BleError::MalformedPdu("remove-pairing reply not state M2")),
156 }
157}
158
159async fn dedup_should_emit(emitted: &Mutex<HashMap<u64, u16>>, iid: u64, gsn: u16) -> bool {
168 let mut e = emitted.lock().await;
169 let prev = e.get(&iid).copied();
170 if prev == Some(gsn) {
171 return false;
172 }
173 if prev.is_none_or(|p| gsn_is_newer(gsn, p)) {
174 e.insert(iid, gsn);
175 }
176 true
177}
178
179async fn read_char_raw(
184 gatt: &dyn GattConnection,
185 secure: &Mutex<Secure>,
186 reviver: &Reviver,
187 uuid: &str,
188 iid: u64,
189 frag_size: usize,
190) -> Result<Vec<u8>> {
191 let iid16 = u16::try_from(iid).map_err(|_| BleError::CharacteristicNotFound { aid: 0, iid })?;
192 let mut s = secure.lock().await;
193 let mut attempts = 0;
194 loop {
195 revive_if_stale(gatt, &mut s, reviver).await?;
196 s.tid = s.tid.wrapping_add(1);
197 let tid = s.tid;
198 match pdu::request_secure(
199 gatt,
200 &mut s.session,
201 uuid,
202 OpCode::CharacteristicRead,
203 tid,
204 iid16,
205 &[],
206 frag_size,
207 )
208 .await
209 {
210 Ok(resp) => return pdu::value_param(&resp.body),
211 Err(e) => {
215 attempts += 1;
216 if attempts < MAX_REVIVE_RETRIES && gatt.generation().await > s.generation {
217 continue;
218 }
219 return Err(e);
220 }
221 }
222 }
223}
224
225async fn write_char_raw(
232 gatt: &dyn GattConnection,
233 secure: &Mutex<Secure>,
234 reviver: &Reviver,
235 uuid: &str,
236 iid: u64,
237 value_bytes: &[u8],
238 frag_size: usize,
239) -> Result<()> {
240 let iid16 = u16::try_from(iid).map_err(|_| BleError::CharacteristicNotFound { aid: 0, iid })?;
241 let body = pdu::encode_write_body(value_bytes);
242 let mut s = secure.lock().await;
243 let mut attempts = 0;
244 loop {
245 revive_if_stale(gatt, &mut s, reviver).await?;
246 s.tid = s.tid.wrapping_add(1);
247 let tid = s.tid;
248 match pdu::request_secure(
249 gatt,
250 &mut s.session,
251 uuid,
252 OpCode::CharacteristicWrite,
253 tid,
254 iid16,
255 &body,
256 frag_size,
257 )
258 .await
259 {
260 Ok(resp) if resp.status != 0 => return Err(BleError::RequestRejected(resp.status)),
261 Ok(_) => return Ok(()),
262 Err(e) => {
263 attempts += 1;
264 if attempts < MAX_REVIVE_RETRIES && gatt.generation().await > s.generation {
265 continue;
266 }
267 return Err(e);
268 }
269 }
270 }
271}
272
273pub struct BleAccessory {
277 gatt: Arc<dyn GattConnection>,
278 secure: Arc<Mutex<Secure>>,
279 reviver: Arc<Reviver>,
280 pairings: (String, u16),
282 frag_size: usize,
283 accessories: Vec<Accessory>,
284 chars: HashMap<(u64, u64), (String, CharFormat)>,
286 events_tx: tokio::sync::broadcast::Sender<CharacteristicEvent>,
287 tasks: Vec<tokio::task::JoinHandle<()>>,
289 last_gsn: Arc<Mutex<u16>>,
291 emitted: Arc<Mutex<HashMap<u64, u16>>>,
294 broadcast_key: hap_crypto::BroadcastKey,
296}
297
298impl Drop for BleAccessory {
299 fn drop(&mut self) {
300 for task in &self.tasks {
301 task.abort();
302 }
303 }
304}
305
306impl BleAccessory {
307 pub(crate) fn new(
314 gatt: Arc<dyn GattConnection>,
315 ctx: SecureContext,
316 frag_size: usize,
317 gatt_services: &[GattService],
318 accessories: Vec<Accessory>,
319 ) -> Self {
320 let (events_tx, _) = tokio::sync::broadcast::channel(64);
321 let mut uuid_by_iid: HashMap<u64, String> = HashMap::new();
325 for gs in gatt_services {
326 for gc in &gs.characteristics {
327 uuid_by_iid.insert(u64::from(gc.iid), gc.uuid.clone());
328 }
329 }
330 let mut chars = HashMap::new();
331 for acc in &accessories {
332 for svc in &acc.services {
333 for ch in &svc.characteristics {
334 if let Some(uuid) = uuid_by_iid.get(&ch.iid) {
335 chars.insert((acc.aid, ch.iid), (uuid.clone(), ch.format));
336 }
337 }
338 }
339 }
340 Self {
341 gatt,
342 secure: Arc::new(Mutex::new(Secure {
343 session: ctx.session,
344 tid: 0,
345 generation: ctx.session_generation,
346 })),
347 reviver: Arc::new(Reviver {
348 keypair: ctx.keypair,
349 pairing: ctx.pairing,
350 verify_char: ctx.verify_char,
351 verify_iid: ctx.verify_iid,
352 frag_size,
353 }),
354 pairings: (ctx.pairings_char, ctx.pairings_iid),
355 frag_size,
356 accessories,
357 chars,
358 events_tx,
359 tasks: Vec::new(),
360 last_gsn: Arc::new(Mutex::new(ctx.initial_gsn)),
361 emitted: Arc::new(Mutex::new(HashMap::new())),
362 broadcast_key: ctx.broadcast_key,
363 }
364 }
365
366 pub fn accessories(&self) -> &[Accessory] {
368 &self.accessories
369 }
370
371 pub async fn broadcast_state(&self) -> BleBroadcastState {
374 BleBroadcastState {
375 key: self.broadcast_key.clone(),
376 gsn: *self.last_gsn.lock().await,
377 }
378 }
379
380 #[allow(clippy::needless_pass_by_value)]
388 pub fn find(&self, svc: ServiceType, chr: CharacteristicType) -> Result<(u64, u64)> {
389 for acc in &self.accessories {
390 for service in &acc.services {
391 if service.service_type == svc {
392 for ch in &service.characteristics {
393 if ch.char_type == chr {
394 return Ok((acc.aid, ch.iid));
395 }
396 }
397 }
398 }
399 }
400 Err(BleError::CharacteristicNotFound { aid: 0, iid: 0 })
401 }
402
403 pub async fn read(&mut self, aid: u64, iid: u64) -> Result<CharValue> {
408 let (uuid, format) = self
409 .chars
410 .get(&(aid, iid))
411 .cloned()
412 .ok_or(BleError::CharacteristicNotFound { aid, iid })?;
413 let raw = read_char_raw(
414 self.gatt.as_ref(),
415 &self.secure,
416 &self.reviver,
417 &uuid,
418 iid,
419 self.frag_size,
420 )
421 .await?;
422 db::decode_value(format, &raw)
423 }
424
425 pub async fn remove_pairing(&mut self, controller_id: &str) -> Result<()> {
446 let (uuid, iid) = self.pairings.clone();
447 let removing_self = controller_id == self.reviver.keypair.id;
448 let tlv = encode_remove_pairing(controller_id);
449 let body = pdu::encode_write_body(&tlv);
450 let mut s = self.secure.lock().await;
451 revive_if_stale(self.gatt.as_ref(), &mut s, &self.reviver).await?;
452 s.tid = s.tid.wrapping_add(1);
453 let tid = s.tid;
454 let result = pdu::request_secure(
455 self.gatt.as_ref(),
456 &mut s.session,
457 &uuid,
458 OpCode::CharacteristicWrite,
459 tid,
460 iid,
461 &body,
462 self.frag_size,
463 )
464 .await;
465 match result {
466 Ok(resp) if resp.status != 0 => Err(BleError::PairingRejected(resp.status)),
467 Ok(resp) => expect_remove_m2(&pdu::value_param(&resp.body)?),
468 Err(BleError::Disconnected | BleError::Crypto(_)) if removing_self => Ok(()),
472 Err(e) => Err(e),
473 }
474 }
475
476 pub async fn write(&mut self, aid: u64, iid: u64, value: CharValue) -> Result<()> {
485 let (uuid, format) = self
486 .chars
487 .get(&(aid, iid))
488 .cloned()
489 .ok_or(BleError::CharacteristicNotFound { aid, iid })?;
490 let bytes = db::encode_value(format, &value)?;
491 write_char_raw(
492 self.gatt.as_ref(),
493 &self.secure,
494 &self.reviver,
495 &uuid,
496 iid,
497 &bytes,
498 self.frag_size,
499 )
500 .await
501 }
502
503 #[must_use]
505 pub fn pairing_id(&self) -> &str {
506 &self.reviver.pairing.pairing_id
507 }
508
509 pub async fn enable_broadcasts(&mut self, iids: &[u64]) -> Result<()> {
520 let mut s = self.secure.lock().await;
521 for &iid in iids {
522 let Some((uuid, _)) = self.chars.get(&(1, iid)).cloned() else {
523 continue;
524 };
525 let Ok(iid16) = u16::try_from(iid) else {
526 continue;
527 };
528 revive_if_stale(self.gatt.as_ref(), &mut s, &self.reviver).await?;
529 s.tid = s.tid.wrapping_add(1);
530 let tid = s.tid;
531 let _ = pdu::request_secure(
532 self.gatt.as_ref(),
533 &mut s.session,
534 &uuid,
535 OpCode::CharacteristicConfig,
536 tid,
537 iid16,
538 &ENABLE_BROADCAST_BODY,
539 self.frag_size,
540 )
541 .await;
542 }
543 Ok(())
544 }
545
546 pub async fn subscribe(&mut self, aid: u64, iid: u64) -> Result<()> {
559 let (uuid, format) = self
560 .chars
561 .get(&(aid, iid))
562 .cloned()
563 .ok_or(BleError::CharacteristicNotFound { aid, iid })?;
564 let mut rx = self.gatt.subscribe(&uuid).await?;
565 let tx = self.events_tx.clone();
566 let gatt = self.gatt.clone();
567 let secure = self.secure.clone();
568 let reviver = self.reviver.clone();
569 let frag_size = self.frag_size;
570 let task = tokio::spawn(async move {
571 while rx.recv().await.is_some() {
573 if let Ok(raw) =
574 read_char_raw(gatt.as_ref(), &secure, &reviver, &uuid, iid, frag_size).await
575 {
576 if let Ok(value) = db::decode_value(format, &raw) {
577 let _ = tx.send(CharacteristicEvent { aid, iid, value });
578 }
579 }
580 }
581 });
582 self.tasks.push(task);
583 Ok(())
584 }
585
586 #[allow(clippy::too_many_lines)]
602 pub async fn watch_sleepy_events(
603 &mut self,
604 advert_source: Arc<dyn crate::gatt::AdvertSource>,
605 device_id: [u8; 6],
606 poll_iids: Vec<(u64, u64)>,
607 ) -> Result<()> {
608 let mut targets = Vec::new();
611 for (aid, iid) in poll_iids {
612 if let Some((uuid, format)) = self.chars.get(&(aid, iid)).cloned() {
613 targets.push((aid, iid, uuid, format));
614 }
615 }
616 let formats: std::collections::HashMap<u64, CharFormat> = self
618 .chars
619 .iter()
620 .map(|((_, iid), (_, f))| (*iid, *f))
621 .collect();
622 let broadcast_key = self.broadcast_key.clone();
623
624 let mut adverts = advert_source.watch_adverts().await?;
625
626 let (poll_tx, mut poll_rx) = tokio::sync::watch::channel(0u16);
631 if !targets.is_empty() {
632 let gatt = self.gatt.clone();
633 let secure = self.secure.clone();
634 let reviver = self.reviver.clone();
635 let frag = self.frag_size;
636 let poll_events = self.events_tx.clone();
637 let poll_emitted = self.emitted.clone();
638 let poll_task = tokio::spawn(async move {
639 while poll_rx.changed().await.is_ok() {
640 let gsn = *poll_rx.borrow_and_update();
641 for (aid, iid, uuid, format) in &targets {
642 if let Ok(raw_val) =
643 read_char_raw(gatt.as_ref(), &secure, &reviver, uuid, *iid, frag).await
644 {
645 if let Ok(value) = db::decode_value(*format, &raw_val) {
646 if dedup_should_emit(&poll_emitted, *iid, gsn).await {
647 let _ = poll_events.send(CharacteristicEvent {
648 aid: *aid,
649 iid: *iid,
650 value,
651 });
652 }
653 }
654 }
655 }
656 }
657 });
658 self.tasks.push(poll_task);
659 }
660
661 let tx = self.events_tx.clone();
662 let last_gsn = self.last_gsn.clone();
663 let emitted = self.emitted.clone();
664 let advert_task = tokio::spawn(async move {
665 while let Some(raw) = adverts.recv().await {
666 match crate::advert::HapAdvert::parse(&raw.manufacturer_data) {
667 Some(crate::advert::HapAdvert::Regular {
668 device_id: d, gsn, ..
669 }) => {
670 if d != device_id {
671 continue;
672 }
673 {
674 let mut lg = last_gsn.lock().await;
675 if !gsn_is_newer(gsn, *lg) {
676 continue;
677 }
678 *lg = gsn;
679 }
680 let _ = poll_tx.send(gsn);
683 }
684 Some(crate::advert::HapAdvert::EncryptedNotification {
685 advertising_id,
686 payload,
687 }) => {
688 if advertising_id != device_id {
689 continue;
690 }
691 let start = *last_gsn.lock().await;
692 let candidates = std::iter::once(start.wrapping_add(1))
695 .chain(std::iter::once(start))
696 .chain((2..=100u16).map(|d| start.wrapping_add(d)));
697 for gsn in candidates {
698 let Ok(pt) = broadcast_key.open(gsn, &payload, &advertising_id) else {
699 continue;
700 };
701 if pt.len() < 12 {
702 continue;
703 }
704 if u16::from_le_bytes([pt[0], pt[1]]) != gsn {
705 continue;
706 }
707 if !gsn_is_newer(gsn, start) {
709 break;
710 }
711 let iid = u64::from(u16::from_le_bytes([pt[2], pt[3]]));
712 {
716 let mut lg = last_gsn.lock().await;
717 *lg = gsn;
718 }
719 let Some(format) = formats.get(&iid).copied() else {
720 break;
721 };
722 if let Ok(value) = db::decode_value(format, &pt[4..12]) {
723 if dedup_should_emit(&emitted, iid, gsn).await {
724 let _ = tx.send(CharacteristicEvent { aid: 1, iid, value });
725 }
726 }
727 break;
728 }
729 }
730 _ => {}
731 }
732 }
733 });
734 self.tasks.push(advert_task);
735 Ok(())
736 }
737
738 pub fn events(&self) -> impl tokio_stream::Stream<Item = CharacteristicEvent> {
741 tokio_stream::wrappers::BroadcastStream::new(self.events_tx.subscribe())
742 .filter_map(std::result::Result::ok)
743 }
744}
745
746#[cfg(test)]
747mod tests {
748 use super::*;
749 use crate::test_support::ble_accessory_with_db;
750
751 #[tokio::test]
752 #[allow(clippy::unwrap_used)]
753 async fn find_locates_characteristic() {
754 let (h, _g) = ble_accessory_with_db().await;
755 let (aid, iid) = h
756 .find(ServiceType::LightBulb, CharacteristicType::On)
757 .unwrap();
758 assert_eq!((aid, iid), (1, 11));
759 }
760
761 #[tokio::test]
762 #[allow(clippy::unwrap_used)]
763 async fn find_missing_errors() {
764 let (h, _g) = ble_accessory_with_db().await;
765 let err = h
766 .find(ServiceType::LightBulb, CharacteristicType::Brightness)
767 .unwrap_err();
768 assert!(matches!(err, BleError::CharacteristicNotFound { .. }));
769 }
770
771 #[test]
772 fn encode_remove_pairing_matches_hap_layout() {
773 let tlv = encode_remove_pairing("c2");
775 assert_eq!(
776 tlv,
777 vec![0x06, 0x01, 0x01, 0x00, 0x01, 0x04, 0x01, 0x02, b'c', b'2']
778 );
779 }
780
781 #[test]
782 fn expect_remove_m2_accepts_m2_and_rejects_error() {
783 assert!(expect_remove_m2(&[0x06, 0x01, 0x02]).is_ok());
784 assert!(matches!(
786 expect_remove_m2(&[0x07, 0x01, 0x02]),
787 Err(BleError::PairingRejected(2))
788 ));
789 assert!(matches!(
791 expect_remove_m2(&[0x06, 0x01, 0x01]),
792 Err(BleError::MalformedPdu(_))
793 ));
794 }
795
796 #[tokio::test]
797 #[allow(clippy::unwrap_used)]
798 async fn remove_pairing_writes_request_and_accepts_m2() {
799 let (mut h, gatt) = ble_accessory_with_db().await;
800
801 let m2 = vec![0x06, 0x01, 0x02];
804 let vbody = crate::pdu::encode_value_param(&m2);
805 let mut plain = vec![0x02, 0x01, 0x00];
806 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
807 plain.extend_from_slice(&vbody);
808 let sealed =
809 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
810 gatt.queue_read("00000050-0000-1000-8000-0026bb765291", sealed);
811
812 h.remove_pairing("AE:EC:86:C0:BF:D7").await.unwrap();
813 }
814
815 #[tokio::test]
816 #[allow(clippy::unwrap_used)]
817 async fn remove_own_pairing_tolerates_session_teardown() {
818 let (mut h, gatt) = ble_accessory_with_db().await;
820 gatt.queue_read("00000050-0000-1000-8000-0026bb765291", vec![0u8; 24]);
824 h.remove_pairing("test-controller").await.unwrap();
825 }
826
827 #[tokio::test]
828 #[allow(clippy::unwrap_used)]
829 async fn remove_other_pairing_propagates_teardown_error() {
830 let (mut h, gatt) = ble_accessory_with_db().await;
833 gatt.queue_read("00000050-0000-1000-8000-0026bb765291", vec![0u8; 24]);
834 let err = h.remove_pairing("some-other-controller").await.unwrap_err();
835 assert!(matches!(err, BleError::Crypto(_)));
836 }
837
838 #[tokio::test]
839 #[allow(clippy::unwrap_used)]
840 async fn subscribe_then_event_decodes_value() {
841 use tokio_stream::StreamExt as _;
842 let (mut h, gatt) = ble_accessory_with_db().await;
843
844 let mut plain = vec![0x02, 0x01, 0x00];
848 let vbody = crate::pdu::encode_value_param(&[0x01]); plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
850 plain.extend_from_slice(&vbody);
851 let sealed =
852 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
853 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
854
855 h.subscribe(1, 11).await.unwrap();
856 let mut events = h.events();
857
858 gatt.notifier("00000025-0000-1000-8000-0026bb765291")
860 .unwrap()
861 .send(Vec::new())
862 .await
863 .unwrap();
864
865 let ev = events.next().await.unwrap();
866 assert_eq!(ev.iid, 11);
867 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
868 }
869
870 #[tokio::test]
871 #[allow(clippy::unwrap_used)]
872 async fn gsn_bump_triggers_disconnected_event_read() {
873 use tokio_stream::StreamExt as _;
874 let (mut h, gatt) = ble_accessory_with_db().await;
875
876 let mut plain = vec![0x02, 0x01, 0x00];
879 let vbody = crate::pdu::encode_value_param(&[0x01]);
880 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
881 plain.extend_from_slice(&vbody);
882 let sealed =
883 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
884 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
885
886 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
887 h.watch_sleepy_events(advert_source, [1, 2, 3, 4, 5, 6], vec![(1, 11)])
888 .await
889 .unwrap();
890 let mut events = h.events();
891
892 gatt.advert_sender()
894 .send(crate::gatt::RawAdvert {
895 manufacturer_data: vec![
896 0x06, 0x21, 0x01, 1, 2, 3, 4, 5, 6, 0x01, 0x00, 0x09, 0x00, 0x01, 0x00,
897 ],
898 })
899 .await
900 .unwrap();
901
902 let ev = events.next().await.unwrap();
903 assert_eq!(ev.iid, 11);
904 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
905 }
906
907 #[tokio::test]
908 #[allow(clippy::unwrap_used)]
909 async fn encrypted_broadcast_0x11_decrypts_and_emits_event() {
910 use tokio_stream::StreamExt as _;
911 let (mut h, gatt) = ble_accessory_with_db().await;
913
914 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
917 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
918 let mut pt = Vec::new();
919 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);
923
924 let mut mfg = vec![0x11u8, 0x00];
926 mfg.extend_from_slice(&aid_bytes);
927 mfg.extend_from_slice(&sealed);
928
929 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
930 h.watch_sleepy_events(advert_source, aid_bytes, vec![])
932 .await
933 .unwrap();
934 let mut events = h.events();
935
936 gatt.advert_sender()
937 .send(crate::gatt::RawAdvert {
938 manufacturer_data: mfg,
939 })
940 .await
941 .unwrap();
942
943 let ev = events.next().await.unwrap();
944 assert_eq!(ev.aid, 1);
945 assert_eq!(ev.iid, 11);
946 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
947 }
948
949 #[test]
950 fn gsn_is_newer_handles_wraparound() {
951 assert!(gsn_is_newer(6, 5));
952 assert!(!gsn_is_newer(5, 5));
953 assert!(!gsn_is_newer(4, 5));
954 assert!(gsn_is_newer(1, 65535)); assert!(!gsn_is_newer(65535, 1)); }
957
958 #[tokio::test]
959 #[allow(clippy::unwrap_used)]
960 async fn same_change_via_poll_and_broadcast_emits_once() {
961 use tokio_stream::StreamExt as _;
962 let (mut h, gatt) = ble_accessory_with_db().await;
963
964 let mut plain = vec![0x02, 0x01, 0x00];
967 let vbody = crate::pdu::encode_value_param(&[0x01]);
968 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
969 plain.extend_from_slice(&vbody);
970 let sealed =
971 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
972 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
973
974 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
975 h.watch_sleepy_events(advert_source, [1, 2, 3, 4, 5, 6], vec![(1, 11)])
976 .await
977 .unwrap();
978 let mut events = h.events();
979
980 gatt.advert_sender()
982 .send(crate::gatt::RawAdvert {
983 manufacturer_data: vec![
984 0x06, 0x21, 0x01, 1, 2, 3, 4, 5, 6, 0x01, 0x00, 0x09, 0x00, 0x01, 0x00,
985 ],
986 })
987 .await
988 .unwrap();
989
990 let ev = events.next().await.unwrap();
992 assert_eq!(ev.iid, 11);
993 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
994
995 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
997 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
998 let mut pt = Vec::new();
999 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);
1003
1004 let mut mfg = vec![0x11u8, 0x00];
1005 mfg.extend_from_slice(&aid_bytes);
1006 mfg.extend_from_slice(&sealed_bc);
1007
1008 gatt.advert_sender()
1009 .send(crate::gatt::RawAdvert {
1010 manufacturer_data: mfg,
1011 })
1012 .await
1013 .unwrap();
1014
1015 let timeout_result =
1017 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1018 assert!(
1019 timeout_result.is_err(),
1020 "expected dedup to suppress the duplicate 0x11 broadcast event, but got one"
1021 );
1022 }
1023
1024 #[tokio::test]
1029 #[allow(clippy::unwrap_used)]
1030 async fn broadcast_delivered_while_poll_read_blocked() {
1031 use tokio_stream::StreamExt as _;
1032 let (mut h, gatt) = ble_accessory_with_db().await;
1033
1034 let release = gatt.block_next_read("00000025-0000-1000-8000-0026bb765291");
1037 let mut plain = vec![0x02, 0x01, 0x00];
1038 let vbody = crate::pdu::encode_value_param(&[0x01]);
1039 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
1040 plain.extend_from_slice(&vbody);
1041 let sealed =
1042 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1043 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1044
1045 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1046 h.watch_sleepy_events(advert_source, [1, 2, 3, 4, 5, 6], vec![(1, 11)])
1047 .await
1048 .unwrap();
1049 let mut events = h.events();
1050
1051 gatt.advert_sender()
1053 .send(crate::gatt::RawAdvert {
1054 manufacturer_data: vec![
1055 0x06, 0x21, 0x01, 1, 2, 3, 4, 5, 6, 0x01, 0x00, 0x09, 0x00, 0x01, 0x00,
1056 ],
1057 })
1058 .await
1059 .unwrap();
1060
1061 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
1064 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1065 let mut pt = Vec::new();
1066 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);
1070 let mut mfg = vec![0x11u8, 0x00];
1071 mfg.extend_from_slice(&aid_bytes);
1072 mfg.extend_from_slice(&sealed_bc);
1073 gatt.advert_sender()
1074 .send(crate::gatt::RawAdvert {
1075 manufacturer_data: mfg,
1076 })
1077 .await
1078 .unwrap();
1079
1080 let ev = tokio::time::timeout(std::time::Duration::from_secs(2), events.next())
1084 .await
1085 .unwrap()
1086 .unwrap();
1087 assert_eq!(ev.iid, 11);
1088 assert_eq!(ev.value, hap_model::format::CharValue::Bool(false));
1089
1090 release.notify_one();
1092 let ev2 = tokio::time::timeout(std::time::Duration::from_secs(2), events.next())
1093 .await
1094 .unwrap()
1095 .unwrap();
1096 assert_eq!(ev2.iid, 11);
1097 assert_eq!(ev2.value, hap_model::format::CharValue::Bool(true));
1098 }
1099
1100 #[tokio::test]
1105 #[allow(clippy::unwrap_used)]
1106 async fn foreign_device_advert_ignored() {
1107 use tokio_stream::StreamExt as _;
1108 let (mut h, gatt) = ble_accessory_with_db().await;
1109
1110 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1111 h.watch_sleepy_events(advert_source, [1, 2, 3, 4, 5, 6], vec![(1, 11)])
1113 .await
1114 .unwrap();
1115 let mut events = h.events();
1116
1117 gatt.advert_sender()
1119 .send(crate::gatt::RawAdvert {
1120 manufacturer_data: vec![
1121 0x06, 0x21, 0x01, 9, 9, 9, 9, 9, 9, 0x01, 0x00, 0x09, 0x00, 0x01, 0x00,
1122 ],
1123 })
1124 .await
1125 .unwrap();
1126
1127 let timeout_result =
1128 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1129 assert!(
1130 timeout_result.is_err(),
1131 "foreign device advert must not emit an event, but one was received"
1132 );
1133 }
1134
1135 #[tokio::test]
1138 #[allow(clippy::unwrap_used)]
1139 async fn stale_gsn_broadcast_ignored() {
1140 use tokio_stream::StreamExt as _;
1141 let (mut h, gatt) = ble_accessory_with_db().await;
1142
1143 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
1144 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1145
1146 let mut pt = Vec::new();
1149 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);
1153
1154 let mut mfg = vec![0x11u8, 0x00];
1155 mfg.extend_from_slice(&aid_bytes);
1156 mfg.extend_from_slice(&sealed);
1157
1158 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1159 h.watch_sleepy_events(advert_source, aid_bytes, vec![])
1160 .await
1161 .unwrap();
1162 let mut events = h.events();
1163
1164 gatt.advert_sender()
1166 .send(crate::gatt::RawAdvert {
1167 manufacturer_data: mfg.clone(),
1168 })
1169 .await
1170 .unwrap();
1171
1172 let ev = events.next().await.unwrap();
1173 assert_eq!(ev.iid, 11);
1174 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
1175
1176 gatt.advert_sender()
1178 .send(crate::gatt::RawAdvert {
1179 manufacturer_data: mfg,
1180 })
1181 .await
1182 .unwrap();
1183
1184 let timeout_result =
1185 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1186 assert!(
1187 timeout_result.is_err(),
1188 "duplicate GSN 5 broadcast must not emit a second event"
1189 );
1190 }
1191
1192 #[tokio::test]
1195 #[allow(clippy::unwrap_used)]
1196 async fn wrong_broadcast_key_ignored() {
1197 use tokio_stream::StreamExt as _;
1198 let (mut h, gatt) = ble_accessory_with_db().await;
1200
1201 let wrong_key = hap_crypto::BroadcastKey::from_bytes([0xFF; 32]);
1203 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1204
1205 let mut pt = Vec::new();
1206 pt.extend_from_slice(&1u16.to_le_bytes());
1207 pt.extend_from_slice(&11u16.to_le_bytes());
1208 pt.extend_from_slice(&[0x01, 0, 0, 0, 0, 0, 0, 0]);
1209 let sealed = wrong_key.seal(1, &pt, &aid_bytes);
1210
1211 let mut mfg = vec![0x11u8, 0x00];
1212 mfg.extend_from_slice(&aid_bytes);
1213 mfg.extend_from_slice(&sealed);
1214
1215 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1216 h.watch_sleepy_events(advert_source, aid_bytes, vec![])
1217 .await
1218 .unwrap();
1219 let mut events = h.events();
1220
1221 gatt.advert_sender()
1222 .send(crate::gatt::RawAdvert {
1223 manufacturer_data: mfg,
1224 })
1225 .await
1226 .unwrap();
1227
1228 let timeout_result =
1229 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1230 assert!(
1231 timeout_result.is_err(),
1232 "wrong-key broadcast must not emit any event (all candidate opens fail)"
1233 );
1234 }
1235
1236 #[tokio::test]
1240 #[allow(clippy::unwrap_used)]
1241 async fn malformed_0x11_advert_ignored() {
1242 use tokio_stream::StreamExt as _;
1243 let (mut h, gatt) = ble_accessory_with_db().await;
1244
1245 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1246 h.watch_sleepy_events(advert_source, [1, 2, 3, 4, 5, 6], vec![])
1247 .await
1248 .unwrap();
1249 let mut events = h.events();
1250
1251 let manufacturer_data = vec![0x11, 0x00, 1, 2, 3, 4, 5, 6, 0xAA, 0xBB];
1253 gatt.advert_sender()
1254 .send(crate::gatt::RawAdvert { manufacturer_data })
1255 .await
1256 .unwrap();
1257
1258 let timeout_result =
1259 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1260 assert!(
1261 timeout_result.is_err(),
1262 "malformed (too-short payload) 0x11 advert must not emit any event"
1263 );
1264 }
1265
1266 #[tokio::test]
1270 #[allow(clippy::unwrap_used)]
1271 async fn broadcast_value_self_inconsistent_gsn_ignored() {
1272 use tokio_stream::StreamExt as _;
1273 let (mut h, gatt) = ble_accessory_with_db().await;
1274
1275 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
1276 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1277
1278 let mut pt = Vec::new();
1282 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];
1288 mfg.extend_from_slice(&aid_bytes);
1289 mfg.extend_from_slice(&sealed);
1290
1291 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1292 h.watch_sleepy_events(advert_source, aid_bytes, vec![])
1293 .await
1294 .unwrap();
1295 let mut events = h.events();
1296
1297 gatt.advert_sender()
1298 .send(crate::gatt::RawAdvert {
1299 manufacturer_data: mfg,
1300 })
1301 .await
1302 .unwrap();
1303
1304 let timeout_result =
1305 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1306 assert!(
1307 timeout_result.is_err(),
1308 "self-inconsistent GSN (embedded 3 != nonce 7) must not emit any event"
1309 );
1310 }
1311
1312 #[tokio::test]
1313 #[allow(clippy::unwrap_used)]
1314 async fn read_after_reconnect_re_verifies_before_using_session() {
1315 let (mut h, gatt) = ble_accessory_with_db().await;
1316
1317 let mut plain = vec![0x02, 0x01, 0x00];
1320 let vbody = crate::pdu::encode_value_param(&[0x01]);
1321 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
1322 plain.extend_from_slice(&vbody);
1323 let sealed =
1324 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1325 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1326
1327 gatt.bump_generation();
1332 let err = h.read(1, 11).await.unwrap_err();
1333 assert!(
1334 !matches!(err, BleError::CharacteristicNotFound { .. }),
1335 "expected a verify/transport error from the re-verify attempt, got {err:?}"
1336 );
1337 }
1338
1339 #[tokio::test]
1340 async fn dedup_emits_once_per_gsn_and_never_downgrades() {
1341 let emitted = Mutex::new(HashMap::new());
1342 assert!(dedup_should_emit(&emitted, 11, 9).await);
1344 assert!(!dedup_should_emit(&emitted, 11, 9).await);
1346 assert!(dedup_should_emit(&emitted, 11, 10).await);
1348 assert!(dedup_should_emit(&emitted, 11, 9).await);
1351 assert!(!dedup_should_emit(&emitted, 11, 10).await);
1353 assert!(dedup_should_emit(&emitted, 12, 65535).await);
1355 assert!(dedup_should_emit(&emitted, 12, 1).await);
1356 assert!(!dedup_should_emit(&emitted, 12, 1).await);
1357 }
1358
1359 #[tokio::test]
1360 #[allow(clippy::unwrap_used)]
1361 async fn write_sends_secure_pdu_and_accepts_success() {
1362 let (mut h, gatt) = ble_accessory_with_db().await;
1363 let plain = vec![0x02, 0x01, 0x00];
1365 let sealed =
1366 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1367 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1368 h.write(1, 11, hap_model::format::CharValue::Bool(true))
1369 .await
1370 .unwrap();
1371 }
1372
1373 #[tokio::test]
1374 #[allow(clippy::unwrap_used)]
1375 async fn write_surfaces_nonzero_pdu_status() {
1376 let (mut h, gatt) = ble_accessory_with_db().await;
1377 let plain = vec![0x02, 0x01, 0x06]; let sealed =
1379 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1380 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1381 let err = h
1382 .write(1, 11, hap_model::format::CharValue::Bool(true))
1383 .await
1384 .unwrap_err();
1385 assert!(matches!(err, BleError::RequestRejected(6)));
1386 }
1387
1388 #[tokio::test]
1389 #[allow(clippy::unwrap_used)]
1390 async fn pairing_id_exposes_the_stored_pairing() {
1391 let (h, _g) = ble_accessory_with_db().await;
1392 assert_eq!(h.pairing_id(), "AE:EC:86:C0:BF:D7");
1393 }
1394}