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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
#![allow(dead_code)]
use embedded_time::duration::Milliseconds;
use crate::retry::{Attempts, Strategy};
use crate::time::Millis;
/// Bytes / Second
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BytesPerSecond(pub u16);
/// Configuration options related to parsing & handling outbound CON requests
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Con {
/// Retry strategy for CON requests that
/// have not yet been ACKed.
///
/// Defaults to an exponential retry strategy:
/// ```
/// use embedded_time::duration::Milliseconds;
/// use toad::config::Con;
/// use toad::retry::Strategy;
///
/// assert_eq!(Con::default().unacked_retry_strategy,
/// Strategy::Exponential { init_min: Milliseconds(500),
/// init_max: Milliseconds(1_000) });
/// ```
pub unacked_retry_strategy: Strategy,
/// Retry strategy for CON requests that have been ACKed.
///
/// Usually this should be **lazier** than `unacked_retry_strategy`,
/// since we can reasonably expect the duration between "received request"
/// and "responded with ACK" to be much shorter than "responded with ACK" and
/// "sent actual response."
///
/// Defaults to a lazy exponential retry strategy:
/// ```
/// use embedded_time::duration::Milliseconds;
/// use toad::config::Con;
/// use toad::retry::Strategy;
///
/// assert_eq!(Con::default().acked_retry_strategy,
/// Strategy::Exponential { init_min: Milliseconds(1_000),
/// init_max: Milliseconds(2_000) });
/// ```
pub acked_retry_strategy: Strategy,
/// Number of times we are allowed to resend a CON request
/// before erroring.
//
/// Defaults to 4 attempts.
/// ```
/// use toad::config::Con;
/// use toad::retry::Attempts;
///
/// assert_eq!(Con::default().max_attempts, Attempts(4));
/// ```
pub max_attempts: Attempts,
}
/// Configuration options related to parsing & handling outbound NON requests
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Non {
/// Strategy to use when we sent a NON request and haven't yet
/// received a response.
///
/// **Note** that in a future commit there will be a method by which NON
/// requests can be "flung" without any expectation of a response.
///
/// Defaults to a pessimistic exponential retry strategy:
/// ```
/// use embedded_time::duration::Milliseconds;
/// use toad::config::Non;
/// use toad::retry::Strategy;
///
/// assert_eq!(Non::default().retry_strategy,
/// Strategy::Exponential { init_min: Milliseconds(250),
/// init_max: Milliseconds(500) });
/// ```
pub retry_strategy: Strategy,
/// Number of times we are allowed to resend a NON request
/// before erroring.
///
/// Defaults to 4 attempts.
/// ```
/// use toad::config::Non;
/// use toad::retry::Attempts;
///
/// assert_eq!(Non::default().max_attempts, Attempts(4));
/// ```
pub max_attempts: Attempts,
}
/// Configuration options related to parsing & handling messages
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Msg {
/// Seed used to generate message [`Token`](toad_msg::Token)s,
/// customizable to allow for your application to generate tokens
/// less guessably.
///
/// The default value is 0, although it is
/// best practice to set this to something else.
/// (random integer, machine identifier)
///
/// _e.g. if you're developing a swarm of
/// smart CoAP-enabled thermostats, each one would ideally
/// have a distinct token seed._
///
/// ```
/// use toad::config::Msg;
///
/// assert_eq!(Msg::default().token_seed, 0);
/// ```
// token_seed
// ||
// xx xxxxxxxx
// | |
// timestamp
pub token_seed: u16,
/// Set the transmission rate that we should do our best
/// not to exceed when waiting for:
/// - responses to our NON requests
/// - responses to our acked CON requests
///
/// Defaults to `BytesPerSecond(1000)`
///
/// ```
/// use toad::config::{BytesPerSecond, Msg};
///
/// assert_eq!(Msg::default().probing_rate, BytesPerSecond(1000));
/// ```
pub probing_rate: BytesPerSecond,
/// See [`Con`]
pub con: Con,
/// See [`Non`]
pub non: Non,
/// Set the maximum amount of time we should delay
/// our response to multicast requests.
///
/// The actual delay will be random between zero
/// and this value.
///
/// Defaults to 5000 milliseconds.
///
/// ```
/// use embedded_time::duration::Milliseconds;
/// use toad::config::Msg;
///
/// assert_eq!(Msg::default().multicast_response_leisure,
/// Milliseconds(5000u64));
/// ```
pub multicast_response_leisure: Millis,
}
impl Default for Con {
fn default() -> Self {
Con { unacked_retry_strategy: Strategy::Exponential { init_min: Milliseconds(500),
init_max: Milliseconds(1_000) },
acked_retry_strategy: Strategy::Exponential { init_min: Milliseconds(1_000),
init_max: Milliseconds(2_000) },
max_attempts: Attempts(4) }
}
}
impl Default for Non {
fn default() -> Self {
Non { retry_strategy: Strategy::Exponential { init_min: Milliseconds(250),
init_max: Milliseconds(500) },
max_attempts: Attempts(4) }
}
}
impl Default for Msg {
fn default() -> Self {
Msg { token_seed: 0,
probing_rate: BytesPerSecond(1000),
con: Con::default(),
non: Non::default(),
multicast_response_leisure: Milliseconds(5000) }
}
}
/// Runtime config
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Config {
/// See [`Msg`]
pub msg: Msg,
/// Maximum number of requests that
/// can be in flight at a given moment
///
/// Default value is `1` (no concurrency)
///
/// ```
/// use toad::config::Config;
///
/// assert_eq!(Config::default().max_concurrent_requests, 1);
/// ```
pub max_concurrent_requests: u8,
}
impl Default for Config {
fn default() -> Self {
Config { msg: Msg::default(),
max_concurrent_requests: 1 }
}
}
impl Config {
pub(crate) fn max_transmit_span_millis(&self) -> u64 {
let acked_con = self.msg
.con
.acked_retry_strategy
.max_time(self.msg.con.max_attempts - Attempts(1))
.0 as u64;
let unacked_con = self.msg
.con
.unacked_retry_strategy
.max_time(self.msg.con.max_attempts - Attempts(1))
.0 as u64;
let non = self.msg
.non
.retry_strategy
.max_time(self.msg.non.max_attempts - Attempts(1))
.0 as u64;
acked_con.max(unacked_con).max(non)
}
pub(crate) fn max_transmit_wait_millis(&self) -> u64 {
let acked_con = self.msg
.con
.acked_retry_strategy
.max_time(self.msg.con.max_attempts)
.0 as u64;
let unacked_con = self.msg
.con
.unacked_retry_strategy
.max_time(self.msg.con.max_attempts)
.0 as u64;
let non = self.msg
.non
.retry_strategy
.max_time(self.msg.non.max_attempts)
.0 as u64;
acked_con.max(unacked_con).max(non)
}
// TODO: adjust these on the fly based on actual timings?
pub(crate) fn max_latency_millis(&self) -> u64 {
100_000
}
pub(crate) fn expected_processing_delay_millis(&self) -> u64 {
200
}
pub(crate) fn exchange_lifetime_millis(&self) -> u64 {
self.max_transmit_span_millis()
+ (2 * self.max_latency_millis())
+ self.expected_processing_delay_millis()
}
}