use std::{
borrow::{Borrow, BorrowMut},
fmt,
marker::PhantomData,
os::fd::{AsFd as _, AsRawFd as _},
path::Path,
};
use crate::{
maps::{FromMapData, InnerMap, MapData, MapError, PinError, check_bounds, check_kv_size},
sys::{SyscallError, bpf_map_lookup_elem, bpf_map_update_elem},
};
#[doc(alias = "BPF_MAP_TYPE_ARRAY_OF_MAPS")]
pub struct ArrayOfMaps<T, V = MapData> {
pub(crate) inner: T,
_v: PhantomData<V>,
}
impl<T: fmt::Debug, V> fmt::Debug for ArrayOfMaps<T, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ArrayOfMaps")
.field("inner", &self.inner)
.finish()
}
}
impl<T: Borrow<MapData>, V> ArrayOfMaps<T, V> {
pub(crate) fn new(map: T) -> Result<Self, MapError> {
let data = map.borrow();
check_kv_size::<u32, u32>(data)?;
Ok(Self {
inner: map,
_v: PhantomData,
})
}
pub fn len(&self) -> u32 {
self.inner.borrow().obj.max_entries()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<T: Borrow<MapData>, V: FromMapData> ArrayOfMaps<T, V> {
pub fn get(&self, index: &u32, flags: u64) -> Result<V, MapError> {
let data = self.inner.borrow();
check_bounds(data, *index)?;
let fd = data.fd().as_fd();
let value: Option<u32> =
bpf_map_lookup_elem(fd, index, flags).map_err(|io_error| SyscallError {
call: "bpf_map_lookup_elem",
io_error,
})?;
match value {
Some(id) => super::map_from_id(id),
None => Err(MapError::KeyNotFound),
}
}
}
impl<T: BorrowMut<MapData>, V: InnerMap> ArrayOfMaps<T, V> {
pub fn set(&mut self, index: u32, value: &V, flags: u64) -> Result<(), MapError> {
let data = self.inner.borrow_mut();
check_bounds(data, index)?;
let fd = data.fd().as_fd();
bpf_map_update_elem(fd, Some(&index), &value.fd().as_fd().as_raw_fd(), flags).map_err(
|io_error| SyscallError {
call: "bpf_map_update_elem",
io_error,
},
)?;
Ok(())
}
}
impl<V> ArrayOfMaps<MapData, V> {
pub fn pin<P: AsRef<Path>>(self, path: P) -> Result<(), PinError> {
self.inner.pin(path)
}
}
#[cfg(test)]
mod tests {
use std::io;
use assert_matches::assert_matches;
use aya_obj::generated::{bpf_cmd, bpf_map_type::BPF_MAP_TYPE_ARRAY_OF_MAPS};
use libc::{EFAULT, ENOENT};
use super::*;
use crate::{
maps::{Map, test_utils},
sys::{SysResult, Syscall, override_syscall},
};
fn new_obj_map() -> aya_obj::Map {
test_utils::new_obj_map::<u32>(BPF_MAP_TYPE_ARRAY_OF_MAPS)
}
fn new_map(obj: aya_obj::Map) -> MapData {
test_utils::new_map(obj)
}
fn sys_error(value: i32) -> SysResult {
Err((-1, io::Error::from_raw_os_error(value)))
}
#[test]
fn test_wrong_key_size() {
let map = new_map(test_utils::new_obj_map::<u8>(BPF_MAP_TYPE_ARRAY_OF_MAPS));
assert_matches!(
ArrayOfMaps::<_>::new(&map),
Err(MapError::InvalidKeySize {
size: 4,
expected: 1
})
);
}
#[test]
fn test_try_from_wrong_map() {
let map = new_map(test_utils::new_obj_map::<u32>(
aya_obj::generated::bpf_map_type::BPF_MAP_TYPE_HASH,
));
let map = Map::HashMap(map);
assert_matches!(
ArrayOfMaps::<_>::try_from(&map),
Err(MapError::InvalidMapType { .. })
);
}
#[test]
fn test_new_ok() {
let map = new_map(new_obj_map());
let _: ArrayOfMaps<_> = ArrayOfMaps::new(&map).unwrap();
}
#[test]
fn test_set_syscall_error() {
let mut map = new_map(new_obj_map());
let inner_map = new_map(test_utils::new_obj_map::<u32>(
aya_obj::generated::bpf_map_type::BPF_MAP_TYPE_ARRAY,
));
let mut arr = ArrayOfMaps::new(&mut map).unwrap();
override_syscall(|_| sys_error(EFAULT));
assert_matches!(
arr.set(0, &inner_map, 0),
Err(MapError::SyscallError(SyscallError {
call: "bpf_map_update_elem",
..
}))
);
}
#[test]
fn test_set_ok() {
let mut map = new_map(new_obj_map());
let inner_map = new_map(test_utils::new_obj_map::<u32>(
aya_obj::generated::bpf_map_type::BPF_MAP_TYPE_ARRAY,
));
let mut arr = ArrayOfMaps::new(&mut map).unwrap();
override_syscall(|call| match call {
Syscall::Ebpf {
cmd: bpf_cmd::BPF_MAP_UPDATE_ELEM,
..
} => Ok(0),
_ => sys_error(EFAULT),
});
arr.set(0, &inner_map, 0).unwrap();
}
#[test]
fn test_set_out_of_bounds() {
let mut map = new_map(new_obj_map());
let inner_map = new_map(test_utils::new_obj_map::<u32>(
aya_obj::generated::bpf_map_type::BPF_MAP_TYPE_ARRAY,
));
let mut arr = ArrayOfMaps::new(&mut map).unwrap();
assert_matches!(
arr.set(1024, &inner_map, 0),
Err(MapError::OutOfBounds { .. })
);
}
#[test]
fn test_get_syscall_error() {
let map = new_map(new_obj_map());
let arr: ArrayOfMaps<_> = ArrayOfMaps::new(&map).unwrap();
override_syscall(|_| sys_error(EFAULT));
assert_matches!(
arr.get(&0, 0),
Err(MapError::SyscallError(SyscallError {
call: "bpf_map_lookup_elem",
..
}))
);
}
#[test]
fn test_get_not_found() {
let map = new_map(new_obj_map());
let arr: ArrayOfMaps<_> = ArrayOfMaps::new(&map).unwrap();
override_syscall(|call| match call {
Syscall::Ebpf {
cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM,
..
} => sys_error(ENOENT),
_ => sys_error(EFAULT),
});
assert_matches!(arr.get(&0, 0), Err(MapError::KeyNotFound));
}
#[test]
fn test_get_out_of_bounds() {
let map = new_map(new_obj_map());
let arr: ArrayOfMaps<_> = ArrayOfMaps::new(&map).unwrap();
assert_matches!(arr.get(&1024, 0), Err(MapError::OutOfBounds { .. }));
}
}