Expand description
§Importing tables from other plugins (or Falco core)
Your plugin can access tables exported by other plugins (or Falco core) by importing them.
The recommended approach is to use the #[derive(TableMetadata)]
macro for that purpose.
You will probably want to define two additional type aliases, so that the full definition involves:
- a type alias for the whole table
- a type alias for a single table entry
- a metadata struct, describing an entry (somewhat indirectly)
For example:
type NestedThing = Entry<Arc<NestedThingMetadata>>;
type NestedThingTable = Table<u64, NestedThing>;
#[derive(TableMetadata)]
#[entry_type(NestedThing)]
struct NestedThingMetadata {
number: Field<u64, NestedThing>,
}
type ImportedThing = Entry<Arc<ImportedThingMetadata>>;
type ImportedThingTable = Table<u64, ImportedThing>;
#[derive(TableMetadata)]
#[entry_type(ImportedThing)]
struct ImportedThingMetadata {
imported: Field<u64, ImportedThing>,
nested: Field<NestedThingTable, ImportedThing>,
#[name(c"type")]
thing_type: Field<u64, ImportedThing>,
#[custom]
added: Field<CStr, ImportedThing>,
}
In contrast to exported tables, the entry struct does not contain any accessible fields. It only provides generated methods to access each field using the plugin API. This means that each read/write is fairly expensive (involves method calls), so you should probably cache the values in local variables.
§Declaring fields
You need to declare each field you’re going to use in a particular table, by providing
a corresponding import::Field
field in the metadata struct. You do not need
to declare all fields in the table, or put the fields in any particular order, but you
do need to get the type right (otherwise you’ll get an error at initialization time).
The Falco table field name is the same as the field name in your metadata struct,
unless overridden by #[name(c"foo")]
. This is useful if a field’s name is a Rust reserved
word (e.g. type
).
You can also add fields to imported tables. To do that, tag the field with a #[custom]
attribute. It will be then added to the table instead of looking it up in existing fields.
Note that multiple plugins can add a field with the same name and type, which will make them
all use the same field (they will share the data). Adding a field multiple times
with different types is not allowed and will cause an error at initialization time.
§Generated methods
Each scalar field gets a getter and setter method, e.g. declaring a metadata struct like
the above example will generate the following methods on the ImportedThing
type
(for the scalar fields):
fn get_imported(&self, reader: &TableReader) -> Result<u64, anyhow::Error>;
fn set_imported(&self, writer: &TableWriter, value: &u64) -> Result<(), anyhow::Error>;
fn get_thing_type(&self, reader: &TableReader) -> Result<u64, anyhow::Error>;
fn set_thing_type(&self, writer: &TableWriter, value: &u64) -> Result<(), anyhow::Error>;
fn get_added<'a>(&'a self, reader: &TableReader) -> Result<&'a CStr, anyhow::Error>;
fn set_added(&self, writer: &TableWriter, value: &CStr) -> Result<(), anyhow::Error>;
Each table-typed field (nested table) gets a getter and a nested getter, so the above example
will generate the following methods for the nested
field:
fn get_nested(&self, reader: &TableReader) -> Result<NestedThingTable, anyhow::Error>;
fn get_nested_by_key(&self, reader: &TableReader, key: &u64)
-> Result<NestedThing, anyhow::Error>;
Note: setters do not take &mut self
as all the mutation happens on the other side
of the API (presumably in another plugin).
§Visibility of generated methods
The generated methods are actually trait implementations, not inherent impls (due to proc
macro limitations). The traits in question are automatically generated and use
d
in the scope that defines the table struct. However, when you try to use them from
a different module, you will get an error since they’re not in scope (as traits need to
be use
d explicitly before their methods can be called).
The module they’re defined in has an autogenerated name (__falco_plugin_private_<TABLE_TYPE_NAME>
)
that doesn’t look too nice when explicitly used, so for larger plugins (where the table
access is split across modules) you can use the #[accessors_mod]
attribute to choose
a friendlier name for this module:
pub mod imported_table {
pub type ImportedThing = Entry<Arc<ImportedThingMetadata>>;
type ImportedThingTable = Table<u64, ImportedThing>;
#[derive(TableMetadata)]
#[entry_type(ImportedThing)]
#[accessors_mod(thing_accessors)]
pub struct ImportedThingMetadata {
imported: Field<u64, ImportedThing>,
}
}
mod use_the_table {
use super::imported_table::ImportedThing;
use super::imported_table::thing_accessors::*;
fn use_the_table(thing: &ImportedThing, reader: &impl TableReader)
-> Result<u64, anyhow::Error> {
thing.get_imported(reader)
}
}
§Example
use std::ffi::CStr;
use std::sync::Arc;
use falco_plugin::anyhow::Error;
use falco_plugin::base::Plugin;
use falco_plugin::event::events::types::EventType;
use falco_plugin::parse::{EventInput, ParseInput, ParsePlugin};
use falco_plugin::tables::TablesInput;
use falco_plugin::tables::import::{Entry, Field, Table, TableMetadata};
#[derive(TableMetadata)]
#[entry_type(ImportedThing)]
struct ImportedThingMetadata {
imported: Field<u64, ImportedThing>,
#[name(c"type")]
thing_type: Field<u64, ImportedThing>,
#[custom]
added: Field<CStr, ImportedThing>,
}
type ImportedThing = Entry<Arc<ImportedThingMetadata>>;
type ImportedThingTable = Table<u64, ImportedThing>;
struct MyPlugin {
things: ImportedThingTable,
}
impl Plugin for MyPlugin {
// ...
fn new(input: Option<&TablesInput>, _config: Self::ConfigType) -> Result<Self, Error> {
let input = input.ok_or_else(|| anyhow::anyhow!("did not get table input"))?;
let things: ImportedThingTable = input.get_table(c"things")?;
Ok(Self { things })
}
}
impl ParsePlugin for MyPlugin {
const EVENT_TYPES: &'static [EventType] = &[];
const EVENT_SOURCES: &'static [&'static str] = &[];
fn parse_event(&mut self, event: &EventInput, parse_input: &ParseInput)
-> anyhow::Result<()> {
// creating and accessing entries
let reader = &parse_input.reader;
let writer = &parse_input.writer;
// create a new entry (not yet attached to a table key)
let entry = self.things.create_entry(writer)?;
entry.set_imported(writer, &5u64)?;
// attach the entry to a table key
self.things.insert(reader, writer, &1u64, entry)?;
// look up the entry we have just added
let entry = self.things.get_entry(reader, &1u64)?;
assert_eq!(entry.get_imported(reader).ok(), Some(5u64));
Ok(())
}
}
Note: The derive macro involves creating a private module (to avoid polluting the top-level namespace with a bunch of one-off traits), so you cannot use it inside a function due to scoping issues. See https://github.com/rust-lang/rust/issues/83583 for details.
§Bypassing the derive macro
The derive macro boils down to automatically calling get_field/add_field for each
field defined in the metadata struct (and generating getters/setters). If you don’t know
the field names in advance (e.g. when supporting different versions of “parent” plugins),
there is the import::RuntimeEntry
type alias, which makes you responsible for holding
the field structs (probably in your plugin type) and requires you to use the generic
read_field/write_field methods, in exchange for the flexibility.
The above example can be rewritten without the derive macro as follows:
use std::ffi::CStr;
use falco_plugin::anyhow::Error;
use falco_plugin::base::Plugin;
use falco_plugin::event::events::types::EventType;
use falco_plugin::parse::{EventInput, ParseInput, ParsePlugin};
use falco_plugin::tables::TablesInput;
use falco_plugin::tables::import::{Field, RuntimeEntry, Table};
struct ImportedThingTag;
type ImportedThing = RuntimeEntry<ImportedThingTag>;
type ImportedThingTable = Table<u64, ImportedThing>;
struct MyPlugin {
things: ImportedThingTable,
thing_imported_field: Field<u64, ImportedThing>,
thing_type_field: Field<u64, ImportedThing>,
thing_added_field: Field<CStr, ImportedThing>,
}
impl Plugin for MyPlugin {
// ...
fn new(input: Option<&TablesInput>, _config: Self::ConfigType) -> Result<Self, Error> {
let input = input.ok_or_else(|| anyhow::anyhow!("did not get table input"))?;
let things: ImportedThingTable = input.get_table(c"things")?;
let thing_imported_field = things.get_field(input, c"imported")?;
let thing_type_field = things.get_field(input, c"type")?;
let thing_added_field = things.add_field(input, c"added")?;
Ok(Self {
things,
thing_imported_field,
thing_type_field,
thing_added_field,
})
}
}
impl ParsePlugin for MyPlugin {
const EVENT_TYPES: &'static [EventType] = &[];
const EVENT_SOURCES: &'static [&'static str] = &[];
fn parse_event(&mut self, event: &EventInput, parse_input: &ParseInput)
-> anyhow::Result<()> {
// creating and accessing entries
let reader = &parse_input.reader;
let writer = &parse_input.writer;
// create a new entry (not yet attached to a table key)
let entry = self.things.create_entry(writer)?;
entry.write_field(writer, &self.thing_imported_field, &5u64)?;
// attach the entry to a table key
self.things.insert(reader, writer, &1u64, entry)?;
// look up the entry we have just added
let entry = self.things.get_entry(reader, &1u64)?;
assert_eq!(
entry.read_field(reader, &self.thing_imported_field).ok(),
Some(5u64),
);
Ok(())
}
}
Note: in the above example, ImportedThingTag
is just an empty struct, used to
distinguish entries (and fields) from different types between one another. You can
skip this and do not pass the second generic argument to Field
and Table
(it will default to RuntimeEntry<()>
), but you lose compile time validation for
accessing fields from the wrong table. It will still be caught at runtime.
See the import::Table
type for additional methods on tables, to e.g. iterate
over entries or clear the whole table.
Structs§
- Bool
- A boolean value to use in tables
- Entry
- An entry in a Falco plugin table
- Field
- Table field descriptor
- Table
- A table imported via the Falco plugin API
Traits§
- Table
Data - A trait describing types usable as table keys and values
Type Aliases§
- Runtime
Entry - A table entry with no predefined fields
Derive Macros§
- Table
Metadata - Mark a struct type as an imported table entry metadata