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
// Licensed under the Apache License, Version 2.0 or the MIT License.
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Copyright Tock Contributors 2022.

//! In-kernel structure for tracking UDP ports bound by capsules.
//!
//! When kernel capsules wish to send or receive UDP packets, the UDP sending / receiving
//! capsules will only allow this if the capsule has bound to the port it wishes to
//! send from / receive on. Binding to a port is accomplished via calls on the
//! `UdpPortManager` struct defined in this file. Calls to bind on this table enforce that only
//! one capsule can be bound to a given port at any time. Once capsules successfully bind
//! using this table, they receive back binding structures (`UdpPortBindingTx`/`UdpPortBindingRx`)
//! that act as proof that the holder
//! is bound to that port. These structures can only be created within this file, and calls
//! to unbind must consume these structures, enforcing this invariant.
//! The UDP tx/rx capsules require these bindings be passed in order to send/receive on a given
//! port. Separate bindings are used for sending and receiving because the UdpReceiver must
//! hold onto the binding for as long as a capsule wishes to receive packets on a port, so
//! a separate binding must be available to enable sending packets on a port while
//! listening on the same port.
//!
//! To reduce the size of data structures required for this task, a fixed size
//! array is used to store bindings in the kernel. This means that a limited
//! number of bindings can be stored at any point in time. Reserving a slot
//! in this table is done by requesting a socket, which represents a reserved slot.
//! These sockets are then used to request bindings on a particular port.
//!
//! This file only stores information about which ports are bound by capsules.
//! The files `udp_send.rs` and `udp_recv.rs` enforce that only capsules possessing
//! the correct bindings can actually send / recv on a given port.
//!
//! Userspace port bindings are managed separately by the userspace UDP driver
//! (`capsules/src/net/udp/driver.rs`), because apps can be dynamically added or
//! removed. Bindings for userspace apps are stored in the grant regions of each app,
//! such that removing an app automatically unbinds it. This file is able to query the
//! userspace UDP driver to check which ports are bound, and vice-versa, such that
//! exclusive access to ports between userspace apps and capsules is still enforced.

use crate::net::network_capabilities::{NetworkCapability, UdpVisibilityCapability};

use core::fmt;

use kernel::capabilities::{CreatePortTableCapability, UdpDriverCapability};
use kernel::utilities::cells::{OptionalCell, TakeCell};
use kernel::ErrorCode;

// Sets the maximum number of UDP ports that can be bound by capsules. Reducing this number
// can save a small amount of memory, and slightly reduces the overhead of iterating through the
// table to check whether a port is already bound. Note: if this numberis changed,
// port_table_test2 in udp_lowpan_test.rs will fail since it tests the capacity of
// the port table -- therefore that test should be modified when MAX_NUM_BOUND_PORTS
// is.
pub const MAX_NUM_BOUND_PORTS: usize = 16;

/// The SocketBindingEntry struct is stored in the PORT_TABLE and conveys what port is bound
/// at the given index if one is bound. If no port is bound, the value stored
/// at that location in the table is Unbound.
#[derive(Clone, Copy, PartialEq)]
pub enum SocketBindingEntry {
    Port(u16),
    Unbound,
}

/// The PortQuery trait enables the UdpPortManager to query the userspace bound
/// ports in the UDP driver. The UDP driver struct implements this trait.
pub trait PortQuery {
    fn is_bound(&self, port: u16) -> bool;
}

/// A UdpSocket provides a handle into the bound port table. When binding to
/// a port, the socket is consumed and Udp{Sender, Receiver}Binding structs are returned. When
/// undbinding, the socket is returned and can be used to bind to other ports.
#[derive(Debug)]
pub struct UdpSocket {
    idx: usize,
    port_table: &'static UdpPortManager,
}

/// The UdpPortManager maintains a reference to the port_array, which manages what
/// ports are bound at any given moment, and user_ports, which provides a
/// handle to userspace port bindings in the UDP driver.
pub struct UdpPortManager {
    port_array: TakeCell<'static, [Option<SocketBindingEntry>]>,
    user_ports: OptionalCell<&'static dyn PortQuery>,
    udp_vis: &'static UdpVisibilityCapability,
}

impl fmt::Debug for UdpPortManager {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[Port Table]")
    }
}

impl UdpSocket {
    // important that this function is not public. If it were, capsules could
    // obtain access to ports bound by other capsules
    fn new(idx: usize, pt: &'static UdpPortManager) -> UdpSocket {
        UdpSocket {
            idx,
            port_table: pt,
        }
    }
}

impl Drop for UdpSocket {
    fn drop(&mut self) {
        self.port_table.destroy_socket(self);
    }
}

/// An opaque descriptor that allows the holder to obtain a binding on a port
/// for receiving UDP packets.
#[derive(Debug)]
pub struct UdpPortBindingRx {
    idx: usize,
    port: u16,
}

/// An opaque descriptor that allows the holder to obtain a binding on a port
/// for sending UDP packets.
#[derive(Debug)]
pub struct UdpPortBindingTx {
    idx: usize,
    port: u16,
}

impl UdpPortBindingTx {
    fn new(idx: usize, port: u16) -> UdpPortBindingTx {
        UdpPortBindingTx { idx, port }
    }

    pub fn get_port(&self) -> u16 {
        self.port
    }
}

impl UdpPortBindingRx {
    fn new(idx: usize, port: u16) -> UdpPortBindingRx {
        UdpPortBindingRx { idx, port }
    }

    pub fn get_port(&self) -> u16 {
        self.port
    }
}

impl UdpPortManager {
    // Require capability so that the port table is only created by kernel
    pub fn new(
        _cap: &dyn CreatePortTableCapability,
        used_kernel_ports: &'static mut [Option<SocketBindingEntry>],
        udp_vis: &'static UdpVisibilityCapability,
    ) -> UdpPortManager {
        UdpPortManager {
            port_array: TakeCell::new(used_kernel_ports),
            user_ports: OptionalCell::empty(),
            udp_vis,
        }
    }

    // This function is called to set a reference to the UDP driver, so that the ports
    // bound by applications can be queried from within this file.
    pub fn set_user_ports(
        &self,
        user_ports_ref: &'static dyn PortQuery,
        _driver_cap: &dyn UdpDriverCapability,
    ) {
        self.user_ports.replace(user_ports_ref);
    }

    /// Called by capsules that would like to eventually be able to bind to a
    /// UDP port. This call will succeed unless MAX_NUM_BOUND_PORTS capsules
    /// have already bound to a port.
    pub fn create_socket(&'static self) -> Result<UdpSocket, Result<(), ErrorCode>> {
        self.port_array
            .map_or(Err(Err(ErrorCode::NOSUPPORT)), |table| {
                let mut result: Result<UdpSocket, Result<(), ErrorCode>> =
                    Err(Err(ErrorCode::FAIL));
                for i in 0..MAX_NUM_BOUND_PORTS {
                    match table[i] {
                        None => {
                            result = Ok(UdpSocket::new(i, self));
                            table[i] = Some(SocketBindingEntry::Unbound);
                            break;
                        }
                        _ => (),
                    }
                }
                result
            })
    }

    /// Called when sockets are dropped to free their slots in the table.
    /// The slot in the table is only freed if the socket that is dropped is
    /// unbound. If the slot is bound, the socket is being dropped after a call to
    /// bind(), and the slot in the table should remain reserved.
    fn destroy_socket(&self, socket: &UdpSocket) {
        self.port_array.map(|table| match table[socket.idx] {
            Some(entry) => {
                if entry == SocketBindingEntry::Unbound {
                    table[socket.idx] = None;
                }
            }
            _ => {}
        });
    }

    /// Check if a given port is already bound, by either an app or capsule.
    pub fn is_bound(&self, port: u16) -> Result<bool, ()> {
        // First, check the user bindings.
        if self.user_ports.is_none() {
            return Err(());
        }
        let user_bound = self
            .user_ports
            .map_or(true, |port_query| port_query.is_bound(port));
        if user_bound {
            return Ok(true);
        };
        let ret = self
            .port_array
            .map(|table| {
                let mut port_exists = false;
                for i in 0..MAX_NUM_BOUND_PORTS {
                    match table[i] {
                        Some(SocketBindingEntry::Port(p)) => {
                            if p == port {
                                port_exists = true;
                                break;
                            }
                        }
                        _ => (),
                    }
                }
                port_exists
            })
            .unwrap();
        Ok(ret)
    }

    /// Called by capsules that have already reserved a socket to attempt to bind to
    /// a UDP port. The socket is passed by value.
    /// On success, bindings is returned. On failure, the same
    /// UdpSocket is returned.
    pub fn bind(
        &self,
        socket: UdpSocket,
        port: u16,
        net_cap: &'static NetworkCapability,
    ) -> Result<(UdpPortBindingTx, UdpPortBindingRx), UdpSocket> {
        if net_cap.local_port_valid(port, self.udp_vis) {
            match self.is_bound(port) {
                Ok(bound) => {
                    if bound {
                        Err(socket)
                    } else {
                        self.port_array
                            .map(|table| {
                                table[socket.idx] = Some(SocketBindingEntry::Port(port));
                                let binding_pair = (
                                    UdpPortBindingTx::new(socket.idx, port),
                                    UdpPortBindingRx::new(socket.idx, port),
                                );
                                // Add socket to the linked list.
                                Ok(binding_pair)
                            })
                            .unwrap()
                    }
                }
                Err(()) => Err(socket),
            }
        } else {
            Err(socket)
        }
    }

    /// Disassociate the port from the given binding. Return the socket associated
    /// with the passed bindings. On Err, return the passed bindings.
    pub fn unbind(
        &'static self,
        sender_binding: UdpPortBindingTx,
        receiver_binding: UdpPortBindingRx,
    ) -> Result<UdpSocket, (UdpPortBindingTx, UdpPortBindingRx)> {
        // Verify that the indices match up
        if sender_binding.idx != receiver_binding.idx {
            return Err((sender_binding, receiver_binding));
        }
        let idx = sender_binding.idx;
        self.port_array.map(|table| {
            table[idx] = Some(SocketBindingEntry::Unbound);
        });
        // Search the list and return the appropriate socket
        Ok(UdpSocket::new(idx, self))
    }
}