pamoja_lorawan/relay/limits.rs
1//! The token buckets that bound what a relay forwards, TS011-1.0.1 section 8.8.
2
3use crate::mac::RELAY_BUCKET_MULTIPLIER;
4
5/// How often every bucket earns its reload rate, in microseconds: an hour.
6pub const RELOAD_PERIOD_US: u64 = 3_600_000_000;
7
8/// The reload rate a trusted end device's bucket is coded with to mean no limit,
9/// TS011-1.0.1 table 54.
10pub const UNLIMITED_DEVICE_RATE: u8 = 63;
11
12/// The reload rate a relay-wide bucket is coded with to mean no limit, TS011-1.0.1 table 64.
13pub const UNLIMITED_RELAY_RATE: u8 = 127;
14
15/// A token bucket: it earns a number of tokens an hour up to its size, and a relay spends
16/// one on each message it lets through.
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18pub struct TokenBucket {
19 reload_rate: Option<u8>,
20 size: u16,
21 tokens: u16,
22}
23
24impl TokenBucket {
25 /// A bucket that earns `reload_rate` tokens an hour and holds `size`, starting full.
26 ///
27 /// # Arguments
28 ///
29 /// * `reload_rate` - tokens earned an hour.
30 /// * `size` - the most tokens it holds.
31 ///
32 /// # Returns
33 ///
34 /// The bucket.
35 #[must_use]
36 pub const fn new(reload_rate: u8, size: u16) -> TokenBucket {
37 TokenBucket {
38 reload_rate: Some(reload_rate),
39 size,
40 tokens: size,
41 }
42 }
43
44 /// A bucket as a relay command codes it, starting full.
45 ///
46 /// # Arguments
47 ///
48 /// * `reload_rate` - tokens earned an hour, or `unlimited` for no limit.
49 /// * `bucket_size` - the coded size multiplier, TS011-1.0.1 table 55; only its low two
50 /// bits are read.
51 /// * `unlimited` - the rate that means no limit: [`UNLIMITED_DEVICE_RATE`] or
52 /// [`UNLIMITED_RELAY_RATE`].
53 ///
54 /// # Returns
55 ///
56 /// The bucket.
57 #[must_use]
58 pub const fn coded(reload_rate: u8, bucket_size: u8, unlimited: u8) -> TokenBucket {
59 if reload_rate >= unlimited {
60 return TokenBucket::unlimited();
61 }
62 let multiplier = RELAY_BUCKET_MULTIPLIER[(bucket_size & 0x03) as usize];
63 TokenBucket::new(reload_rate, reload_rate as u16 * multiplier)
64 }
65
66 /// A bucket that never runs out.
67 ///
68 /// # Returns
69 ///
70 /// The bucket.
71 #[must_use]
72 pub const fn unlimited() -> TokenBucket {
73 TokenBucket {
74 reload_rate: None,
75 size: 0,
76 tokens: 0,
77 }
78 }
79
80 /// Returns how many tokens the bucket earns an hour.
81 ///
82 /// # Returns
83 ///
84 /// The rate, or `None` for no limit.
85 #[must_use]
86 pub const fn reload_rate(&self) -> Option<u8> {
87 self.reload_rate
88 }
89
90 /// Returns the most tokens the bucket holds.
91 ///
92 /// # Returns
93 ///
94 /// The size, 0 for a bucket with no limit.
95 #[must_use]
96 pub const fn size(&self) -> u16 {
97 self.size
98 }
99
100 /// Returns the tokens the bucket holds now.
101 ///
102 /// # Returns
103 ///
104 /// The count, 0 for a bucket with no limit.
105 #[must_use]
106 pub const fn tokens(&self) -> u16 {
107 self.tokens
108 }
109
110 /// Reports whether a message may pass.
111 ///
112 /// # Returns
113 ///
114 /// `true` for a bucket with no limit or a token left.
115 #[must_use]
116 pub const fn has_token(&self) -> bool {
117 self.reload_rate.is_none() || self.tokens > 0
118 }
119
120 /// Reports whether the bucket is empty and never refills, which a relay reports as
121 /// forwarding disabled.
122 ///
123 /// # Returns
124 ///
125 /// `true` for an empty bucket that earns nothing.
126 #[must_use]
127 pub const fn disabled(&self) -> bool {
128 matches!(self.reload_rate, Some(0)) && self.tokens == 0
129 }
130
131 /// Spends a token.
132 ///
133 /// # Returns
134 ///
135 /// `false`, spending nothing, when the bucket is empty.
136 pub fn take(&mut self) -> bool {
137 match self.reload_rate {
138 None => true,
139 Some(_) if self.tokens > 0 => {
140 self.tokens -= 1;
141 true
142 }
143 Some(_) => false,
144 }
145 }
146
147 /// Adds what the bucket earns over some hours, keeping no more than its size.
148 ///
149 /// # Arguments
150 ///
151 /// * `hours` - the whole hours since the last reload.
152 pub fn reload(&mut self, hours: u64) {
153 if let Some(rate) = self.reload_rate {
154 let earned = u64::from(rate).saturating_mul(hours);
155 let tokens = u64::from(self.tokens).saturating_add(earned);
156 self.tokens = tokens.min(u64::from(self.size)) as u16;
157 }
158 }
159
160 /// Sets the tokens, keeping no more than the bucket's size.
161 ///
162 /// # Arguments
163 ///
164 /// * `tokens` - the count.
165 pub fn set_tokens(&mut self, tokens: u16) {
166 if self.reload_rate.is_some() {
167 self.tokens = tokens.min(self.size);
168 }
169 }
170
171 /// Sets the tokens to one hour's reload, as a relay does when it starts scanning.
172 pub fn refill_one_hour(&mut self) {
173 if let Some(rate) = self.reload_rate {
174 self.set_tokens(u16::from(rate));
175 }
176 }
177}
178
179/// What `ConfigureFwdLimitReq` does to the token counters, TS011-1.0.1 table 63.
180#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
181pub enum CounterReset {
182 /// Empty every bucket.
183 Empty,
184 /// Set each to its reload rate.
185 ReloadRate,
186 /// Fill each.
187 Full,
188 /// Leave the counters, trimmed to any smaller size. A bucket that had no limit starts
189 /// full.
190 Keep,
191}
192
193impl CounterReset {
194 /// Reads a coded value, 0 through 3.
195 ///
196 /// # Arguments
197 ///
198 /// * `code` - the value; only its low two bits are read.
199 ///
200 /// # Returns
201 ///
202 /// The reset.
203 #[must_use]
204 pub const fn from_code(code: u8) -> CounterReset {
205 match code & 0x03 {
206 0 => CounterReset::Empty,
207 1 => CounterReset::ReloadRate,
208 2 => CounterReset::Full,
209 _ => CounterReset::Keep,
210 }
211 }
212
213 /// Returns the value a `ConfigureFwdLimitReq` carries.
214 ///
215 /// # Returns
216 ///
217 /// The code, 0 through 3.
218 #[must_use]
219 pub const fn code(self) -> u8 {
220 self as u8
221 }
222
223 fn apply(self, bucket: &mut TokenBucket, previous: u16) {
224 match self {
225 CounterReset::Empty => bucket.set_tokens(0),
226 CounterReset::ReloadRate => bucket.refill_one_hour(),
227 CounterReset::Full => bucket.set_tokens(bucket.size()),
228 CounterReset::Keep => bucket.set_tokens(previous),
229 }
230 }
231}
232
233/// A relay's four relay-wide limits, TS011-1.0.1 tables 23 and 24.
234///
235/// A join request spends a token of `join_request` and `overall`, an uplink from a trusted
236/// end device one of its own bucket, `global_uplink` and `overall`, and a notification of an
237/// unknown end device one of `notify` and `overall`.
238///
239/// # Examples
240///
241/// ```
242/// use pamoja_lorawan::relay::ForwardLimits;
243///
244/// let mut limits = ForwardLimits::new();
245/// assert_eq!(limits.join_request.reload_rate(), Some(4));
246/// assert_eq!(limits.join_request.size(), 8);
247///
248/// // A relay starts with an hour's worth of tokens, so a fifth join request waits.
249/// for _ in 0..4 {
250/// assert!(limits.join_request.take());
251/// }
252/// assert!(!limits.join_request.has_token());
253///
254/// // An hour on, four more arrive.
255/// limits.reload(1);
256/// assert_eq!(limits.join_request.tokens(), 4);
257/// ```
258#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
259pub struct ForwardLimits {
260 /// Join requests forwarded.
261 pub join_request: TokenBucket,
262 /// Notifications of end devices the relay does not trust.
263 pub notify: TokenBucket,
264 /// Uplinks forwarded for every trusted end device together.
265 pub global_uplink: TokenBucket,
266 /// Everything the relay sends on behalf of others.
267 pub overall: TokenBucket,
268}
269
270impl ForwardLimits {
271 /// The limits of TS011-1.0.1 table 23, each holding one hour's reload.
272 ///
273 /// # Returns
274 ///
275 /// Four join requests and four notifications an hour, each bucket twice that; and eight
276 /// uplinks and eight messages overall an hour, each bucket sixteen.
277 #[must_use]
278 pub const fn new() -> ForwardLimits {
279 ForwardLimits {
280 join_request: starting(4, 8),
281 notify: starting(4, 8),
282 global_uplink: starting(8, 16),
283 overall: starting(8, 16),
284 }
285 }
286
287 /// Takes a `ConfigureFwdLimitReq`, TS011-1.0.1 section 10.6.
288 ///
289 /// # Arguments
290 ///
291 /// * `reset` - what happens to the counters.
292 /// * `reload_rates` - the coded rates, join request, notify, global uplink and overall,
293 /// 127 meaning no limit.
294 /// * `bucket_sizes` - the coded size multipliers in the same order, table 55.
295 pub fn configure(&mut self, reset: CounterReset, reload_rates: [u8; 4], bucket_sizes: [u8; 4]) {
296 for (at, bucket) in [
297 &mut self.join_request,
298 &mut self.notify,
299 &mut self.global_uplink,
300 &mut self.overall,
301 ]
302 .into_iter()
303 .enumerate()
304 {
305 let previous = if bucket.reload_rate().is_none() {
306 u16::MAX
307 } else {
308 bucket.tokens()
309 };
310 *bucket = TokenBucket::coded(reload_rates[at], bucket_sizes[at], UNLIMITED_RELAY_RATE);
311 reset.apply(bucket, previous);
312 }
313 }
314
315 /// Adds what every bucket earns over some hours.
316 ///
317 /// # Arguments
318 ///
319 /// * `hours` - the whole hours since the last reload.
320 pub fn reload(&mut self, hours: u64) {
321 self.join_request.reload(hours);
322 self.notify.reload(hours);
323 self.global_uplink.reload(hours);
324 self.overall.reload(hours);
325 }
326
327 /// Sets every bucket to one hour's reload, as TS011-1.0.1 section 8.8 has a relay do
328 /// when it starts scanning.
329 pub fn restart(&mut self) {
330 self.join_request.refill_one_hour();
331 self.notify.refill_one_hour();
332 self.global_uplink.refill_one_hour();
333 self.overall.refill_one_hour();
334 }
335}
336
337impl Default for ForwardLimits {
338 fn default() -> ForwardLimits {
339 ForwardLimits::new()
340 }
341}
342
343const fn starting(reload_rate: u8, size: u16) -> TokenBucket {
344 TokenBucket {
345 reload_rate: Some(reload_rate),
346 size,
347 tokens: reload_rate as u16,
348 }
349}