Browse Source
I might have gone a bit overboard with the type-checking, but typesafe_cb_cast is quite nice to use, so why not. The macro to register a new hook encapsulates the entire flow from param serialization, to dispatch, parsing and callback dispatch in one bundle. I was tempted to have the callback outside of the registration, but it's unlikely that we'll have two calls to the same hook with different callbacks. Signed-off-by: Christian Decker <decker.christian@gmail.com>plugin-timeout-inc
committed by
Rusty Russell
4 changed files with 127 additions and 0 deletions
@ -0,0 +1,6 @@ |
|||
#include <lightningd/plugin_hook.h> |
|||
|
|||
void plugin_hook_call_(struct lightningd *ld, const struct plugin_hook *hook, |
|||
void *payload, void *cb_arg) |
|||
{ |
|||
} |
@ -0,0 +1,115 @@ |
|||
#ifndef LIGHTNING_LIGHTNINGD_PLUGIN_HOOK_H |
|||
#define LIGHTNING_LIGHTNINGD_PLUGIN_HOOK_H |
|||
|
|||
#include "config.h" |
|||
#include <ccan/autodata/autodata.h> |
|||
#include <ccan/tal/tal.h> |
|||
#include <ccan/typesafe_cb/typesafe_cb.h> |
|||
#include <lightningd/json_stream.h> |
|||
#include <lightningd/lightningd.h> |
|||
#include <lightningd/plugin.h> |
|||
|
|||
/**
|
|||
* Plugin hooks are a way for plugins to implement custom behavior and |
|||
* reactions to certain things in `lightningd`. `lightningd` will ask |
|||
* plugins that have registered a hook for a given event how it'd like |
|||
* to proceed. This allows plugins to deviate from the default |
|||
* behavior that `lightningd` otherwise implements. |
|||
* |
|||
* Examples include storing an additional backup of important |
|||
* information belonging to the wallet before committing to it, or |
|||
* holding an incoming payment that is guaranteed to succeed for some |
|||
* time in order to check that the delivery of goods works correctly, |
|||
* giving the option of instantly refunding should something go wrong. |
|||
* |
|||
* Hooks are commonly structured into a number of converter functions |
|||
* and a callback. The converter functions convert from an internal |
|||
* struct representation of the method arguments to a JSON-object for |
|||
* delivery to the plugin, and from a JSON-object to the internal |
|||
* representation: |
|||
* |
|||
* - `serialize_payload` which takes a payload of type `payload_type` |
|||
* and serializes it into the given `json_stream`. ` |
|||
* |
|||
* - `deserialize_response` takes a `json_stream` and parses it into a |
|||
* new struct of type `response_type`, |
|||
* |
|||
* - `response_cb` is called once the plugin has responded and the |
|||
* response has been parsed by `deserialize_response`. In addition |
|||
* an arbitrary additional argument of type `cb_arg_type` can be |
|||
* passed along that may contain any additional context necessary. |
|||
* |
|||
* |
|||
* To make hook invocations easier, each hook registered with |
|||
* `REGISTER_PLUGIN_HOOK` provides a `plugin_hook_call_hookname` |
|||
* function that performs typechecking at compile time, and makes sure |
|||
* that all the provided functions for serialization, deserialization |
|||
* and callback have the correct type. |
|||
*/ |
|||
|
|||
struct plugin_hook { |
|||
const char *name; |
|||
void (*response_cb)(void *arg, void *response); |
|||
void (*serialize_payload)(void *src, struct json_stream *dest); |
|||
void *(*deserialize_response)(const tal_t *, const char *buffer, |
|||
const jsmntok_t *toks); |
|||
|
|||
/* Which plugin has registered this hook? */ |
|||
struct plugin *plugin; |
|||
}; |
|||
AUTODATA_TYPE(hooks, struct plugin_hook); |
|||
|
|||
/* Do not call this directly, rather use the `plugin_hook_call_name`
|
|||
* wrappers generated by the `PLUGIN_HOOK_REGISTER` macro. |
|||
*/ |
|||
void plugin_hook_call_(struct lightningd *ld, const struct plugin_hook *hook, |
|||
void *payload, void *cb_arg); |
|||
|
|||
|
|||
/* Create a small facade in from of `plugin_hook_call_` to make sure
|
|||
* arguments are of the correct type before downcasting them to `void |
|||
* *`. Not really necessary, but nice since it also makes sure that |
|||
* the method-name is correct for the call. |
|||
*/ |
|||
/* FIXME: Find a way to avoid back-to-back declaration and definition */ |
|||
#define PLUGIN_HOOK_CALL_DEF(name, payload_type, response_cb_arg_type) \ |
|||
static inline void plugin_hook_call_##name( \ |
|||
struct lightningd *ld, payload_type payload, \ |
|||
response_cb_arg_type cb_arg) \ |
|||
{ \ |
|||
plugin_hook_call_(ld, &name##_hook_gen, (void *)payload, \ |
|||
(void *)cb_arg); \ |
|||
} |
|||
|
|||
/* Typechecked registration of a plugin hook. We check that the
|
|||
* serialize_payload function converts an object of type payload_type |
|||
* to a json_stream (.params object in the JSON-RPC request), that the |
|||
* deserialize_response function converts from the JSON-RPC response |
|||
* json_stream to an object of type response_type and that the |
|||
* response_cb function accepts the deserialized response format and |
|||
* an arbitrary extra argument used to maintain context. |
|||
*/ |
|||
#define REGISTER_PLUGIN_HOOK(name, response_cb, response_cb_arg_type, \ |
|||
serialize_payload, payload_type, \ |
|||
deserialize_response, response_type) \ |
|||
struct plugin_hook name##_hook_gen = { \ |
|||
stringify(name), \ |
|||
typesafe_cb_cast(void (*)(void *, void *), \ |
|||
void (*)(response_cb_arg_type, response_type), \ |
|||
response_cb), \ |
|||
typesafe_cb_cast(void (*)(void *, struct json_stream *), \ |
|||
void (*)(payload_type, struct json_stream *), \ |
|||
serialize_payload), \ |
|||
typesafe_cb_cast( \ |
|||
void *(*)(const tal_t *, const char *, const jsmntok_t *), \ |
|||
response_type (*)(const tal_t *, const char *, \ |
|||
const jsmntok_t *), \ |
|||
deserialize_response), \ |
|||
NULL, /* .plugin */ \ |
|||
}; \ |
|||
AUTODATA(hooks, &name##_hook_gen); \ |
|||
PLUGIN_HOOK_CALL_DEF(name, payload_type, response_cb_arg_type); |
|||
|
|||
bool plugin_hook_register(struct plugin *plugin, const char *method); |
|||
|
|||
#endif /* LIGHTNING_LIGHTNINGD_PLUGIN_HOOK_H */ |
Loading…
Reference in new issue