Skip to main content

ebpf_api/maps/
mod.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![allow(non_upper_case_globals)]
6
7mod array;
8mod buffer;
9mod hashmap;
10mod lock;
11mod lpm_trie;
12mod ring_buffer;
13mod vmar;
14
15pub use ring_buffer::RINGBUF_SIGNAL;
16pub(crate) use ring_buffer::{RingBuffer, RingBufferWakeupPolicy};
17
18use ebpf::{BpfValue, EbpfBufferPtr, MapFlags, MapReference, MapSchema};
19use fidl_fuchsia_ebpf as febpf;
20use inspect_stubs::track_stub;
21use linux_uapi::{
22    BPF_EXIST, BPF_NOEXIST, bpf_map_type, bpf_map_type_BPF_MAP_TYPE_ARENA,
23    bpf_map_type_BPF_MAP_TYPE_ARRAY, bpf_map_type_BPF_MAP_TYPE_ARRAY_OF_MAPS,
24    bpf_map_type_BPF_MAP_TYPE_BLOOM_FILTER, bpf_map_type_BPF_MAP_TYPE_CGROUP_ARRAY,
25    bpf_map_type_BPF_MAP_TYPE_CGROUP_STORAGE, bpf_map_type_BPF_MAP_TYPE_CGRP_STORAGE,
26    bpf_map_type_BPF_MAP_TYPE_CPUMAP, bpf_map_type_BPF_MAP_TYPE_DEVMAP,
27    bpf_map_type_BPF_MAP_TYPE_DEVMAP_HASH, bpf_map_type_BPF_MAP_TYPE_HASH,
28    bpf_map_type_BPF_MAP_TYPE_HASH_OF_MAPS, bpf_map_type_BPF_MAP_TYPE_INODE_STORAGE,
29    bpf_map_type_BPF_MAP_TYPE_LPM_TRIE, bpf_map_type_BPF_MAP_TYPE_LRU_HASH,
30    bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH, bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY,
31    bpf_map_type_BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE, bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH,
32    bpf_map_type_BPF_MAP_TYPE_PERF_EVENT_ARRAY, bpf_map_type_BPF_MAP_TYPE_PROG_ARRAY,
33    bpf_map_type_BPF_MAP_TYPE_QUEUE, bpf_map_type_BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
34    bpf_map_type_BPF_MAP_TYPE_RINGBUF, bpf_map_type_BPF_MAP_TYPE_SK_STORAGE,
35    bpf_map_type_BPF_MAP_TYPE_SOCKHASH, bpf_map_type_BPF_MAP_TYPE_SOCKMAP,
36    bpf_map_type_BPF_MAP_TYPE_STACK, bpf_map_type_BPF_MAP_TYPE_STACK_TRACE,
37    bpf_map_type_BPF_MAP_TYPE_STRUCT_OPS, bpf_map_type_BPF_MAP_TYPE_TASK_STORAGE,
38    bpf_map_type_BPF_MAP_TYPE_UNSPEC, bpf_map_type_BPF_MAP_TYPE_USER_RINGBUF,
39    bpf_map_type_BPF_MAP_TYPE_XSKMAP,
40};
41use std::fmt::Debug;
42use std::ops::Deref;
43use std::pin::Pin;
44use std::sync::Arc;
45use zx::HandleBased;
46
47use crate::maps::buffer::VmoOrName;
48
49#[derive(Debug, Eq, PartialEq)]
50pub enum MapError {
51    // Equivalent of EINVAL.
52    InvalidParam,
53
54    // No entry with the specified key,
55    InvalidKey,
56
57    // Entry already exists..
58    EntryExists,
59
60    // Map size limit has been reached.
61    SizeLimit,
62
63    // Cannot allocate memory.
64    NoMemory,
65
66    // Invalid VMO was passed for a shared map.
67    InvalidVmo,
68
69    // Specified map type is not supported.
70    MapTypeNotSupported,
71
72    // Specified map configuration is not supported.
73    NotSupported,
74
75    // An internal issue, e.g. failed to allocate VMO.
76    Internal,
77}
78const SUPPORTED_FLAGS: MapFlags = MapFlags::NoPrealloc
79    .union(MapFlags::SyscallReadOnly)
80    .union(MapFlags::SyscallWriteOnly)
81    .union(MapFlags::Mmapable);
82
83fn map_flags_from_fidl(flags: febpf::MapFlags) -> MapFlags {
84    let mut r = MapFlags::empty();
85    if flags.contains(febpf::MapFlags::NO_PREALLOC) {
86        r = r | MapFlags::NoPrealloc;
87    }
88    if flags.contains(febpf::MapFlags::SYSCALL_READ_ONLY) {
89        r = r | MapFlags::SyscallReadOnly;
90    }
91    if flags.contains(febpf::MapFlags::SYSCALL_WRITE_ONLY) {
92        r = r | MapFlags::SyscallWriteOnly;
93    }
94    if flags.contains(febpf::MapFlags::MMAPABLE) {
95        r = r | MapFlags::Mmapable;
96    }
97    r
98}
99
100fn map_flags_to_fidl(flags: MapFlags) -> Result<febpf::MapFlags, MapError> {
101    if flags.contains(!SUPPORTED_FLAGS) {
102        return Err(MapError::NotSupported);
103    }
104
105    let mut r = febpf::MapFlags::empty();
106    if flags.contains(MapFlags::NoPrealloc) {
107        r = r | febpf::MapFlags::NO_PREALLOC;
108    }
109    if flags.contains(MapFlags::SyscallReadOnly) {
110        r = r | febpf::MapFlags::SYSCALL_READ_ONLY;
111    }
112    if flags.contains(MapFlags::SyscallWriteOnly) {
113        r = r | febpf::MapFlags::SYSCALL_WRITE_ONLY;
114    }
115    if flags.contains(MapFlags::Mmapable) {
116        r = r | febpf::MapFlags::MMAPABLE;
117    }
118    Ok(r)
119}
120
121fn validate_map_flags(schema: &MapSchema) -> Result<(), MapError> {
122    let flags = schema.flags;
123    if flags.contains(!SUPPORTED_FLAGS) {
124        return Err(MapError::InvalidParam);
125    }
126
127    // Read-only and write-only flags are mutually exclusive.
128    if flags.contains(MapFlags::SyscallReadOnly) && flags.contains(MapFlags::SyscallWriteOnly) {
129        return Err(MapError::InvalidParam);
130    }
131
132    // `MMAPABLE` is valid only for arrays.
133    if flags.contains(MapFlags::Mmapable) && schema.map_type != bpf_map_type_BPF_MAP_TYPE_ARRAY {
134        return Err(MapError::InvalidParam);
135    }
136
137    Ok(())
138}
139
140trait MapImpl: Send + Sync + Debug {
141    fn lookup<'a>(&'a self, key: &[u8]) -> Option<MapValueRef<'a>>;
142    fn update(&self, key: &[u8], value: EbpfBufferPtr<'_>, flags: u64) -> Result<(), MapError>;
143    fn delete(&self, key: &[u8]) -> Result<(), MapError>;
144    fn get_next_key(&self, key: Option<&[u8]>) -> Result<MapKey, MapError>;
145    fn vmo(&self) -> &Arc<zx::Vmo>;
146
147    // Returns true if `POLLIN` is signaled for the map FD. Should be
148    // overridden only for ring buffers.
149    fn can_read(&self) -> Option<bool> {
150        None
151    }
152
153    fn ringbuf_reserve(&self, _size: u32, _flags: u64) -> Result<usize, MapError> {
154        Err(MapError::InvalidParam)
155    }
156}
157
158/// A BPF map. This is a hashtable that can be accessed both by BPF programs and userspace.
159#[derive(Debug)]
160pub struct Map {
161    pub schema: MapSchema,
162
163    // The impl because it's required for some map implementations need to be
164    // pinned, particularly ring buffers.
165    map_impl: Pin<Box<dyn MapImpl + Sync>>,
166}
167
168/// Maps are normally kept pinned in memory since linked eBPF programs store direct pointers to
169/// the maps they depend on.
170#[derive(Debug, Clone)]
171pub struct PinnedMap(Pin<Arc<Map>>);
172
173impl Deref for PinnedMap {
174    type Target = Map;
175    fn deref(&self) -> &Self::Target {
176        self.0.deref()
177    }
178}
179
180impl MapReference for PinnedMap {
181    fn schema(&self) -> &MapSchema {
182        &self.0.schema
183    }
184
185    fn as_bpf_value(&self) -> BpfValue {
186        BpfValue::from(self.deref() as *const Map)
187    }
188
189    fn get_data_ptr(&self) -> Option<BpfValue> {
190        assert!(self.0.schema.map_type == bpf_map_type_BPF_MAP_TYPE_ARRAY);
191
192        let key = [0u8; 4];
193        self.0.lookup(&key).map(|v| BpfValue::from(v.ptr().raw_ptr()))
194    }
195}
196
197// Avoid allocation for eBPF keys smaller than 16 bytes.
198pub type MapKey = smallvec::SmallVec<[u8; 16]>;
199
200// Avoid allocation for eBPF values smaller than 64 bytes.
201pub type MapValue = smallvec::SmallVec<[u8; 64]>;
202
203// Access rights required for a map VMO handle. Should be consistent with the
204// rights specified in FIDL. READ, WRITE and MAP rights are required to access
205// the map contents. SIGNAL and WAIT rights are used for synchronization.
206// LINT.IfChange(map_rights)
207const BASE_MAP_RIGHTS: zx::Rights = zx::Rights::READ
208    .union(zx::Rights::WRITE)
209    .union(zx::Rights::MAP)
210    .union(zx::Rights::SIGNAL)
211    .union(zx::Rights::WAIT);
212// LINT.ThenChange(//sdk/fidl/fuchsia.ebpf/ebpf.fidl:map_rights)
213
214// Rights for the VMO handle when sharing a map.
215const SHARED_MAP_RIGHTS: zx::Rights = BASE_MAP_RIGHTS.union(zx::Rights::TRANSFER);
216
217impl Map {
218    pub fn new(schema: MapSchema, name: &str) -> Result<PinnedMap, MapError> {
219        validate_map_flags(&schema)?;
220        let map_impl = create_map_impl(&schema, name.to_string())?;
221        Ok(PinnedMap(Arc::pin(Self { schema, map_impl })))
222    }
223
224    pub fn new_shared(shared: febpf::Map) -> Result<PinnedMap, MapError> {
225        let febpf::Map { schema: Some(fidl_schema), vmo: Some(vmo), .. } = shared else {
226            return Err(MapError::InvalidParam);
227        };
228
229        // Check VMO rights.
230        let vmo_info = vmo.basic_info().map_err(|_| MapError::InvalidVmo)?;
231        if !vmo_info.rights.contains(BASE_MAP_RIGHTS) {
232            return Err(MapError::InvalidVmo);
233        }
234
235        let schema = MapSchema {
236            map_type: fidl_map_type_to_bpf_map_type(fidl_schema.type_),
237            key_size: fidl_schema.key_size,
238            value_size: fidl_schema.value_size,
239            max_entries: fidl_schema.max_entries,
240            flags: map_flags_from_fidl(fidl_schema.flags),
241        };
242
243        let map_impl = create_map_impl(&schema, vmo)?;
244        Ok(PinnedMap(Arc::pin(Self { schema, map_impl })))
245    }
246
247    pub fn share(&self) -> Result<febpf::Map, MapError> {
248        Ok(febpf::Map {
249            schema: Some(febpf::MapSchema {
250                type_: bpf_map_type_to_fidl_map_type(self.schema.map_type),
251                key_size: self.schema.key_size,
252                value_size: self.schema.value_size,
253                max_entries: self.schema.max_entries,
254                flags: map_flags_to_fidl(self.schema.flags)?,
255            }),
256            vmo: Some(
257                self.map_impl
258                    .vmo()
259                    .duplicate_handle(SHARED_MAP_RIGHTS)
260                    .map_err(|_| MapError::Internal)?,
261            ),
262            ..Default::default()
263        })
264    }
265
266    pub fn lookup<'a>(&'a self, key: &[u8]) -> Option<MapValueRef<'a>> {
267        self.map_impl.lookup(key)
268    }
269
270    pub fn load(&self, key: &[u8]) -> Option<MapValue> {
271        self.lookup(key).map(|v| v.ptr().load())
272    }
273
274    pub fn update(&self, key: &[u8], value: EbpfBufferPtr<'_>, flags: u64) -> Result<(), MapError> {
275        if flags & (BPF_EXIST as u64) > 0 && flags & (BPF_NOEXIST as u64) > 0 {
276            return Err(MapError::InvalidParam);
277        }
278
279        self.map_impl.update(key, value, flags)
280    }
281
282    pub fn delete(&self, key: &[u8]) -> Result<(), MapError> {
283        self.map_impl.delete(key)
284    }
285
286    pub fn get_next_key(&self, key: Option<&[u8]>) -> Result<MapKey, MapError> {
287        self.map_impl.get_next_key(key)
288    }
289
290    pub fn vmo(&self) -> &Arc<zx::Vmo> {
291        self.map_impl.vmo()
292    }
293
294    pub fn can_read(&self) -> Option<bool> {
295        self.map_impl.can_read()
296    }
297
298    pub fn ringbuf_reserve(&self, size: u32, flags: u64) -> Result<usize, MapError> {
299        self.map_impl.ringbuf_reserve(size, flags)
300    }
301
302    pub fn uses_locks(&self) -> bool {
303        self.schema.map_type != bpf_map_type_BPF_MAP_TYPE_ARRAY
304    }
305}
306
307pub enum MapValueRef<'a> {
308    PlainRef(EbpfBufferPtr<'a>),
309    HashMapRef(hashmap::HashMapEntryRef<'a>),
310    LpmTrieRef(lpm_trie::LpmTrieEntryRef<'a>),
311}
312
313impl<'a> MapValueRef<'a> {
314    fn new(buf: EbpfBufferPtr<'a>) -> Self {
315        Self::PlainRef(buf)
316    }
317
318    fn new_from_hashmap(hash_map_ref: hashmap::HashMapEntryRef<'a>) -> Self {
319        Self::HashMapRef(hash_map_ref)
320    }
321
322    fn new_from_lpm_trie(lpm_trie_ref: lpm_trie::LpmTrieEntryRef<'a>) -> Self {
323        Self::LpmTrieRef(lpm_trie_ref)
324    }
325
326    pub fn is_ref_counted(&self) -> bool {
327        match self {
328            Self::PlainRef(_) => false,
329            Self::HashMapRef(_) | Self::LpmTrieRef(_) => true,
330        }
331    }
332
333    pub fn ptr(&self) -> EbpfBufferPtr<'a> {
334        match self {
335            Self::PlainRef(buf) => *buf,
336            Self::HashMapRef(hash_map_ref) => hash_map_ref.ptr(),
337            Self::LpmTrieRef(lpm_trie_ref) => lpm_trie_ref.ptr(),
338        }
339    }
340}
341
342const SK_STORAGE_MAX_ENTRIES: u32 = 8192;
343
344fn create_map_impl(
345    schema: &MapSchema,
346    vmo: impl Into<VmoOrName>,
347) -> Result<Pin<Box<dyn MapImpl>>, MapError> {
348    // The list of supported maps should be kept in sync with the enum values in
349    // `fuchsia.ebpf.MapType`.
350    match schema.map_type {
351        // LINT.IfChange(supported_maps)
352        bpf_map_type_BPF_MAP_TYPE_ARRAY => Ok(Box::pin(array::Array::new(schema, vmo)?)),
353        bpf_map_type_BPF_MAP_TYPE_HASH => Ok(Box::pin(hashmap::HashMap::new(schema, vmo)?)),
354        bpf_map_type_BPF_MAP_TYPE_RINGBUF => Ok(ring_buffer::RingBuffer::new(schema, vmo)?),
355        bpf_map_type_BPF_MAP_TYPE_LPM_TRIE => Ok(Box::pin(lpm_trie::LpmTrie::new(schema, vmo)?)),
356        bpf_map_type_BPF_MAP_TYPE_SK_STORAGE => {
357            if schema.key_size != 4
358                || schema.max_entries != 0
359                || schema.flags != MapFlags::NoPrealloc
360            {
361                return Err(MapError::InvalidParam);
362            }
363
364            // SK_STORAGE maps are implemented as hashmaps with socket cookie used as a key.
365            let schema = MapSchema {
366                map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
367                key_size: 8,
368                max_entries: SK_STORAGE_MAX_ENTRIES,
369                value_size: schema.value_size,
370                flags: MapFlags::NoPrealloc,
371            };
372            Ok(Box::pin(hashmap::HashMap::new(&schema, vmo)?))
373        }
374
375        // These types are in use, but not yet implemented. Incorrectly use Array or Hash for
376        // these
377        bpf_map_type_BPF_MAP_TYPE_DEVMAP_HASH => {
378            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_DEVMAP_HASH");
379            // `BPF_F_RDONLY_PROG` is not yet implemented, but it's always set
380            // for `DEVMAP` maps.
381            let schema =
382                MapSchema { flags: schema.flags.difference(MapFlags::ProgReadOnly), ..*schema };
383            Ok(Box::pin(hashmap::HashMap::new(&schema, vmo)?))
384        }
385        bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH => {
386            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PERCPU_HASH");
387            Ok(Box::pin(hashmap::HashMap::new(schema, vmo)?))
388        }
389        bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY => {
390            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PERCPU_ARRAY");
391            Ok(Box::pin(array::Array::new(schema, vmo)?))
392        }
393        bpf_map_type_BPF_MAP_TYPE_LRU_HASH => {
394            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_LRU_HASH");
395            Ok(Box::pin(hashmap::HashMap::new(schema, vmo)?))
396        }
397        // LINT.ThenChange(:fidl_map_types)
398
399        // Unimplemented types
400        bpf_map_type_BPF_MAP_TYPE_UNSPEC => {
401            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_UNSPEC");
402            Err(MapError::MapTypeNotSupported)
403        }
404        bpf_map_type_BPF_MAP_TYPE_PROG_ARRAY => {
405            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PROG_ARRAY");
406            Err(MapError::MapTypeNotSupported)
407        }
408        bpf_map_type_BPF_MAP_TYPE_PERF_EVENT_ARRAY => {
409            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PERF_EVENT_ARRAY");
410            Err(MapError::MapTypeNotSupported)
411        }
412        bpf_map_type_BPF_MAP_TYPE_STACK_TRACE => {
413            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_STACK_TRACE");
414            Err(MapError::MapTypeNotSupported)
415        }
416        bpf_map_type_BPF_MAP_TYPE_CGROUP_ARRAY => {
417            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_CGROUP_ARRAY");
418            Err(MapError::MapTypeNotSupported)
419        }
420        bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH => {
421            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_LRU_PERCPU_HASH");
422            Err(MapError::MapTypeNotSupported)
423        }
424        bpf_map_type_BPF_MAP_TYPE_ARRAY_OF_MAPS => {
425            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_ARRAY_OF_MAPS");
426            Err(MapError::MapTypeNotSupported)
427        }
428        bpf_map_type_BPF_MAP_TYPE_HASH_OF_MAPS => {
429            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_HASH_OF_MAPS");
430            Err(MapError::MapTypeNotSupported)
431        }
432        bpf_map_type_BPF_MAP_TYPE_DEVMAP => {
433            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_DEVMAP");
434            Err(MapError::MapTypeNotSupported)
435        }
436        bpf_map_type_BPF_MAP_TYPE_SOCKMAP => {
437            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_SOCKMAP");
438            Err(MapError::MapTypeNotSupported)
439        }
440        bpf_map_type_BPF_MAP_TYPE_CPUMAP => {
441            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_CPUMAP");
442            Err(MapError::MapTypeNotSupported)
443        }
444        bpf_map_type_BPF_MAP_TYPE_XSKMAP => {
445            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_XSKMAP");
446            Err(MapError::MapTypeNotSupported)
447        }
448        bpf_map_type_BPF_MAP_TYPE_SOCKHASH => {
449            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_SOCKHASH");
450            Err(MapError::MapTypeNotSupported)
451        }
452        bpf_map_type_BPF_MAP_TYPE_CGROUP_STORAGE => {
453            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_CGROUP_STORAGE");
454            Err(MapError::MapTypeNotSupported)
455        }
456        bpf_map_type_BPF_MAP_TYPE_REUSEPORT_SOCKARRAY => {
457            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_REUSEPORT_SOCKARRAY");
458            Err(MapError::MapTypeNotSupported)
459        }
460        bpf_map_type_BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE => {
461            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE");
462            Err(MapError::MapTypeNotSupported)
463        }
464        bpf_map_type_BPF_MAP_TYPE_QUEUE => {
465            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_QUEUE");
466            Err(MapError::MapTypeNotSupported)
467        }
468        bpf_map_type_BPF_MAP_TYPE_STACK => {
469            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_STACK");
470            Err(MapError::MapTypeNotSupported)
471        }
472        bpf_map_type_BPF_MAP_TYPE_STRUCT_OPS => {
473            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_STRUCT_OPS");
474            Err(MapError::MapTypeNotSupported)
475        }
476        bpf_map_type_BPF_MAP_TYPE_INODE_STORAGE => {
477            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_INODE_STORAGE");
478            Err(MapError::MapTypeNotSupported)
479        }
480        bpf_map_type_BPF_MAP_TYPE_TASK_STORAGE => {
481            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_TASK_STORAGE");
482            Err(MapError::MapTypeNotSupported)
483        }
484        bpf_map_type_BPF_MAP_TYPE_BLOOM_FILTER => {
485            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_BLOOM_FILTER");
486            Err(MapError::MapTypeNotSupported)
487        }
488        bpf_map_type_BPF_MAP_TYPE_USER_RINGBUF => {
489            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_USER_RINGBUF");
490            Err(MapError::MapTypeNotSupported)
491        }
492        bpf_map_type_BPF_MAP_TYPE_CGRP_STORAGE => {
493            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_CGRP_STORAGE");
494            Err(MapError::MapTypeNotSupported)
495        }
496        bpf_map_type_BPF_MAP_TYPE_ARENA => {
497            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_ARENA");
498            Err(MapError::MapTypeNotSupported)
499        }
500        _ => {
501            track_stub!(
502                TODO("https://fxbug.dev/323847465"),
503                "unknown bpf map type",
504                schema.map_type
505            );
506            Err(MapError::InvalidParam)
507        }
508    }
509}
510
511pub fn compute_map_storage_size(schema: &MapSchema) -> Result<usize, MapError> {
512    schema.value_size.checked_mul(schema.max_entries).map(|v| v as usize).ok_or(MapError::NoMemory)
513}
514
515// LINT.IfChange(fidl_map_types)
516fn bpf_map_type_to_fidl_map_type(map_type: bpf_map_type) -> febpf::MapType {
517    match map_type {
518        bpf_map_type_BPF_MAP_TYPE_ARRAY => febpf::MapType::Array,
519        bpf_map_type_BPF_MAP_TYPE_HASH => febpf::MapType::HashMap,
520        bpf_map_type_BPF_MAP_TYPE_RINGBUF => febpf::MapType::RingBuffer,
521        bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY => febpf::MapType::PercpuArray,
522        bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH => febpf::MapType::PercpuHash,
523        bpf_map_type_BPF_MAP_TYPE_DEVMAP_HASH => febpf::MapType::DevmapHash,
524        bpf_map_type_BPF_MAP_TYPE_LPM_TRIE => febpf::MapType::LpmTrie,
525        bpf_map_type_BPF_MAP_TYPE_LRU_HASH => febpf::MapType::LruHash,
526        bpf_map_type_BPF_MAP_TYPE_SK_STORAGE => febpf::MapType::SkStorage,
527        _ =>
528        // Other map types are rejected in `create_map_impl()`.
529        {
530            unreachable!("unsupported map type {:?}", map_type)
531        }
532    }
533}
534
535fn fidl_map_type_to_bpf_map_type(map_type: febpf::MapType) -> bpf_map_type {
536    match map_type {
537        febpf::MapType::Array => bpf_map_type_BPF_MAP_TYPE_ARRAY,
538        febpf::MapType::HashMap => bpf_map_type_BPF_MAP_TYPE_HASH,
539        febpf::MapType::RingBuffer => bpf_map_type_BPF_MAP_TYPE_RINGBUF,
540        febpf::MapType::PercpuArray => bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY,
541        febpf::MapType::PercpuHash => bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH,
542        febpf::MapType::DevmapHash => bpf_map_type_BPF_MAP_TYPE_DEVMAP_HASH,
543        febpf::MapType::LpmTrie => bpf_map_type_BPF_MAP_TYPE_LPM_TRIE,
544        febpf::MapType::LruHash => bpf_map_type_BPF_MAP_TYPE_LRU_HASH,
545        febpf::MapType::SkStorage => bpf_map_type_BPF_MAP_TYPE_SK_STORAGE,
546    }
547}
548// LINT.ThenChange(:supported_maps, //sdk/fidl/fuchsia.ebpf/ebpf.fidl:map_types)
549
550#[cfg(test)]
551mod test {
552    use super::*;
553
554    #[fuchsia::test]
555    fn test_sharing_array() {
556        let schema = MapSchema {
557            map_type: bpf_map_type_BPF_MAP_TYPE_ARRAY,
558            key_size: 4,
559            value_size: 4,
560            max_entries: 10,
561            flags: MapFlags::empty(),
562        };
563
564        // Create two array maps sharing the content.
565        let map1 = Map::new(schema, "test").unwrap();
566        let map2 = Map::new_shared(map1.share().unwrap()).unwrap();
567
568        // Set a value in one map and check that it's updated in the other.
569        let key = vec![0, 0, 0, 0];
570        let mut value = [0, 1, 2, 3];
571        map1.update(&MapKey::from_vec(key.clone()), (&mut value).into(), 0).unwrap();
572        assert_eq!(&map2.load(&key).unwrap()[..], &value);
573    }
574
575    #[fuchsia::test]
576    fn test_sharing_hash_map() {
577        let schema = MapSchema {
578            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
579            key_size: 4,
580            value_size: 4,
581            max_entries: 10,
582            flags: MapFlags::empty(),
583        };
584
585        // Create two array maps sharing the content.
586        let map1 = Map::new(schema, "test").unwrap();
587        let map2 = Map::new_shared(map1.share().unwrap()).unwrap();
588
589        // Set a value in one map and check that it's updated in the other.
590        let key = vec![0, 0, 0, 0];
591        let mut value = [0, 1, 2, 3];
592        map1.update(&MapKey::from_vec(key.clone()), (&mut value).into(), 0).unwrap();
593        assert_eq!(&map2.load(&key).unwrap()[..], &value);
594    }
595
596    #[fuchsia::test]
597    fn test_hash_map() {
598        let schema = MapSchema {
599            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
600            key_size: 5,
601            value_size: 25,
602            max_entries: 10000,
603            flags: MapFlags::empty(),
604        };
605
606        let get_key = |i| {
607            MapKey::from_vec(vec![
608                (i & 0xffusize) as u8,
609                0,
610                ((i >> 4) & 0xffusize) as u8,
611                0,
612                ((i >> 8) & 0xffusize) as u8,
613            ])
614        };
615        let get_value = |i, v| format!("--{:010} {:010}--", i, v).into_bytes();
616
617        let map = Map::new(schema, "test").unwrap();
618
619        for i in 0..10000 {
620            assert!(map.update(&get_key(i), (&mut get_value(i, 0)).into(), 0).is_ok());
621        }
622
623        // Should fail to add another entry when the map is full.
624        assert_eq!(
625            map.update(&get_key(10001), (&mut get_value(10001, 1)).into(), 0),
626            Err(MapError::SizeLimit)
627        );
628
629        for i in 0..10000 {
630            assert_eq!(&map.load(&get_key(i)).unwrap()[..], &get_value(i, 0));
631        }
632
633        // Update some elements.
634        for i in 8000..9000 {
635            assert!(map.update(&get_key(i), (&mut get_value(i, 1)).into(), 0).is_ok());
636        }
637        for i in 8000..9000 {
638            assert_eq!(&map.load(&get_key(i)).unwrap()[..], &get_value(i, 1));
639        }
640
641        // Delete half of the entries.
642        for i in 5000..10000 {
643            assert!(map.delete(&get_key(i)).is_ok());
644        }
645        for i in 5000..10000 {
646            assert_eq!(map.load(&get_key(i)), None);
647        }
648
649        // Replace removed entries with new ones
650        for i in 10000..15000 {
651            assert!(map.update(&get_key(i), (&mut get_value(i, 2)).into(), 0).is_ok());
652        }
653
654        for i in 0..5000 {
655            assert_eq!(&map.load(&get_key(i)).unwrap()[..], &get_value(i, 0));
656        }
657        for i in 10000..15000 {
658            assert_eq!(&map.load(&get_key(i)).unwrap()[..], &get_value(i, 2));
659        }
660    }
661
662    #[fuchsia::test]
663    fn test_hash_map_update_direct() {
664        let schema = MapSchema {
665            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
666            key_size: 5,
667            value_size: 11,
668            max_entries: 10,
669            flags: MapFlags::empty(),
670        };
671
672        let map = Map::new(schema, "test").unwrap();
673        let key = MapKey::from_vec("12345".to_string().into_bytes());
674        let mut value = (0..11).collect::<Vec<u8>>();
675        assert!(map.update(&key.clone(), (&mut value).into(), 0).is_ok());
676
677        // Access a value directly the way eBPF programs do.
678        let value_ref = map.lookup(&key).unwrap();
679        #[allow(
680            clippy::undocumented_unsafe_blocks,
681            reason = "Force documented unsafe blocks in Starnix"
682        )]
683        unsafe {
684            *value_ref.ptr().get_ptr::<u32>(0).unwrap().deref_mut() = 0xabacadae;
685        }
686
687        assert_eq!(&map.load(&key).unwrap()[..], &[0xae, 0xad, 0xac, 0xab, 4, 5, 6, 7, 8, 9, 10]);
688    }
689
690    #[fuchsia::test]
691    fn test_hash_map_ref_counting() {
692        let schema = MapSchema {
693            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
694            key_size: 5,
695            value_size: 11,
696            max_entries: 2,
697            flags: MapFlags::empty(),
698        };
699
700        let map = Map::new(schema, "test").unwrap();
701        let key = MapKey::from_vec("12345".to_string().into_bytes());
702        let key2 = MapKey::from_vec("24122".to_string().into_bytes());
703        let mut value = (0..11).collect::<Vec<u8>>();
704        assert!(map.update(&key.clone(), (&mut value).into(), 0).is_ok());
705        assert!(map.update(&key2.clone(), (&mut value).into(), 0).is_ok());
706
707        let value_ref = map.lookup(&key).unwrap();
708
709        // Delete an element. The corresponding data entry should not be
710        // released until `value_ref` is dropped.
711        assert!(map.delete(&key).is_ok());
712        assert_eq!(map.update(&key.clone(), (&mut value).into(), 0), Err(MapError::SizeLimit));
713        drop(value_ref);
714        assert!(map.update(&key.clone(), (&mut value).into(), 0).is_ok());
715    }
716
717    #[fuchsia::test]
718    fn test_ringbug_sharing() {
719        let schema = MapSchema {
720            map_type: bpf_map_type_BPF_MAP_TYPE_RINGBUF,
721            key_size: 0,
722            value_size: 0,
723            max_entries: 4096 * 2,
724            flags: MapFlags::empty(),
725        };
726
727        let map = Map::new(schema, "test").unwrap();
728        map.ringbuf_reserve(8000, 0).expect("ringbuf_reserve failed");
729
730        let map2 = Map::new_shared(map.share().unwrap()).unwrap();
731
732        // Expected to fail since there is no space left.
733        map2.ringbuf_reserve(2000, 0).expect_err("ringbuf_reserve expected to fail");
734    }
735
736    // Verifies that all supported map types are shareable.
737    #[fuchsia::test]
738    fn test_all_maps_shareable() {
739        for map_type in 1..linux_uapi::bpf_map_type___MAX_BPF_MAP_TYPE {
740            let (key_size, value_size, max_entries, flags) = match map_type {
741                bpf_map_type_BPF_MAP_TYPE_RINGBUF => (0, 0, 4096, MapFlags::empty()),
742                bpf_map_type_BPF_MAP_TYPE_LPM_TRIE => (8, 4, 4096, MapFlags::NoPrealloc),
743                bpf_map_type_BPF_MAP_TYPE_SK_STORAGE => (4, 4, 0, MapFlags::NoPrealloc),
744                _ => (4, 4, 1, MapFlags::empty()),
745            };
746            let schema = MapSchema { map_type, key_size, value_size, max_entries, flags };
747
748            let map = match Map::new(schema, "test") {
749                Ok(map) => map,
750                Err(MapError::MapTypeNotSupported) => {
751                    continue;
752                }
753                Err(e) => {
754                    panic!("Failed to create map of type {:?}: {:?}", map_type, e);
755                }
756            };
757
758            let map_fidl = map.share().expect("Failed to share map");
759            let _: PinnedMap = Map::new_shared(map_fidl).expect("Failed to initialize shared map");
760        }
761    }
762}