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 #[must_use]
528 pub fn pairing_id(&self) -> &str {
529 &self.reviver.pairing.pairing_id
530 }
531
532 pub async fn enable_broadcasts(&mut self, iids: &[u64]) -> Result<()> {
543 let mut s = self.secure.lock().await;
544 for &iid in iids {
545 let Some((uuid, _)) = self.chars.get(&(1, iid)).cloned() else {
546 continue;
547 };
548 let Ok(iid16) = u16::try_from(iid) else {
549 continue;
550 };
551 revive_if_stale(self.gatt.as_ref(), &mut s, &self.reviver).await?;
552 s.tid = s.tid.wrapping_add(1);
553 let tid = s.tid;
554 let _ = pdu::request_secure(
555 self.gatt.as_ref(),
556 &mut s.session,
557 &uuid,
558 OpCode::CharacteristicConfig,
559 tid,
560 iid16,
561 &ENABLE_BROADCAST_BODY,
562 self.frag_size,
563 )
564 .await;
565 }
566 Ok(())
567 }
568
569 pub async fn disconnect(&self) {
572 self.gatt.disconnect().await;
573 }
574
575 pub async fn subscribe(&mut self, aid: u64, iid: u64) -> Result<()> {
588 let (uuid, format) = self
589 .chars
590 .get(&(aid, iid))
591 .cloned()
592 .ok_or(BleError::CharacteristicNotFound { aid, iid })?;
593 let mut rx = self.gatt.subscribe(&uuid).await?;
594 let tx = self.events_tx.clone();
595 let gatt = self.gatt.clone();
596 let secure = self.secure.clone();
597 let reviver = self.reviver.clone();
598 let frag_size = self.frag_size;
599 let task = tokio::spawn(async move {
600 while rx.recv().await.is_some() {
602 if let Ok(raw) =
603 read_char_raw(gatt.as_ref(), &secure, &reviver, &uuid, iid, frag_size).await
604 {
605 if let Ok(value) = db::decode_value(format, &raw) {
606 let _ = tx.send(CharacteristicEvent { aid, iid, value });
607 }
608 }
609 }
610 });
611 self.tasks.push(task);
612 Ok(())
613 }
614
615 #[allow(clippy::too_many_lines)]
631 pub async fn watch_sleepy_events_with_source(
632 &mut self,
633 advert_source: Arc<dyn crate::gatt::AdvertSource>,
634 device_id: [u8; 6],
635 poll_iids: Vec<(u64, u64)>,
636 ) -> Result<()> {
637 let mut targets = Vec::new();
640 for (aid, iid) in poll_iids {
641 if let Some((uuid, format)) = self.chars.get(&(aid, iid)).cloned() {
642 targets.push((aid, iid, uuid, format));
643 }
644 }
645 let formats: std::collections::HashMap<u64, CharFormat> = self
647 .chars
648 .iter()
649 .map(|((_, iid), (_, f))| (*iid, *f))
650 .collect();
651 let broadcast_key = self.broadcast_key.clone();
652
653 let mut adverts = advert_source.watch_adverts().await?;
654
655 let (poll_tx, mut poll_rx) = tokio::sync::watch::channel(0u16);
660 if !targets.is_empty() {
661 let gatt = self.gatt.clone();
662 let secure = self.secure.clone();
663 let reviver = self.reviver.clone();
664 let frag = self.frag_size;
665 let poll_events = self.events_tx.clone();
666 let poll_emitted = self.emitted.clone();
667 let poll_task = tokio::spawn(async move {
668 while poll_rx.changed().await.is_ok() {
669 let gsn = *poll_rx.borrow_and_update();
670 for (aid, iid, uuid, format) in &targets {
671 if let Ok(raw_val) =
672 read_char_raw(gatt.as_ref(), &secure, &reviver, uuid, *iid, frag).await
673 {
674 if let Ok(value) = db::decode_value(*format, &raw_val) {
675 if dedup_should_emit(&poll_emitted, *iid, gsn).await {
676 let _ = poll_events.send(CharacteristicEvent {
677 aid: *aid,
678 iid: *iid,
679 value,
680 });
681 }
682 }
683 }
684 }
685 }
686 });
687 self.tasks.push(poll_task);
688 }
689
690 let tx = self.events_tx.clone();
691 let last_gsn = self.last_gsn.clone();
692 let emitted = self.emitted.clone();
693 let advert_task = tokio::spawn(async move {
694 while let Some(raw) = adverts.recv().await {
695 match crate::advert::HapAdvert::parse(&raw.manufacturer_data) {
696 Some(crate::advert::HapAdvert::Regular {
697 device_id: d, gsn, ..
698 }) => {
699 if d != device_id {
700 continue;
701 }
702 {
703 let mut lg = last_gsn.lock().await;
704 if !gsn_is_newer(gsn, *lg) {
705 continue;
706 }
707 *lg = gsn;
708 }
709 let _ = poll_tx.send(gsn);
712 }
713 Some(crate::advert::HapAdvert::EncryptedNotification {
714 advertising_id,
715 payload,
716 }) => {
717 if advertising_id != device_id {
718 continue;
719 }
720 let start = *last_gsn.lock().await;
721 let candidates = std::iter::once(start.wrapping_add(1))
724 .chain(std::iter::once(start))
725 .chain((2..=100u16).map(|d| start.wrapping_add(d)));
726 for gsn in candidates {
727 let Ok(pt) = broadcast_key.open(gsn, &payload, &advertising_id) else {
728 continue;
729 };
730 if pt.len() < 12 {
731 continue;
732 }
733 if u16::from_le_bytes([pt[0], pt[1]]) != gsn {
734 continue;
735 }
736 if !gsn_is_newer(gsn, start) {
738 break;
739 }
740 let iid = u64::from(u16::from_le_bytes([pt[2], pt[3]]));
741 {
745 let mut lg = last_gsn.lock().await;
746 *lg = gsn;
747 }
748 let Some(format) = formats.get(&iid).copied() else {
749 break;
750 };
751 if let Ok(value) = db::decode_value(format, &pt[4..12]) {
752 if dedup_should_emit(&emitted, iid, gsn).await {
753 let _ = tx.send(CharacteristicEvent { aid: 1, iid, value });
754 }
755 }
756 break;
757 }
758 }
759 _ => {}
760 }
761 }
762 });
763 self.tasks.push(advert_task);
764 Ok(())
765 }
766
767 pub fn set_advert_source(&mut self, src: Arc<dyn crate::gatt::AdvertSource>) {
770 self.advert_source = Some(src);
771 }
772
773 pub async fn watch_sleepy_events(&mut self, poll_iids: Vec<(u64, u64)>) -> Result<()> {
783 let src = self.advert_source.clone().ok_or(BleError::NoAdvertSource)?;
784 let device_id =
785 parse_device_id(self.reviver.pairing.pairing_id.as_str()).ok_or_else(|| {
786 BleError::Backend("malformed pairing id; cannot derive device id".into())
787 })?;
788 self.watch_sleepy_events_with_source(src, device_id, poll_iids)
789 .await
790 }
791
792 pub fn events(&self) -> impl tokio_stream::Stream<Item = CharacteristicEvent> {
795 tokio_stream::wrappers::BroadcastStream::new(self.events_tx.subscribe())
796 .filter_map(std::result::Result::ok)
797 }
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803 use crate::test_support::ble_accessory_with_db;
804
805 #[tokio::test]
806 #[allow(clippy::unwrap_used)]
807 async fn find_locates_characteristic() {
808 let (h, _g) = ble_accessory_with_db().await;
809 let (aid, iid) = h
810 .find(ServiceType::LightBulb, CharacteristicType::On)
811 .unwrap();
812 assert_eq!((aid, iid), (1, 11));
813 }
814
815 #[tokio::test]
816 #[allow(clippy::unwrap_used)]
817 async fn find_missing_errors() {
818 let (h, _g) = ble_accessory_with_db().await;
819 let err = h
820 .find(ServiceType::LightBulb, CharacteristicType::Brightness)
821 .unwrap_err();
822 assert!(matches!(err, BleError::CharacteristicNotFound { .. }));
823 }
824
825 #[test]
826 fn encode_remove_pairing_matches_hap_layout() {
827 let tlv = encode_remove_pairing("c2");
829 assert_eq!(
830 tlv,
831 vec![0x06, 0x01, 0x01, 0x00, 0x01, 0x04, 0x01, 0x02, b'c', b'2']
832 );
833 }
834
835 #[test]
836 fn expect_remove_m2_accepts_m2_and_rejects_error() {
837 assert!(expect_remove_m2(&[0x06, 0x01, 0x02]).is_ok());
838 assert!(matches!(
840 expect_remove_m2(&[0x07, 0x01, 0x02]),
841 Err(BleError::PairingRejected(2))
842 ));
843 assert!(matches!(
845 expect_remove_m2(&[0x06, 0x01, 0x01]),
846 Err(BleError::MalformedPdu(_))
847 ));
848 }
849
850 #[tokio::test]
851 #[allow(clippy::unwrap_used)]
852 async fn remove_pairing_writes_request_and_accepts_m2() {
853 let (mut h, gatt) = ble_accessory_with_db().await;
854
855 let m2 = vec![0x06, 0x01, 0x02];
858 let vbody = crate::pdu::encode_value_param(&m2);
859 let mut plain = vec![0x02, 0x01, 0x00];
860 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
861 plain.extend_from_slice(&vbody);
862 let sealed =
863 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
864 gatt.queue_read("00000050-0000-1000-8000-0026bb765291", sealed);
865
866 h.remove_pairing("AE:EC:86:C0:BF:D7").await.unwrap();
867 }
868
869 #[tokio::test]
870 #[allow(clippy::unwrap_used)]
871 async fn remove_own_pairing_tolerates_session_teardown() {
872 let (mut h, gatt) = ble_accessory_with_db().await;
874 gatt.queue_read("00000050-0000-1000-8000-0026bb765291", vec![0u8; 24]);
878 h.remove_pairing("test-controller").await.unwrap();
879 }
880
881 #[tokio::test]
882 #[allow(clippy::unwrap_used)]
883 async fn remove_other_pairing_propagates_teardown_error() {
884 let (mut h, gatt) = ble_accessory_with_db().await;
887 gatt.queue_read("00000050-0000-1000-8000-0026bb765291", vec![0u8; 24]);
888 let err = h.remove_pairing("some-other-controller").await.unwrap_err();
889 assert!(matches!(err, BleError::Crypto(_)));
890 }
891
892 #[tokio::test]
893 #[allow(clippy::unwrap_used)]
894 async fn subscribe_then_event_decodes_value() {
895 use tokio_stream::StreamExt as _;
896 let (mut h, gatt) = ble_accessory_with_db().await;
897
898 let mut plain = vec![0x02, 0x01, 0x00];
902 let vbody = crate::pdu::encode_value_param(&[0x01]); plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
904 plain.extend_from_slice(&vbody);
905 let sealed =
906 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
907 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
908
909 h.subscribe(1, 11).await.unwrap();
910 let mut events = h.events();
911
912 gatt.notifier("00000025-0000-1000-8000-0026bb765291")
914 .unwrap()
915 .send(Vec::new())
916 .await
917 .unwrap();
918
919 let ev = events.next().await.unwrap();
920 assert_eq!(ev.iid, 11);
921 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
922 }
923
924 #[tokio::test]
925 #[allow(clippy::unwrap_used)]
926 async fn gsn_bump_triggers_disconnected_event_read() {
927 use tokio_stream::StreamExt as _;
928 let (mut h, gatt) = ble_accessory_with_db().await;
929
930 let mut plain = vec![0x02, 0x01, 0x00];
933 let vbody = crate::pdu::encode_value_param(&[0x01]);
934 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
935 plain.extend_from_slice(&vbody);
936 let sealed =
937 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
938 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
939
940 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
941 h.watch_sleepy_events_with_source(advert_source, [1, 2, 3, 4, 5, 6], vec![(1, 11)])
942 .await
943 .unwrap();
944 let mut events = h.events();
945
946 gatt.advert_sender()
948 .send(crate::gatt::RawAdvert {
949 manufacturer_data: vec![
950 0x06, 0x21, 0x01, 1, 2, 3, 4, 5, 6, 0x01, 0x00, 0x09, 0x00, 0x01, 0x00,
951 ],
952 })
953 .await
954 .unwrap();
955
956 let ev = events.next().await.unwrap();
957 assert_eq!(ev.iid, 11);
958 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
959 }
960
961 #[tokio::test]
962 #[allow(clippy::unwrap_used)]
963 async fn encrypted_broadcast_0x11_decrypts_and_emits_event() {
964 use tokio_stream::StreamExt as _;
965 let (mut h, gatt) = ble_accessory_with_db().await;
967
968 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
971 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
972 let mut pt = Vec::new();
973 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);
977
978 let mut mfg = vec![0x11u8, 0x00];
980 mfg.extend_from_slice(&aid_bytes);
981 mfg.extend_from_slice(&sealed);
982
983 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
984 h.watch_sleepy_events_with_source(advert_source, aid_bytes, vec![])
986 .await
987 .unwrap();
988 let mut events = h.events();
989
990 gatt.advert_sender()
991 .send(crate::gatt::RawAdvert {
992 manufacturer_data: mfg,
993 })
994 .await
995 .unwrap();
996
997 let ev = events.next().await.unwrap();
998 assert_eq!(ev.aid, 1);
999 assert_eq!(ev.iid, 11);
1000 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
1001 }
1002
1003 #[test]
1004 fn gsn_is_newer_handles_wraparound() {
1005 assert!(gsn_is_newer(6, 5));
1006 assert!(!gsn_is_newer(5, 5));
1007 assert!(!gsn_is_newer(4, 5));
1008 assert!(gsn_is_newer(1, 65535)); assert!(!gsn_is_newer(65535, 1)); }
1011
1012 #[tokio::test]
1013 #[allow(clippy::unwrap_used)]
1014 async fn same_change_via_poll_and_broadcast_emits_once() {
1015 use tokio_stream::StreamExt as _;
1016 let (mut h, gatt) = ble_accessory_with_db().await;
1017
1018 let mut plain = vec![0x02, 0x01, 0x00];
1021 let vbody = crate::pdu::encode_value_param(&[0x01]);
1022 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
1023 plain.extend_from_slice(&vbody);
1024 let sealed =
1025 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1026 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1027
1028 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1029 h.watch_sleepy_events_with_source(advert_source, [1, 2, 3, 4, 5, 6], vec![(1, 11)])
1030 .await
1031 .unwrap();
1032 let mut events = h.events();
1033
1034 gatt.advert_sender()
1036 .send(crate::gatt::RawAdvert {
1037 manufacturer_data: vec![
1038 0x06, 0x21, 0x01, 1, 2, 3, 4, 5, 6, 0x01, 0x00, 0x09, 0x00, 0x01, 0x00,
1039 ],
1040 })
1041 .await
1042 .unwrap();
1043
1044 let ev = events.next().await.unwrap();
1046 assert_eq!(ev.iid, 11);
1047 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
1048
1049 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
1051 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1052 let mut pt = Vec::new();
1053 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);
1057
1058 let mut mfg = vec![0x11u8, 0x00];
1059 mfg.extend_from_slice(&aid_bytes);
1060 mfg.extend_from_slice(&sealed_bc);
1061
1062 gatt.advert_sender()
1063 .send(crate::gatt::RawAdvert {
1064 manufacturer_data: mfg,
1065 })
1066 .await
1067 .unwrap();
1068
1069 let timeout_result =
1071 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1072 assert!(
1073 timeout_result.is_err(),
1074 "expected dedup to suppress the duplicate 0x11 broadcast event, but got one"
1075 );
1076 }
1077
1078 #[tokio::test]
1083 #[allow(clippy::unwrap_used)]
1084 async fn broadcast_delivered_while_poll_read_blocked() {
1085 use tokio_stream::StreamExt as _;
1086 let (mut h, gatt) = ble_accessory_with_db().await;
1087
1088 let release = gatt.block_next_read("00000025-0000-1000-8000-0026bb765291");
1091 let mut plain = vec![0x02, 0x01, 0x00];
1092 let vbody = crate::pdu::encode_value_param(&[0x01]);
1093 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
1094 plain.extend_from_slice(&vbody);
1095 let sealed =
1096 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1097 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1098
1099 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1100 h.watch_sleepy_events_with_source(advert_source, [1, 2, 3, 4, 5, 6], vec![(1, 11)])
1101 .await
1102 .unwrap();
1103 let mut events = h.events();
1104
1105 gatt.advert_sender()
1107 .send(crate::gatt::RawAdvert {
1108 manufacturer_data: vec![
1109 0x06, 0x21, 0x01, 1, 2, 3, 4, 5, 6, 0x01, 0x00, 0x09, 0x00, 0x01, 0x00,
1110 ],
1111 })
1112 .await
1113 .unwrap();
1114
1115 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
1118 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1119 let mut pt = Vec::new();
1120 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);
1124 let mut mfg = vec![0x11u8, 0x00];
1125 mfg.extend_from_slice(&aid_bytes);
1126 mfg.extend_from_slice(&sealed_bc);
1127 gatt.advert_sender()
1128 .send(crate::gatt::RawAdvert {
1129 manufacturer_data: mfg,
1130 })
1131 .await
1132 .unwrap();
1133
1134 let ev = tokio::time::timeout(std::time::Duration::from_secs(2), events.next())
1138 .await
1139 .unwrap()
1140 .unwrap();
1141 assert_eq!(ev.iid, 11);
1142 assert_eq!(ev.value, hap_model::format::CharValue::Bool(false));
1143
1144 release.notify_one();
1146 let ev2 = tokio::time::timeout(std::time::Duration::from_secs(2), events.next())
1147 .await
1148 .unwrap()
1149 .unwrap();
1150 assert_eq!(ev2.iid, 11);
1151 assert_eq!(ev2.value, hap_model::format::CharValue::Bool(true));
1152 }
1153
1154 #[tokio::test]
1159 #[allow(clippy::unwrap_used)]
1160 async fn foreign_device_advert_ignored() {
1161 use tokio_stream::StreamExt as _;
1162 let (mut h, gatt) = ble_accessory_with_db().await;
1163
1164 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1165 h.watch_sleepy_events_with_source(advert_source, [1, 2, 3, 4, 5, 6], vec![(1, 11)])
1167 .await
1168 .unwrap();
1169 let mut events = h.events();
1170
1171 gatt.advert_sender()
1173 .send(crate::gatt::RawAdvert {
1174 manufacturer_data: vec![
1175 0x06, 0x21, 0x01, 9, 9, 9, 9, 9, 9, 0x01, 0x00, 0x09, 0x00, 0x01, 0x00,
1176 ],
1177 })
1178 .await
1179 .unwrap();
1180
1181 let timeout_result =
1182 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1183 assert!(
1184 timeout_result.is_err(),
1185 "foreign device advert must not emit an event, but one was received"
1186 );
1187 }
1188
1189 #[tokio::test]
1192 #[allow(clippy::unwrap_used)]
1193 async fn stale_gsn_broadcast_ignored() {
1194 use tokio_stream::StreamExt as _;
1195 let (mut h, gatt) = ble_accessory_with_db().await;
1196
1197 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
1198 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1199
1200 let mut pt = Vec::new();
1203 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);
1207
1208 let mut mfg = vec![0x11u8, 0x00];
1209 mfg.extend_from_slice(&aid_bytes);
1210 mfg.extend_from_slice(&sealed);
1211
1212 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1213 h.watch_sleepy_events_with_source(advert_source, aid_bytes, vec![])
1214 .await
1215 .unwrap();
1216 let mut events = h.events();
1217
1218 gatt.advert_sender()
1220 .send(crate::gatt::RawAdvert {
1221 manufacturer_data: mfg.clone(),
1222 })
1223 .await
1224 .unwrap();
1225
1226 let ev = events.next().await.unwrap();
1227 assert_eq!(ev.iid, 11);
1228 assert_eq!(ev.value, hap_model::format::CharValue::Bool(true));
1229
1230 gatt.advert_sender()
1232 .send(crate::gatt::RawAdvert {
1233 manufacturer_data: mfg,
1234 })
1235 .await
1236 .unwrap();
1237
1238 let timeout_result =
1239 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1240 assert!(
1241 timeout_result.is_err(),
1242 "duplicate GSN 5 broadcast must not emit a second event"
1243 );
1244 }
1245
1246 #[tokio::test]
1249 #[allow(clippy::unwrap_used)]
1250 async fn wrong_broadcast_key_ignored() {
1251 use tokio_stream::StreamExt as _;
1252 let (mut h, gatt) = ble_accessory_with_db().await;
1254
1255 let wrong_key = hap_crypto::BroadcastKey::from_bytes([0xFF; 32]);
1257 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1258
1259 let mut pt = Vec::new();
1260 pt.extend_from_slice(&1u16.to_le_bytes());
1261 pt.extend_from_slice(&11u16.to_le_bytes());
1262 pt.extend_from_slice(&[0x01, 0, 0, 0, 0, 0, 0, 0]);
1263 let sealed = wrong_key.seal(1, &pt, &aid_bytes);
1264
1265 let mut mfg = vec![0x11u8, 0x00];
1266 mfg.extend_from_slice(&aid_bytes);
1267 mfg.extend_from_slice(&sealed);
1268
1269 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1270 h.watch_sleepy_events_with_source(advert_source, aid_bytes, vec![])
1271 .await
1272 .unwrap();
1273 let mut events = h.events();
1274
1275 gatt.advert_sender()
1276 .send(crate::gatt::RawAdvert {
1277 manufacturer_data: mfg,
1278 })
1279 .await
1280 .unwrap();
1281
1282 let timeout_result =
1283 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1284 assert!(
1285 timeout_result.is_err(),
1286 "wrong-key broadcast must not emit any event (all candidate opens fail)"
1287 );
1288 }
1289
1290 #[tokio::test]
1294 #[allow(clippy::unwrap_used)]
1295 async fn malformed_0x11_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![])
1301 .await
1302 .unwrap();
1303 let mut events = h.events();
1304
1305 let manufacturer_data = vec![0x11, 0x00, 1, 2, 3, 4, 5, 6, 0xAA, 0xBB];
1307 gatt.advert_sender()
1308 .send(crate::gatt::RawAdvert { manufacturer_data })
1309 .await
1310 .unwrap();
1311
1312 let timeout_result =
1313 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1314 assert!(
1315 timeout_result.is_err(),
1316 "malformed (too-short payload) 0x11 advert must not emit any event"
1317 );
1318 }
1319
1320 #[tokio::test]
1324 #[allow(clippy::unwrap_used)]
1325 async fn broadcast_value_self_inconsistent_gsn_ignored() {
1326 use tokio_stream::StreamExt as _;
1327 let (mut h, gatt) = ble_accessory_with_db().await;
1328
1329 let key = hap_crypto::BroadcastKey::from_bytes([0u8; 32]);
1330 let aid_bytes: [u8; 6] = [1, 2, 3, 4, 5, 6];
1331
1332 let mut pt = Vec::new();
1336 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];
1342 mfg.extend_from_slice(&aid_bytes);
1343 mfg.extend_from_slice(&sealed);
1344
1345 let advert_source: std::sync::Arc<dyn crate::gatt::AdvertSource> = gatt.clone();
1346 h.watch_sleepy_events_with_source(advert_source, aid_bytes, vec![])
1347 .await
1348 .unwrap();
1349 let mut events = h.events();
1350
1351 gatt.advert_sender()
1352 .send(crate::gatt::RawAdvert {
1353 manufacturer_data: mfg,
1354 })
1355 .await
1356 .unwrap();
1357
1358 let timeout_result =
1359 tokio::time::timeout(std::time::Duration::from_millis(200), events.next()).await;
1360 assert!(
1361 timeout_result.is_err(),
1362 "self-inconsistent GSN (embedded 3 != nonce 7) must not emit any event"
1363 );
1364 }
1365
1366 #[tokio::test]
1367 #[allow(clippy::unwrap_used)]
1368 async fn read_after_reconnect_re_verifies_before_using_session() {
1369 let (mut h, gatt) = ble_accessory_with_db().await;
1370
1371 let mut plain = vec![0x02, 0x01, 0x00];
1374 let vbody = crate::pdu::encode_value_param(&[0x01]);
1375 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
1376 plain.extend_from_slice(&vbody);
1377 let sealed =
1378 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1379 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1380
1381 gatt.bump_generation();
1386 let err = h.read(1, 11).await.unwrap_err();
1387 assert!(
1388 !matches!(err, BleError::CharacteristicNotFound { .. }),
1389 "expected a verify/transport error from the re-verify attempt, got {err:?}"
1390 );
1391 }
1392
1393 #[tokio::test]
1394 async fn dedup_emits_once_per_gsn_and_never_downgrades() {
1395 let emitted = Mutex::new(HashMap::new());
1396 assert!(dedup_should_emit(&emitted, 11, 9).await);
1398 assert!(!dedup_should_emit(&emitted, 11, 9).await);
1400 assert!(dedup_should_emit(&emitted, 11, 10).await);
1402 assert!(dedup_should_emit(&emitted, 11, 9).await);
1405 assert!(!dedup_should_emit(&emitted, 11, 10).await);
1407 assert!(dedup_should_emit(&emitted, 12, 65535).await);
1409 assert!(dedup_should_emit(&emitted, 12, 1).await);
1410 assert!(!dedup_should_emit(&emitted, 12, 1).await);
1411 }
1412
1413 #[tokio::test]
1414 #[allow(clippy::unwrap_used)]
1415 async fn write_sends_secure_pdu_and_accepts_success() {
1416 let (mut h, gatt) = ble_accessory_with_db().await;
1417 let plain = vec![0x02, 0x01, 0x00];
1419 let sealed =
1420 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1421 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1422 h.write(1, 11, hap_model::format::CharValue::Bool(true))
1423 .await
1424 .unwrap();
1425 }
1426
1427 #[tokio::test]
1428 #[allow(clippy::unwrap_used)]
1429 async fn write_surfaces_nonzero_pdu_status() {
1430 let (mut h, gatt) = ble_accessory_with_db().await;
1431 let plain = vec![0x02, 0x01, 0x06]; let sealed =
1433 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1434 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1435 let err = h
1436 .write(1, 11, hap_model::format::CharValue::Bool(true))
1437 .await
1438 .unwrap_err();
1439 assert!(matches!(err, BleError::RequestRejected(6)));
1440 }
1441
1442 #[tokio::test]
1443 #[allow(clippy::unwrap_used)]
1444 async fn pairing_id_exposes_the_stored_pairing() {
1445 let (h, _g) = ble_accessory_with_db().await;
1446 assert_eq!(h.pairing_id(), "AE:EC:86:C0:BF:D7");
1447 }
1448
1449 #[tokio::test]
1450 #[allow(clippy::unwrap_used)]
1451 async fn disconnect_is_callable_on_the_accessory() {
1452 let (h, _g) = ble_accessory_with_db().await;
1453 h.disconnect().await; }
1455
1456 #[tokio::test]
1457 #[allow(clippy::unwrap_used)]
1458 async fn self_sourcing_watch_errors_without_source() {
1459 let (mut h, _g) = ble_accessory_with_db().await;
1460 let err = h.watch_sleepy_events(vec![(1, 11)]).await.unwrap_err();
1462 assert!(matches!(err, BleError::NoAdvertSource));
1463 }
1464
1465 #[tokio::test]
1466 #[allow(clippy::unwrap_used)]
1467 async fn self_sourcing_watch_emits_via_set_source() {
1468 use tokio_stream::StreamExt as _;
1469 let (mut h, gatt) = ble_accessory_with_db().await;
1470 h.set_advert_source(gatt.clone() as std::sync::Arc<dyn crate::gatt::AdvertSource>);
1472 let mut plain = vec![0x02, 0x01, 0x00];
1474 let vbody = crate::pdu::encode_value_param(&[0x01]);
1475 plain.extend_from_slice(&u16::try_from(vbody.len()).unwrap().to_le_bytes());
1476 plain.extend_from_slice(&vbody);
1477 let sealed =
1478 hap_crypto::aead::chacha20poly1305_seal(&[0u8; 32], &[0u8; 12], &[], &plain).unwrap();
1479 gatt.queue_read("00000025-0000-1000-8000-0026bb765291", sealed);
1480 h.watch_sleepy_events(vec![(1, 11)]).await.unwrap();
1481 let mut events = h.events();
1482 gatt.advert_sender()
1484 .send(crate::gatt::RawAdvert {
1485 manufacturer_data: vec![
1486 0x06, 0x21, 0x01, 0xAE, 0xEC, 0x86, 0xC0, 0xBF, 0xD7, 0x01, 0x00, 0x09, 0x00,
1487 0x01, 0x00,
1488 ],
1489 })
1490 .await
1491 .unwrap();
1492 let ev = events.next().await.unwrap();
1493 assert_eq!((ev.aid, ev.iid), (1, 11));
1494 }
1495}