1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! BLE beacon support, without dealing with Link-Layer stuff.

use crate::link::advertising::{Header, Pdu, PduBuf};
use crate::link::filter::{self, AddressFilter, ScanFilter};
use crate::link::{
    ad_structure::AdStructure, Cmd, DeviceAddress, NextUpdate, RadioCmd, Transmitter,
};
use crate::phy::AdvertisingChannel;
use crate::time::{Duration, Instant};
use crate::{bytes::*, Error};

/// A BLE beacon.
///
/// FIXME: This has to randomly offset the broadcast interval
pub struct Beacon {
    pdu: PduBuf,
}

impl Beacon {
    /// Creates a new beacon that will broadcast a packet on all advertisement
    /// channels.
    ///
    /// # Parameters
    ///
    /// * **`addr`**: Address of the beacon device.
    /// * **`data`**: Data to broadcast. This must fit within a single PDU.
    ///
    /// # Errors
    ///
    /// If `data` doesn't fit in a single PDU, an error will be returned.
    pub fn new(addr: DeviceAddress, data: &[AdStructure<'_>]) -> Result<Self, Error> {
        let pdu = PduBuf::beacon(addr, data)?;
        Ok(Self { pdu })
    }

    /// Broadcasts the beacon data using `tx`.
    ///
    /// This will broadcast once on every advertising channel.
    pub fn broadcast<T: Transmitter>(&self, tx: &mut T) {
        // The spec says that we have to broadcast on all 3 channels in sequence, so that the total
        // time of this broadcast ("advertising event") is <10ms.

        // The transmitter retains the old transmit buffer, but it might be used for other purposes
        // (like sending multiple beacon payloads), so we always copy our payload into the tx
        // buffer here.
        let payload = self.pdu.payload();
        let buf = tx.tx_payload_buf();
        buf[..payload.len()].copy_from_slice(payload);

        for channel in AdvertisingChannel::iter_all() {
            tx.transmit_advertising(self.pdu.header(), channel);
        }
    }
}

/// Callback for the [`BeaconScanner`].
pub trait ScanCallback {
    /// Called when a beacon is received and has passed the configured device address filter.
    ///
    /// # Parameters
    ///
    /// * **`adv_addr`**: Address of the device sending the beacon.
    /// * **`adv_data`**: Advertising data structures attached to the beacon.
    fn beacon<'a, I>(&mut self, adv_addr: DeviceAddress, adv_data: I)
    where
        I: Iterator<Item = AdStructure<'a>>;
}

/// A passive scanner for non-connectable beacon advertisements.
pub struct BeaconScanner<C: ScanCallback, F: AddressFilter> {
    cb: C,
    filter: ScanFilter<F>,
    interval: Duration,
    channel: AdvertisingChannel,
}

impl<C: ScanCallback> BeaconScanner<C, filter::AllowAll> {
    /// Creates a `BeaconScanner` that will report beacons from any device.
    pub fn new(callback: C) -> Self {
        Self::with_filter(callback, filter::AllowAll)
    }
}

impl<C: ScanCallback, F: AddressFilter> BeaconScanner<C, F> {
    /// Creates a `BeaconScanner` with a custom device filter.
    pub fn with_filter(callback: C, scan_filter: F) -> Self {
        Self {
            cb: callback,
            filter: ScanFilter::new(scan_filter),
            interval: Duration::from_micros(0),
            channel: AdvertisingChannel::first(),
        }
    }

    /// Configures the `BeaconScanner` and returns a `Cmd` to apply to the radio.
    ///
    /// The `next_update` field of the returned `Cmd` specifies when to call `timer_update` the next
    /// time. The timer used for this does not have to be very accurate, it is only used to switch
    /// to the next advertising channel after `interval` elapses.
    pub fn configure(&mut self, now: Instant, interval: Duration) -> Cmd {
        self.interval = interval;
        self.channel = AdvertisingChannel::first();

        Cmd {
            // Switch channels
            next_update: NextUpdate::At(now + self.interval),

            radio: RadioCmd::ListenAdvertising {
                channel: self.channel,
            },

            queued_work: false,
        }
    }

    /// Updates the `BeaconScanner` after the configured timer has fired.
    ///
    /// This switches to the next advertising channel and will listen there.
    pub fn timer_update(&mut self, now: Instant) -> Cmd {
        self.channel = self.channel.cycle();

        Cmd {
            // Switch channels
            next_update: NextUpdate::At(now + self.interval),

            radio: RadioCmd::ListenAdvertising {
                channel: self.channel,
            },

            queued_work: false,
        }
    }

    /// Processes a received advertising channel packet.
    ///
    /// This should be called whenever the radio receives a packet on the configured advertising
    /// channel.
    pub fn process_adv_packet(&mut self, header: Header, payload: &[u8], crc_ok: bool) -> Cmd {
        if crc_ok && header.type_().is_beacon() {
            // Partially decode to get the device ID and run it through the filter
            if let Ok(pdu) = Pdu::from_header_and_payload(header, &mut ByteReader::new(payload)) {
                if self.filter.should_scan(*pdu.sender()) {
                    let ad = pdu.advertising_data().unwrap();
                    self.cb.beacon(*pdu.sender(), ad);
                }
            }
        }

        Cmd {
            next_update: NextUpdate::Keep,
            radio: RadioCmd::ListenAdvertising {
                channel: self.channel,
            },
            queued_work: false,
        }
    }
}