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
#![allow(dead_code)]

use embedded_time::duration::Milliseconds;
use kwap_macros::rfc_7252_doc;

use crate::retry::{Attempts, Strategy};

pub(crate) struct ConfigData {
  pub(crate) token_seed: u16,
  pub(crate) con_retry_strategy: Strategy,
  pub(crate) default_leisure_millis: u32,
  pub(crate) max_retransmit_attempts: u16,
  pub(crate) nstart: u8,
  pub(crate) probing_rate_bytes_per_sec: u16,
}

impl ConfigData {
  pub(crate) fn max_transmit_span_millis(&self) -> u32 {
    self.con_retry_strategy
        .max_time(Attempts(self.max_retransmit_attempts - 1))
        .0 as u32
  }

  pub(crate) fn max_transmit_wait_millis(&self) -> u32 {
    self.con_retry_strategy
        .max_time(Attempts(self.max_retransmit_attempts))
        .0 as u32
  }

  // TODO: adjust these on the fly based on actual timings?
  pub(crate) fn max_latency_millis(&self) -> u32 {
    100_000
  }

  pub(crate) fn expected_processing_delay_millis(&self) -> u32 {
    200
  }

  pub(crate) fn exchange_lifetime_millis(&self) -> u32 {
    self.max_transmit_span_millis()
    + (2 * self.max_latency_millis())
    + self.expected_processing_delay_millis()
  }
}

/// CoAP runtime config
///
/// Allows you to configure things like
/// "how many concurrent requests are we allowed
/// to send?" and "how long should we wait to resend
/// unacknowledged confirmable requests?"
///
/// For an example see [`Config::new`].
#[derive(Debug, Default, Clone, Copy)]
pub struct Config {
  token_seed: Option<u16>,
  con_retry_strategy: Option<Strategy>,
  default_leisure_millis: Option<u32>,
  max_retransmit_attempts: Option<u16>,
  nstart: Option<u8>,
  probing_rate_bytes_per_sec: Option<u16>,
  /// Users who use a struct literal to initialize this
  /// /must/ use ..Default::default(),
  /// which makes adding fields to this struct non-breaking.
  __non_exhaustive: (),
}

/// Bytes / Second
#[derive(Debug, Clone, Copy)]
pub struct BytesPerSecond(pub u16);

impl Config {
  /// Creates a new (empty) runtime config
  ///
  /// ```
  /// use embedded_time::duration::Milliseconds as Millis;
  /// use kwap::config::{BytesPerSecond, Config};
  /// use kwap::retry::Attempts;
  /// use kwap::retry::Strategy::Exponential;
  ///
  /// let config = Config::new().token_seed(35718)
  ///                           .max_concurrent_requests(142)
  ///                           .probing_rate(BytesPerSecond(10_000))
  ///                           .max_con_request_retries(Attempts(10))
  ///                           .con_retry_strategy(Exponential { init_min: Millis(500),
  ///                                                             init_max: Millis(750) });
  /// ```
  pub fn new() -> Self {
    Default::default()
  }

  /// Set the retry strategy we should use to figure out when
  /// we should resend outgoing CON requests that have not been
  /// ACKed yet.
  ///
  /// Default value:
  /// ```ignore
  /// Strategy::Exponential { init_min: Seconds(2), init_max: Seconds(3) }
  /// ```
  pub fn con_retry_strategy(mut self, strat: Strategy) -> Self {
    self.con_retry_strategy = Some(strat);
    self
  }

  /// Set the seed used to generate message [`Token`](kwap_msg::Token)s.
  ///
  /// The default value is 0, although it is
  /// best practice to set this to something else.
  /// This could be a random integer, or a machine identifier.
  ///
  /// _e.g. if you're developing a swarm of
  /// smart CoAP-enabled thermostats, each one would ideally
  /// have a distinct token_seed._
  ///
  /// The purpose of the seed is to make it more
  /// difficult for an observer of unencrypted
  /// CoAP traffic to guess what the next token will be.
  ///
  /// Tokens are generated by smooshing together
  /// the 2-byte seed with an 8-byte timestamp from
  /// the system clock.
  ///
  /// ```text
  /// Core.token_seed
  /// ||
  /// xx xxxxxxxx
  ///    |      |
  ///    timestamp
  /// ```
  ///
  /// Then a hashing algorithm is used to make it opaque and
  /// reduce the size to 8 bytes.
  pub fn token_seed(mut self, token_seed: u16) -> Self {
    self.token_seed = Some(token_seed);
    self
  }

  /// 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
  ///
  /// The default value is 1,000 (1KB)
  pub fn probing_rate(mut self, probing_rate: BytesPerSecond) -> Self {
    self.probing_rate_bytes_per_sec = Some(probing_rate.0);
    self
  }

  /// Set the number of concurrent requests we are allowed
  /// to have in-flight for each server.
  ///
  /// The default value is 1 (no concurrency)
  pub fn max_concurrent_requests(mut self, n: u8) -> Self {
    self.nstart = Some(n);
    self
  }

  /// Set the maximum number of times we should re-send
  /// confirmable requests before getting a response.
  ///
  /// The default value is 4 attempts
  pub fn max_con_request_retries(mut self, max_tries: Attempts) -> Self {
    self.max_retransmit_attempts = Some(max_tries.0);
    self
  }

  /// Set the maximum amount of time we should wait to
  /// respond to incoming multicast requests.
  ///
  /// The default value is 5 seconds.
  #[doc = rfc_7252_doc!("8.2")]
  pub fn default_leisure(mut self, default_leisure: Milliseconds<u32>) -> Self {
    self.default_leisure_millis = Some(default_leisure.0);
    self
  }
}

impl From<Config> for ConfigData {
  fn from(Config { token_seed,
                   default_leisure_millis,
                   max_retransmit_attempts,
                   nstart,
                   probing_rate_bytes_per_sec,
                   con_retry_strategy,
                   .. }: Config)
          -> Self {
    ConfigData { token_seed: token_seed.unwrap_or(0),
                 default_leisure_millis: default_leisure_millis.unwrap_or(5_000),
                 max_retransmit_attempts: max_retransmit_attempts.unwrap_or(4),
                 nstart: nstart.unwrap_or(1),
                 probing_rate_bytes_per_sec: probing_rate_bytes_per_sec.unwrap_or(1_000),
                 con_retry_strategy:
                   con_retry_strategy.unwrap_or(Strategy::Exponential { init_min:
                                                                          Milliseconds(2_000),
                                                                        init_max:
                                                                          Milliseconds(3_000) }) }
  }
}