Skip to main content

pamoja_lorawan/device/
relayed.rs

1//! Sending through a relay: when the wake-on-radio frame goes out, on which channel, with
2//! how long a preamble, and what the relay's answer changes, TS011-1.0.1 chapters 3 and 5.
3
4use pamoja_lora::region::{Modulation, RelayChannel};
5use pamoja_lora::LinkSettings;
6
7use super::{DeviceError, EndDevice, Window};
8use crate::relay::{
9    open_wor_ack, wor_join_request, wor_uplink, CadPeriodicity, CadToRx, Carrier, Forward,
10    RelayActivation, RelaySync, Synchronization, XtalAccuracy, FORWARD_OVERHEAD, RXR_DELAY_US,
11    WOR_ACK_DELAY_US, WOR_ACK_LEN, WOR_ATTEMPTS_WO_ACK, WOR_DATA_DELAY_US, WOR_JOIN_REQUEST_LEN,
12    WOR_UPLINK_LEN,
13};
14
15/// How many join attempts a device left to itself makes before one goes through a relay,
16/// TS011-1.0.1 appendix 5.
17const JOINS_PER_RELAYED: u32 = 4;
18
19/// How many uplinks may go unanswered before a device left to itself starts using a relay,
20/// TS011-1.0.1 appendix 5.
21const QUIET_UPLINKS_TO_RELAY: u16 = 16;
22
23/// The wake-on-radio frame an uplink goes out behind, and where it is heard, TS011-1.0.1
24/// section 5.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub struct WakeUp {
27    frame: [u8; WOR_UPLINK_LEN],
28    len: usize,
29    /// When to start sending it, in the caller's microseconds.
30    pub start_us: u64,
31    /// Where and how fast.
32    pub carrier: Carrier,
33    /// The LoRa settings, with the preamble this frame needs: an explicit header and a
34    /// payload CRC, sent with inverted IQ, as RP002-1.0.5 table 124 has.
35    pub link: LinkSettings,
36    /// The power to ask of the radio, the region's default.
37    pub output_dbm: i8,
38    /// How long it holds the air, in microseconds.
39    pub airtime_us: u64,
40}
41
42impl WakeUp {
43    /// Returns the frame to send.
44    ///
45    /// # Returns
46    ///
47    /// Five bytes ahead of a join request, fifteen ahead of an uplink.
48    #[must_use]
49    pub fn frame(&self) -> &[u8] {
50        &self.frame[..self.len]
51    }
52}
53
54/// When and where a relay's acknowledgment would arrive.
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub struct AckWindow {
57    /// When it starts, in the caller's microseconds.
58    pub start_us: u64,
59    /// Where and how fast: the channel's acknowledgment frequency, at the WOR frame's data
60    /// rate.
61    pub carrier: Carrier,
62    /// The LoRa settings to listen with, RP002-1.0.5 table 125.
63    pub link: LinkSettings,
64    /// How long the acknowledgment lasts, in microseconds.
65    pub airtime_us: u64,
66}
67
68/// The wake-on-radio exchange an uplink under a relay goes out behind.
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub struct RelayExchange {
71    /// The frame that wakes the relay.
72    pub wake_up: WakeUp,
73    /// Where the relay's acknowledgment would arrive, or `None` ahead of a join request,
74    /// which TS011-1.0.1 section 3.3 has no relay acknowledge.
75    pub ack: Option<AckWindow>,
76    /// When the uplink itself goes out, in the caller's microseconds. It is the same
77    /// whether or not the acknowledgment arrives, as section 3.7.2 asks.
78    pub uplink_start_us: u64,
79    /// The window a forwarded downlink arrives in, timed from the end of the uplink like
80    /// the other two, RP002-1.0.5 table 126.
81    pub rxr: Window,
82}
83
84/// What an end device does once its wake-on-radio frame went unanswered.
85#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub enum WorNext {
87    /// Send the uplink at the time the exchange named anyway, TS011-1.0.1 section 3.4.
88    Uplink,
89    /// Wake the relay again first, as the network's `BackOff` asks.
90    WakeUp(RelayExchange),
91}
92
93/// What a relay's last acknowledgment said about itself, TS011-1.0.1 table 14.
94#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
95pub struct RelayStatus {
96    /// How often it scans.
97    pub cad_periodicity: CadPeriodicity,
98    /// How accurate its crystal is.
99    pub xtal_accuracy: XtalAccuracy,
100    /// How long it takes to start receiving.
101    pub cad_to_rx: CadToRx,
102    /// The data rate it forwards at, which bounds what the device may send.
103    pub relay_data_rate: u8,
104    /// Whether it will forward.
105    pub forward: Forward,
106}
107
108/// The wake-on-radio frame a device sent and is waiting on.
109#[derive(Clone, Copy, Debug, PartialEq, Eq)]
110struct Sent {
111    join: bool,
112    started_us: u64,
113    preamble_symbols: u16,
114    wor: Carrier,
115    ack: Carrier,
116    uplink: Carrier,
117    wfcnt: u32,
118    second_channel: bool,
119    symbol_us: u64,
120}
121
122/// What an end device keeps to send through a relay.
123#[derive(Clone, Copy, Debug, PartialEq, Eq)]
124pub(crate) struct Relayed {
125    pub(crate) activation: RelayActivation,
126    pub(crate) smart_level: u8,
127    pub(crate) back_off: u8,
128    pub(crate) second_channel: Option<RelayChannel>,
129    pub(crate) enabled: bool,
130    pub(crate) automatic: bool,
131    pub(crate) wfcnt: u32,
132    pub(crate) attempts: u8,
133    pub(crate) quiet_uplinks: u16,
134    pub(crate) channel: u8,
135    pub(crate) fixed_channel: bool,
136    pub(crate) heard: Option<RelayStatus>,
137    sync: Option<Synchronization>,
138    reference_second: bool,
139    sent: Option<Sent>,
140}
141
142impl Relayed {
143    pub(crate) const fn new() -> Relayed {
144        Relayed {
145            activation: RelayActivation::DeviceControlled,
146            smart_level: 0,
147            back_off: 0,
148            second_channel: None,
149            enabled: false,
150            automatic: true,
151            wfcnt: 0,
152            attempts: 0,
153            quiet_uplinks: 0,
154            channel: 0,
155            fixed_channel: false,
156            heard: None,
157            sync: None,
158            reference_second: false,
159            sent: None,
160        }
161    }
162
163    /// What the device knows about when its relay listens.
164    ///
165    /// A device is out of the initialized state once a relay has answered it at all, on a
166    /// wake-on-radio acknowledgment or a downlink in the relay window, which is also when
167    /// it stops alternating the region's default channels, TS011-1.0.1 section 3.9.
168    pub(crate) const fn sync(&self) -> RelaySync {
169        match (
170            self.sync.is_some(),
171            self.heard.is_some() || self.fixed_channel,
172        ) {
173            (true, _) => RelaySync::Synchronized,
174            (false, true) => RelaySync::Unsynchronized,
175            (false, false) => RelaySync::Initialized,
176        }
177    }
178
179    /// A new join request: the mode a network set lapses with the session, and a device
180    /// left to itself tries a relay on one join in four, TS011-1.0.1 section 10.2 and
181    /// appendix 5.
182    pub(crate) fn joining(&mut self, joins: u32) {
183        self.activation = RelayActivation::DeviceControlled;
184        if self.automatic {
185            self.enabled = joins % JOINS_PER_RELAYED == JOINS_PER_RELAYED - 1;
186        }
187        self.attempts = 0;
188        self.sent = None;
189    }
190
191    /// A join accept arrived: the wake-on-radio counter restarts, and a device left to
192    /// itself keeps the relay only if the accept came back through one.
193    pub(crate) fn joined(&mut self, through_relay: bool) {
194        self.wfcnt = 0;
195        self.attempts = 0;
196        self.quiet_uplinks = 0;
197        self.sent = None;
198        self.sync = None;
199        self.fixed_channel = through_relay;
200        if self.automatic {
201            self.enabled = through_relay;
202        }
203    }
204
205    /// A downlink arrived in the relay window, so the relay heard the device and it need no
206    /// longer alternate channels.
207    pub(crate) fn heard_on_rxr(&mut self) {
208        self.fixed_channel = true;
209    }
210
211    /// Takes what an `EndDeviceConfReq` sets, TS011-1.0.1 section 10.2.
212    pub(crate) fn configure(
213        &mut self,
214        activation: RelayActivation,
215        smart_level: u8,
216        back_off: u8,
217        second_channel: Option<RelayChannel>,
218    ) {
219        self.activation = activation;
220        self.automatic = activation == RelayActivation::DeviceControlled;
221        self.smart_level = smart_level;
222        self.back_off = back_off;
223        self.second_channel = second_channel;
224        match activation {
225            RelayActivation::Disabled => {
226                self.enabled = false;
227                self.sent = None;
228            }
229            RelayActivation::Enabled => self.enabled = true,
230            RelayActivation::Dynamic => self.quiet_uplinks = 0,
231            RelayActivation::DeviceControlled => {}
232        }
233    }
234
235    /// Forgets the timing, and with it the relay's settings once the frames keep going
236    /// unanswered, TS011-1.0.1 section 3.9.
237    fn lose_synchronization(&mut self) {
238        if self.sync.take().is_none() {
239            self.heard = None;
240        }
241    }
242
243    /// Counts an uplink that went unanswered, and says whether a device left to itself
244    /// should start using a relay, TS011-1.0.1 appendix 5 and table 41.
245    pub(crate) fn uplink_unanswered(&mut self, smart_uplinks: u16) {
246        self.quiet_uplinks = self.quiet_uplinks.saturating_add(1);
247        let enough = match self.activation {
248            RelayActivation::Dynamic => smart_uplinks,
249            RelayActivation::DeviceControlled if self.automatic => QUIET_UPLINKS_TO_RELAY,
250            _ => return,
251        };
252        if self.quiet_uplinks >= enough {
253            self.enabled = true;
254        }
255    }
256
257    /// Counts a downlink, which is what a device in dynamic mode is waiting for.
258    pub(crate) fn heard_downlink(&mut self) {
259        self.quiet_uplinks = 0;
260    }
261}
262
263impl EndDevice<'_> {
264    /// Returns how the device decides to use a relay, TS011-1.0.1 table 40.
265    ///
266    /// # Returns
267    ///
268    /// The mode, which the network sets with `EndDeviceConfReq` and which every join puts
269    /// back to [`RelayActivation::DeviceControlled`].
270    #[must_use]
271    pub const fn relay_activation(&self) -> RelayActivation {
272        self.relayed.activation
273    }
274
275    /// Reports whether the next uplink goes through a relay.
276    ///
277    /// # Returns
278    ///
279    /// `true` while relay mode is on.
280    #[must_use]
281    pub const fn relaying(&self) -> bool {
282        self.relayed.enabled
283    }
284
285    /// Turns relay mode on or off, and takes the decision from the device itself.
286    ///
287    /// A device left to itself, which is where every device starts, decides this as
288    /// TS011-1.0.1 appendix 5 recommends: it tries a relay on one join in four, keeps it
289    /// only if the join accept comes back through one, turns it on again after sixteen
290    /// unanswered uplinks, and gives it up after eight frames a relay never answered. From
291    /// this call on, that is the caller's to decide, until the network takes it over with
292    /// `EndDeviceConfReq` or hands it back with its end-device controlled mode. What a
293    /// device knows about a relay it stops hearing is still given up either way.
294    ///
295    /// # Arguments
296    ///
297    /// * `on` - whether to send through a relay.
298    ///
299    /// # Returns
300    ///
301    /// `false` when the network has taken the decision with `EndDeviceConfReq`, leaving the
302    /// mode as it was.
303    ///
304    /// # Examples
305    ///
306    /// A reading goes out behind a frame that wakes the relay:
307    ///
308    /// ```
309    /// use pamoja_lora::region::Region;
310    /// use pamoja_lorawan::device::{EndDevice, Settings, WorNext};
311    /// use pamoja_lorawan::relay::Carrier;
312    /// use pamoja_lorawan::Session;
313    ///
314    /// let session = Session::new(0x2601_1BDA, [0x2B; 16], [0x99; 16]);
315    /// let mut device = EndDevice::personalized(Region::Eu868.plan(), session, Settings::new(2, 14))?;
316    /// assert!(device.use_relay(true));
317    ///
318    /// // The wake-on-radio frame goes out on the region's first relay channel, ahead of
319    /// // the uplink it names.
320    /// let reading = device.send(2, b"21.5", false, 0)?;
321    /// let exchange = reading.relay.expect("relay mode is on");
322    /// assert_eq!(exchange.wake_up.carrier, Carrier::new(865_100_000, 3));
323    /// assert_eq!(exchange.wake_up.start_us, 0);
324    ///
325    /// // A device that has heard from no relay covers a whole second of scanning, and the
326    /// // uplink follows once the acknowledgment would have come and gone.
327    /// assert_eq!(exchange.wake_up.link.preamble_symbols(), 259);
328    /// let ack = exchange.ack.expect("an uplink is acknowledged");
329    /// assert_eq!(exchange.uplink_start_us, ack.start_us + ack.airtime_us + 50_000);
330    ///
331    /// // With nothing in the acknowledgment window, the uplink goes out anyway.
332    /// assert_eq!(device.no_wor_ack(ack.start_us + ack.airtime_us)?, WorNext::Uplink);
333    /// # Ok::<(), pamoja_lorawan::device::DeviceError>(())
334    /// ```
335    pub fn use_relay(&mut self, on: bool) -> bool {
336        match self.relayed.activation {
337            RelayActivation::Dynamic | RelayActivation::DeviceControlled => {
338                self.relayed.enabled = on;
339                self.relayed.automatic = false;
340                if !on {
341                    self.relayed.sent = None;
342                }
343                true
344            }
345            _ => false,
346        }
347    }
348
349    /// Returns what the device knows about when its relay listens, TS011-1.0.1 section 3.9.
350    ///
351    /// # Returns
352    ///
353    /// The synchronization state.
354    #[must_use]
355    pub const fn relay_sync(&self) -> RelaySync {
356        self.relayed.sync()
357    }
358
359    /// Returns what the relay's last acknowledgment said about itself.
360    ///
361    /// # Returns
362    ///
363    /// Its scan period, crystal, switching time, forwarding data rate and whether it
364    /// forwards, or `None` before one has arrived.
365    #[must_use]
366    pub const fn relay_status(&self) -> Option<RelayStatus> {
367        self.relayed.heard
368    }
369
370    /// Returns the wake-on-radio frame counter, which every frame raises and a join accept
371    /// resets, TS011-1.0.1 section 5.3.2.
372    ///
373    /// # Returns
374    ///
375    /// The counter the next frame will use.
376    #[must_use]
377    pub const fn wor_counter(&self) -> u32 {
378        self.relayed.wfcnt
379    }
380
381    /// Reads the acknowledgment a relay answered the last wake-on-radio frame with.
382    ///
383    /// The device is now synchronized: it knows when the relay scans, so its next frames
384    /// carry only as much preamble as the two crystals could drift, and it holds its
385    /// payloads to what the relay forwards.
386    ///
387    /// # Arguments
388    ///
389    /// * `frame` - the bytes the radio received in the acknowledgment window.
390    ///
391    /// # Returns
392    ///
393    /// What the relay said about itself.
394    ///
395    /// # Errors
396    ///
397    /// Returns [`DeviceError::NothingPending`] with no wake-on-radio frame waiting on an
398    /// answer, [`DeviceError::NotJoined`] before the device has a session, and
399    /// [`DeviceError::Frame`] for an acknowledgment that does not decode or verify.
400    pub fn heard_wor_ack(&mut self, frame: &[u8]) -> Result<RelayStatus, DeviceError> {
401        let session = self.session.ok_or(DeviceError::NotJoined)?;
402        let sent = self.relayed.sent.ok_or(DeviceError::NothingPending)?;
403        let state = open_wor_ack(
404            frame,
405            &session.wor_keys(),
406            session.dev_addr(),
407            sent.wfcnt,
408            sent.ack,
409            sent.uplink,
410        )?;
411        self.relayed.sent = None;
412        self.relayed.attempts = 0;
413        self.relayed.sync = Some(Synchronization::from_ack(
414            sent.started_us,
415            sent.preamble_symbols,
416            sent.symbol_us,
417            &state,
418        ));
419        self.relayed.reference_second = sent.second_channel;
420        self.relayed.fixed_channel = true;
421        let heard = RelayStatus {
422            cad_periodicity: state.cad_periodicity,
423            xtal_accuracy: state.xtal_accuracy,
424            cad_to_rx: state.cad_to_rx,
425            relay_data_rate: state.relay_data_rate,
426            forward: state.forward,
427        };
428        self.relayed.heard = Some(heard);
429        Ok(heard)
430    }
431
432    /// Says what to do once the acknowledgment window closed with nothing in it.
433    ///
434    /// The uplink goes out anyway unless the network's `BackOff` asks for more attempts
435    /// first. After [`WOR_ATTEMPTS_WO_ACK`] frames without an answer the device gives up
436    /// what it knows: first when the relay scans, then the relay altogether.
437    ///
438    /// # Arguments
439    ///
440    /// * `now_us` - the time, in microseconds.
441    ///
442    /// # Returns
443    ///
444    /// Whether to send the uplink or wake the relay again.
445    ///
446    /// # Errors
447    ///
448    /// Returns [`DeviceError::NothingPending`] with no wake-on-radio frame waiting on an
449    /// answer, and whatever building another frame returns.
450    pub fn no_wor_ack(&mut self, now_us: u64) -> Result<WorNext, DeviceError> {
451        let sent = self.relayed.sent.ok_or(DeviceError::NothingPending)?;
452        self.relayed.sent = None;
453        self.relayed.attempts = self.relayed.attempts.saturating_add(1);
454        if self.relayed.attempts >= WOR_ATTEMPTS_WO_ACK {
455            self.relayed.attempts = 0;
456            self.relayed.lose_synchronization();
457            if self.relayed.automatic
458                && self.relayed.activation == RelayActivation::DeviceControlled
459            {
460                // TS011-1.0.1 appendix 5: a device left to itself stops using a relay that
461                // never answers, and counts its unanswered uplinks afresh before trying
462                // another one.
463                self.relayed.enabled = false;
464                self.relayed.quiet_uplinks = 0;
465                return Ok(WorNext::Uplink);
466            }
467        }
468        // The next frame alternates the region's default channels while the device has no
469        // relay of its own to aim at, TS011-1.0.1 section 3.2.2.
470        if !self.relayed.fixed_channel {
471            self.relayed.channel = self.relayed.channel.wrapping_add(1);
472        }
473        if self.relayed.back_off != 0
474            && !self.relayed.attempts.is_multiple_of(self.relayed.back_off)
475        {
476            match self.wor_exchange(sent.uplink, sent.join, now_us)? {
477                Some(exchange) => return Ok(WorNext::WakeUp(exchange)),
478                None => return Ok(WorNext::Uplink),
479            }
480        }
481        Ok(WorNext::Uplink)
482    }
483
484    /// Builds the wake-on-radio exchange an uplink goes out behind, and records what it
485    /// costs the device's duty cycle.
486    pub(super) fn wor_exchange(
487        &mut self,
488        uplink: Carrier,
489        join: bool,
490        now_us: u64,
491    ) -> Result<Option<RelayExchange>, DeviceError> {
492        if !self.relayed.enabled {
493            return Ok(None);
494        }
495        let Some(channel) = self.wor_channel() else {
496            return Ok(None);
497        };
498        let link = self.wor_link(channel.data_rate)?;
499        let symbol_us = link.symbol_time_us();
500        let earliest = now_us.max(self.free_at(channel.wor_frequency_hz));
501        let second_channel = self.relayed.second_channel.is_some();
502
503        let (start_us, preamble_symbols) = match self.relayed.sync {
504            Some(sync) => {
505                let other = second_channel != self.relayed.reference_second;
506                match sync.next_wor(earliest, self.settings.crystal_ppm, symbol_us, other) {
507                    Some(slot) => (slot.start_us, slot.preamble_symbols),
508                    None => {
509                        self.relayed.sync = None;
510                        (earliest, self.unsynchronized_preamble(symbol_us))
511                    }
512                }
513            }
514            None => (earliest, self.unsynchronized_preamble(symbol_us)),
515        };
516
517        let wor = Carrier::new(channel.wor_frequency_hz, channel.data_rate);
518        let ack_carrier = Carrier::new(channel.ack_frequency_hz, channel.data_rate);
519        let mut frame = [0u8; WOR_UPLINK_LEN];
520        let (len, wfcnt) = if join {
521            frame[..WOR_JOIN_REQUEST_LEN].copy_from_slice(&wor_join_request(uplink)?);
522            (WOR_JOIN_REQUEST_LEN, 0)
523        } else {
524            let session = self.session.ok_or(DeviceError::NotJoined)?;
525            let wfcnt = self.relayed.wfcnt;
526            frame = wor_uplink(&session.wor_keys(), session.dev_addr(), wfcnt, uplink, wor)?;
527            self.relayed.wfcnt = wfcnt.saturating_add(1);
528            (WOR_UPLINK_LEN, wfcnt)
529        };
530
531        let link = link.with_preamble(preamble_symbols);
532        let airtime_us = link.airtime_us(len);
533        let ended_us = start_us + airtime_us;
534        let ack_link = self.wor_link(channel.data_rate)?;
535        let ack_airtime_us = ack_link.airtime_us(WOR_ACK_LEN);
536        let (ack, uplink_start_us) = if join {
537            (None, ended_us + u64::from(WOR_DATA_DELAY_US))
538        } else {
539            let start_us = ended_us + u64::from(WOR_ACK_DELAY_US);
540            (
541                Some(AckWindow {
542                    start_us,
543                    carrier: ack_carrier,
544                    link: ack_link,
545                    airtime_us: ack_airtime_us,
546                }),
547                start_us + ack_airtime_us + u64::from(WOR_DATA_DELAY_US),
548            )
549        };
550
551        let rxr_data_rate = self
552            .plan
553            .rx1_data_rate(uplink.data_rate, 0)
554            .ok_or(DeviceError::DataRate(uplink.data_rate))?;
555        let rxr = Window {
556            delay_us: RXR_DELAY_US,
557            frequency_hz: channel.wor_frequency_hz,
558            data_rate: rxr_data_rate,
559            link: self.wor_link(rxr_data_rate)?,
560        };
561
562        self.record_air(start_us, airtime_us, channel.wor_frequency_hz);
563        self.relayed.sent = Some(Sent {
564            join,
565            started_us: start_us,
566            preamble_symbols,
567            wor,
568            ack: ack_carrier,
569            uplink,
570            wfcnt,
571            second_channel,
572            symbol_us,
573        });
574        Ok(Some(RelayExchange {
575            wake_up: WakeUp {
576                frame,
577                len,
578                start_us,
579                carrier: wor,
580                link,
581                output_dbm: self.output_dbm(channel.wor_frequency_hz, 0),
582                airtime_us,
583            },
584            ack,
585            uplink_start_us,
586            rxr,
587        }))
588    }
589
590    /// The channel the next wake-on-radio frame goes out on: the second channel a network
591    /// set, or the region's default channels in turn, TS011-1.0.1 sections 3.2.2 and 3.2.3.
592    fn wor_channel(&self) -> Option<RelayChannel> {
593        if let Some(second) = self.relayed.second_channel {
594            return Some(second);
595        }
596        let count = self.plan.relay_channels.len();
597        if count == 0 {
598            return None;
599        }
600        self.plan.relay_channel(self.relayed.channel % count as u8)
601    }
602
603    /// The preamble of a device that cannot predict the relay's next scan: one scan period
604    /// of it, or a whole second before any acknowledgment has arrived, TS011-1.0.1
605    /// section 5.2.
606    fn unsynchronized_preamble(&self, symbol_us: u64) -> u16 {
607        let (period, cad_to_rx) = match self.relayed.heard {
608            Some(heard) => (heard.cad_periodicity, heard.cad_to_rx),
609            None => (CadPeriodicity::Ms1000, CadToRx::Symbols8),
610        };
611        crate::relay::unsynchronized_preamble_symbols(period, symbol_us, cad_to_rx)
612    }
613
614    /// The LoRa settings of a data rate the downlink table numbers, with the payload CRC
615    /// every relay frame carries and a receive window does not, RP002-1.0.5 tables 124, 125
616    /// and 127.
617    fn wor_link(&self, data_rate: u8) -> Result<LinkSettings, DeviceError> {
618        match self
619            .plan
620            .downlink_data_rate(data_rate)
621            .map(|rate| rate.modulation)
622        {
623            Some(Modulation::LoRa {
624                spreading_factor,
625                bandwidth_hz,
626            }) => Ok(LinkSettings::new(spreading_factor, bandwidth_hz)),
627            _ => Err(DeviceError::DataRate(data_rate)),
628        }
629    }
630
631    /// What a relay's forwarding leaves for the application payload: its own frame at the
632    /// data rate the relay forwards at, less the forwarded metadata and the end device's
633    /// own header, TS011-1.0.1 section 8.3.
634    pub(super) fn relayed_room(&self) -> Option<usize> {
635        let heard = self.relayed.heard?;
636        let carried = self
637            .plan
638            .max_payload(heard.relay_data_rate, false)
639            .map(|limit| usize::from(limit.application))?;
640        Some(carried.saturating_sub(FORWARD_OVERHEAD + 13))
641    }
642}