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;
45
46use crate::maps::buffer::VmoOrName;
47
48#[derive(Debug, Eq, PartialEq)]
49pub enum MapError {
50    // Equivalent of EINVAL.
51    InvalidParam,
52
53    // No entry with the specified key,
54    InvalidKey,
55
56    // Entry already exists..
57    EntryExists,
58
59    // Map size limit has been reached.
60    SizeLimit,
61
62    // Cannot allocate memory.
63    NoMemory,
64
65    // Invalid VMO was passed for a shared map.
66    InvalidVmo,
67
68    // Specified map type is not supported.
69    MapTypeNotSupported,
70
71    // Specified map configuration is not supported.
72    NotSupported,
73
74    // An internal issue, e.g. failed to allocate VMO.
75    Internal,
76}
77const SUPPORTED_FLAGS: MapFlags = MapFlags::NoPrealloc
78    .union(MapFlags::SyscallReadOnly)
79    .union(MapFlags::SyscallWriteOnly)
80    .union(MapFlags::Mmapable)
81    .union(MapFlags::Clone)
82    .union(MapFlags::ProgReadOnly);
83
84fn map_flags_from_fidl(flags: febpf::MapFlags) -> MapFlags {
85    let mut r = MapFlags::empty();
86    if flags.contains(febpf::MapFlags::NO_PREALLOC) {
87        r = r | MapFlags::NoPrealloc;
88    }
89    if flags.contains(febpf::MapFlags::SYSCALL_READ_ONLY) {
90        r = r | MapFlags::SyscallReadOnly;
91    }
92    if flags.contains(febpf::MapFlags::SYSCALL_WRITE_ONLY) {
93        r = r | MapFlags::SyscallWriteOnly;
94    }
95    if flags.contains(febpf::MapFlags::MMAPABLE) {
96        r = r | MapFlags::Mmapable;
97    }
98    if flags.contains(febpf::MapFlags::CLONE) {
99        r = r | MapFlags::Clone;
100    }
101    if flags.contains(febpf::MapFlags::PROG_READ_ONLY) {
102        r = r | MapFlags::ProgReadOnly;
103    }
104    r
105}
106
107fn map_flags_to_fidl(flags: MapFlags) -> Result<febpf::MapFlags, MapError> {
108    if flags.contains(!SUPPORTED_FLAGS) {
109        return Err(MapError::NotSupported);
110    }
111
112    let mut r = febpf::MapFlags::empty();
113    if flags.contains(MapFlags::NoPrealloc) {
114        r = r | febpf::MapFlags::NO_PREALLOC;
115    }
116    if flags.contains(MapFlags::SyscallReadOnly) {
117        r = r | febpf::MapFlags::SYSCALL_READ_ONLY;
118    }
119    if flags.contains(MapFlags::SyscallWriteOnly) {
120        r = r | febpf::MapFlags::SYSCALL_WRITE_ONLY;
121    }
122    if flags.contains(MapFlags::Mmapable) {
123        r = r | febpf::MapFlags::MMAPABLE;
124    }
125    if flags.contains(MapFlags::Clone) {
126        r = r | febpf::MapFlags::CLONE;
127    }
128    if flags.contains(MapFlags::ProgReadOnly) {
129        r = r | febpf::MapFlags::PROG_READ_ONLY;
130    }
131    Ok(r)
132}
133
134fn validate_map_flags(schema: &MapSchema) -> Result<(), MapError> {
135    let flags = schema.flags;
136    if flags.contains(!SUPPORTED_FLAGS) {
137        return Err(MapError::InvalidParam);
138    }
139
140    // Read-only and write-only flags are mutually exclusive.
141    if flags.contains(MapFlags::SyscallReadOnly) && flags.contains(MapFlags::SyscallWriteOnly) {
142        return Err(MapError::InvalidParam);
143    }
144
145    // `MMAPABLE` is valid only for arrays.
146    if flags.contains(MapFlags::Mmapable) && schema.map_type != bpf_map_type_BPF_MAP_TYPE_ARRAY {
147        return Err(MapError::InvalidParam);
148    }
149
150    // `CLONE` is valid only for socket storage maps.
151    if flags.contains(MapFlags::Clone) && schema.map_type != bpf_map_type_BPF_MAP_TYPE_SK_STORAGE {
152        return Err(MapError::InvalidParam);
153    }
154
155    // `ProgReadOnly` is not valid for socket storage or ring buffer maps.
156    if flags.contains(MapFlags::ProgReadOnly)
157        && (schema.map_type == bpf_map_type_BPF_MAP_TYPE_SK_STORAGE
158            || schema.map_type == bpf_map_type_BPF_MAP_TYPE_RINGBUF)
159    {
160        return Err(MapError::InvalidParam);
161    }
162
163    Ok(())
164}
165
166trait MapImpl: Send + Sync + Debug {
167    fn lookup<'a>(&'a self, key: &[u8]) -> Option<MapValueRef<'a>>;
168    fn update(&self, key: &[u8], value: EbpfBufferPtr<'_>, flags: u64) -> Result<(), MapError>;
169    fn delete(&self, key: &[u8]) -> Result<(), MapError>;
170    fn get_next_key(&self, key: Option<&[u8]>) -> Result<MapKey, MapError>;
171    fn vmo(&self) -> &Arc<zx::Vmo>;
172
173    // Returns true if `POLLIN` is signaled for the map FD. Should be
174    // overridden only for ring buffers.
175    fn can_read(&self) -> Option<bool> {
176        None
177    }
178
179    fn ringbuf_reserve(&self, _size: u32, _flags: u64) -> Result<usize, MapError> {
180        Err(MapError::InvalidParam)
181    }
182}
183
184/// A BPF map. This is a hashtable that can be accessed both by BPF programs and userspace.
185#[derive(Debug)]
186pub struct Map {
187    pub schema: MapSchema,
188
189    // The impl because it's required for some map implementations need to be
190    // pinned, particularly ring buffers.
191    map_impl: Pin<Box<dyn MapImpl + Sync>>,
192}
193
194/// Maps are normally kept pinned in memory since linked eBPF programs store direct pointers to
195/// the maps they depend on.
196#[derive(Debug, Clone)]
197pub struct PinnedMap(Pin<Arc<Map>>);
198
199impl Deref for PinnedMap {
200    type Target = Map;
201    fn deref(&self) -> &Self::Target {
202        self.0.deref()
203    }
204}
205
206impl MapReference for PinnedMap {
207    fn schema(&self) -> &MapSchema {
208        &self.0.schema
209    }
210
211    fn as_bpf_value(&self) -> BpfValue {
212        BpfValue::from(self.deref() as *const Map)
213    }
214
215    fn get_data_ptr(&self) -> Option<BpfValue> {
216        assert!(self.0.schema.map_type == bpf_map_type_BPF_MAP_TYPE_ARRAY);
217
218        let key = [0u8; 4];
219        self.0.lookup(&key).map(|v| BpfValue::from(v.ptr().raw_ptr()))
220    }
221}
222
223// Avoid allocation for eBPF keys smaller than 16 bytes.
224pub type MapKey = smallvec::SmallVec<[u8; 16]>;
225
226// Avoid allocation for eBPF values smaller than 64 bytes.
227pub type MapValue = smallvec::SmallVec<[u8; 64]>;
228
229// Access rights required for a map VMO handle. Should be consistent with the
230// rights specified in FIDL. READ, WRITE and MAP rights are required to access
231// the map contents. SIGNAL and WAIT rights are used for synchronization.
232// LINT.IfChange(map_rights)
233const BASE_MAP_RIGHTS: zx::Rights = zx::Rights::READ
234    .union(zx::Rights::WRITE)
235    .union(zx::Rights::MAP)
236    .union(zx::Rights::SIGNAL)
237    .union(zx::Rights::WAIT);
238// LINT.ThenChange(//sdk/fidl/fuchsia.ebpf/ebpf.fidl:map_rights)
239
240// Rights for the VMO handle when sharing a map.
241const SHARED_MAP_RIGHTS: zx::Rights = BASE_MAP_RIGHTS.union(zx::Rights::TRANSFER);
242
243/// Converts a `fuchsia.ebpf/MapSchema` to a `MapSchema`.
244pub fn map_schema_from_fidl(fidl_schema: febpf::MapSchema) -> MapSchema {
245    MapSchema {
246        map_type: fidl_map_type_to_bpf_map_type(fidl_schema.type_),
247        key_size: fidl_schema.key_size,
248        value_size: fidl_schema.value_size,
249        max_entries: fidl_schema.max_entries,
250        flags: map_flags_from_fidl(fidl_schema.flags),
251    }
252}
253
254impl Map {
255    pub fn new(schema: MapSchema, name: &str) -> Result<PinnedMap, MapError> {
256        validate_map_flags(&schema)?;
257        let map_impl = create_map_impl(&schema, name.to_string())?;
258        Ok(PinnedMap(Arc::pin(Self { schema, map_impl })))
259    }
260
261    pub fn new_shared(shared: febpf::Map) -> Result<PinnedMap, MapError> {
262        let febpf::Map { schema: Some(fidl_schema), vmo: Some(vmo), .. } = shared else {
263            return Err(MapError::InvalidParam);
264        };
265
266        // Check VMO rights.
267        let vmo_info = vmo.basic_info().map_err(|_| MapError::InvalidVmo)?;
268        if !vmo_info.rights.contains(BASE_MAP_RIGHTS) {
269            return Err(MapError::InvalidVmo);
270        }
271
272        let schema = map_schema_from_fidl(fidl_schema);
273
274        validate_map_flags(&schema)?;
275
276        let map_impl = create_map_impl(&schema, vmo)?;
277        Ok(PinnedMap(Arc::pin(Self { schema, map_impl })))
278    }
279
280    pub fn share(&self) -> Result<febpf::Map, MapError> {
281        Ok(febpf::Map {
282            schema: Some(febpf::MapSchema {
283                type_: bpf_map_type_to_fidl_map_type(self.schema.map_type),
284                key_size: self.schema.key_size,
285                value_size: self.schema.value_size,
286                max_entries: self.schema.max_entries,
287                flags: map_flags_to_fidl(self.schema.flags)?,
288            }),
289            vmo: Some(
290                self.map_impl
291                    .vmo()
292                    .duplicate_handle(SHARED_MAP_RIGHTS)
293                    .map_err(|_| MapError::Internal)?,
294            ),
295            ..Default::default()
296        })
297    }
298
299    pub fn lookup<'a>(&'a self, key: &[u8]) -> Option<MapValueRef<'a>> {
300        self.map_impl.lookup(key)
301    }
302
303    pub fn load(&self, key: &[u8]) -> Option<MapValue> {
304        self.lookup(key).map(|v| v.ptr().load())
305    }
306
307    pub fn update(&self, key: &[u8], value: EbpfBufferPtr<'_>, flags: u64) -> Result<(), MapError> {
308        if flags & (BPF_EXIST as u64) > 0 && flags & (BPF_NOEXIST as u64) > 0 {
309            return Err(MapError::InvalidParam);
310        }
311
312        self.map_impl.update(key, value, flags)
313    }
314
315    pub fn delete(&self, key: &[u8]) -> Result<(), MapError> {
316        self.map_impl.delete(key)
317    }
318
319    pub fn get_next_key(&self, key: Option<&[u8]>) -> Result<MapKey, MapError> {
320        self.map_impl.get_next_key(key)
321    }
322
323    pub fn vmo(&self) -> &Arc<zx::Vmo> {
324        self.map_impl.vmo()
325    }
326
327    pub fn can_read(&self) -> Option<bool> {
328        self.map_impl.can_read()
329    }
330
331    pub fn ringbuf_reserve(&self, size: u32, flags: u64) -> Result<usize, MapError> {
332        self.map_impl.ringbuf_reserve(size, flags)
333    }
334
335    pub fn uses_locks(&self) -> bool {
336        self.schema.map_type != bpf_map_type_BPF_MAP_TYPE_ARRAY
337    }
338}
339
340pub enum MapValueRef<'a> {
341    PlainRef(EbpfBufferPtr<'a>),
342    HashMapRef(hashmap::HashMapEntryRef<'a>),
343    LpmTrieRef(lpm_trie::LpmTrieEntryRef<'a>),
344}
345
346impl<'a> MapValueRef<'a> {
347    fn new(buf: EbpfBufferPtr<'a>) -> Self {
348        Self::PlainRef(buf)
349    }
350
351    fn new_from_hashmap(hash_map_ref: hashmap::HashMapEntryRef<'a>) -> Self {
352        Self::HashMapRef(hash_map_ref)
353    }
354
355    fn new_from_lpm_trie(lpm_trie_ref: lpm_trie::LpmTrieEntryRef<'a>) -> Self {
356        Self::LpmTrieRef(lpm_trie_ref)
357    }
358
359    pub fn is_ref_counted(&self) -> bool {
360        match self {
361            Self::PlainRef(_) => false,
362            Self::HashMapRef(_) | Self::LpmTrieRef(_) => true,
363        }
364    }
365
366    pub fn ptr(&self) -> EbpfBufferPtr<'a> {
367        match self {
368            Self::PlainRef(buf) => *buf,
369            Self::HashMapRef(hash_map_ref) => hash_map_ref.ptr(),
370            Self::LpmTrieRef(lpm_trie_ref) => lpm_trie_ref.ptr(),
371        }
372    }
373}
374
375const SK_STORAGE_MAX_ENTRIES: u32 = 8192;
376
377fn create_map_impl(
378    schema: &MapSchema,
379    vmo: impl Into<VmoOrName>,
380) -> Result<Pin<Box<dyn MapImpl>>, MapError> {
381    // The list of supported maps should be kept in sync with the enum values in
382    // `fuchsia.ebpf.MapType`.
383    match schema.map_type {
384        // LINT.IfChange(supported_maps)
385        bpf_map_type_BPF_MAP_TYPE_ARRAY => Ok(Box::pin(array::Array::new(schema, vmo)?)),
386        bpf_map_type_BPF_MAP_TYPE_HASH => Ok(Box::pin(hashmap::HashMap::new(schema, vmo)?)),
387        bpf_map_type_BPF_MAP_TYPE_RINGBUF => Ok(ring_buffer::RingBuffer::new(schema, vmo)?),
388        bpf_map_type_BPF_MAP_TYPE_LPM_TRIE => Ok(Box::pin(lpm_trie::LpmTrie::new(schema, vmo)?)),
389        bpf_map_type_BPF_MAP_TYPE_SK_STORAGE => {
390            if schema.key_size != 4 || schema.max_entries != 0 {
391                return Err(MapError::InvalidParam);
392            }
393
394            // SK_STORAGE maps are implemented as hashmaps with socket cookie used as a key.
395            let schema = MapSchema {
396                map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
397                key_size: 8,
398                max_entries: SK_STORAGE_MAX_ENTRIES,
399                value_size: schema.value_size,
400                flags: MapFlags::NoPrealloc,
401            };
402            Ok(Box::pin(hashmap::HashMap::new(&schema, vmo)?))
403        }
404
405        // These types are in use, but not yet implemented. Incorrectly use Array or Hash for
406        // these
407        bpf_map_type_BPF_MAP_TYPE_DEVMAP_HASH => {
408            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_DEVMAP_HASH");
409            Ok(Box::pin(hashmap::HashMap::new(schema, vmo)?))
410        }
411        bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH => {
412            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PERCPU_HASH");
413            Ok(Box::pin(hashmap::HashMap::new(schema, vmo)?))
414        }
415        bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY => {
416            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PERCPU_ARRAY");
417            Ok(Box::pin(array::Array::new(schema, vmo)?))
418        }
419        bpf_map_type_BPF_MAP_TYPE_LRU_HASH => {
420            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_LRU_HASH");
421            Ok(Box::pin(hashmap::HashMap::new(schema, vmo)?))
422        }
423        // LINT.ThenChange(:fidl_map_types)
424
425        // Unimplemented types
426        bpf_map_type_BPF_MAP_TYPE_UNSPEC => {
427            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_UNSPEC");
428            Err(MapError::MapTypeNotSupported)
429        }
430        bpf_map_type_BPF_MAP_TYPE_PROG_ARRAY => {
431            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PROG_ARRAY");
432            Err(MapError::MapTypeNotSupported)
433        }
434        bpf_map_type_BPF_MAP_TYPE_PERF_EVENT_ARRAY => {
435            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PERF_EVENT_ARRAY");
436            Err(MapError::MapTypeNotSupported)
437        }
438        bpf_map_type_BPF_MAP_TYPE_STACK_TRACE => {
439            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_STACK_TRACE");
440            Err(MapError::MapTypeNotSupported)
441        }
442        bpf_map_type_BPF_MAP_TYPE_CGROUP_ARRAY => {
443            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_CGROUP_ARRAY");
444            Err(MapError::MapTypeNotSupported)
445        }
446        bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH => {
447            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_LRU_PERCPU_HASH");
448            Err(MapError::MapTypeNotSupported)
449        }
450        bpf_map_type_BPF_MAP_TYPE_ARRAY_OF_MAPS => {
451            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_ARRAY_OF_MAPS");
452            Err(MapError::MapTypeNotSupported)
453        }
454        bpf_map_type_BPF_MAP_TYPE_HASH_OF_MAPS => {
455            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_HASH_OF_MAPS");
456            Err(MapError::MapTypeNotSupported)
457        }
458        bpf_map_type_BPF_MAP_TYPE_DEVMAP => {
459            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_DEVMAP");
460            Err(MapError::MapTypeNotSupported)
461        }
462        bpf_map_type_BPF_MAP_TYPE_SOCKMAP => {
463            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_SOCKMAP");
464            Err(MapError::MapTypeNotSupported)
465        }
466        bpf_map_type_BPF_MAP_TYPE_CPUMAP => {
467            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_CPUMAP");
468            Err(MapError::MapTypeNotSupported)
469        }
470        bpf_map_type_BPF_MAP_TYPE_XSKMAP => {
471            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_XSKMAP");
472            Err(MapError::MapTypeNotSupported)
473        }
474        bpf_map_type_BPF_MAP_TYPE_SOCKHASH => {
475            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_SOCKHASH");
476            Err(MapError::MapTypeNotSupported)
477        }
478        bpf_map_type_BPF_MAP_TYPE_CGROUP_STORAGE => {
479            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_CGROUP_STORAGE");
480            Err(MapError::MapTypeNotSupported)
481        }
482        bpf_map_type_BPF_MAP_TYPE_REUSEPORT_SOCKARRAY => {
483            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_REUSEPORT_SOCKARRAY");
484            Err(MapError::MapTypeNotSupported)
485        }
486        bpf_map_type_BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE => {
487            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE");
488            Err(MapError::MapTypeNotSupported)
489        }
490        bpf_map_type_BPF_MAP_TYPE_QUEUE => {
491            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_QUEUE");
492            Err(MapError::MapTypeNotSupported)
493        }
494        bpf_map_type_BPF_MAP_TYPE_STACK => {
495            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_STACK");
496            Err(MapError::MapTypeNotSupported)
497        }
498        bpf_map_type_BPF_MAP_TYPE_STRUCT_OPS => {
499            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_STRUCT_OPS");
500            Err(MapError::MapTypeNotSupported)
501        }
502        bpf_map_type_BPF_MAP_TYPE_INODE_STORAGE => {
503            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_INODE_STORAGE");
504            Err(MapError::MapTypeNotSupported)
505        }
506        bpf_map_type_BPF_MAP_TYPE_TASK_STORAGE => {
507            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_TASK_STORAGE");
508            Err(MapError::MapTypeNotSupported)
509        }
510        bpf_map_type_BPF_MAP_TYPE_BLOOM_FILTER => {
511            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_BLOOM_FILTER");
512            Err(MapError::MapTypeNotSupported)
513        }
514        bpf_map_type_BPF_MAP_TYPE_USER_RINGBUF => {
515            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_USER_RINGBUF");
516            Err(MapError::MapTypeNotSupported)
517        }
518        bpf_map_type_BPF_MAP_TYPE_CGRP_STORAGE => {
519            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_CGRP_STORAGE");
520            Err(MapError::MapTypeNotSupported)
521        }
522        bpf_map_type_BPF_MAP_TYPE_ARENA => {
523            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_ARENA");
524            Err(MapError::MapTypeNotSupported)
525        }
526        _ => {
527            track_stub!(
528                TODO("https://fxbug.dev/323847465"),
529                "unknown bpf map type",
530                schema.map_type
531            );
532            Err(MapError::InvalidParam)
533        }
534    }
535}
536
537pub fn compute_map_storage_size(schema: &MapSchema) -> Result<usize, MapError> {
538    schema.value_size.checked_mul(schema.max_entries).map(|v| v as usize).ok_or(MapError::NoMemory)
539}
540
541// LINT.IfChange(fidl_map_types)
542fn bpf_map_type_to_fidl_map_type(map_type: bpf_map_type) -> febpf::MapType {
543    match map_type {
544        bpf_map_type_BPF_MAP_TYPE_ARRAY => febpf::MapType::Array,
545        bpf_map_type_BPF_MAP_TYPE_HASH => febpf::MapType::HashMap,
546        bpf_map_type_BPF_MAP_TYPE_RINGBUF => febpf::MapType::RingBuffer,
547        bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY => febpf::MapType::PercpuArray,
548        bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH => febpf::MapType::PercpuHash,
549        bpf_map_type_BPF_MAP_TYPE_DEVMAP_HASH => febpf::MapType::DevmapHash,
550        bpf_map_type_BPF_MAP_TYPE_LPM_TRIE => febpf::MapType::LpmTrie,
551        bpf_map_type_BPF_MAP_TYPE_LRU_HASH => febpf::MapType::LruHash,
552        bpf_map_type_BPF_MAP_TYPE_SK_STORAGE => febpf::MapType::SkStorage,
553        _ =>
554        // Other map types are rejected in `create_map_impl()`.
555        {
556            unreachable!("unsupported map type {:?}", map_type)
557        }
558    }
559}
560
561fn fidl_map_type_to_bpf_map_type(map_type: febpf::MapType) -> bpf_map_type {
562    match map_type {
563        febpf::MapType::Array => bpf_map_type_BPF_MAP_TYPE_ARRAY,
564        febpf::MapType::HashMap => bpf_map_type_BPF_MAP_TYPE_HASH,
565        febpf::MapType::RingBuffer => bpf_map_type_BPF_MAP_TYPE_RINGBUF,
566        febpf::MapType::PercpuArray => bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY,
567        febpf::MapType::PercpuHash => bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH,
568        febpf::MapType::DevmapHash => bpf_map_type_BPF_MAP_TYPE_DEVMAP_HASH,
569        febpf::MapType::LpmTrie => bpf_map_type_BPF_MAP_TYPE_LPM_TRIE,
570        febpf::MapType::LruHash => bpf_map_type_BPF_MAP_TYPE_LRU_HASH,
571        febpf::MapType::SkStorage => bpf_map_type_BPF_MAP_TYPE_SK_STORAGE,
572    }
573}
574// LINT.ThenChange(:supported_maps, //sdk/fidl/fuchsia.ebpf/ebpf.fidl:map_types)
575
576#[cfg(test)]
577mod test {
578    use super::*;
579
580    #[fuchsia::test]
581    fn test_sharing_array() {
582        let schema = MapSchema {
583            map_type: bpf_map_type_BPF_MAP_TYPE_ARRAY,
584            key_size: 4,
585            value_size: 4,
586            max_entries: 10,
587            flags: MapFlags::empty(),
588        };
589
590        // Create two array maps sharing the content.
591        let map1 = Map::new(schema, "test").unwrap();
592        let map2 = Map::new_shared(map1.share().unwrap()).unwrap();
593
594        // Set a value in one map and check that it's updated in the other.
595        let key = vec![0, 0, 0, 0];
596        let mut value = [0, 1, 2, 3];
597        map1.update(&MapKey::from_vec(key.clone()), (&mut value).into(), 0).unwrap();
598        assert_eq!(&map2.load(&key).unwrap()[..], &value);
599    }
600
601    #[fuchsia::test]
602    fn test_sharing_hash_map() {
603        let schema = MapSchema {
604            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
605            key_size: 4,
606            value_size: 4,
607            max_entries: 10,
608            flags: MapFlags::empty(),
609        };
610
611        // Create two array maps sharing the content.
612        let map1 = Map::new(schema, "test").unwrap();
613        let map2 = Map::new_shared(map1.share().unwrap()).unwrap();
614
615        // Set a value in one map and check that it's updated in the other.
616        let key = vec![0, 0, 0, 0];
617        let mut value = [0, 1, 2, 3];
618        map1.update(&MapKey::from_vec(key.clone()), (&mut value).into(), 0).unwrap();
619        assert_eq!(&map2.load(&key).unwrap()[..], &value);
620    }
621
622    #[fuchsia::test]
623    fn test_hash_map() {
624        let schema = MapSchema {
625            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
626            key_size: 5,
627            value_size: 25,
628            max_entries: 10000,
629            flags: MapFlags::empty(),
630        };
631
632        let get_key = |i| {
633            MapKey::from_vec(vec![
634                (i & 0xffusize) as u8,
635                0,
636                ((i >> 4) & 0xffusize) as u8,
637                0,
638                ((i >> 8) & 0xffusize) as u8,
639            ])
640        };
641        let get_value = |i, v| format!("--{:010} {:010}--", i, v).into_bytes();
642
643        let map = Map::new(schema, "test").unwrap();
644
645        for i in 0..10000 {
646            assert!(map.update(&get_key(i), (&mut get_value(i, 0)).into(), 0).is_ok());
647        }
648
649        // Should fail to add another entry when the map is full.
650        assert_eq!(
651            map.update(&get_key(10001), (&mut get_value(10001, 1)).into(), 0),
652            Err(MapError::SizeLimit)
653        );
654
655        for i in 0..10000 {
656            assert_eq!(&map.load(&get_key(i)).unwrap()[..], &get_value(i, 0));
657        }
658
659        // Update some elements.
660        for i in 8000..9000 {
661            assert!(map.update(&get_key(i), (&mut get_value(i, 1)).into(), 0).is_ok());
662        }
663        for i in 8000..9000 {
664            assert_eq!(&map.load(&get_key(i)).unwrap()[..], &get_value(i, 1));
665        }
666
667        // Delete half of the entries.
668        for i in 5000..10000 {
669            assert!(map.delete(&get_key(i)).is_ok());
670        }
671        for i in 5000..10000 {
672            assert_eq!(map.load(&get_key(i)), None);
673        }
674
675        // Replace removed entries with new ones
676        for i in 10000..15000 {
677            assert!(map.update(&get_key(i), (&mut get_value(i, 2)).into(), 0).is_ok());
678        }
679
680        for i in 0..5000 {
681            assert_eq!(&map.load(&get_key(i)).unwrap()[..], &get_value(i, 0));
682        }
683        for i in 10000..15000 {
684            assert_eq!(&map.load(&get_key(i)).unwrap()[..], &get_value(i, 2));
685        }
686    }
687
688    #[fuchsia::test]
689    fn test_hash_map_overflow() {
690        let schema = MapSchema {
691            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
692            key_size: 8,
693            value_size: u32::MAX,
694            max_entries: u32::MAX,
695            flags: MapFlags::empty(),
696        };
697        assert_eq!(Map::new(schema, "test").err(), Some(MapError::InvalidParam));
698
699        let schema = MapSchema {
700            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
701            key_size: 8,
702            value_size: 0xffff_fff0,
703            max_entries: 0xffff_fff8,
704            flags: MapFlags::empty(),
705        };
706        assert_eq!(Map::new(schema, "test").err(), Some(MapError::InvalidParam));
707
708        let schema = MapSchema {
709            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
710            key_size: 8,
711            value_size: 0x1_0000,
712            max_entries: 0x8000_0000,
713            flags: MapFlags::empty(),
714        };
715        assert_eq!(Map::new(schema, "test").err(), Some(MapError::NoMemory));
716    }
717
718    #[fuchsia::test]
719    fn test_lpm_trie_overflow() {
720        let schema = MapSchema {
721            map_type: bpf_map_type_BPF_MAP_TYPE_LPM_TRIE,
722            key_size: 8,
723            value_size: u32::MAX,
724            max_entries: u32::MAX,
725            flags: MapFlags::NoPrealloc,
726        };
727        assert_eq!(Map::new(schema, "test").err(), Some(MapError::InvalidParam));
728
729        let schema = MapSchema {
730            map_type: bpf_map_type_BPF_MAP_TYPE_LPM_TRIE,
731            key_size: 5,
732            value_size: 0xffff_ffd0,
733            max_entries: 0xffff_fff8,
734            flags: MapFlags::NoPrealloc,
735        };
736        assert_eq!(Map::new(schema, "test").err(), Some(MapError::InvalidParam));
737
738        let schema = MapSchema {
739            map_type: bpf_map_type_BPF_MAP_TYPE_LPM_TRIE,
740            key_size: 8,
741            value_size: 0x1_0000,
742            max_entries: 0x8000_0000,
743            flags: MapFlags::NoPrealloc,
744        };
745        assert_eq!(Map::new(schema, "test").err(), Some(MapError::NoMemory));
746    }
747
748    #[fuchsia::test]
749    fn test_lpm_trie_invalid_key_size() {
750        let make_schema = |key_size| MapSchema {
751            map_type: bpf_map_type_BPF_MAP_TYPE_LPM_TRIE,
752            key_size,
753            value_size: 4,
754            max_entries: 10,
755            flags: MapFlags::NoPrealloc,
756        };
757
758        // Key size must be at least 5 bytes
759        assert_eq!(Map::new(make_schema(4), "test").err(), Some(MapError::InvalidParam));
760        assert!(Map::new(make_schema(5), "test").is_ok());
761
762        // Key size must be at most 260 bytes
763        assert!(Map::new(make_schema(260), "test").is_ok());
764        assert_eq!(Map::new(make_schema(261), "test").err(), Some(MapError::InvalidParam));
765    }
766
767    #[fuchsia::test]
768    fn test_hash_map_update_direct() {
769        let schema = MapSchema {
770            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
771            key_size: 5,
772            value_size: 11,
773            max_entries: 10,
774            flags: MapFlags::empty(),
775        };
776
777        let map = Map::new(schema, "test").unwrap();
778        let key = MapKey::from_vec("12345".to_string().into_bytes());
779        let mut value = (0..11).collect::<Vec<u8>>();
780        assert!(map.update(&key.clone(), (&mut value).into(), 0).is_ok());
781
782        // Access a value directly the way eBPF programs do.
783        let value_ref = map.lookup(&key).unwrap();
784        value_ref.ptr().slice(0..4).unwrap().store(&[0xae, 0xad, 0xac, 0xab]);
785
786        assert_eq!(&map.load(&key).unwrap()[..], &[0xae, 0xad, 0xac, 0xab, 4, 5, 6, 7, 8, 9, 10]);
787    }
788
789    #[fuchsia::test]
790    fn test_hash_map_ref_counting() {
791        let schema = MapSchema {
792            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
793            key_size: 5,
794            value_size: 11,
795            max_entries: 2,
796            flags: MapFlags::empty(),
797        };
798
799        let map = Map::new(schema, "test").unwrap();
800        let key = MapKey::from_vec("12345".to_string().into_bytes());
801        let key2 = MapKey::from_vec("24122".to_string().into_bytes());
802        let mut value = (0..11).collect::<Vec<u8>>();
803        assert!(map.update(&key.clone(), (&mut value).into(), 0).is_ok());
804        assert!(map.update(&key2.clone(), (&mut value).into(), 0).is_ok());
805
806        let value_ref = map.lookup(&key).unwrap();
807
808        // Delete an element. The corresponding data entry should not be
809        // released until `value_ref` is dropped.
810        assert!(map.delete(&key).is_ok());
811        assert_eq!(map.update(&key.clone(), (&mut value).into(), 0), Err(MapError::SizeLimit));
812        drop(value_ref);
813        assert!(map.update(&key.clone(), (&mut value).into(), 0).is_ok());
814    }
815
816    #[fuchsia::test]
817    fn test_ringbug_sharing() {
818        let schema = MapSchema {
819            map_type: bpf_map_type_BPF_MAP_TYPE_RINGBUF,
820            key_size: 0,
821            value_size: 0,
822            max_entries: 4096 * 2,
823            flags: MapFlags::empty(),
824        };
825
826        let map = Map::new(schema, "test").unwrap();
827        map.ringbuf_reserve(8000, 0).expect("ringbuf_reserve failed");
828
829        let map2 = Map::new_shared(map.share().unwrap()).unwrap();
830
831        // Expected to fail since there is no space left.
832        map2.ringbuf_reserve(2000, 0).expect_err("ringbuf_reserve expected to fail");
833    }
834
835    // Verifies that all supported map types are shareable.
836    #[fuchsia::test]
837    fn test_all_maps_shareable() {
838        for map_type in 1..linux_uapi::bpf_map_type___MAX_BPF_MAP_TYPE {
839            let (key_size, value_size, max_entries, flags) = match map_type {
840                bpf_map_type_BPF_MAP_TYPE_RINGBUF => (0, 0, 4096, MapFlags::empty()),
841                bpf_map_type_BPF_MAP_TYPE_LPM_TRIE => (8, 4, 4096, MapFlags::NoPrealloc),
842                bpf_map_type_BPF_MAP_TYPE_SK_STORAGE => (4, 4, 0, MapFlags::NoPrealloc),
843                _ => (4, 4, 1, MapFlags::empty()),
844            };
845            let schema = MapSchema { map_type, key_size, value_size, max_entries, flags };
846
847            let map = match Map::new(schema, "test") {
848                Ok(map) => map,
849                Err(MapError::MapTypeNotSupported) => {
850                    continue;
851                }
852                Err(e) => {
853                    panic!("Failed to create map of type {:?}: {:?}", map_type, e);
854                }
855            };
856
857            let map_fidl = map.share().expect("Failed to share map");
858            let _: PinnedMap = Map::new_shared(map_fidl).expect("Failed to initialize shared map");
859        }
860    }
861
862    #[fuchsia::test]
863    fn test_hash_map_get_next_key() {
864        let schema = MapSchema {
865            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
866            key_size: 4,
867            value_size: 4,
868            max_entries: 4,
869            flags: MapFlags::empty(),
870        };
871
872        let map = Map::new(schema, "test").unwrap();
873        let missing_key = u32::MAX.to_ne_bytes();
874
875        // Empty map returns InvalidKey for both None and a non-existent key.
876        assert_eq!(map.get_next_key(None), Err(MapError::InvalidKey));
877        assert_eq!(map.get_next_key(Some(&missing_key)), Err(MapError::InvalidKey));
878
879        let k1 = 1u32.to_ne_bytes();
880        let k2 = 2u32.to_ne_bytes();
881        let mut val = [1, 2, 3, 4];
882        map.update(&k1, (&mut val).into(), 0).unwrap();
883        map.update(&k2, (&mut val).into(), 0).unwrap();
884
885        let first = map.get_next_key(None).unwrap();
886        assert!(first.as_slice() == k1 || first.as_slice() == k2);
887
888        // Non-existent key returns the first key.
889        assert_eq!(map.get_next_key(Some(&missing_key)), Ok(first.clone()));
890
891        let second = map.get_next_key(Some(&first)).unwrap();
892        assert!(second.as_slice() == k1 || second.as_slice() == k2);
893        assert_ne!(first, second);
894
895        // Last key returns InvalidKey.
896        assert_eq!(map.get_next_key(Some(&second)), Err(MapError::InvalidKey));
897    }
898
899    #[fuchsia::test]
900    fn test_prog_read_only_map() {
901        let array_schema = MapSchema {
902            map_type: bpf_map_type_BPF_MAP_TYPE_ARRAY,
903            key_size: 4,
904            value_size: 4,
905            max_entries: 4,
906            flags: MapFlags::ProgReadOnly,
907        };
908        let array_map = Map::new(array_schema, "test_array").unwrap();
909        assert!(array_map.schema.flags.contains(MapFlags::ProgReadOnly));
910        let array_fidl = array_map.share().expect("Failed to share array map");
911        let shared_array =
912            Map::new_shared(array_fidl).expect("Failed to initialize shared array map");
913        assert!(shared_array.schema.flags.contains(MapFlags::ProgReadOnly));
914
915        let hash_schema = MapSchema {
916            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
917            key_size: 4,
918            value_size: 4,
919            max_entries: 4,
920            flags: MapFlags::ProgReadOnly,
921        };
922        let hash_map = Map::new(hash_schema, "test_hash").unwrap();
923        assert!(hash_map.schema.flags.contains(MapFlags::ProgReadOnly));
924        let hash_fidl = hash_map.share().expect("Failed to share hash map");
925        let shared_hash = Map::new_shared(hash_fidl).expect("Failed to initialize shared hash map");
926        assert!(shared_hash.schema.flags.contains(MapFlags::ProgReadOnly));
927
928        // Invalid on SK_STORAGE.
929        let sk_storage_schema = MapSchema {
930            map_type: bpf_map_type_BPF_MAP_TYPE_SK_STORAGE,
931            key_size: 4,
932            value_size: 4,
933            max_entries: 0,
934            flags: MapFlags::ProgReadOnly | MapFlags::NoPrealloc,
935        };
936        assert_eq!(Map::new(sk_storage_schema, "test_sk").err(), Some(MapError::InvalidParam));
937
938        // Invalid on RINGBUF.
939        let ringbuf_schema = MapSchema {
940            map_type: bpf_map_type_BPF_MAP_TYPE_RINGBUF,
941            key_size: 0,
942            value_size: 0,
943            max_entries: 4096,
944            flags: MapFlags::ProgReadOnly,
945        };
946        assert_eq!(Map::new(ringbuf_schema, "test_rb").err(), Some(MapError::InvalidParam));
947
948        // Invalid flags rejected in Map::new_shared.
949        let ringbuf_valid_schema = MapSchema {
950            map_type: bpf_map_type_BPF_MAP_TYPE_RINGBUF,
951            key_size: 0,
952            value_size: 0,
953            max_entries: 4096,
954            flags: MapFlags::empty(),
955        };
956        let valid_rb = Map::new(ringbuf_valid_schema, "test_rb_valid").unwrap();
957        let mut shared_rb = valid_rb.share().expect("Failed to share ringbuf");
958        shared_rb.schema.as_mut().unwrap().flags |= febpf::MapFlags::PROG_READ_ONLY;
959        assert_eq!(Map::new_shared(shared_rb).err(), Some(MapError::InvalidParam));
960    }
961}