aboutsummaryrefslogtreecommitdiff
path: root/embassy-net-ppp/src/lib.rs
blob: df583fb376cc6f2ccfeb6d64689bfb1daafa77b2 (plain)
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
#![no_std]
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]

// must be first
mod fmt;

use core::convert::Infallible;
use core::mem::MaybeUninit;

use embassy_futures::select::{select, Either};
use embassy_net_driver_channel as ch;
use embassy_net_driver_channel::driver::LinkState;
use embedded_io_async::{BufRead, Write, WriteAllError};
use ppproto::pppos::{BufferFullError, PPPoS, PPPoSAction};
pub use ppproto::Config;

const MTU: usize = 1500;

/// Type alias for the embassy-net driver.
pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>;

/// Internal state for the embassy-net integration.
pub struct State<const N_RX: usize, const N_TX: usize> {
    ch_state: ch::State<MTU, N_RX, N_TX>,
}

impl<const N_RX: usize, const N_TX: usize> State<N_RX, N_TX> {
    /// Create a new `State`.
    pub const fn new() -> Self {
        Self {
            ch_state: ch::State::new(),
        }
    }
}

/// Background runner for the driver.
///
/// You must call `.run()` in a background task for the driver to operate.
pub struct Runner<'d> {
    ch: ch::Runner<'d, MTU>,
}

/// Error returned by [`Runner::run`].
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RunError<E> {
    /// Reading from the serial port failed.
    Read(E),
    /// Writing to the serial port failed.
    Write(E),
    /// Writing to the serial port wrote zero bytes, indicating it can't accept more data.
    WriteZero,
    /// Writing to the serial got EOF.
    Eof,
}

impl<'d> Runner<'d> {
    /// You must call this in a background task for the driver to operate.
    ///
    /// If reading/writing to the underlying serial port fails, the link state
    /// is set to Down and the error is returned.
    ///
    /// It is allowed to cancel this function's future (i.e. drop it). This will terminate
    /// the PPP connection and set the link state to Down.
    ///
    /// After this function returns or is canceled, you can call it again to establish
    /// a new PPP connection.
    pub async fn run<RW: BufRead + Write>(
        &mut self,
        mut rw: RW,
        config: ppproto::Config<'_>,
    ) -> Result<Infallible, RunError<RW::Error>> {
        let mut ppp = PPPoS::new(config);
        ppp.open().unwrap();

        let (state_chan, mut rx_chan, mut tx_chan) = self.ch.borrow_split();
        state_chan.set_link_state(LinkState::Down);
        let _ondrop = OnDrop::new(|| state_chan.set_link_state(LinkState::Down));

        let mut rx_buf = [0; 2048];
        let mut tx_buf = [0; 2048];

        let mut needs_poll = true;

        loop {
            let rx_fut = async {
                let buf = rx_chan.rx_buf().await;
                let rx_data = match needs_poll {
                    true => &[][..],
                    false => match rw.fill_buf().await {
                        Ok(rx_data) if rx_data.len() == 0 => return Err(RunError::Eof),
                        Ok(rx_data) => rx_data,
                        Err(e) => return Err(RunError::Read(e)),
                    },
                };
                Ok((buf, rx_data))
            };
            let tx_fut = tx_chan.tx_buf();
            match select(rx_fut, tx_fut).await {
                Either::First(r) => {
                    needs_poll = false;

                    let (buf, rx_data) = r?;
                    let n = ppp.consume(rx_data, &mut rx_buf);
                    rw.consume(n);

                    match ppp.poll(&mut tx_buf, &mut rx_buf) {
                        PPPoSAction::None => {}
                        PPPoSAction::Received(rg) => {
                            let pkt = &rx_buf[rg];
                            buf[..pkt.len()].copy_from_slice(pkt);
                            rx_chan.rx_done(pkt.len());
                        }
                        PPPoSAction::Transmit(n) => match rw.write_all(&tx_buf[..n]).await {
                            Ok(()) => {}
                            Err(WriteAllError::WriteZero) => return Err(RunError::WriteZero),
                            Err(WriteAllError::Other(e)) => return Err(RunError::Write(e)),
                        },
                    }

                    match ppp.status().phase {
                        ppproto::Phase::Open => state_chan.set_link_state(LinkState::Up),
                        _ => state_chan.set_link_state(LinkState::Down),
                    }
                }
                Either::Second(pkt) => {
                    match ppp.send(pkt, &mut tx_buf) {
                        Ok(n) => match rw.write_all(&tx_buf[..n]).await {
                            Ok(()) => {}
                            Err(WriteAllError::WriteZero) => return Err(RunError::WriteZero),
                            Err(WriteAllError::Other(e)) => return Err(RunError::Write(e)),
                        },
                        Err(BufferFullError) => unreachable!(),
                    }
                    tx_chan.tx_done();
                }
            }
        }
    }
}

/// Create a PPP embassy-net driver instance.
///
/// This returns two structs:
/// - a `Device` that you must pass to the `embassy-net` stack.
/// - a `Runner`. You must call `.run()` on it in a background task.
pub fn new<'a, const N_RX: usize, const N_TX: usize>(state: &'a mut State<N_RX, N_TX>) -> (Device<'a>, Runner<'a>) {
    let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ip);
    (device, Runner { ch: runner })
}

struct OnDrop<F: FnOnce()> {
    f: MaybeUninit<F>,
}

impl<F: FnOnce()> OnDrop<F> {
    fn new(f: F) -> Self {
        Self { f: MaybeUninit::new(f) }
    }
}

impl<F: FnOnce()> Drop for OnDrop<F> {
    fn drop(&mut self) {
        unsafe { self.f.as_ptr().read()() }
    }
}