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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
//! Target interface
//!
//! This interface is used when operating as a Target.

use embassy_sync::{
    blocking_mutex::raw::RawMutex,
    channel::{Receiver, Sender},
};
use embassy_time::{with_timeout, Duration, Timer};
use futures::FutureExt;
use rand_core::RngCore;

use crate::{
    frame_pool::{FrameBox, RawFrameSlice},
    CmdAddr, FrameSerial,
};

/// The default number of "in-flight" packets FROM Target TO Controller
pub const OUTGOING_SIZE: usize = 8;
/// The default number of "in-flight" packets FROM Controller TO Target
pub const INCOMING_SIZE: usize = 4;

/// Metadata trait to contain relevant generics
pub trait TgtCfg {
    /// Mutex type used for channels
    type Mutex: RawMutex + 'static;

    /// Serial interface type
    type Serial: FrameSerial;

    /// Random number generator
    type Rand: RngCore;

    /// Amount of time to delay from hearing a response to
    /// sending a reply.
    const TURNAROUND_DELAY: Duration;

    /// Amount of time from initiating a claim to getting an address
    const ADDRESS_CLAIM_TIMEOUT: Duration;

    /// Amount of time being unaddressed before trying to get a new
    /// address
    const SELECT_TIMEOUT: Duration;
}

enum TargetError<S> {
    Serial(S),
    Oom,
}

impl<S> From<crate::Error<S>> for TargetError<S> {
    fn from(value: crate::Error<S>) -> Self {
        match value {
            crate::Error::Serial(s) => Self::Serial(s),
        }
    }
}

/// Interface for the Target
///
/// Note that UNLIKE the [`Controller`][crate::Controller], which uses a Mutex to share between the
/// "application facing" and "wire facing" parts, we instead use a pair of
/// [`Channel`][embassy_sync::channel::Channel]s for the Target instead.
///
/// This is because the Target is MUCH more timing critical, holding a mutex locked when addressed
/// by the Controller could cause us to totally miss a message.
pub struct Target<'a, Cfg, const IN: usize = INCOMING_SIZE, const OUT: usize = OUTGOING_SIZE>
where
    Cfg: TgtCfg,
{
    serial: Cfg::Serial,
    to_app: Sender<'a, Cfg::Mutex, FrameBox, IN>,
    from_app: Receiver<'a, Cfg::Mutex, FrameBox, OUT>,
    pool: RawFrameSlice,
    mac: [u8; 8],
    rand: Cfg::Rand,
}

impl<'a, Cfg, const IN: usize, const OUT: usize> Target<'a, Cfg, IN, OUT>
where
    Cfg: TgtCfg,
{
    /// Create a new [Target] worker.
    pub fn new(
        serial: Cfg::Serial,
        to_app: Sender<'a, Cfg::Mutex, FrameBox, IN>,
        from_app: Receiver<'a, Cfg::Mutex, FrameBox, OUT>,
        pool: RawFrameSlice,
        mac: [u8; 8],
        rand: Cfg::Rand,
    ) -> Self {
        Self {
            serial,
            to_app,
            from_app,
            mac,
            rand,
            pool,
        }
    }

    /// Run forever, exchanging messages
    pub async fn run(&mut self) {
        'outer: loop {
            let addr = self.get_addr().await;
            nut_info!("Got addr: {=u8}", addr);

            loop {
                match with_timeout(Cfg::SELECT_TIMEOUT, self.exchange_one(addr)).await {
                    // Exchange happened w/in timeout
                    Ok(Ok(())) => {}
                    // Exchange happened w/in timeout, but errored
                    Ok(Err(_)) => {
                        nut_error!("Error :(");
                        continue 'outer;
                    }
                    // Timed out
                    Err(_) => {
                        nut_warn!("Timed out!");
                        continue 'outer;
                    }
                }
            }
        }
    }

    async fn exchange_one(
        &mut self,
        addr: u8,
    ) -> Result<(), TargetError<<Cfg::Serial as FrameSerial>::SerError>> {
        // Wait for us to be acknowledged, and pass on the frame if we get one
        let time = self.get_incoming(addr).await?;

        // Is there something to send now? If not, empty-ack.
        let mut tx_frame = self.from_app.receive().now_or_never();
        let mut fallback = [0u8; 1];
        let out = match tx_frame.as_deref_mut() {
            Some(g) => g,
            None => fallback.as_mut_slice(),
        };
        out[0] = CmdAddr::ReplyFromAddr(addr).into();

        // Send reply
        Timer::at(time + Cfg::TURNAROUND_DELAY).await;
        self.serial.send_frame(out).await?;
        Ok(())
    }

    async fn get_incoming(
        &mut self,
        addr: u8,
    ) -> Result<crate::Instant, TargetError<<Cfg::Serial as FrameSerial>::SerError>> {
        let mut frame = self.pool.allocate_raw().ok_or(TargetError::Oom)?;
        loop {
            let buf = &mut frame[..];
            let got = self.serial.recv(buf).await?;
            if got.frame.is_empty() {
                continue;
            }
            let Ok(cmd_addr) = CmdAddr::try_from(got.frame[0]) else {
                continue;
            };
            if cmd_addr != CmdAddr::SelectAddr(addr) {
                continue;
            }
            let len = got.frame.len();
            let stamp = got.end_of_rx;

            if len != 1 {
                frame.set_len(len);
                self.to_app.send(frame).await;
            }
            return Ok(stamp);
        }
    }

    async fn get_addr(&mut self) -> u8 {
        loop {
            nut_info!("get_addr...");
            let goforit = self.rand.next_u32();

            // Wait for an offer frame
            let (offer_addr, offer_challenge) = self.get_offer().await;

            // do we go for it? (1/8 chance)
            if goforit & 0b0000_0111 != 0 {
                nut_info!("skipping!");
                continue;
            } else {
                nut_info!("going for it!");
            }

            let claim_dance = async {
                self.send_claim(offer_addr, &offer_challenge).await?;
                self.get_success(offer_addr).await?;
                let msg: [u8; 1] = [CmdAddr::ReplyFromAddr(offer_addr).into()];
                self.serial.send_frame(&msg).await?;
                Result::<(), TargetError<<Cfg::Serial as FrameSerial>::SerError>>::Ok(())
            };

            // Give ourselves some time to complete, if not try again
            match with_timeout(Cfg::ADDRESS_CLAIM_TIMEOUT, claim_dance).await {
                Ok(Ok(())) => return offer_addr,
                _ => continue,
            }
        }
    }

    async fn get_success(
        &mut self,
        offer_addr: u8,
    ) -> Result<(), TargetError<<Cfg::Serial as FrameSerial>::SerError>> {
        // A success packet should be 9 bytes plus one extra for
        // the line break.
        let mut scratch = [0u8; 16];
        loop {
            nut_info!("get success");
            let Ok(tframe) = self.serial.recv(&mut scratch).await else {
                // log: wat
                // NOTE: I feel like this is probably going to be due to
                // hearing messages longer than `scratch`.
                continue;
            };
            // Is this a valid cmd addr?
            let Some(ca) = tframe
                .frame
                .first()
                .and_then(|b| CmdAddr::try_from(*b).ok())
            else {
                continue;
            };
            // Is this an offer?
            let CmdAddr::DiscoverySuccess(addr) = ca else {
                continue;
            };

            // Is this long enough?
            if tframe.frame.len() < 9 {
                continue;
            }

            // Is this for us?
            if addr == offer_addr && (tframe.frame[1..9] == self.mac) {
                return Ok(());
            } else {
                continue;
            }
        }
    }

    async fn send_claim(
        &mut self,
        offer_addr: u8,
        challenge: &[u8; 8],
    ) -> Result<(), TargetError<<Cfg::Serial as FrameSerial>::SerError>> {
        nut_info!("send claim");
        let mut claim = [0u8; 9];

        claim[0] = CmdAddr::DiscoveryClaim(offer_addr).into();
        let data = &mut claim[1..9];
        data.copy_from_slice(&self.mac);

        data.iter_mut()
            .zip(challenge.iter())
            .for_each(|(a, b)| *a ^= *b);

        self.serial.send_frame(&claim).await?;
        Ok(())
    }

    async fn get_offer(&mut self) -> (u8, [u8; 8]) {
        // offer should be 1 + 8 + 1 for line break
        let mut scratch = [0u8; 16];
        loop {
            let Ok(tframe) = self.serial.recv(&mut scratch).await else {
                // log: wat
                // probably due to scratch being too small if we hear
                // someone elses frame
                continue;
            };
            // Is this a valid cmd addr?
            let Some(ca) = tframe
                .frame
                .first()
                .and_then(|b| CmdAddr::try_from(*b).ok())
            else {
                continue;
            };
            // Is this an offer?
            let CmdAddr::DiscoveryOffer(addr) = ca else {
                continue;
            };
            // Is this long enough?
            if tframe.frame.len() < 9 {
                continue;
            }
            let mut challenge = [0u8; 8];
            challenge.copy_from_slice(&tframe.frame[1..9]);

            return (addr, challenge);
        }
    }
}