Skip to main content

trouble_host/security_manager/pairing/
mod.rs

1use bt_hci::param::ConnHandle;
2use embassy_time::Instant;
3use rand_core::{CryptoRng, RngCore};
4
5use self::util::PairingMethod;
6use crate::connection::{ConnectionEvent, SecurityLevel};
7use crate::security_manager::types::{BondingFlag, Command, PairingFeatures};
8use crate::security_manager::TxPacket;
9use crate::{Address, BondInformation, Error, IoCapabilities, LongTermKey, PacketPool};
10
11#[derive(Debug)]
12#[cfg_attr(feature = "defmt", derive(defmt::Format))]
13struct PairingData {
14    local_address: Address,
15    peer_address: Address,
16    local_features: PairingFeatures,
17    peer_features: PairingFeatures,
18    pairing_method: PairingMethod,
19    timeout_at: Instant,
20    bond_information: Option<BondInformation>,
21}
22
23impl PairingData {
24    fn new(
25        local_address: Address,
26        peer_address: Address,
27        local_io: IoCapabilities,
28        timeout: embassy_time::Duration,
29    ) -> PairingData {
30        PairingData {
31            local_address,
32            peer_address,
33            local_features: PairingFeatures {
34                io_capabilities: local_io,
35                ..Default::default()
36            },
37            peer_features: PairingFeatures::default(),
38            pairing_method: PairingMethod::JustWorks,
39            timeout_at: Instant::now() + timeout,
40            bond_information: None,
41        }
42    }
43
44    fn want_bonding(&self) -> bool {
45        matches!(self.local_features.security_properties.bond(), BondingFlag::Bonding)
46            && matches!(self.peer_features.security_properties.bond(), BondingFlag::Bonding)
47    }
48}
49
50pub mod central;
51#[cfg(feature = "legacy-pairing")]
52pub mod legacy_central;
53#[cfg(feature = "legacy-pairing")]
54pub mod legacy_peripheral;
55pub mod peripheral;
56mod util;
57
58pub(super) enum Input<'a> {
59    Command(Command, &'a [u8]),
60    Event(Event),
61}
62
63pub trait PairingOps<P: PacketPool> {
64    fn try_send_packet(&mut self, packet: TxPacket<P>) -> Result<(), Error>;
65    fn find_bond(&self) -> Option<BondInformation>;
66    fn try_enable_bonded_encryption(&mut self) -> Result<Option<BondInformation>, Error>;
67    fn try_enable_encryption(
68        &mut self,
69        ltk: &LongTermKey,
70        security_level: SecurityLevel,
71        is_bonded: bool,
72        #[cfg(feature = "legacy-pairing")] ediv: u16,
73        #[cfg(feature = "legacy-pairing")] rand: [u8; 8],
74        #[cfg(feature = "legacy-pairing")] encryption_key_len: u8,
75    ) -> Result<BondInformation, Error>;
76    fn try_update_bond_information(&mut self, bond: &BondInformation) -> Result<(), Error>;
77    fn connection_handle(&mut self) -> ConnHandle;
78    fn try_send_connection_event(&mut self, event: ConnectionEvent) -> Result<(), Error>;
79    fn bonding_flag(&self) -> BondingFlag;
80    /// Whether OOB data is available for this connection.
81    fn oob_available(&self) -> bool;
82    /// The persistent LESC secret key.
83    fn secret_key(&self) -> &crate::security_manager::crypto::SecretKey;
84    /// The persistent LESC public key.
85    fn public_key(&self) -> &crate::security_manager::crypto::PublicKey;
86    /// The local Identity Resolving Key, if privacy is enabled.
87    /// If there is no local IRK, all zeros are returned.
88    fn local_irk(&self) -> [u8; 16];
89    /// The local identity address (public or static random), used for Identity Address Information.
90    /// This is distinct from the address used for pairing calculations, which may be an RPA.
91    fn local_identity_address(&self) -> Result<Address, Error>;
92}
93
94#[derive(Debug)]
95#[cfg_attr(feature = "defmt", derive(defmt::Format))]
96enum State {
97    Central(central::Pairing),
98    Peripheral(peripheral::Pairing),
99    #[cfg(feature = "legacy-pairing")]
100    LegacyCentral(legacy_central::Pairing),
101    #[cfg(feature = "legacy-pairing")]
102    LegacyPeripheral(legacy_peripheral::Pairing),
103}
104
105#[derive(Debug)]
106#[cfg_attr(feature = "defmt", derive(defmt::Format))]
107pub struct Pairing {
108    pairing_data: PairingData,
109    state: State,
110}
111
112impl Pairing {
113    pub(crate) fn is_central(&self) -> bool {
114        match &self.state {
115            State::Central(_) => true,
116            #[cfg(feature = "legacy-pairing")]
117            State::LegacyCentral(_) => true,
118            _ => false,
119        }
120    }
121
122    #[cfg(feature = "legacy-pairing")]
123    pub(crate) fn is_lesc_central(&self) -> bool {
124        matches!(&self.state, State::Central(_))
125    }
126
127    #[cfg(feature = "legacy-pairing")]
128    pub(crate) fn is_lesc_peripheral(&self) -> bool {
129        matches!(&self.state, State::Peripheral(_))
130    }
131
132    pub(crate) fn result(&self) -> Option<Result<(), Error>> {
133        match &self.state {
134            State::Central(c) => c.result(),
135            State::Peripheral(p) => p.result(),
136            #[cfg(feature = "legacy-pairing")]
137            State::LegacyCentral(c) => c.result(),
138            #[cfg(feature = "legacy-pairing")]
139            State::LegacyPeripheral(p) => p.result(),
140        }
141    }
142
143    pub(crate) fn handle_l2cap_command<P: PacketPool, OPS: PairingOps<P>, RNG: CryptoRng + RngCore>(
144        &mut self,
145        command: Command,
146        payload: &[u8],
147        ops: &mut OPS,
148        rng: &mut RNG,
149    ) -> Result<(), Error> {
150        self.handle_input(Input::Command(command, payload), ops, rng)
151    }
152
153    pub(crate) fn handle_event<P: PacketPool, OPS: PairingOps<P>, RNG: CryptoRng + RngCore>(
154        &mut self,
155        event: Event,
156        ops: &mut OPS,
157        rng: &mut RNG,
158    ) -> Result<(), Error> {
159        self.handle_input(Input::Event(event), ops, rng)
160    }
161
162    fn handle_input<P: PacketPool, OPS: PairingOps<P>, RNG: CryptoRng + RngCore>(
163        &mut self,
164        input: Input<'_>,
165        ops: &mut OPS,
166        rng: &mut RNG,
167    ) -> Result<(), Error> {
168        match &mut self.state {
169            State::Central(central) => central.handle_input(input, &mut self.pairing_data, ops, rng),
170            State::Peripheral(peripheral) => peripheral.handle_input(input, &mut self.pairing_data, ops, rng),
171            #[cfg(feature = "legacy-pairing")]
172            State::LegacyCentral(central) => central.handle_input(input, &mut self.pairing_data, ops, rng),
173            #[cfg(feature = "legacy-pairing")]
174            State::LegacyPeripheral(peripheral) => peripheral.handle_input(input, &mut self.pairing_data, ops, rng),
175        }
176    }
177
178    pub(crate) fn security_level(&self) -> SecurityLevel {
179        let is_encrypted = match &self.state {
180            State::Central(c) => c.is_encrypted(),
181            State::Peripheral(p) => p.is_encrypted(),
182            #[cfg(feature = "legacy-pairing")]
183            State::LegacyCentral(c) => c.is_encrypted(),
184            #[cfg(feature = "legacy-pairing")]
185            State::LegacyPeripheral(p) => p.is_encrypted(),
186        };
187        if is_encrypted {
188            self.pairing_data
189                .bond_information
190                .as_ref()
191                .map(|x| x.security_level)
192                .unwrap_or(SecurityLevel::NoEncryption)
193        } else {
194            SecurityLevel::NoEncryption
195        }
196    }
197
198    pub(crate) fn bond_information(&self) -> Option<&BondInformation> {
199        self.pairing_data.bond_information.as_ref()
200    }
201
202    pub(crate) fn new_central(local_address: Address, peer_address: Address, local_io: IoCapabilities) -> Pairing {
203        Pairing {
204            pairing_data: PairingData::new(
205                local_address,
206                peer_address,
207                local_io,
208                crate::security_manager::constants::TIMEOUT_DISABLE,
209            ),
210            state: State::Central(central::Pairing::new_idle()),
211        }
212    }
213
214    pub(crate) fn initiate_central<P: PacketPool, OPS: PairingOps<P>>(
215        local_address: Address,
216        peer_address: Address,
217        ops: &mut OPS,
218        local_io: IoCapabilities,
219        user_initiated: bool,
220    ) -> Result<Self, Error> {
221        let mut pairing_data = PairingData::new(
222            local_address,
223            peer_address,
224            local_io,
225            crate::security_manager::constants::TIMEOUT_DISABLE,
226        );
227        let state = central::Pairing::initiate(&mut pairing_data, ops, user_initiated)?;
228        pairing_data.timeout_at = Instant::now() + crate::security_manager::constants::TIMEOUT;
229        Ok(Pairing {
230            pairing_data,
231            state: State::Central(state),
232        })
233    }
234
235    pub(crate) fn new_peripheral(local_address: Address, peer_address: Address, local_io: IoCapabilities) -> Pairing {
236        Pairing {
237            pairing_data: PairingData::new(
238                local_address,
239                peer_address,
240                local_io,
241                crate::security_manager::constants::TIMEOUT,
242            ),
243            state: State::Peripheral(peripheral::Pairing::new()),
244        }
245    }
246
247    #[cfg(feature = "legacy-pairing")]
248    pub(crate) fn new_legacy_peripheral(
249        local_address: Address,
250        peer_address: Address,
251        local_io: IoCapabilities,
252    ) -> Pairing {
253        Pairing {
254            pairing_data: PairingData::new(
255                local_address,
256                peer_address,
257                local_io,
258                crate::security_manager::constants::TIMEOUT,
259            ),
260            state: State::LegacyPeripheral(legacy_peripheral::Pairing::new()),
261        }
262    }
263
264    pub(crate) fn initiate_peripheral<P: PacketPool, OPS: PairingOps<P>>(
265        local_address: Address,
266        peer_address: Address,
267        ops: &mut OPS,
268        local_io: IoCapabilities,
269        user_initiated: bool,
270    ) -> Result<Self, Error> {
271        let pairing_data = PairingData::new(
272            local_address,
273            peer_address,
274            local_io,
275            crate::security_manager::constants::TIMEOUT,
276        );
277        let state = peripheral::Pairing::initiate(&pairing_data, ops, user_initiated)?;
278        Ok(Pairing {
279            pairing_data,
280            state: State::Peripheral(state),
281        })
282    }
283
284    /// Switch from a LESC Central to a Legacy Central when the peer doesn't support SC.
285    #[cfg(feature = "legacy-pairing")]
286    pub(crate) fn switch_to_legacy_central(self) -> Result<Pairing, Error> {
287        use crate::codec::Encode;
288
289        let Pairing { pairing_data, state } = self;
290        match state {
291            State::Central(_) => {
292                let mut preq = [0u8; 7];
293                preq[0] = u8::from(Command::PairingRequest);
294                pairing_data.local_features.encode(&mut preq[1..]).unwrap();
295                Ok(Pairing {
296                    pairing_data,
297                    state: State::LegacyCentral(legacy_central::Pairing::from_lesc_switch(preq)),
298                })
299            }
300            _ => Err(Error::InvalidState),
301        }
302    }
303
304    /// Switch from a LESC Peripheral to a Legacy Peripheral when the peer doesn't support SC.
305    #[cfg(feature = "legacy-pairing")]
306    pub(crate) fn switch_to_legacy_peripheral(self) -> Result<Pairing, Error> {
307        let Pairing { pairing_data, state } = self;
308        match state {
309            State::Peripheral(_) => Ok(Pairing {
310                pairing_data,
311                state: State::LegacyPeripheral(legacy_peripheral::Pairing::new()),
312            }),
313            _ => Err(Error::InvalidState),
314        }
315    }
316
317    pub(crate) fn is_waiting_bonded_encryption(&self) -> bool {
318        match &self.state {
319            State::Central(c) => c.is_waiting_bonded_encryption(),
320            _ => false,
321        }
322    }
323
324    pub(crate) fn peer_address(&self) -> Address {
325        self.pairing_data.peer_address
326    }
327
328    pub(crate) fn timeout_at(&self) -> Instant {
329        if self.result().is_some() {
330            Instant::now() + crate::security_manager::constants::TIMEOUT_DISABLE
331        } else {
332            self.pairing_data.timeout_at
333        }
334    }
335
336    pub(crate) fn reset_timeout(&mut self) {
337        self.pairing_data.timeout_at = Instant::now() + crate::security_manager::constants::TIMEOUT;
338    }
339
340    pub(crate) fn mark_timeout(&mut self) {
341        match &mut self.state {
342            State::Central(c) => c.mark_timeout(),
343            State::Peripheral(p) => p.mark_timeout(),
344            #[cfg(feature = "legacy-pairing")]
345            State::LegacyCentral(c) => c.mark_timeout(),
346            #[cfg(feature = "legacy-pairing")]
347            State::LegacyPeripheral(p) => p.mark_timeout(),
348        }
349    }
350}
351
352/// OOB data for BLE pairing, exchanged via an out-of-band channel.
353///
354/// For LESC: `random` is the random nonce r, `confirm` is c = f4(PKx, PKx, r, 0).
355/// For legacy: `random` is the TK value, `confirm` is unused (set to zero).
356#[derive(Clone, Copy, Debug)]
357#[cfg_attr(feature = "defmt", derive(defmt::Format))]
358pub struct OobData {
359    /// Random nonce (LESC) or TK value (legacy).
360    pub random: [u8; 16],
361    /// Confirm value (LESC only, zero for legacy).
362    pub confirm: [u8; 16],
363}
364
365pub enum Event {
366    LinkEncryptedResult(bool),
367    PassKeyConfirm,
368    PassKeyCancel,
369    PassKeyInput(u32),
370    OobDataReceived { local: OobData, peer: OobData },
371}
372
373#[cfg(test)]
374mod tests {
375    use rand_chacha::{ChaCha12Core, ChaCha12Rng};
376    use rand_core::SeedableRng;
377
378    use super::*;
379    use crate::{Identity, Packet};
380
381    #[derive(Debug)]
382    pub(crate) struct TestPacket(pub(crate) heapless::Vec<u8, 128>);
383
384    impl AsRef<[u8]> for TestPacket {
385        fn as_ref(&self) -> &[u8] {
386            self.0.as_slice()
387        }
388    }
389
390    impl AsMut<[u8]> for TestPacket {
391        fn as_mut(&mut self) -> &mut [u8] {
392            self.0.as_mut_slice()
393        }
394    }
395
396    impl Packet for TestPacket {}
397
398    #[derive(Debug)]
399    pub(crate) struct HeaplessPool;
400
401    impl PacketPool for HeaplessPool {
402        type Packet = TestPacket;
403        const MTU: usize = 128;
404
405        fn allocate() -> Option<Self::Packet> {
406            let mut ret = TestPacket(heapless::Vec::new());
407            ret.0.resize(Self::MTU, 0).unwrap();
408            Some(ret)
409        }
410
411        fn capacity() -> usize {
412            isize::MAX as usize
413        }
414    }
415
416    pub(crate) struct TestOps<const N: usize> {
417        pub(crate) sent_packets: heapless::Vec<TxPacket<HeaplessPool>, N>,
418        pub(crate) encryptions: heapless::Vec<LongTermKey, 10>,
419        pub(crate) connection_events: heapless::Vec<ConnectionEvent, 10>,
420        pub(crate) bond_information: Option<BondInformation>,
421        pub(crate) bondable: bool,
422        pub(crate) oob_available: bool,
423        pub(crate) secret_key: crate::security_manager::crypto::SecretKey,
424        pub(crate) public_key: crate::security_manager::crypto::PublicKey,
425    }
426
427    impl<const N: usize> TestOps<N> {
428        pub(crate) fn new(seed: u64) -> Self {
429            let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(seed).into();
430            let secret_key = crate::security_manager::crypto::SecretKey::new(&mut rng);
431            let public_key = secret_key.public_key();
432            Self {
433                sent_packets: heapless::Vec::new(),
434                encryptions: heapless::Vec::new(),
435                connection_events: heapless::Vec::new(),
436                bond_information: None,
437                bondable: false,
438                oob_available: false,
439                secret_key,
440                public_key,
441            }
442        }
443    }
444
445    impl<const N: usize> PairingOps<HeaplessPool> for TestOps<N> {
446        fn try_send_packet(&mut self, packet: TxPacket<HeaplessPool>) -> Result<(), Error> {
447            self.sent_packets.push(packet).map_err(|_| Error::OutOfMemory)
448        }
449
450        fn try_enable_encryption(
451            &mut self,
452            ltk: &LongTermKey,
453            security_level: SecurityLevel,
454            is_bonded: bool,
455            #[cfg(feature = "legacy-pairing")] ediv: u16,
456            #[cfg(feature = "legacy-pairing")] rand: [u8; 8],
457            #[cfg(feature = "legacy-pairing")] encryption_key_len: u8,
458        ) -> Result<BondInformation, Error> {
459            self.encryptions.push(ltk.clone()).unwrap();
460            Ok(BondInformation {
461                security_level,
462                identity: Identity::default(),
463                ltk: ltk.clone(),
464                is_bonded,
465                #[cfg(feature = "legacy-pairing")]
466                ediv,
467                #[cfg(feature = "legacy-pairing")]
468                rand,
469                #[cfg(feature = "legacy-pairing")]
470                encryption_key_len,
471            })
472        }
473
474        fn find_bond(&self) -> Option<BondInformation> {
475            self.bond_information.clone()
476        }
477
478        fn try_enable_bonded_encryption(&mut self) -> Result<Option<BondInformation>, Error> {
479            if let Some(bond) = &self.bond_information {
480                self.encryptions.push(bond.ltk.clone()).unwrap();
481                Ok(Some(bond.clone()))
482            } else {
483                Ok(None)
484            }
485        }
486
487        fn try_update_bond_information(&mut self, bond: &BondInformation) -> Result<(), Error> {
488            Ok(())
489        }
490
491        fn connection_handle(&mut self) -> ConnHandle {
492            ConnHandle::new(2)
493        }
494
495        fn try_send_connection_event(&mut self, event: ConnectionEvent) -> Result<(), Error> {
496            self.connection_events.push(event).unwrap();
497            Ok(())
498        }
499
500        fn bonding_flag(&self) -> BondingFlag {
501            if self.bondable {
502                BondingFlag::Bonding
503            } else {
504                BondingFlag::NoBonding
505            }
506        }
507
508        fn oob_available(&self) -> bool {
509            self.oob_available
510        }
511
512        fn secret_key(&self) -> &crate::security_manager::crypto::SecretKey {
513            &self.secret_key
514        }
515
516        fn public_key(&self) -> &crate::security_manager::crypto::PublicKey {
517            &self.public_key
518        }
519
520        fn local_irk(&self) -> [u8; 16] {
521            [0; 16]
522        }
523
524        fn local_identity_address(&self) -> Result<Address, Error> {
525            Ok(Address::random([0xff, 0x8f, 0x08, 0x05, 0xe4, 0xff]))
526        }
527    }
528
529    #[test]
530    fn just_works() {
531        let peripheral = Address::random([0xff, 1, 2, 3, 4, 5]);
532        let central = Address::random([0xff, 2, 2, 3, 4, 5]);
533
534        let mut peripheral_ops = TestOps::<10>::new(0xDEAD);
535        let mut central_ops = TestOps::<10>::new(0xBEEF);
536
537        let mut peripheral_pairing = Pairing::new_peripheral(peripheral, central, IoCapabilities::NoInputNoOutput);
538        let mut central_pairing = Pairing::initiate_central(
539            central,
540            peripheral,
541            &mut central_ops,
542            IoCapabilities::NoInputNoOutput,
543            true,
544        )
545        .unwrap();
546
547        let mut num_central_data_sent = 0;
548        let mut num_peripheral_data_sent = 0;
549        let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(1).into();
550        transmit_packets(
551            &mut peripheral_ops,
552            &mut central_ops,
553            &mut rng,
554            &mut peripheral_pairing,
555            &mut central_pairing,
556            &mut num_central_data_sent,
557            &mut num_peripheral_data_sent,
558        );
559
560        assert_eq!(central_ops.encryptions[0], peripheral_ops.encryptions[0]);
561        central_pairing
562            .handle_event(Event::LinkEncryptedResult(true), &mut central_ops, &mut rng)
563            .unwrap();
564        peripheral_pairing
565            .handle_event(Event::LinkEncryptedResult(true), &mut peripheral_ops, &mut rng)
566            .unwrap();
567
568        assert!(matches!(
569            central_ops.connection_events[0],
570            ConnectionEvent::PairingComplete {
571                security_level: SecurityLevel::Encrypted,
572                bond: None
573            }
574        ));
575        assert!(matches!(
576            peripheral_ops.connection_events[0],
577            ConnectionEvent::PairingComplete {
578                security_level: SecurityLevel::Encrypted,
579                bond: None
580            }
581        ));
582        assert_eq!(central_pairing.security_level(), SecurityLevel::Encrypted);
583        assert_eq!(peripheral_pairing.security_level(), SecurityLevel::Encrypted);
584    }
585
586    #[test]
587    fn numeric_compare() {
588        let peripheral = Address::random([0xff, 1, 2, 3, 4, 5]);
589        let central = Address::random([0xff, 2, 2, 3, 4, 5]);
590
591        let mut peripheral_ops = TestOps::<10>::new(0xDEAD);
592        let mut central_ops = TestOps::<10>::new(0xBEEF);
593
594        let mut peripheral_pairing = Pairing::new_peripheral(peripheral, central, IoCapabilities::DisplayYesNo);
595        let mut central_pairing = Pairing::initiate_central(
596            central,
597            peripheral,
598            &mut central_ops,
599            IoCapabilities::DisplayYesNo,
600            true,
601        )
602        .unwrap();
603
604        let mut num_central_data_sent = 0;
605        let mut num_peripheral_data_sent = 0;
606        let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(1).into();
607        transmit_packets(
608            &mut peripheral_ops,
609            &mut central_ops,
610            &mut rng,
611            &mut peripheral_pairing,
612            &mut central_pairing,
613            &mut num_central_data_sent,
614            &mut num_peripheral_data_sent,
615        );
616
617        let (central_numeric, peripheral_numeric) = {
618            let central = match &central_ops.connection_events[0] {
619                ConnectionEvent::PassKeyConfirm(n) => n,
620                _ => panic!("Unexpected connection event"),
621            };
622
623            let peripheral = match &peripheral_ops.connection_events[0] {
624                ConnectionEvent::PassKeyConfirm(n) => n,
625                _ => panic!("Unexpected connection event"),
626            };
627
628            (*central, *peripheral)
629        };
630
631        assert_eq!(central_numeric, peripheral_numeric);
632        central_pairing
633            .handle_event(Event::PassKeyConfirm, &mut central_ops, &mut rng)
634            .unwrap();
635        peripheral_pairing
636            .handle_event(Event::PassKeyConfirm, &mut peripheral_ops, &mut rng)
637            .unwrap();
638
639        transmit_packets(
640            &mut peripheral_ops,
641            &mut central_ops,
642            &mut rng,
643            &mut peripheral_pairing,
644            &mut central_pairing,
645            &mut num_central_data_sent,
646            &mut num_peripheral_data_sent,
647        );
648
649        assert_eq!(central_ops.encryptions[0], peripheral_ops.encryptions[0]);
650        central_pairing
651            .handle_event(Event::LinkEncryptedResult(true), &mut central_ops, &mut rng)
652            .unwrap();
653        peripheral_pairing
654            .handle_event(Event::LinkEncryptedResult(true), &mut peripheral_ops, &mut rng)
655            .unwrap();
656
657        assert!(matches!(
658            central_ops.connection_events[1],
659            ConnectionEvent::PairingComplete {
660                security_level: SecurityLevel::EncryptedAuthenticated,
661                bond: None
662            }
663        ));
664        assert!(matches!(
665            peripheral_ops.connection_events[1],
666            ConnectionEvent::PairingComplete {
667                security_level: SecurityLevel::EncryptedAuthenticated,
668                bond: None
669            }
670        ));
671        assert_eq!(central_pairing.security_level(), SecurityLevel::EncryptedAuthenticated);
672        assert_eq!(
673            peripheral_pairing.security_level(),
674            SecurityLevel::EncryptedAuthenticated
675        );
676    }
677
678    #[test]
679    fn pass_key_entry_keyboard_only() {
680        let peripheral = Address::random([0xff, 1, 2, 3, 4, 5]);
681        let central = Address::random([0xff, 2, 2, 3, 4, 5]);
682
683        let mut peripheral_ops = TestOps::<80>::new(0xDEAD);
684        let mut central_ops = TestOps::<80>::new(0xBEEF);
685
686        let mut peripheral_pairing = Pairing::new_peripheral(peripheral, central, IoCapabilities::KeyboardOnly);
687        let mut central_pairing = Pairing::initiate_central(
688            central,
689            peripheral,
690            &mut central_ops,
691            IoCapabilities::KeyboardOnly,
692            true,
693        )
694        .unwrap();
695
696        let mut num_central_data_sent = 0;
697        let mut num_peripheral_data_sent = 0;
698        let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(1).into();
699        transmit_packets(
700            &mut peripheral_ops,
701            &mut central_ops,
702            &mut rng,
703            &mut peripheral_pairing,
704            &mut central_pairing,
705            &mut num_central_data_sent,
706            &mut num_peripheral_data_sent,
707        );
708
709        assert!(matches!(
710            central_ops.connection_events[0],
711            ConnectionEvent::PassKeyInput
712        ));
713        assert!(matches!(
714            peripheral_ops.connection_events[0],
715            ConnectionEvent::PassKeyInput
716        ));
717
718        central_pairing
719            .handle_event(Event::PassKeyInput(123456), &mut central_ops, &mut rng)
720            .unwrap();
721        peripheral_pairing
722            .handle_event(Event::PassKeyInput(123456), &mut peripheral_ops, &mut rng)
723            .unwrap();
724
725        transmit_packets(
726            &mut peripheral_ops,
727            &mut central_ops,
728            &mut rng,
729            &mut peripheral_pairing,
730            &mut central_pairing,
731            &mut num_central_data_sent,
732            &mut num_peripheral_data_sent,
733        );
734
735        assert_eq!(central_ops.encryptions[0], peripheral_ops.encryptions[0]);
736        central_pairing
737            .handle_event(Event::LinkEncryptedResult(true), &mut central_ops, &mut rng)
738            .unwrap();
739        peripheral_pairing
740            .handle_event(Event::LinkEncryptedResult(true), &mut peripheral_ops, &mut rng)
741            .unwrap();
742
743        assert!(matches!(
744            central_ops.connection_events[1],
745            ConnectionEvent::PairingComplete {
746                security_level: SecurityLevel::EncryptedAuthenticated,
747                bond: None
748            }
749        ));
750        assert!(matches!(
751            peripheral_ops.connection_events[1],
752            ConnectionEvent::PairingComplete {
753                security_level: SecurityLevel::EncryptedAuthenticated,
754                bond: None
755            }
756        ));
757        assert_eq!(central_pairing.security_level(), SecurityLevel::EncryptedAuthenticated);
758        assert_eq!(
759            peripheral_pairing.security_level(),
760            SecurityLevel::EncryptedAuthenticated
761        );
762    }
763
764    #[test]
765    fn pass_key_entry_peripheral_display() {
766        let peripheral = Address::random([0xff, 1, 2, 3, 4, 5]);
767        let central = Address::random([0xff, 2, 2, 3, 4, 5]);
768
769        let mut peripheral_ops = TestOps::<80>::new(0xDEAD);
770        let mut central_ops = TestOps::<80>::new(0xBEEF);
771
772        let mut peripheral_pairing = Pairing::new_peripheral(peripheral, central, IoCapabilities::DisplayOnly);
773        let mut central_pairing = Pairing::initiate_central(
774            central,
775            peripheral,
776            &mut central_ops,
777            IoCapabilities::KeyboardOnly,
778            true,
779        )
780        .unwrap();
781
782        let mut num_central_data_sent = 0;
783        let mut num_peripheral_data_sent = 0;
784        let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(1).into();
785        transmit_packets(
786            &mut peripheral_ops,
787            &mut central_ops,
788            &mut rng,
789            &mut peripheral_pairing,
790            &mut central_pairing,
791            &mut num_central_data_sent,
792            &mut num_peripheral_data_sent,
793        );
794
795        let pass_key = match &peripheral_ops.connection_events[0] {
796            ConnectionEvent::PassKeyDisplay(pk) => *pk,
797            _ => panic!("Unexpected connection event"),
798        };
799
800        assert!(matches!(
801            central_ops.connection_events[0],
802            ConnectionEvent::PassKeyInput
803        ));
804
805        central_pairing
806            .handle_event(Event::PassKeyInput(pass_key.value()), &mut central_ops, &mut rng)
807            .unwrap();
808
809        transmit_packets(
810            &mut peripheral_ops,
811            &mut central_ops,
812            &mut rng,
813            &mut peripheral_pairing,
814            &mut central_pairing,
815            &mut num_central_data_sent,
816            &mut num_peripheral_data_sent,
817        );
818
819        assert_eq!(central_ops.encryptions[0], peripheral_ops.encryptions[0]);
820        central_pairing
821            .handle_event(Event::LinkEncryptedResult(true), &mut central_ops, &mut rng)
822            .unwrap();
823        peripheral_pairing
824            .handle_event(Event::LinkEncryptedResult(true), &mut peripheral_ops, &mut rng)
825            .unwrap();
826
827        assert!(matches!(
828            central_ops.connection_events[1],
829            ConnectionEvent::PairingComplete {
830                security_level: SecurityLevel::EncryptedAuthenticated,
831                bond: None
832            }
833        ));
834        assert!(matches!(
835            peripheral_ops.connection_events[1],
836            ConnectionEvent::PairingComplete {
837                security_level: SecurityLevel::EncryptedAuthenticated,
838                bond: None
839            }
840        ));
841        assert_eq!(central_pairing.security_level(), SecurityLevel::EncryptedAuthenticated);
842        assert_eq!(
843            peripheral_pairing.security_level(),
844            SecurityLevel::EncryptedAuthenticated
845        );
846    }
847
848    #[test]
849    fn pass_key_entry_central_display() {
850        let peripheral = Address::random([0xff, 1, 2, 3, 4, 5]);
851        let central = Address::random([0xff, 2, 2, 3, 4, 5]);
852
853        let mut peripheral_ops = TestOps::<80>::new(0xDEAD);
854        let mut central_ops = TestOps::<80>::new(0xBEEF);
855
856        let mut peripheral_pairing = Pairing::new_peripheral(peripheral, central, IoCapabilities::KeyboardOnly);
857        let mut central_pairing =
858            Pairing::initiate_central(central, peripheral, &mut central_ops, IoCapabilities::DisplayOnly, true)
859                .unwrap();
860
861        let mut num_central_data_sent = 0;
862        let mut num_peripheral_data_sent = 0;
863        let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(1).into();
864        transmit_packets(
865            &mut peripheral_ops,
866            &mut central_ops,
867            &mut rng,
868            &mut peripheral_pairing,
869            &mut central_pairing,
870            &mut num_central_data_sent,
871            &mut num_peripheral_data_sent,
872        );
873
874        let pass_key = match &central_ops.connection_events[0] {
875            ConnectionEvent::PassKeyDisplay(pk) => *pk,
876            _ => panic!("Unexpected connection event"),
877        };
878
879        assert!(matches!(
880            peripheral_ops.connection_events[0],
881            ConnectionEvent::PassKeyInput
882        ));
883
884        peripheral_pairing
885            .handle_event(Event::PassKeyInput(pass_key.value()), &mut peripheral_ops, &mut rng)
886            .unwrap();
887
888        transmit_packets(
889            &mut peripheral_ops,
890            &mut central_ops,
891            &mut rng,
892            &mut peripheral_pairing,
893            &mut central_pairing,
894            &mut num_central_data_sent,
895            &mut num_peripheral_data_sent,
896        );
897
898        assert_eq!(central_ops.encryptions[0], peripheral_ops.encryptions[0]);
899        central_pairing
900            .handle_event(Event::LinkEncryptedResult(true), &mut central_ops, &mut rng)
901            .unwrap();
902        peripheral_pairing
903            .handle_event(Event::LinkEncryptedResult(true), &mut peripheral_ops, &mut rng)
904            .unwrap();
905
906        assert!(matches!(
907            central_ops.connection_events[1],
908            ConnectionEvent::PairingComplete {
909                security_level: SecurityLevel::EncryptedAuthenticated,
910                bond: None
911            }
912        ));
913        assert!(matches!(
914            peripheral_ops.connection_events[1],
915            ConnectionEvent::PairingComplete {
916                security_level: SecurityLevel::EncryptedAuthenticated,
917                bond: None
918            }
919        ));
920        assert_eq!(central_pairing.security_level(), SecurityLevel::EncryptedAuthenticated);
921        assert_eq!(
922            peripheral_pairing.security_level(),
923            SecurityLevel::EncryptedAuthenticated
924        );
925    }
926
927    #[test]
928    fn bondable_just_works() {
929        let peripheral = Address::random([0xff, 1, 2, 3, 4, 5]);
930        let central = Address::random([0xff, 2, 2, 3, 4, 5]);
931
932        let mut peripheral_ops = TestOps::<80>::new(0xDEAD);
933        let mut central_ops = TestOps::<80>::new(0xBEEF);
934        peripheral_ops.bondable = true;
935        central_ops.bondable = true;
936
937        let mut peripheral_pairing = Pairing::new_peripheral(peripheral, central, IoCapabilities::NoInputNoOutput);
938        let mut central_pairing = Pairing::initiate_central(
939            central,
940            peripheral,
941            &mut central_ops,
942            IoCapabilities::NoInputNoOutput,
943            true,
944        )
945        .unwrap();
946
947        let mut num_central_data_sent = 0;
948        let mut num_peripheral_data_sent = 0;
949        let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(1).into();
950        transmit_packets(
951            &mut peripheral_ops,
952            &mut central_ops,
953            &mut rng,
954            &mut peripheral_pairing,
955            &mut central_pairing,
956            &mut num_central_data_sent,
957            &mut num_peripheral_data_sent,
958        );
959
960        assert_eq!(central_ops.encryptions[0], peripheral_ops.encryptions[0]);
961        central_pairing
962            .handle_event(Event::LinkEncryptedResult(true), &mut central_ops, &mut rng)
963            .unwrap();
964        peripheral_pairing
965            .handle_event(Event::LinkEncryptedResult(true), &mut peripheral_ops, &mut rng)
966            .unwrap();
967
968        // Exchange identity keys after encryption
969        transmit_packets(
970            &mut peripheral_ops,
971            &mut central_ops,
972            &mut rng,
973            &mut peripheral_pairing,
974            &mut central_pairing,
975            &mut num_central_data_sent,
976            &mut num_peripheral_data_sent,
977        );
978
979        assert!(matches!(
980            central_ops.connection_events[0],
981            ConnectionEvent::PairingComplete {
982                security_level: SecurityLevel::Encrypted,
983                bond: Some(BondInformation {
984                    is_bonded: true,
985                    security_level: SecurityLevel::Encrypted,
986                    ..
987                })
988            }
989        ));
990        assert!(matches!(
991            peripheral_ops.connection_events[0],
992            ConnectionEvent::PairingComplete {
993                security_level: SecurityLevel::Encrypted,
994                bond: Some(BondInformation {
995                    is_bonded: true,
996                    security_level: SecurityLevel::Encrypted,
997                    ..
998                })
999            }
1000        ));
1001        assert_eq!(central_pairing.security_level(), SecurityLevel::Encrypted);
1002        assert_eq!(peripheral_pairing.security_level(), SecurityLevel::Encrypted);
1003    }
1004
1005    #[test]
1006    fn bonded_central_initiates() {
1007        let peripheral = Address::random([0xff, 1, 2, 3, 4, 5]);
1008        let central = Address::random([0xff, 2, 2, 3, 4, 5]);
1009
1010        let mut peripheral_ops = TestOps::<80>::new(0xDEAD);
1011        let mut central_ops = TestOps::<80>::new(0xBEEF);
1012        central_ops.bond_information = Some(BondInformation {
1013            security_level: SecurityLevel::EncryptedAuthenticated,
1014            is_bonded: true,
1015            ltk: LongTermKey(1),
1016            identity: peripheral.into(),
1017            #[cfg(feature = "legacy-pairing")]
1018            ediv: 0,
1019            #[cfg(feature = "legacy-pairing")]
1020            rand: [0; 8],
1021            #[cfg(feature = "legacy-pairing")]
1022            encryption_key_len: 16,
1023        });
1024
1025        peripheral_ops.bond_information = Some(BondInformation {
1026            security_level: SecurityLevel::EncryptedAuthenticated,
1027            is_bonded: true,
1028            ltk: LongTermKey(1),
1029            identity: central.into(),
1030            #[cfg(feature = "legacy-pairing")]
1031            ediv: 0,
1032            #[cfg(feature = "legacy-pairing")]
1033            rand: [0; 8],
1034            #[cfg(feature = "legacy-pairing")]
1035            encryption_key_len: 16,
1036        });
1037
1038        let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(1).into();
1039
1040        let mut peripheral_pairing = Pairing::new_peripheral(peripheral, central, IoCapabilities::NoInputNoOutput);
1041        let mut central_pairing = Pairing::initiate_central(
1042            central,
1043            peripheral,
1044            &mut central_ops,
1045            IoCapabilities::NoInputNoOutput,
1046            true,
1047        )
1048        .unwrap();
1049        assert_eq!(central_ops.sent_packets.len(), 0);
1050        assert_eq!(peripheral_ops.sent_packets.len(), 0);
1051        assert_eq!(central_ops.encryptions.len(), 1);
1052        assert_eq!(central_ops.encryptions[0], LongTermKey(1));
1053
1054        central_pairing
1055            .handle_event(Event::LinkEncryptedResult(true), &mut central_ops, &mut rng)
1056            .unwrap();
1057        peripheral_pairing
1058            .handle_event(Event::LinkEncryptedResult(true), &mut peripheral_ops, &mut rng)
1059            .unwrap();
1060
1061        assert!(matches!(
1062            central_ops.connection_events[0],
1063            ConnectionEvent::PairingComplete {
1064                security_level: SecurityLevel::EncryptedAuthenticated,
1065                bond: None
1066            }
1067        ));
1068        assert!(matches!(
1069            peripheral_ops.connection_events[0],
1070            ConnectionEvent::PairingComplete {
1071                security_level: SecurityLevel::EncryptedAuthenticated,
1072                bond: None
1073            }
1074        ));
1075        assert_eq!(central_ops.connection_events.len(), 1);
1076        assert_eq!(peripheral_ops.connection_events.len(), 1);
1077        assert_eq!(central_pairing.security_level(), SecurityLevel::EncryptedAuthenticated);
1078        assert_eq!(
1079            peripheral_pairing.security_level(),
1080            SecurityLevel::EncryptedAuthenticated
1081        );
1082    }
1083
1084    #[test]
1085    fn bonded_peripheral_initiates() {
1086        let peripheral = Address::random([0xff, 1, 2, 3, 4, 5]);
1087        let central = Address::random([0xff, 2, 2, 3, 4, 5]);
1088
1089        let mut peripheral_ops = TestOps::<80>::new(0xDEAD);
1090        let mut central_ops = TestOps::<80>::new(0xBEEF);
1091        central_ops.bond_information = Some(BondInformation {
1092            security_level: SecurityLevel::EncryptedAuthenticated,
1093            is_bonded: true,
1094            ltk: LongTermKey(1),
1095            identity: peripheral.into(),
1096            #[cfg(feature = "legacy-pairing")]
1097            ediv: 0,
1098            #[cfg(feature = "legacy-pairing")]
1099            rand: [0; 8],
1100            #[cfg(feature = "legacy-pairing")]
1101            encryption_key_len: 16,
1102        });
1103
1104        peripheral_ops.bond_information = Some(BondInformation {
1105            security_level: SecurityLevel::EncryptedAuthenticated,
1106            is_bonded: true,
1107            ltk: LongTermKey(1),
1108            identity: central.into(),
1109            #[cfg(feature = "legacy-pairing")]
1110            ediv: 0,
1111            #[cfg(feature = "legacy-pairing")]
1112            rand: [0; 8],
1113            #[cfg(feature = "legacy-pairing")]
1114            encryption_key_len: 16,
1115        });
1116
1117        let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(1).into();
1118
1119        let mut peripheral_pairing = Pairing::initiate_peripheral(
1120            peripheral,
1121            central,
1122            &mut peripheral_ops,
1123            IoCapabilities::NoInputNoOutput,
1124            false,
1125        )
1126        .unwrap();
1127        let mut central_pairing = Pairing::new_central(central, peripheral, IoCapabilities::NoInputNoOutput);
1128
1129        let mut num_central_data_sent = 0;
1130        let mut num_peripheral_data_sent = 0;
1131        transmit_packets(
1132            &mut peripheral_ops,
1133            &mut central_ops,
1134            &mut rng,
1135            &mut peripheral_pairing,
1136            &mut central_pairing,
1137            &mut num_central_data_sent,
1138            &mut num_peripheral_data_sent,
1139        );
1140
1141        assert_eq!(central_ops.sent_packets.len(), 0);
1142        assert_eq!(peripheral_ops.sent_packets.len(), 1);
1143        assert_eq!(central_ops.encryptions.len(), 1);
1144        assert_eq!(central_ops.encryptions[0], LongTermKey(1));
1145
1146        central_pairing
1147            .handle_event(Event::LinkEncryptedResult(true), &mut central_ops, &mut rng)
1148            .unwrap();
1149        peripheral_pairing
1150            .handle_event(Event::LinkEncryptedResult(true), &mut peripheral_ops, &mut rng)
1151            .unwrap();
1152
1153        assert!(matches!(
1154            central_ops.connection_events[0],
1155            ConnectionEvent::PairingComplete {
1156                security_level: SecurityLevel::EncryptedAuthenticated,
1157                bond: None
1158            }
1159        ));
1160        assert!(matches!(
1161            peripheral_ops.connection_events[0],
1162            ConnectionEvent::PairingComplete {
1163                security_level: SecurityLevel::EncryptedAuthenticated,
1164                bond: None
1165            }
1166        ));
1167        assert_eq!(central_ops.connection_events.len(), 1);
1168        assert_eq!(peripheral_ops.connection_events.len(), 1);
1169        assert_eq!(central_pairing.security_level(), SecurityLevel::EncryptedAuthenticated);
1170        assert_eq!(
1171            peripheral_pairing.security_level(),
1172            SecurityLevel::EncryptedAuthenticated
1173        );
1174    }
1175
1176    fn transmit_packets<const N: usize>(
1177        peripheral_ops: &mut TestOps<N>,
1178        central_ops: &mut TestOps<N>,
1179        rng: &mut ChaCha12Rng,
1180        peripheral_pairing: &mut Pairing,
1181        central_pairing: &mut Pairing,
1182        num_central_data_sent: &mut usize,
1183        num_peripheral_data_sent: &mut usize,
1184    ) {
1185        let mut loop_count = 0;
1186        loop {
1187            let saved_num_central_data_sent = *num_central_data_sent;
1188            let saved_num_peripheral_data_sent = *num_peripheral_data_sent;
1189
1190            while *num_central_data_sent < central_ops.sent_packets.len() {
1191                peripheral_pairing
1192                    .handle_l2cap_command(
1193                        central_ops.sent_packets[*num_central_data_sent].command,
1194                        central_ops.sent_packets[*num_central_data_sent].payload(),
1195                        peripheral_ops,
1196                        rng,
1197                    )
1198                    .unwrap();
1199                *num_central_data_sent += 1;
1200            }
1201
1202            while *num_peripheral_data_sent < peripheral_ops.sent_packets.len() {
1203                central_pairing
1204                    .handle_l2cap_command(
1205                        peripheral_ops.sent_packets[*num_peripheral_data_sent].command,
1206                        peripheral_ops.sent_packets[*num_peripheral_data_sent].payload(),
1207                        central_ops,
1208                        rng,
1209                    )
1210                    .unwrap();
1211                *num_peripheral_data_sent += 1;
1212            }
1213
1214            if saved_num_central_data_sent == *num_central_data_sent
1215                && saved_num_peripheral_data_sent == *num_peripheral_data_sent
1216            {
1217                break;
1218            }
1219
1220            loop_count += 1;
1221            if loop_count > 10000 {
1222                panic!("Too many loops");
1223            }
1224        }
1225    }
1226
1227    #[test]
1228    fn oob_lesc_bilateral() {
1229        use crate::security_manager::crypto::Nonce;
1230
1231        let peripheral_addr = Address::random([0xff, 1, 2, 3, 4, 5]);
1232        let central_addr = Address::random([0xff, 2, 2, 3, 4, 5]);
1233
1234        let mut peripheral_ops = TestOps::<10>::new(0xDEAD);
1235        let mut central_ops = TestOps::<10>::new(0xBEEF);
1236        peripheral_ops.oob_available = true;
1237        central_ops.oob_available = true;
1238
1239        // Generate OOB data for each side from their persistent keypair
1240        let central_oob = {
1241            let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(100).into();
1242            let r = Nonce::new(&mut rng);
1243            let c = r.f4(central_ops.public_key.x(), central_ops.public_key.x(), 0);
1244            OobData {
1245                random: r.0.to_le_bytes(),
1246                confirm: c.0.to_le_bytes(),
1247            }
1248        };
1249        let peripheral_oob = {
1250            let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(200).into();
1251            let r = Nonce::new(&mut rng);
1252            let c = r.f4(peripheral_ops.public_key.x(), peripheral_ops.public_key.x(), 0);
1253            OobData {
1254                random: r.0.to_le_bytes(),
1255                confirm: c.0.to_le_bytes(),
1256            }
1257        };
1258
1259        let mut peripheral_pairing =
1260            Pairing::new_peripheral(peripheral_addr, central_addr, IoCapabilities::NoInputNoOutput);
1261        let mut central_pairing = Pairing::initiate_central(
1262            central_addr,
1263            peripheral_addr,
1264            &mut central_ops,
1265            IoCapabilities::NoInputNoOutput,
1266            true,
1267        )
1268        .unwrap();
1269
1270        let mut num_central_data_sent = 0;
1271        let mut num_peripheral_data_sent = 0;
1272        let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(1).into();
1273
1274        // Exchange packets until both sides pause for OOB data
1275        transmit_packets(
1276            &mut peripheral_ops,
1277            &mut central_ops,
1278            &mut rng,
1279            &mut peripheral_pairing,
1280            &mut central_pairing,
1281            &mut num_central_data_sent,
1282            &mut num_peripheral_data_sent,
1283        );
1284
1285        // Both sides should have sent OobRequest
1286        assert!(peripheral_ops
1287            .connection_events
1288            .iter()
1289            .any(|e| matches!(e, ConnectionEvent::OobRequest)));
1290        assert!(central_ops
1291            .connection_events
1292            .iter()
1293            .any(|e| matches!(e, ConnectionEvent::OobRequest)));
1294
1295        // Provide OOB data to both sides
1296        // Central gets: local=central_oob, peer=peripheral_oob
1297        central_pairing
1298            .handle_event(
1299                Event::OobDataReceived {
1300                    local: central_oob.clone(),
1301                    peer: peripheral_oob.clone(),
1302                },
1303                &mut central_ops,
1304                &mut rng,
1305            )
1306            .unwrap();
1307        // Peripheral gets: local=peripheral_oob, peer=central_oob
1308        peripheral_pairing
1309            .handle_event(
1310                Event::OobDataReceived {
1311                    local: peripheral_oob,
1312                    peer: central_oob,
1313                },
1314                &mut peripheral_ops,
1315                &mut rng,
1316            )
1317            .unwrap();
1318
1319        // Continue packet exchange
1320        transmit_packets(
1321            &mut peripheral_ops,
1322            &mut central_ops,
1323            &mut rng,
1324            &mut peripheral_pairing,
1325            &mut central_pairing,
1326            &mut num_central_data_sent,
1327            &mut num_peripheral_data_sent,
1328        );
1329
1330        // Both sides should agree on the LTK
1331        assert_eq!(central_ops.encryptions[0], peripheral_ops.encryptions[0]);
1332
1333        // Simulate encryption success
1334        central_pairing
1335            .handle_event(Event::LinkEncryptedResult(true), &mut central_ops, &mut rng)
1336            .unwrap();
1337        peripheral_pairing
1338            .handle_event(Event::LinkEncryptedResult(true), &mut peripheral_ops, &mut rng)
1339            .unwrap();
1340
1341        // OOB is authenticated
1342        assert!(central_ops.connection_events.iter().any(|e| matches!(
1343            e,
1344            ConnectionEvent::PairingComplete {
1345                security_level: SecurityLevel::EncryptedAuthenticated,
1346                ..
1347            }
1348        )));
1349        assert!(peripheral_ops.connection_events.iter().any(|e| matches!(
1350            e,
1351            ConnectionEvent::PairingComplete {
1352                security_level: SecurityLevel::EncryptedAuthenticated,
1353                ..
1354            }
1355        )));
1356    }
1357
1358    /// OOB pairing where only the central has OOB data (central OOB=1, peripheral OOB=0).
1359    /// Per spec 2.3.5.6.3, the peripheral skips the confirm check for the central's OOB
1360    /// and sets ra=0 since it didn't receive the central's OOB data.
1361    #[test]
1362    fn oob_lesc_central_only() {
1363        use crate::security_manager::crypto::Nonce;
1364
1365        let peripheral_addr = Address::random([0xff, 1, 2, 3, 4, 5]);
1366        let central_addr = Address::random([0xff, 2, 2, 3, 4, 5]);
1367
1368        let mut peripheral_ops = TestOps::<10>::new(0xDEAD);
1369        let mut central_ops = TestOps::<10>::new(0xBEEF);
1370        // Only central has OOB
1371        peripheral_ops.oob_available = false;
1372        central_ops.oob_available = true;
1373
1374        // Generate OOB data for central (the side that has it)
1375        let central_oob = {
1376            let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(100).into();
1377            let r = Nonce::new(&mut rng);
1378            let c = r.f4(central_ops.public_key.x(), central_ops.public_key.x(), 0);
1379            OobData {
1380                random: r.0.to_le_bytes(),
1381                confirm: c.0.to_le_bytes(),
1382            }
1383        };
1384        // Peripheral generates local OOB but has no peer OOB data
1385        let peripheral_oob = {
1386            let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(200).into();
1387            let r = Nonce::new(&mut rng);
1388            let c = r.f4(peripheral_ops.public_key.x(), peripheral_ops.public_key.x(), 0);
1389            OobData {
1390                random: r.0.to_le_bytes(),
1391                confirm: c.0.to_le_bytes(),
1392            }
1393        };
1394        let no_oob = OobData {
1395            random: [0; 16],
1396            confirm: [0; 16],
1397        };
1398
1399        let mut peripheral_pairing =
1400            Pairing::new_peripheral(peripheral_addr, central_addr, IoCapabilities::NoInputNoOutput);
1401        let mut central_pairing = Pairing::initiate_central(
1402            central_addr,
1403            peripheral_addr,
1404            &mut central_ops,
1405            IoCapabilities::NoInputNoOutput,
1406            true,
1407        )
1408        .unwrap();
1409
1410        let mut num_central_data_sent = 0;
1411        let mut num_peripheral_data_sent = 0;
1412        let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(1).into();
1413
1414        transmit_packets(
1415            &mut peripheral_ops,
1416            &mut central_ops,
1417            &mut rng,
1418            &mut peripheral_pairing,
1419            &mut central_pairing,
1420            &mut num_central_data_sent,
1421            &mut num_peripheral_data_sent,
1422        );
1423
1424        // Central has peripheral's OOB data (received out of band)
1425        central_pairing
1426            .handle_event(
1427                Event::OobDataReceived {
1428                    local: central_oob,
1429                    peer: peripheral_oob,
1430                },
1431                &mut central_ops,
1432                &mut rng,
1433            )
1434            .unwrap();
1435        // Peripheral has no peer OOB data — zeros for peer
1436        peripheral_pairing
1437            .handle_event(
1438                Event::OobDataReceived {
1439                    local: peripheral_oob,
1440                    peer: no_oob,
1441                },
1442                &mut peripheral_ops,
1443                &mut rng,
1444            )
1445            .unwrap();
1446
1447        transmit_packets(
1448            &mut peripheral_ops,
1449            &mut central_ops,
1450            &mut rng,
1451            &mut peripheral_pairing,
1452            &mut central_pairing,
1453            &mut num_central_data_sent,
1454            &mut num_peripheral_data_sent,
1455        );
1456
1457        assert_eq!(central_ops.encryptions[0], peripheral_ops.encryptions[0]);
1458
1459        central_pairing
1460            .handle_event(Event::LinkEncryptedResult(true), &mut central_ops, &mut rng)
1461            .unwrap();
1462        peripheral_pairing
1463            .handle_event(Event::LinkEncryptedResult(true), &mut peripheral_ops, &mut rng)
1464            .unwrap();
1465
1466        assert!(central_ops.connection_events.iter().any(|e| matches!(
1467            e,
1468            ConnectionEvent::PairingComplete {
1469                security_level: SecurityLevel::EncryptedAuthenticated,
1470                ..
1471            }
1472        )));
1473        assert!(peripheral_ops.connection_events.iter().any(|e| matches!(
1474            e,
1475            ConnectionEvent::PairingComplete {
1476                security_level: SecurityLevel::EncryptedAuthenticated,
1477                ..
1478            }
1479        )));
1480    }
1481
1482    /// OOB pairing where only the peripheral has OOB data (central OOB=0, peripheral OOB=1).
1483    #[test]
1484    fn oob_lesc_peripheral_only() {
1485        use crate::security_manager::crypto::Nonce;
1486
1487        let peripheral_addr = Address::random([0xff, 1, 2, 3, 4, 5]);
1488        let central_addr = Address::random([0xff, 2, 2, 3, 4, 5]);
1489
1490        let mut peripheral_ops = TestOps::<10>::new(0xDEAD);
1491        let mut central_ops = TestOps::<10>::new(0xBEEF);
1492        // Only peripheral has OOB
1493        peripheral_ops.oob_available = true;
1494        central_ops.oob_available = false;
1495
1496        // Peripheral generates local OOB data
1497        let peripheral_oob = {
1498            let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(200).into();
1499            let r = Nonce::new(&mut rng);
1500            let c = r.f4(peripheral_ops.public_key.x(), peripheral_ops.public_key.x(), 0);
1501            OobData {
1502                random: r.0.to_le_bytes(),
1503                confirm: c.0.to_le_bytes(),
1504            }
1505        };
1506        // Central generates local OOB but has no peer OOB data
1507        let central_oob = {
1508            let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(100).into();
1509            let r = Nonce::new(&mut rng);
1510            let c = r.f4(central_ops.public_key.x(), central_ops.public_key.x(), 0);
1511            OobData {
1512                random: r.0.to_le_bytes(),
1513                confirm: c.0.to_le_bytes(),
1514            }
1515        };
1516        let no_oob = OobData {
1517            random: [0; 16],
1518            confirm: [0; 16],
1519        };
1520
1521        let mut peripheral_pairing =
1522            Pairing::new_peripheral(peripheral_addr, central_addr, IoCapabilities::NoInputNoOutput);
1523        let mut central_pairing = Pairing::initiate_central(
1524            central_addr,
1525            peripheral_addr,
1526            &mut central_ops,
1527            IoCapabilities::NoInputNoOutput,
1528            true,
1529        )
1530        .unwrap();
1531
1532        let mut num_central_data_sent = 0;
1533        let mut num_peripheral_data_sent = 0;
1534        let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(1).into();
1535
1536        transmit_packets(
1537            &mut peripheral_ops,
1538            &mut central_ops,
1539            &mut rng,
1540            &mut peripheral_pairing,
1541            &mut central_pairing,
1542            &mut num_central_data_sent,
1543            &mut num_peripheral_data_sent,
1544        );
1545
1546        // Central has no peer OOB data — zeros for peer
1547        central_pairing
1548            .handle_event(
1549                Event::OobDataReceived {
1550                    local: central_oob,
1551                    peer: no_oob,
1552                },
1553                &mut central_ops,
1554                &mut rng,
1555            )
1556            .unwrap();
1557        // Peripheral has central's OOB data (received out of band)
1558        peripheral_pairing
1559            .handle_event(
1560                Event::OobDataReceived {
1561                    local: peripheral_oob,
1562                    peer: central_oob,
1563                },
1564                &mut peripheral_ops,
1565                &mut rng,
1566            )
1567            .unwrap();
1568
1569        transmit_packets(
1570            &mut peripheral_ops,
1571            &mut central_ops,
1572            &mut rng,
1573            &mut peripheral_pairing,
1574            &mut central_pairing,
1575            &mut num_central_data_sent,
1576            &mut num_peripheral_data_sent,
1577        );
1578
1579        assert_eq!(central_ops.encryptions[0], peripheral_ops.encryptions[0]);
1580
1581        central_pairing
1582            .handle_event(Event::LinkEncryptedResult(true), &mut central_ops, &mut rng)
1583            .unwrap();
1584        peripheral_pairing
1585            .handle_event(Event::LinkEncryptedResult(true), &mut peripheral_ops, &mut rng)
1586            .unwrap();
1587
1588        assert!(central_ops.connection_events.iter().any(|e| matches!(
1589            e,
1590            ConnectionEvent::PairingComplete {
1591                security_level: SecurityLevel::EncryptedAuthenticated,
1592                ..
1593            }
1594        )));
1595        assert!(peripheral_ops.connection_events.iter().any(|e| matches!(
1596            e,
1597            ConnectionEvent::PairingComplete {
1598                security_level: SecurityLevel::EncryptedAuthenticated,
1599                ..
1600            }
1601        )));
1602    }
1603
1604    #[test]
1605    fn oob_lesc_confirm_mismatch() {
1606        use crate::security_manager::crypto::Nonce;
1607
1608        let peripheral_addr = Address::random([0xff, 1, 2, 3, 4, 5]);
1609        let central_addr = Address::random([0xff, 2, 2, 3, 4, 5]);
1610
1611        let mut peripheral_ops = TestOps::<10>::new(0xDEAD);
1612        let mut central_ops = TestOps::<10>::new(0xBEEF);
1613        peripheral_ops.oob_available = true;
1614        central_ops.oob_available = true;
1615
1616        // Generate valid central OOB
1617        let central_oob = {
1618            let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(100).into();
1619            let r = Nonce::new(&mut rng);
1620            let c = r.f4(central_ops.public_key.x(), central_ops.public_key.x(), 0);
1621            OobData {
1622                random: r.0.to_le_bytes(),
1623                confirm: c.0.to_le_bytes(),
1624            }
1625        };
1626
1627        // Generate peripheral OOB with WRONG confirm
1628        let peripheral_oob = OobData {
1629            random: [1; 16],
1630            confirm: [2; 16], // Wrong confirm
1631        };
1632
1633        let mut peripheral_pairing =
1634            Pairing::new_peripheral(peripheral_addr, central_addr, IoCapabilities::NoInputNoOutput);
1635        let mut central_pairing = Pairing::initiate_central(
1636            central_addr,
1637            peripheral_addr,
1638            &mut central_ops,
1639            IoCapabilities::NoInputNoOutput,
1640            true,
1641        )
1642        .unwrap();
1643
1644        let mut num_central_data_sent = 0;
1645        let mut num_peripheral_data_sent = 0;
1646        let mut rng: ChaCha12Rng = ChaCha12Core::seed_from_u64(1).into();
1647
1648        transmit_packets(
1649            &mut peripheral_ops,
1650            &mut central_ops,
1651            &mut rng,
1652            &mut peripheral_pairing,
1653            &mut central_pairing,
1654            &mut num_central_data_sent,
1655            &mut num_peripheral_data_sent,
1656        );
1657
1658        // Central tries to verify the bad peripheral OOB — should fail
1659        let result = central_pairing.handle_event(
1660            Event::OobDataReceived {
1661                local: central_oob,
1662                peer: peripheral_oob,
1663            },
1664            &mut central_ops,
1665            &mut rng,
1666        );
1667
1668        assert!(matches!(
1669            result,
1670            Err(Error::Security(crate::security_manager::Reason::ConfirmValueFailed))
1671        ));
1672    }
1673}