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> {
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        self.data.write().insert(
236            key.to_owned(),
237            std::sync::Arc::clone(RefGuard::rwlock(&entry)),
238        );
239        drop(entry);
240        self.lookup(key)
241    }
242
243    /// Write a value to a field of an entry
244    pub fn write(
245        &self,
246        entry: &mut TableEntryType<E>,
247        field: &FieldDescriptor,
248        value: &ss_plugin_state_data,
249    ) -> Result<(), anyhow::Error> {
250        if field.read_only {
251            return Err(anyhow::anyhow!("Field is read-only").context(FailureReason::NotSupported));
252        }
253
254        let (type_id, index) = { (field.type_id, field.index) };
255
256        let value = unsafe {
257            DynamicFieldValue::from_data(value, type_id).ok_or_else(|| {
258                anyhow::anyhow!("Cannot store {:?} data (unsupported type)", type_id)
259            })?
260        };
261
262        entry.set(index, value)
263    }
264
265    /// Return a list of fields as a slice of raw FFI objects
266    pub fn list_fields(&mut self) -> &[ss_plugin_table_fieldinfo] {
267        self.field_descriptors.clear();
268        self.field_descriptors.extend(self.metadata.list_fields());
269        self.field_descriptors.as_slice()
270    }
271
272    /// Return a field descriptor for a particular field
273    ///
274    /// The requested `field_type` must match the actual type of the field
275    pub fn get_field(&self, name: &CStr, field_type: FieldTypeId) -> Option<FieldRef> {
276        self.metadata
277            .get_field(name)
278            .filter(|f| f.as_ref().type_id == field_type)
279    }
280
281    /// Add a new field to the table
282    pub fn add_field(
283        &mut self,
284        name: &CStr,
285        field_type: FieldTypeId,
286        read_only: bool,
287    ) -> Option<FieldRef> {
288        self.metadata.add_field(name, field_type, read_only)
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use crate::plugin::exported_tables::entry::dynamic::DynamicEntry;
295    use crate::tables::export::Table;
296    use crate::tables::import::Bool;
297    use crate::tables::TablesInput;
298    use std::ffi::CString;
299
300    // Just a compile test
301    #[allow(unused)]
302    fn add_table(input: &TablesInput) -> anyhow::Result<()> {
303        input.add_table(Table::<i8, DynamicEntry>::new(c"exported")?)?;
304        input.add_table(Table::<i16, DynamicEntry>::new(c"exported")?)?;
305        input.add_table(Table::<i32, DynamicEntry>::new(c"exported")?)?;
306        input.add_table(Table::<i64, DynamicEntry>::new(c"exported")?)?;
307        input.add_table(Table::<u8, DynamicEntry>::new(c"exported")?)?;
308        input.add_table(Table::<u16, DynamicEntry>::new(c"exported")?)?;
309        input.add_table(Table::<u32, DynamicEntry>::new(c"exported")?)?;
310        input.add_table(Table::<u64, DynamicEntry>::new(c"exported")?)?;
311        input.add_table(Table::<Bool, DynamicEntry>::new(c"exported")?)?;
312        input.add_table(Table::<CString, DynamicEntry>::new(c"exported")?)?;
313
314        Ok(())
315    }
316}