Module import

Source
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 used 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 used 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§

TableData
A trait describing types usable as table keys and values

Type Aliases§

RuntimeEntry
A table entry with no predefined fields

Derive Macros§

TableMetadata
Mark a struct type as an imported table entry metadata