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::{AsHandleRef, 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: MapKey, value: &[u8], 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// Access rights required for a map VMO handle. Should be consistent with the
201// rights specified in FIDL. READ, WRITE and MAP rights are required to access
202// the map contents. SIGNAL and WAIT rights are used for synchronization.
203// LINT.IfChange(map_rights)
204const BASE_MAP_RIGHTS: zx::Rights = zx::Rights::READ
205    .union(zx::Rights::WRITE)
206    .union(zx::Rights::MAP)
207    .union(zx::Rights::SIGNAL)
208    .union(zx::Rights::WAIT);
209// LINT.ThenChange(//sdk/fidl/fuchsia.ebpf/ebpf.fidl:map_rights)
210
211// Rights for the VMO handle when sharing a map.
212const SHARED_MAP_RIGHTS: zx::Rights = BASE_MAP_RIGHTS.union(zx::Rights::TRANSFER);
213
214impl Map {
215    pub fn new(schema: MapSchema, name: &str) -> Result<PinnedMap, MapError> {
216        validate_map_flags(&schema)?;
217        let map_impl = create_map_impl(&schema, name.to_string())?;
218        Ok(PinnedMap(Arc::pin(Self { schema, map_impl })))
219    }
220
221    pub fn new_shared(shared: febpf::Map) -> Result<PinnedMap, MapError> {
222        let febpf::Map { schema: Some(fidl_schema), vmo: Some(vmo), .. } = shared else {
223            return Err(MapError::InvalidParam);
224        };
225
226        // Check VMO rights.
227        let vmo_info = vmo.basic_info().map_err(|_| MapError::InvalidVmo)?;
228        if !vmo_info.rights.contains(BASE_MAP_RIGHTS) {
229            return Err(MapError::InvalidVmo);
230        }
231
232        let schema = MapSchema {
233            map_type: fidl_map_type_to_bpf_map_type(fidl_schema.type_),
234            key_size: fidl_schema.key_size,
235            value_size: fidl_schema.value_size,
236            max_entries: fidl_schema.max_entries,
237            flags: map_flags_from_fidl(fidl_schema.flags),
238        };
239
240        let map_impl = create_map_impl(&schema, vmo)?;
241        Ok(PinnedMap(Arc::pin(Self { schema, map_impl })))
242    }
243
244    pub fn share(&self) -> Result<febpf::Map, MapError> {
245        Ok(febpf::Map {
246            schema: Some(febpf::MapSchema {
247                type_: bpf_map_type_to_fidl_map_type(self.schema.map_type),
248                key_size: self.schema.key_size,
249                value_size: self.schema.value_size,
250                max_entries: self.schema.max_entries,
251                flags: map_flags_to_fidl(self.schema.flags)?,
252            }),
253            vmo: Some(
254                self.map_impl
255                    .vmo()
256                    .duplicate_handle(SHARED_MAP_RIGHTS)
257                    .map_err(|_| MapError::Internal)?,
258            ),
259            ..Default::default()
260        })
261    }
262
263    pub fn lookup<'a>(&'a self, key: &[u8]) -> Option<MapValueRef<'a>> {
264        self.map_impl.lookup(key)
265    }
266
267    pub fn load(&self, key: &[u8]) -> Option<Vec<u8>> {
268        let mut result = self.lookup(key)?.ptr().load();
269
270        // Remove padding if any.
271        result.resize(self.schema.value_size as usize, 0);
272
273        Some(result)
274    }
275
276    pub fn update(&self, key: MapKey, value: &[u8], flags: u64) -> Result<(), MapError> {
277        if flags & (BPF_EXIST as u64) > 0 && flags & (BPF_NOEXIST as u64) > 0 {
278            return Err(MapError::InvalidParam);
279        }
280
281        self.map_impl.update(key, value, flags)
282    }
283
284    pub fn delete(&self, key: &[u8]) -> Result<(), MapError> {
285        self.map_impl.delete(key)
286    }
287
288    pub fn get_next_key(&self, key: Option<&[u8]>) -> Result<MapKey, MapError> {
289        self.map_impl.get_next_key(key)
290    }
291
292    pub fn vmo(&self) -> &Arc<zx::Vmo> {
293        self.map_impl.vmo()
294    }
295
296    pub fn can_read(&self) -> Option<bool> {
297        self.map_impl.can_read()
298    }
299
300    pub fn ringbuf_reserve(&self, size: u32, flags: u64) -> Result<usize, MapError> {
301        self.map_impl.ringbuf_reserve(size, flags)
302    }
303
304    pub fn uses_locks(&self) -> bool {
305        self.schema.map_type != bpf_map_type_BPF_MAP_TYPE_ARRAY
306    }
307}
308
309pub enum MapValueRef<'a> {
310    PlainRef(EbpfBufferPtr<'a>),
311    HashMapRef(hashmap::HashMapEntryRef<'a>),
312    LpmTrieRef(lpm_trie::LpmTrieEntryRef<'a>),
313}
314
315impl<'a> MapValueRef<'a> {
316    fn new(buf: EbpfBufferPtr<'a>) -> Self {
317        Self::PlainRef(buf)
318    }
319
320    fn new_from_hashmap(hash_map_ref: hashmap::HashMapEntryRef<'a>) -> Self {
321        Self::HashMapRef(hash_map_ref)
322    }
323
324    fn new_from_lpm_trie(lpm_trie_ref: lpm_trie::LpmTrieEntryRef<'a>) -> Self {
325        Self::LpmTrieRef(lpm_trie_ref)
326    }
327
328    pub fn is_ref_counted(&self) -> bool {
329        match self {
330            Self::PlainRef(_) => false,
331            Self::HashMapRef(_) | Self::LpmTrieRef(_) => true,
332        }
333    }
334
335    pub fn ptr(&self) -> EbpfBufferPtr<'a> {
336        match self {
337            Self::PlainRef(buf) => *buf,
338            Self::HashMapRef(hash_map_ref) => hash_map_ref.ptr(),
339            Self::LpmTrieRef(lpm_trie_ref) => lpm_trie_ref.ptr(),
340        }
341    }
342}
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
357        // These types are in use, but not yet implemented. Incorrectly use Array or Hash for
358        // these
359        bpf_map_type_BPF_MAP_TYPE_DEVMAP_HASH => {
360            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_DEVMAP_HASH");
361            // `BPF_F_RDONLY_PROG` is not yet implemented, but it's always set
362            // for `DEVMAP` maps.
363            let schema =
364                MapSchema { flags: schema.flags.difference(MapFlags::ProgReadOnly), ..*schema };
365            Ok(Box::pin(hashmap::HashMap::new(&schema, vmo)?))
366        }
367        bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH => {
368            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PERCPU_HASH");
369            Ok(Box::pin(hashmap::HashMap::new(schema, vmo)?))
370        }
371        bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY => {
372            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PERCPU_ARRAY");
373            Ok(Box::pin(array::Array::new(schema, vmo)?))
374        }
375        bpf_map_type_BPF_MAP_TYPE_SK_STORAGE => {
376            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_SK_STORAGE");
377            Ok(Box::pin(array::Array::new(&MapSchema { max_entries: 1, ..*schema }, vmo)?))
378        }
379        bpf_map_type_BPF_MAP_TYPE_LRU_HASH => {
380            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_LRU_HASH");
381            Ok(Box::pin(hashmap::HashMap::new(schema, vmo)?))
382        }
383        // LINT.ThenChange(:fidl_map_types)
384
385        // Unimplemented types
386        bpf_map_type_BPF_MAP_TYPE_UNSPEC => {
387            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_UNSPEC");
388            Err(MapError::MapTypeNotSupported)
389        }
390        bpf_map_type_BPF_MAP_TYPE_PROG_ARRAY => {
391            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PROG_ARRAY");
392            Err(MapError::MapTypeNotSupported)
393        }
394        bpf_map_type_BPF_MAP_TYPE_PERF_EVENT_ARRAY => {
395            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PERF_EVENT_ARRAY");
396            Err(MapError::MapTypeNotSupported)
397        }
398        bpf_map_type_BPF_MAP_TYPE_STACK_TRACE => {
399            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_STACK_TRACE");
400            Err(MapError::MapTypeNotSupported)
401        }
402        bpf_map_type_BPF_MAP_TYPE_CGROUP_ARRAY => {
403            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_CGROUP_ARRAY");
404            Err(MapError::MapTypeNotSupported)
405        }
406        bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH => {
407            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_LRU_PERCPU_HASH");
408            Err(MapError::MapTypeNotSupported)
409        }
410        bpf_map_type_BPF_MAP_TYPE_ARRAY_OF_MAPS => {
411            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_ARRAY_OF_MAPS");
412            Err(MapError::MapTypeNotSupported)
413        }
414        bpf_map_type_BPF_MAP_TYPE_HASH_OF_MAPS => {
415            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_HASH_OF_MAPS");
416            Err(MapError::MapTypeNotSupported)
417        }
418        bpf_map_type_BPF_MAP_TYPE_DEVMAP => {
419            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_DEVMAP");
420            Err(MapError::MapTypeNotSupported)
421        }
422        bpf_map_type_BPF_MAP_TYPE_SOCKMAP => {
423            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_SOCKMAP");
424            Err(MapError::MapTypeNotSupported)
425        }
426        bpf_map_type_BPF_MAP_TYPE_CPUMAP => {
427            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_CPUMAP");
428            Err(MapError::MapTypeNotSupported)
429        }
430        bpf_map_type_BPF_MAP_TYPE_XSKMAP => {
431            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_XSKMAP");
432            Err(MapError::MapTypeNotSupported)
433        }
434        bpf_map_type_BPF_MAP_TYPE_SOCKHASH => {
435            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_SOCKHASH");
436            Err(MapError::MapTypeNotSupported)
437        }
438        bpf_map_type_BPF_MAP_TYPE_CGROUP_STORAGE => {
439            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_CGROUP_STORAGE");
440            Err(MapError::MapTypeNotSupported)
441        }
442        bpf_map_type_BPF_MAP_TYPE_REUSEPORT_SOCKARRAY => {
443            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_REUSEPORT_SOCKARRAY");
444            Err(MapError::MapTypeNotSupported)
445        }
446        bpf_map_type_BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE => {
447            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE");
448            Err(MapError::MapTypeNotSupported)
449        }
450        bpf_map_type_BPF_MAP_TYPE_QUEUE => {
451            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_QUEUE");
452            Err(MapError::MapTypeNotSupported)
453        }
454        bpf_map_type_BPF_MAP_TYPE_STACK => {
455            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_STACK");
456            Err(MapError::MapTypeNotSupported)
457        }
458        bpf_map_type_BPF_MAP_TYPE_STRUCT_OPS => {
459            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_STRUCT_OPS");
460            Err(MapError::MapTypeNotSupported)
461        }
462        bpf_map_type_BPF_MAP_TYPE_INODE_STORAGE => {
463            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_INODE_STORAGE");
464            Err(MapError::MapTypeNotSupported)
465        }
466        bpf_map_type_BPF_MAP_TYPE_TASK_STORAGE => {
467            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_TASK_STORAGE");
468            Err(MapError::MapTypeNotSupported)
469        }
470        bpf_map_type_BPF_MAP_TYPE_BLOOM_FILTER => {
471            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_BLOOM_FILTER");
472            Err(MapError::MapTypeNotSupported)
473        }
474        bpf_map_type_BPF_MAP_TYPE_USER_RINGBUF => {
475            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_USER_RINGBUF");
476            Err(MapError::MapTypeNotSupported)
477        }
478        bpf_map_type_BPF_MAP_TYPE_CGRP_STORAGE => {
479            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_CGRP_STORAGE");
480            Err(MapError::MapTypeNotSupported)
481        }
482        bpf_map_type_BPF_MAP_TYPE_ARENA => {
483            track_stub!(TODO("https://fxbug.dev/323847465"), "BPF_MAP_TYPE_ARENA");
484            Err(MapError::MapTypeNotSupported)
485        }
486        _ => {
487            track_stub!(
488                TODO("https://fxbug.dev/323847465"),
489                "unknown bpf map type",
490                schema.map_type
491            );
492            Err(MapError::InvalidParam)
493        }
494    }
495}
496
497pub fn compute_map_storage_size(schema: &MapSchema) -> Result<usize, MapError> {
498    schema.value_size.checked_mul(schema.max_entries).map(|v| v as usize).ok_or(MapError::NoMemory)
499}
500
501// LINT.IfChange(fidl_map_types)
502fn bpf_map_type_to_fidl_map_type(map_type: bpf_map_type) -> febpf::MapType {
503    match map_type {
504        bpf_map_type_BPF_MAP_TYPE_ARRAY => febpf::MapType::Array,
505        bpf_map_type_BPF_MAP_TYPE_HASH => febpf::MapType::HashMap,
506        bpf_map_type_BPF_MAP_TYPE_RINGBUF => febpf::MapType::RingBuffer,
507        bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY => febpf::MapType::PercpuArray,
508        bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH => febpf::MapType::PercpuHash,
509        bpf_map_type_BPF_MAP_TYPE_DEVMAP_HASH => febpf::MapType::DevmapHash,
510        bpf_map_type_BPF_MAP_TYPE_LPM_TRIE => febpf::MapType::LpmTrie,
511        bpf_map_type_BPF_MAP_TYPE_LRU_HASH => febpf::MapType::LruHash,
512        bpf_map_type_BPF_MAP_TYPE_SK_STORAGE => febpf::MapType::SkStorage,
513        _ =>
514        // Other map types are rejected in `create_map_impl()`.
515        {
516            unreachable!("unsupported map type {:?}", map_type)
517        }
518    }
519}
520
521fn fidl_map_type_to_bpf_map_type(map_type: febpf::MapType) -> bpf_map_type {
522    match map_type {
523        febpf::MapType::Array => bpf_map_type_BPF_MAP_TYPE_ARRAY,
524        febpf::MapType::HashMap => bpf_map_type_BPF_MAP_TYPE_HASH,
525        febpf::MapType::RingBuffer => bpf_map_type_BPF_MAP_TYPE_RINGBUF,
526        febpf::MapType::PercpuArray => bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY,
527        febpf::MapType::PercpuHash => bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH,
528        febpf::MapType::DevmapHash => bpf_map_type_BPF_MAP_TYPE_DEVMAP_HASH,
529        febpf::MapType::LpmTrie => bpf_map_type_BPF_MAP_TYPE_LPM_TRIE,
530        febpf::MapType::LruHash => bpf_map_type_BPF_MAP_TYPE_LRU_HASH,
531        febpf::MapType::SkStorage => bpf_map_type_BPF_MAP_TYPE_SK_STORAGE,
532    }
533}
534// LINT.ThenChange(:supported_maps, //sdk/fidl/fuchsia.ebpf/ebpf.fidl:map_types)
535
536#[cfg(test)]
537mod test {
538    use super::*;
539
540    #[fuchsia::test]
541    fn test_sharing_array() {
542        let schema = MapSchema {
543            map_type: bpf_map_type_BPF_MAP_TYPE_ARRAY,
544            key_size: 4,
545            value_size: 4,
546            max_entries: 10,
547            flags: MapFlags::empty(),
548        };
549
550        // Create two array maps sharing the content.
551        let map1 = Map::new(schema, "test").unwrap();
552        let map2 = Map::new_shared(map1.share().unwrap()).unwrap();
553
554        // Set a value in one map and check that it's updated in the other.
555        let key = vec![0, 0, 0, 0];
556        let value = [0, 1, 2, 3];
557        map1.update(MapKey::from_vec(key.clone()), &value, 0).unwrap();
558        assert_eq!(&map2.load(&key).unwrap(), &value);
559    }
560
561    #[fuchsia::test]
562    fn test_sharing_hash_map() {
563        let schema = MapSchema {
564            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
565            key_size: 4,
566            value_size: 4,
567            max_entries: 10,
568            flags: MapFlags::empty(),
569        };
570
571        // Create two array maps sharing the content.
572        let map1 = Map::new(schema, "test").unwrap();
573        let map2 = Map::new_shared(map1.share().unwrap()).unwrap();
574
575        // Set a value in one map and check that it's updated in the other.
576        let key = vec![0, 0, 0, 0];
577        let value = [0, 1, 2, 3];
578        map1.update(MapKey::from_vec(key.clone()), &value, 0).unwrap();
579        assert_eq!(&map2.load(&key).unwrap(), &value);
580    }
581
582    #[fuchsia::test]
583    fn test_hash_map() {
584        let schema = MapSchema {
585            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
586            key_size: 5,
587            value_size: 25,
588            max_entries: 10000,
589            flags: MapFlags::empty(),
590        };
591
592        let get_key = |i| {
593            MapKey::from_vec(vec![
594                (i & 0xffusize) as u8,
595                0,
596                ((i >> 4) & 0xffusize) as u8,
597                0,
598                ((i >> 8) & 0xffusize) as u8,
599            ])
600        };
601        let get_value = |i, v| format!("--{:010} {:010}--", i, v).into_bytes();
602
603        let map = Map::new(schema, "test").unwrap();
604
605        for i in 0..10000 {
606            assert!(map.update(get_key(i), &get_value(i, 0), 0).is_ok());
607        }
608
609        // Should fail to add another entry when the map is full.
610        assert_eq!(map.update(get_key(10001), &get_value(10001, 1), 0), Err(MapError::SizeLimit));
611
612        for i in 0..10000 {
613            assert_eq!(map.load(&get_key(i)), Some(get_value(i, 0)));
614        }
615
616        // Update some elements.
617        for i in 8000..9000 {
618            assert!(map.update(get_key(i), &get_value(i, 1), 0).is_ok());
619        }
620        for i in 8000..9000 {
621            assert_eq!(map.load(&get_key(i)), Some(get_value(i, 1)));
622        }
623
624        // Delete half of the entries.
625        for i in 5000..10000 {
626            assert!(map.delete(&get_key(i)).is_ok());
627        }
628        for i in 5000..10000 {
629            assert_eq!(map.load(&get_key(i)), None);
630        }
631
632        // Replace removed entries with new ones
633        for i in 10000..15000 {
634            assert!(map.update(get_key(i), &get_value(i, 2), 0).is_ok());
635        }
636
637        for i in 0..5000 {
638            assert_eq!(map.load(&get_key(i)), Some(get_value(i, 0)));
639        }
640        for i in 10000..15000 {
641            assert_eq!(map.load(&get_key(i)), Some(get_value(i, 2)));
642        }
643    }
644
645    #[fuchsia::test]
646    fn test_hash_map_update_direct() {
647        let schema = MapSchema {
648            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
649            key_size: 5,
650            value_size: 11,
651            max_entries: 10,
652            flags: MapFlags::empty(),
653        };
654
655        let map = Map::new(schema, "test").unwrap();
656        let key = MapKey::from_vec("12345".to_string().into_bytes());
657        let value = (0..11).collect::<Vec<u8>>();
658        assert!(map.update(key.clone(), &value, 0).is_ok());
659
660        // Access a value directly the way eBPF programs do.
661        let value_ref = map.lookup(&key).unwrap();
662        #[allow(
663            clippy::undocumented_unsafe_blocks,
664            reason = "Force documented unsafe blocks in Starnix"
665        )]
666        unsafe {
667            *value_ref.ptr().get_ptr::<u32>(0).unwrap().deref_mut() = 0xabacadae;
668        }
669
670        assert_eq!(map.load(&key), Some(vec![0xae, 0xad, 0xac, 0xab, 4, 5, 6, 7, 8, 9, 10]));
671    }
672
673    #[fuchsia::test]
674    fn test_hash_map_ref_counting() {
675        let schema = MapSchema {
676            map_type: bpf_map_type_BPF_MAP_TYPE_HASH,
677            key_size: 5,
678            value_size: 11,
679            max_entries: 2,
680            flags: MapFlags::empty(),
681        };
682
683        let map = Map::new(schema, "test").unwrap();
684        let key = MapKey::from_vec("12345".to_string().into_bytes());
685        let key2 = MapKey::from_vec("24122".to_string().into_bytes());
686        let value = (0..11).collect::<Vec<u8>>();
687        assert!(map.update(key.clone(), &value, 0).is_ok());
688        assert!(map.update(key2.clone(), &value, 0).is_ok());
689
690        let value_ref = map.lookup(&key).unwrap();
691
692        // Delete an element. The corresponding data entry should not be
693        // released until `value_ref` is dropped.
694        assert!(map.delete(&key).is_ok());
695        assert_eq!(map.update(key.clone(), &value, 0), Err(MapError::SizeLimit));
696        drop(value_ref);
697        assert!(map.update(key.clone(), &value, 0).is_ok());
698    }
699
700    #[fuchsia::test]
701    fn test_ringbug_sharing() {
702        let schema = MapSchema {
703            map_type: bpf_map_type_BPF_MAP_TYPE_RINGBUF,
704            key_size: 0,
705            value_size: 0,
706            max_entries: 4096 * 2,
707            flags: MapFlags::empty(),
708        };
709
710        let map = Map::new(schema, "test").unwrap();
711        map.ringbuf_reserve(8000, 0).expect("ringbuf_reserve failed");
712
713        let map2 = Map::new_shared(map.share().unwrap()).unwrap();
714
715        // Expected to fail since there is no space left.
716        map2.ringbuf_reserve(2000, 0).expect_err("ringbuf_reserve expected to fail");
717    }
718
719    // Verifies that all supported map types are shareable.
720    #[fuchsia::test]
721    fn test_all_maps_shareable() {
722        for map_type in 1..linux_uapi::bpf_map_type___MAX_BPF_MAP_TYPE {
723            let (key_size, value_size, max_entries, flags) = match map_type {
724                bpf_map_type_BPF_MAP_TYPE_RINGBUF => (0, 0, 4096, MapFlags::empty()),
725                bpf_map_type_BPF_MAP_TYPE_LPM_TRIE => (8, 4, 4096, MapFlags::NoPrealloc),
726                _ => (4, 4, 1, MapFlags::empty()),
727            };
728            let schema = MapSchema { map_type, key_size, value_size, max_entries, flags };
729
730            let map = match Map::new(schema, "test") {
731                Ok(map) => map,
732                Err(MapError::MapTypeNotSupported) => {
733                    continue;
734                }
735                Err(e) => {
736                    panic!("Failed to create map of type {:?}: {:?}", map_type, e);
737                }
738            };
739
740            let map_fidl = map.share().expect("Failed to share map");
741            let _: PinnedMap = Map::new_shared(map_fidl).expect("Failed to initialize shared map");
742        }
743    }
744}