# CNSPRCY Event Handlers As with the rest of the project, the concept of event handlers is heavily inspired by, if not based on, the [`serf` event handlers](https://www.serf.io/docs/agent/event-handlers.html). There are several differences however, most notably the way they are specified: whereas each event handler is specified individually in `serf`, here only a directory is specified, which is then recursively traversed by the daemon to collect all event handlers. Further, the CNSPRCY daemon parses the relative paths of handler files to determine which events they handle. Only the path components relative to the directory specified in the config file are considered. The structure of handler directories is flexible and allows grouping handlers into sub-directories as well as (sym-)linking to files inside and outside the handler directory. The daemon parses all (relative) path components and not just the filename, so this can be used to restrict or expand the set of events a particular file will handle. In the case of sub-directories, any filters they introduce will apply to all of their children, recursively. Using (sym)-links, the set of events any actual file will be called to handle can be increased: filters of the file being pointed to are not considered, only filters that apply to the link itself. #### Isolated Path Components To distinguish filter expressions from other filters and words used for naming purposes, they have to be isolated from other components in the path. This means a filter will only be parsed from a file or directory name if it is separated from other words by a `.` character. This is only necessary inside file/directory names since path components are separated by the `/` character. As such, this is a valid handler entry: `peer/@conspirator_name/seen.notify.sh`. It will be called for `Peer` events about the peer with the name `conspirator_name`, further restricted to only `seen` events. By the same token, this is incorrect: `peer@conspirator_name/seen_notify.sh`. The `peer` and `@conspirator_name` components are not separated by a `.` or `/`, so neither filters apply. Similarly, events are also *not* restricted to `seen` events, rather this handler is taken to be named `seen_notify`. It is planned that the daemon will use the extension of a file to determine how to interact with that handler, but for now they are ignored (or stripped for displaying the handler name). ## Event Types Every entry (either a file or a link to one) can handle one type of event. ### Daemon Events Daemon events are events that trigger when the CNSPRCY daemon that is running on this device enters a new state. States include: - `start` - `stop` - `active` - `inactive` `Start` & `Stop` are guaranteed to only be entered once during the lifetime of the daemon process (at startup and shutdown, respectively). The `Active` & `Inactive` states however may be entered into any number of times. The daemon is `Active` when it is attached to at least one (network) interface of any sort and `Inactive` when it has no way to even try to contact any peer. #### Filter Daemon events will be filtered by state if there is an isolated path component that matches one of the states (all lowercase). For example: `daemon/start.#log_daemon_start.sh`. ### Peer Events There are three types of peer events: - `Seen`: the daemon has received a valid message from this peer - `NewAddress`: the peer is using a different address to communicate\* - `StatusChanged`: this peer is understood to have entered a different state \* Currently, this event is also triggered whenever any connection with the peer is established, (e.g. after the peer was unreachable or had quit), even if it is from the same address as was used the last time it sent a message. This may change in the future. These are the states a peer may assume: - `Active` - `Suspicious` - `Unreachable` - `Indirect` - `Quit` ##### Filters Peer events can be filtered by type and [peer](#filter-by-peer). To filter by type, include the type in snake-case, i.e.: - `seen` - `new_address` - `status_changed` ### Dynamic Events Dynamic events are events created by the user or other applications. They consist of two strings: the tag and the message. The tag - is a `.`-separated string like `util.clipboard.set`, - is used to determine the handler(s) that will receive this event, - may not contain any control characters, the null byte, `@`, `/`, `\`, `#`, or invalid UTF-8. - To avoid confusion with the event type filters, `peer` and `daemon` may also not occur anywhere in the string *unless* they are part of a word (e.g. `config.daemon.setup` is invalid, but `config.daemon_setup` is okay). Fewer constraints are imposed on the message string, which must be valid UTF-8 and not include the null byte (because it is passed to handlers using environment variables). It also should not include control characters. To send completely arbitrary data, encode it using base64 (or something similar). #### Tag Format Besides the constraints mentioned above, users have complete freedom in choosing tags. It is entirely up to the user how (if at all) to group related handlers into directories. As described in [Isolated Path Components](#isolated-path-components), the components of a tag may be reflected in any component of the path of the handler. ## Filter by Peer To filter by peer use the `@conspirator_name` notation introduced above. If you wish to specify peers by ID instead, enclose the ID with square brackets like so: `@[ZY9F3qDO]`. Peer names are resolved to IDs at load-time. As such only peers that are listed in the config file may be specified by name. Currently, filters specified by ID are not verified in this way. ## File Extensions Currently file extensions (even common ones like `.sh` or `.py`) are **not** ignored / stripped. **TODO!** ## Example This is an example directory structure that contains handlers for some dynamic, peer and all daemon events, grouped into directories. ``` cnsprcy_handlers │ handler root directory name (ignored) ├── util │ │ ├── clipboard │ │ │ └── set.sh │ dynamic: util.clipboard.set │ └── notify.py │ dynamic: util.notify ├── media.mpd │ dynamic: media.mpd ├── sys │ │ └── shell.sh │ dynamic: sys.shell ├── #testing │ ignored name │ ├── daemon.#notify.py │ all daemon events │ ├── peer │ │ │ ├── new_address.sh │ peer: "NewAddress" events (from anyone) │ │ └── status_changed.#display.py │ peer: "StatusChanged" events (from anyone) │ └── @laptop │ │ ├── test.log.sh │ dynamic: test.log from "laptop" │ └── peer.seen.#log.sh │ peer: "Seen" events from "laptop" └── sync.file.send.py │ dynamic: sync.file.send ```