falco_plugin/plugin/exported_tables/
table.rs

1use crate::plugin::exported_tables::entry::extensible::ExtensibleEntry;
2use crate::plugin::exported_tables::entry::table_metadata::extensible::ExtensibleEntryMetadata;
3use crate::plugin::exported_tables::entry::table_metadata::traits::TableMetadata;
4use crate::plugin::exported_tables::entry::traits::Entry;
5use crate::plugin::exported_tables::field_descriptor::{FieldDescriptor, FieldRef};
6use crate::plugin::exported_tables::field_value::dynamic::DynamicFieldValue;
7use crate::plugin::exported_tables::metadata::HasMetadata;
8use crate::plugin::exported_tables::metadata::Metadata;
9use crate::plugin::exported_tables::ref_shared::{
10    new_counted_ref, new_shared_ref, RefCounted, RefGuard, RefShared,
11};
12use crate::plugin::exported_tables::vtable::Vtable;
13use crate::plugin::tables::data::{FieldTypeId, Key};
14use crate::FailureReason;
15use falco_plugin_api::{ss_plugin_state_data, ss_plugin_table_fieldinfo};
16use std::borrow::Borrow;
17use std::collections::BTreeMap;
18use std::ffi::CStr;
19use std::fmt::{Debug, Formatter};
20
21/// # A table exported to other plugins
22///
23/// An instance of this type can be exposed to other plugins via
24/// [`tables::TablesInput::add_table`](`crate::tables::TablesInput::add_table`)
25///
26/// The generic parameters are: key type and entry type. The key type is anything
27/// usable as a table key, while the entry type is a type that can be stored in the table.
28/// You can obtain such a type by `#[derive]`ing Entry on a struct describing all the table fields.
29///
30/// Supported key types include:
31/// - integer types (u8/i8, u16/i16, u32/i32, u64/i64)
32/// - [`crate::tables::import::Bool`] (an API equivalent of bool)
33/// - &CStr (spelled as just `CStr` when used as a generic argument)
34///
35/// See [`crate::tables::export`] for details.
36///
37/// The implementation is thread-safe when the `thread-safe-tables` feature is enabled.
38#[must_use]
39pub struct Table<K, E>
40where
41    K: Key + Ord,
42    K: Borrow<<K as Key>::Borrowed>,
43    <K as Key>::Borrowed: Ord + ToOwned<Owned = K>,
44    E: Entry,
45    E::Metadata: TableMetadata,
46{
47    name: &'static CStr,
48    field_descriptors: Vec<ss_plugin_table_fieldinfo>,
49    metadata: RefShared<ExtensibleEntryMetadata<E::Metadata>>,
50    data: RefShared<BTreeMap<K, RefShared<ExtensibleEntry<E>>>>,
51
52    pub(in crate::plugin::exported_tables) vtable: RefCounted<Option<Box<Vtable>>>,
53}
54
55impl<K, E> Debug for Table<K, E>
56where
57    K: Key + Ord + Debug,
58    K: Borrow<<K as Key>::Borrowed>,
59    <K as Key>::Borrowed: Ord + ToOwned<Owned = K>,
60    E: Entry + Debug,
61    E::Metadata: TableMetadata + Debug,
62{
63    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
64        f.debug_struct("Table")
65            .field("name", &self.name)
66            .field("metadata", &self.metadata)
67            .field("data", &self.data)
68            .finish()
69    }
70}
71
72type TableMetadataType<E> = RefShared<ExtensibleEntryMetadata<<E as HasMetadata>::Metadata>>;
73pub(in crate::plugin::exported_tables) type TableEntryType<E> = RefGuard<ExtensibleEntry<E>>;
74
75impl<K, E> Table<K, E>
76where
77    K: Key + Ord,
78    K: Borrow<<K as Key>::Borrowed>,
79    <K as Key>::Borrowed: Ord + ToOwned<Owned = K>,
80    E: Entry,
81    E::Metadata: TableMetadata,
82{
83    /// Create a new table using provided metadata
84    ///
85    /// This is only expected to be used by the derive macro.
86    pub fn new_with_metadata(
87        tag: &'static CStr,
88        metadata: &TableMetadataType<E>,
89    ) -> Result<Self, anyhow::Error> {
90        let table = Self {
91            name: tag,
92            field_descriptors: vec![],
93            metadata: metadata.clone(),
94            data: new_shared_ref(BTreeMap::new()),
95
96            vtable: new_counted_ref(None),
97        };
98
99        Ok(table)
100    }
101
102    /// Create a new table
103    pub fn new(name: &'static CStr) -> Result<Self, anyhow::Error> {
104        Ok(Self {
105            name,
106            field_descriptors: vec![],
107            metadata: new_shared_ref(ExtensibleEntryMetadata::new()?),
108            data: new_shared_ref(BTreeMap::new()),
109
110            vtable: new_counted_ref(None),
111        })
112    }
113
114    /// Get an accessor to the underlying data
115    ///
116    /// This method returns a reference to the underlying BTreeMap, containing all the table's data.
117    /// It can be useful for:
118    /// - accessing the table from a different thread (with the `thread-safe-tables` feature enabled)
119    /// - bypassing the table API for convenience or more control over locking
120    ///
121    /// To actually access the BTreeMap, you first need to lock the returned object for reading
122    /// (`data.read()`) or writing (`data.write()`).
123    pub fn data(&self) -> RefShared<BTreeMap<K, RefShared<ExtensibleEntry<E>>>> {
124        self.data.clone()
125    }
126
127    /// Return the table name.
128    pub fn name(&self) -> &'static CStr {
129        self.name
130    }
131
132    /// Return the number of entries in the table.
133    pub fn size(&self) -> usize {
134        self.data.read().len()
135    }
136
137    /// Get an entry corresponding to a particular key.
138    pub fn lookup<Q>(&self, key: &Q) -> Option<TableEntryType<E>>
139    where
140        K: Borrow<Q>,
141        Q: Ord + ?Sized,
142    {
143        Some(self.data.read().get(key)?.write_arc())
144    }
145
146    /// Get the value for a field in an entry.
147    pub fn get_field_value(
148        &self,
149        entry: &TableEntryType<E>,
150        field: &FieldDescriptor,
151        out: &mut ss_plugin_state_data,
152    ) -> Result<(), anyhow::Error> {
153        let (type_id, index) = { (field.type_id, field.index) };
154
155        entry.get(index, type_id, out)
156    }
157
158    /// Execute a closure on all entries in the table with read-only access.
159    ///
160    /// The iteration continues until all entries are visited or the closure returns false.
161    // TODO(upstream) the closure cannot store away the entry but we could use explicit docs
162    pub fn iterate_entries<F>(&mut self, mut func: F) -> bool
163    where
164        F: FnMut(&mut TableEntryType<E>) -> bool,
165    {
166        for value in &mut self.data.write().values_mut() {
167            if !func(&mut value.write_arc()) {
168                return false;
169            }
170        }
171        true
172    }
173
174    /// Remove all entries from the table.
175    pub fn clear(&mut self) {
176        self.data.write().clear()
177    }
178
179    /// Erase an entry by key.
180    pub fn erase<Q>(&mut self, key: &Q) -> Option<TableEntryType<E>>
181    where
182        K: Borrow<Q>,
183        Q: Ord + ?Sized,
184    {
185        Some(self.data.write().remove(key)?.write_arc())
186    }
187
188    /// Create a new table entry.
189    ///
190    /// This is a detached entry that can be later inserted into the table using [`Table::insert`].
191    pub fn create_entry(&self) -> Result<TableEntryType<E>, anyhow::Error> {
192        Ok(new_shared_ref(ExtensibleEntry::new_with_metadata(
193            self.name,
194            &self.metadata,
195        )?)
196        .write_arc())
197    }
198
199    /// Return a closure for creating table entries
200    ///
201    /// The `Table` object itself cannot be shared between threads safely even with
202    /// the `thread-safe-tables` feature enabled, but almost full functionality can be achieved
203    /// using two objects that can:
204    /// 1. The underlying BTreeMap, obtained from [Table::data]
205    /// 2. A closure capable of creating a new entry (returned from this function)
206    ///
207    /// The only functionality missing is listing table fields, and until a use case comes along,
208    /// it's likely to remain unimplemented.
209    ///
210    /// The entry obtained by calling the closure returned from `create_entry_fn` can be later
211    /// inserted into the table e.g. by calling [BTreeMap::insert].
212    ///
213    /// To actually access the entry's fields, you first need to lock the returned object for reading
214    /// (`data.read()`) or writing (`data.write()`).
215    pub fn create_entry_fn(
216        &self,
217    ) -> impl Fn() -> Result<RefShared<ExtensibleEntry<E>>, anyhow::Error> + use<K, E> {
218        let name = self.name;
219        let metadata = self.metadata.clone();
220
221        move || {
222            Ok(new_shared_ref(ExtensibleEntry::new_with_metadata(
223                name, &metadata,
224            )?))
225        }
226    }
227
228    /// Attach an entry to a table key
229    pub fn insert<Q>(&mut self, key: &Q, entry: TableEntryType<E>) -> Option<TableEntryType<E>>
230    where
231        K: Borrow<Q>,
232        Q: Ord + ToOwned<Owned = K> + ?Sized,
233    {
234        // note: different semantics from data.insert: we return the *new* entry
235        let new_entry = std::sync::Arc::clone(RefGuard::rwlock(&entry));
236
237        self.data
238            .write()
239            .insert(key.to_owned(), std::sync::Arc::clone(&new_entry));
240        drop(entry);
241        Some(new_entry.write_arc())
242    }
243
244    /// Write a value to a field of an entry
245    pub fn write(
246        &self,
247        entry: &mut TableEntryType<E>,
248        field: &FieldDescriptor,
249        value: &ss_plugin_state_data,
250    ) -> Result<(), anyhow::Error> {
251        if field.read_only {
252            return Err(anyhow::anyhow!("Field is read-only").context(FailureReason::NotSupported));
253        }
254
255        let (type_id, index) = { (field.type_id, field.index) };
256
257        let value = unsafe {
258            DynamicFieldValue::from_data(value, type_id).ok_or_else(|| {
259                anyhow::anyhow!("Cannot store {:?} data (unsupported type)", type_id)
260            })?
261        };
262
263        entry.set(index, value)
264    }
265
266    /// Return a list of fields as a slice of raw FFI objects
267    pub fn list_fields(&mut self) -> &[ss_plugin_table_fieldinfo] {
268        self.field_descriptors.clear();
269        self.field_descriptors.extend(self.metadata.list_fields());
270        self.field_descriptors.as_slice()
271    }
272
273    /// Return a field descriptor for a particular field
274    ///
275    /// The requested `field_type` must match the actual type of the field
276    pub fn get_field(&self, name: &CStr, field_type: FieldTypeId) -> Option<FieldRef> {
277        self.metadata
278            .get_field(name)
279            .filter(|f| f.as_ref().type_id == field_type)
280    }
281
282    /// Add a new field to the table
283    pub fn add_field(
284        &mut self,
285        name: &CStr,
286        field_type: FieldTypeId,
287        read_only: bool,
288    ) -> Option<FieldRef> {
289        self.metadata.add_field(name, field_type, read_only)
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use crate::plugin::exported_tables::entry::dynamic::DynamicEntry;
296    use crate::tables::export::Table;
297    use crate::tables::import::Bool;
298    use crate::tables::TablesInput;
299    use std::ffi::CString;
300
301    // Just a compile test
302    #[allow(unused)]
303    fn add_table(input: &TablesInput) -> anyhow::Result<()> {
304        input.add_table(Table::<i8, DynamicEntry>::new(c"exported")?)?;
305        input.add_table(Table::<i16, DynamicEntry>::new(c"exported")?)?;
306        input.add_table(Table::<i32, DynamicEntry>::new(c"exported")?)?;
307        input.add_table(Table::<i64, DynamicEntry>::new(c"exported")?)?;
308        input.add_table(Table::<u8, DynamicEntry>::new(c"exported")?)?;
309        input.add_table(Table::<u16, DynamicEntry>::new(c"exported")?)?;
310        input.add_table(Table::<u32, DynamicEntry>::new(c"exported")?)?;
311        input.add_table(Table::<u64, DynamicEntry>::new(c"exported")?)?;
312        input.add_table(Table::<Bool, DynamicEntry>::new(c"exported")?)?;
313        input.add_table(Table::<CString, DynamicEntry>::new(c"exported")?)?;
314
315        Ok(())
316    }
317}