wlan_ffi_transport/
transport.rs

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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use crate::completers::Completer;
use fdf::{fdf_arena_t, Arena, ArenaStaticBox};
use std::ffi::c_void;
use std::marker::{PhantomData, PhantomPinned};
use std::pin::Pin;
use std::ptr::NonNull;
use std::{mem, slice};
use tracing::error;
use wlan_fidl_ext::{TryUnpack, WithName};
use {fidl_fuchsia_wlan_softmac as fidl_softmac, fuchsia_trace as trace, wlan_trace as wtrace};

// Defined as an opaque type as suggested by
// https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs.
#[repr(C)]
pub struct FfiEthernetRxCtx {
    _data: [u8; 0],
    _marker: PhantomData<(*mut u8, PhantomPinned)>,
}

#[repr(C)]
pub struct FfiEthernetRx {
    ctx: *mut FfiEthernetRxCtx,
    /// Sends an Ethernet frame to the C++ portion of wlansoftmac.
    ///
    /// # Safety
    ///
    /// Behavior is undefined unless `payload` contains a persisted `EthernetRx.Transfer` request
    /// and `payload_len` is the length of the persisted byte array.
    transfer: unsafe extern "C" fn(
        ctx: *mut FfiEthernetRxCtx,
        payload: *const u8,
        payload_len: usize,
    ) -> zx::sys::zx_status_t,
}

// Safety: The FFI provided by FfiEthernetRx is thread-safe. In particular, the wlansoftmac
// driver synchronizes all of its ddk::EthernetIfcProtocolClient calls.
unsafe impl Send for FfiEthernetRx {}

pub struct EthernetRx {
    ffi: FfiEthernetRx,
}

impl EthernetRx {
    pub fn new(ffi: FfiEthernetRx) -> Self {
        Self { ffi }
    }

    pub fn transfer(
        &mut self,
        request: &fidl_softmac::EthernetRxTransferRequest,
    ) -> Result<(), zx::Status> {
        wtrace::duration!(c"EthernetRx transfer");
        let payload = fidl::persist(request);
        match payload {
            Err(e) => {
                error!("Failed to persist EthernetRx.Transfer request: {}", e);
                Err(zx::Status::INTERNAL)
            }
            Ok(payload) => {
                let payload = payload.as_slice();
                // Safety: The `self.ffi.transfer` call is safe because the payload is a persisted
                // `EthernetRx.Transfer` request.
                zx::Status::from_raw(unsafe {
                    (self.ffi.transfer)(self.ffi.ctx, payload.as_ptr(), payload.len())
                })
                .into()
            }
        }
    }
}

// Defined as an opaque type as suggested by
// https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs.
#[repr(C)]
pub struct FfiWlanTxCtx {
    _data: [u8; 0],
    _marker: PhantomData<(*mut u8, PhantomPinned)>,
}

#[repr(C)]
pub struct FfiWlanTx {
    ctx: *mut FfiWlanTxCtx,
    /// Sends a WLAN MAC frame to the C++ portion of wlansoftmac.
    ///
    /// # Safety
    ///
    /// Behavior is undefined unless `payload` contains a persisted `WlanTx.Transfer` request
    /// and `payload_len` is the length of the persisted byte array.
    transfer: unsafe extern "C" fn(
        ctx: *mut FfiWlanTxCtx,
        payload: *const u8,
        payload_len: usize,
    ) -> zx::sys::zx_status_t,
}

// Safety: The FFI provided by FfiWlanTx is thread-safe. In particular, the wlansoftmac
// driver synchronizes all of its fdf::SharedClient<fuchsia_wlan_softmac::WlanSoftmac>
// calls.
unsafe impl Send for FfiWlanTx {}

pub struct WlanTx {
    ffi: FfiWlanTx,
}

impl WlanTx {
    pub fn new(ffi: FfiWlanTx) -> Self {
        Self { ffi }
    }

    pub fn transfer(
        &mut self,
        request: &fidl_softmac::WlanTxTransferRequest,
    ) -> Result<(), zx::Status> {
        wtrace::duration!(c"WlanTx transfer");
        let payload = fidl::persist(request);
        match payload {
            Err(e) => {
                error!("Failed to persist WlanTx.Transfer request: {}", e);
                Err(zx::Status::INTERNAL)
            }
            Ok(payload) => {
                // Safety: The `self.ffi.transfer` call is safe because the payload is a persisted
                // `EthernetRx.Transfer` request.
                zx::Status::from_raw(unsafe {
                    (self.ffi.transfer)(self.ffi.ctx, payload.as_slice().as_ptr(), payload.len())
                })
                .into()
            }
        }
    }
}

pub trait EthernetTxEventSender {
    fn unbounded_send(&self, event: EthernetTxEvent) -> Result<(), (String, EthernetTxEvent)>;
}

#[repr(C)]
pub struct FfiEthernetTxCtx {
    sender: Box<dyn EthernetTxEventSender>,
    pin: PhantomPinned,
}

#[repr(C)]
pub struct FfiEthernetTx {
    ctx: *const FfiEthernetTxCtx,
    transfer: unsafe extern "C" fn(
        ctx: *const FfiEthernetTxCtx,
        request: *const u8,
        request_size: usize,
    ) -> zx::sys::zx_status_t,
}

pub struct EthernetTx {
    ctx: Pin<Box<FfiEthernetTxCtx>>,
}

// TODO(https://fxbug.dev/42119762): We need to keep stats for these events and respond to StatsQueryRequest.
pub struct EthernetTxEvent {
    pub bytes: NonNull<[u8]>,
    pub async_id: trace::Id,
    pub borrowed_operation: Completer<Box<dyn FnOnce(zx::sys::zx_status_t)>>,
}

impl EthernetTx {
    /// Return a pinned `EthernetTx`.
    ///
    /// Pinning the returned value is imperative to ensure future `to_c_binding()` calls will return
    /// pointers that are valid for the lifetime of the returned value.
    pub fn new(sender: Box<dyn EthernetTxEventSender>) -> Self {
        Self { ctx: Box::pin(FfiEthernetTxCtx { sender, pin: PhantomPinned }) }
    }

    /// Returns a `FfiEthernetTx` containing functions to queue `EthernetTxEvent` values into the
    /// corresponding `EthernetTx`.
    ///
    /// Note that the pointers in the returned `FfiEthernetTx` are all to static and pinned values
    /// so it's safe to move this `EthernetTx` after calling this function.
    ///
    /// # Safety
    ///
    /// This method unsafe because we cannot guarantee the returned `FfiEthernetTxCtx`
    /// will have a lifetime that is shorther than this `EthernetTx`.
    ///
    /// By using this method, the caller promises the lifetime of this `EthernetTx` will exceed the
    /// `ctx` pointer used across the FFI boundary.
    pub unsafe fn to_ffi(&self) -> FfiEthernetTx {
        FfiEthernetTx {
            ctx: &*self.ctx.as_ref() as *const FfiEthernetTxCtx,
            transfer: Self::ethernet_tx_transfer,
        }
    }

    /// Queues an Ethernet frame into the `EthernetTx` for processing.
    ///
    /// The caller should either end the async
    /// trace event corresponding to |async_id| if an error occurs or deferred ending the trace to a later call
    /// into the C++ portion of wlansoftmac.
    ///
    /// Assuming no errors occur, the Rust portion of wlansoftmac will eventually
    /// rust_device_interface_t.queue_tx() with the same |async_id|. At that point, the C++ portion of
    /// wlansoftmac will assume responsibility for ending the async trace event.
    ///
    /// # Errors
    ///
    /// This function will return ZX_ERR_BAD_STATE if and only if it did not claim ownership
    /// of the eth::BorrowedOperation before returning.
    ///
    /// # Safety
    ///
    /// Behavior is undefined unless `payload` points to a persisted
    /// `fuchsia.wlan.softmac/EthernetTx.Transfer` request of length `payload_len` that is properly
    /// aligned.
    #[no_mangle]
    unsafe extern "C" fn ethernet_tx_transfer(
        ctx: *const FfiEthernetTxCtx,
        payload: *const u8,
        payload_len: usize,
    ) -> zx::sys::zx_status_t {
        wtrace::duration!(c"EthernetTx transfer");

        // Safety: This call is safe because the caller promises `payload` points to a persisted
        // `fuchsia.wlan.softmac/EthernetTx.Transfer` request of length `payload_len` that is properly
        // aligned.
        let payload = unsafe { slice::from_raw_parts(payload, payload_len) };
        let payload = match fidl::unpersist::<fidl_softmac::EthernetTxTransferRequest>(payload) {
            Ok(payload) => payload,
            Err(e) => {
                error!("Unable to unpersist EthernetTx.Transfer request: {}", e);
                return zx::Status::BAD_STATE.into_raw();
            }
        };

        let borrowed_operation =
            match payload.borrowed_operation.with_name("borrowed_operation").try_unpack() {
                Ok(x) => x as *mut c_void,
                Err(e) => {
                    let e = e.context("Missing required field in EthernetTxTransferRequest.");
                    error!("{}", e);
                    return zx::Status::BAD_STATE.into_raw();
                }
            };

        let complete_borrowed_operation: unsafe extern "C" fn(
            borrowed_operation: *mut c_void,
            status: zx::sys::zx_status_t,
        ) = match payload
            .complete_borrowed_operation
            .with_name("complete_borrowed_operation")
            .try_unpack()
        {
            // Safety: Per the safety documentation of this FFI, the sender promises
            // this field has the type unsafe extern "C" fn(*mut c_void, zx::sys::zx_status_t).
            Ok(x) => unsafe { mem::transmute(x) },
            Err(e) => {
                let e = e.context("Missing required field in EthernetTxTransferRequest.");
                error!("{}", e);
                return zx::Status::BAD_STATE.into_raw();
            }
        };

        // Box the closure so that EthernetTxEventSender can be object-safe.
        let borrowed_operation: Completer<Box<dyn FnOnce(zx::sys::zx_status_t)>> = {
            // Safety: This call of `complete_borrowed_operation` uses the value
            // of the received `borrowed_operation` field as its first argument
            // and will only be called once.
            let completer = Box::new(move |status| unsafe {
                complete_borrowed_operation(borrowed_operation, status);
            });
            // Safety: The borrowed_operation pointer and complete_borrowed_operation
            // function are both thread-safe.
            unsafe { Completer::new_unchecked(completer) }
        };

        let async_id = match payload.async_id.with_name("async_id").try_unpack() {
            Ok(x) => x,
            Err(e) => {
                let e = e.context("Missing required field in EthernetTxTransferRequest.");
                error!("{}", e);
                return zx::Status::INVALID_ARGS.into_raw();
            }
        };

        let (packet_address, packet_size) = match (
            payload.packet_address.with_name("packet_address"),
            payload.packet_size.with_name("packet_size"),
        )
            .try_unpack()
        {
            Ok(x) => x,
            Err(e) => {
                let e = e.context("Missing required field(s) in EthernetTxTransferRequest.");
                error!("{}", e);
                return zx::Status::INVALID_ARGS.into_raw();
            }
        };

        let packet_ptr = packet_address as *mut u8;
        if packet_ptr.is_null() {
            error!("EthernetTx.Transfer request contained NULL packet_address");
            return zx::Status::INVALID_ARGS.into_raw();
        }

        // Safety: This call is safe because a `EthernetTx` request is defined such that a slice
        // such as this one can be constructed from the `packet_address` and `packet_size` fields.
        let bytes = unsafe {
            NonNull::new_unchecked(slice::from_raw_parts_mut(packet_ptr, packet_size as usize))
        };

        // Safety: This dereference is safe because the lifetime of this pointer was promised to
        // live as long as function could be called when `EthernetTx::to_ffi` was called.
        match unsafe {
            (*ctx).sender.unbounded_send(EthernetTxEvent {
                bytes,
                async_id: async_id.into(),
                borrowed_operation,
            })
        } {
            Err((error, _event)) => {
                error!("Failed to queue EthernetTx.Transfer request: {}", error);
                zx::Status::INTERNAL.into_raw()
            }
            Ok(()) => zx::Status::OK.into_raw(),
        }
    }
}

pub trait WlanRxEventSender {
    fn unbounded_send(&self, event: WlanRxEvent) -> Result<(), (String, WlanRxEvent)>;
}

#[repr(C)]
pub struct FfiWlanRxCtx {
    sender: Box<dyn WlanRxEventSender>,
    pin: PhantomPinned,
}

#[repr(C)]
pub struct FfiWlanRx {
    ctx: *const FfiWlanRxCtx,
    transfer:
        unsafe extern "C" fn(ctx: *const FfiWlanRxCtx, request: *const u8, request_size: usize),
}

pub struct WlanRx {
    ctx: Pin<Box<FfiWlanRxCtx>>,
}

/// Indicates receipt of a MAC frame.
// TODO(https://fxbug.dev/42119762): We need to keep stats for these events and respond to StatsQueryRequest.
pub struct WlanRxEvent {
    pub bytes: ArenaStaticBox<[u8]>,
    pub rx_info: fidl_softmac::WlanRxInfo,
    pub async_id: trace::Id,
}

impl WlanRx {
    /// Return a pinned `WlanRx`.
    ///
    /// Pinning the returned value is imperative to ensure future `to_c_binding()` calls will return
    /// pointers that are valid for the lifetime of the returned value.
    pub fn new(sender: Box<dyn WlanRxEventSender>) -> Self {
        Self { ctx: Box::pin(FfiWlanRxCtx { sender, pin: PhantomPinned }) }
    }

    /// Returns a `FfiWlanRx` containing functions to queue `WlanRxEvent` values into the
    /// corresponding `WlanRx`.
    ///
    /// Note that the pointers in the returned `FfiWlanRx` are all to static and pinned values
    /// so it's safe to move this `WlanRx` after calling this function.
    ///
    /// # Safety
    ///
    /// This method unsafe because we cannot guarantee the returned `FfiWlanRxCtx`
    /// will have a lifetime that is shorther than this `WlanRx`.
    ///
    /// By using this method, the caller promises the lifetime of this `WlanRx` will exceed the
    /// `ctx` pointer used across the FFI boundary.
    pub unsafe fn to_ffi(&self) -> FfiWlanRx {
        FfiWlanRx {
            ctx: &*self.ctx.as_ref() as *const FfiWlanRxCtx,
            transfer: Self::wlan_rx_transfer,
        }
    }

    /// Queues a WLAN MAC frame into the `WlanRx` for processing.
    ///
    /// # Safety
    ///
    /// Behavior is undefined unless `payload` points to a persisted
    /// `fuchsia.wlan.softmac/WlanRx.Transfer` request of length `payload_len` that is properly
    /// aligned.
    #[no_mangle]
    unsafe extern "C" fn wlan_rx_transfer(
        ctx: *const FfiWlanRxCtx,
        payload: *const u8,
        payload_len: usize,
    ) {
        wtrace::duration!(c"WlanRx transfer");

        // Safety: This call is safe because the caller promises `payload` points to a persisted
        // `fuchsia.wlan.softmac/WlanRx.Transfer` request of length `payload_len` that is properly
        // aligned.
        let payload = unsafe { slice::from_raw_parts(payload, payload_len) };
        let payload = match fidl::unpersist::<fidl_softmac::WlanRxTransferRequest>(payload) {
            Ok(payload) => payload,
            Err(e) => {
                error!("Unable to unpersist WlanRx.Transfer request: {}", e);
                return;
            }
        };

        let async_id = match payload.async_id.with_name("async_id").try_unpack() {
            Ok(x) => x,
            Err(e) => {
                let e = e.context("Missing required field in WlanRxTransferRequest.");
                error!("{}", e);
                return;
            }
        };

        let arena = match payload.arena.with_name("arena").try_unpack() {
            Ok(x) => {
                if x == 0 {
                    error!("Received arena is null");
                    return;
                }
                // Safety: The received arena is assumed to be valid if it's not null.
                unsafe { Arena::from_raw(NonNull::new_unchecked(x as *mut fdf_arena_t)) }
            }
            Err(e) => {
                let e = e.context("Missing required field in WlanRxTransferRequest.");
                error!("{}", e);
                return;
            }
        };

        let (packet_address, packet_size, packet_info) = match (
            payload.packet_address.with_name("packet_address"),
            payload.packet_size.with_name("packet_size"),
            payload.packet_info.with_name("packet_info"),
        )
            .try_unpack()
        {
            Ok(x) => x,
            Err(e) => {
                let e = e.context("Missing required field(s) in WlanRxTransferRequest.");
                error!("{}", e);
                wtrace::async_end_wlansoftmac_rx(async_id.into(), &e.to_string());
                return;
            }
        };

        let packet_ptr = packet_address as *mut u8;
        if packet_ptr.is_null() {
            let e = "WlanRx.Transfer request contained NULL packet_address";
            error!("{}", e);
            wtrace::async_end_wlansoftmac_rx(async_id.into(), e);
            return;
        }

        // Safety: This call is safe because a `WlanRx` request is defined such that a slice
        // such as this one can be constructed from the `packet_address` and `packet_size` fields.
        // Also, the slice is allocated in `arena`.
        let bytes = unsafe {
            arena.assume_unchecked(NonNull::new_unchecked(slice::from_raw_parts_mut(
                packet_ptr,
                packet_size as usize,
            )))
        };
        let bytes = arena.make_static(bytes);

        // Safety: This dereference is safe because the lifetime of this pointer was promised to
        // live as long as function could be called when `WlanRx::to_ffi` was called.
        let _: Result<(), ()> = unsafe {
            (*ctx).sender.unbounded_send(WlanRxEvent {
                bytes,
                rx_info: packet_info,
                async_id: async_id.into(),
            })
        }
        .map_err(|(error, _event)| {
            let e = format!("Failed to queue WlanRx.Transfer request: {}", error);
            error!("{}", error);
            wtrace::async_end_wlansoftmac_rx(async_id.into(), &e);
        });
    }
}