diff options
Diffstat (limited to 'doc/afb-plugin-writing.md')
-rw-r--r-- | doc/afb-plugin-writing.md | 1227 |
1 files changed, 0 insertions, 1227 deletions
diff --git a/doc/afb-plugin-writing.md b/doc/afb-plugin-writing.md deleted file mode 100644 index 9e13d0d5..00000000 --- a/doc/afb-plugin-writing.md +++ /dev/null @@ -1,1227 +0,0 @@ -HOWTO WRITE a PLUGIN for AFB-DAEMON -=================================== - version: 1 - Date: 09 juin 2016 - Author: José Bollo - - - -Summary -------- - -Afb-daemon binders serve files through HTTP protocol -and offers to developers the capability to expose application API methods through -HTTP or WebSocket protocol. - -Binder plugins are used to add API to afb-daemon. -This part describes how to write a plugin for afb-daemon. - -Excepting this summary, this document target developers. - -Before moving further through an example, here after -a short overview of binder plugins fundamentals. - -### Nature of a plugin - -A plugin is an independent piece of software. A plugin is self contain and exposes application logic as sharable library. -A plugin is intended to be dynamically loaded by afb-daemon to expose application API. - -Technically, a binder plugin does not reference and is not linked with any afb-daemon library. - -### Class of plugins - -Application binder supports two kinds of plugins: application plugins and service plugins. -Technically both class of plugin are equivalent are use the same coding convention. Only sharing mode and security context diverge. - -#### Application-plugins - -Application-plugins implements the glue in between application's UI and services. Every AGL application -has a corresponding binder that typically activates one or many plugins to interface the application logic with lower platform services. -When an application is started by the AGL application framework, a dedicate binder is started that loads/activates application plugin(s). -API expose by application-plugin are executed within corresponding application security context. - -Application plugins generally handle a unique context for a unique client. As the application framework start -a dedicated instance of afb_daemon for each AGL application, if a given plugin is used within multiple application each of those -application get a new and private instance of eventually "shared" plugin. - -#### Service-plugins - -Service-plugins enable API activation within corresponding service security context and not within calling application context. -Service-plugins are intended to run as a unique instance. Service-plugins can be shared in between multiple clients. - -Service-plugins can either be stateless or manage client context. When managing context each client get a private context. - -Sharing may either be global to the platform (ie: GPS service) or dedicated to a given user (ie: user preferences) - -### Live cycle of plugins within afb-daemon - -Application and service plugins are loaded and activated each time a new afb-daemon is started. - -At launch time, every loaded plugin initialise itself. -If a single plugin initialisation fail corresponding instance of afb-daemon self aborts. - -Conversely, when a plugin initialisation succeeds, it should register -its unique name as well as the list of verbs attached to the methods it exposes. - -When initialised, on request from application clients to the right API/verb, plugin methods -are activated by the afb-daemon attached to the application or service. - -At exit time, no special action is enforced by afb-daemon. When a specific actions is required at afb-daemon stop, -developers should use 'atexit/on_exit' during plugin initialisation sequence to register a custom exit function. - -### Plugin Contend - -Afb-daemon's plugin register two classes of objects: names and functions. - -Plugins declare categories of names: - - A unique plugin name to access all API expose by this plugin, - - One name for each methods/verbs provided by this plugin. - -Plugins declare two categories of functions: - - function use for the initialisation - - functions implementing exposed API methods - -Afb-daemon parses URI requests to extract the API(plugin name) and the VERB(method to activate). -As an example, URI **foo/bar** translates to plugin named **foo** and method named **bar**. -To serve such a request, afb-daemon looks for an active plugin named **foo** and then within this plugin for a method named **bar**. -When find afb-daemon calls corresponding method with attached parameter if any. - -Afb-daemon ignores letter case when parsing URI. Thus **TicTacToe/Board** and **tictactoe/board** are equivalent. - -#### The name of the plugin - -The name of a given plugin is also known as the name -of the API prefix that defines the plugin. - -The name of a plugin SHOULD be unique within a given afb-daemon instance. - -For example, when a client of afb-daemon calls a URI named **foo/bar**. Afb-daemon -extracts the prefix **foo** and the suffix **bar**. **foo** must match a plugin name and **bar** a VERB attached to some method. - -#### Names of methods - -Each plugin exposes a set of methods that can be called -by the clients of a given afb-daemon. - -VERB's name attached to a given plugin (API) MUST be unique within a plugin. - -Plugins static declaration link VERBS to corresponding methods. -When clients emit requests on a given API/VERB corresponding method is called by afb-daemon. - -#### Initialisation function - -Plugin's initialisation function serves several purposes. - -1. It allows afb-daemon to control plugin version depending on initialisation function name. -As today, the only supported initialisation function is **pluginAfbV1Register**. This identifies -version "one" of plugins. - -2. It allows plugins to initialise itself. - -3. It enables names declarations: descriptions, requirements and implementations of exposed API/VERB. - -#### Functions instantiation of API/VERBs - -When an API/VERB is called, afb-daemon constructs a request object. Then it -passes this request object to the implementation function corresponding to requested method, this -within attached API plugin. - -An implementation function receives a request object that -is used to: get arguments of the request, send -answer, store session data. - -A plugin MUST set an answer to every received requests. - -Nevertheless it is not mandatory to set the answer -before returning from API/VERB implementing function. -This behaviour is important for asynchronous actions. - -API/VERB implementation that set an answer before returning are called *synchronous implementations*. -Those that do not systematically set an answer before returning are called *asynchronous implementations*. - -Asynchronous implementations typically launch asynchronous actions. They record some context at -request time and provide answer to the request only at completion of asynchronous actions. - -The Tic-Tac-Toe example ------------------------ - -This part explains how to write an afb-plugin. -For the sake of being practical it uses many -examples based on tic-tac-toe. -This plugin example is in *plugins/samples/tic-tac-toe.c*. - -This plugin is named ***tictactoe***. - -Dependencies when compiling ---------------------------- - -Afb-daemon provides a configuration file for *pkg-config*. -Typing the command - - pkg-config --cflags afb-daemon - -Print flags use for compilation: - - $ pkg-config --cflags afb-daemon - -I/opt/local/include -I/usr/include/json-c - -For linking, you should use - - $ pkg-config --libs afb-daemon - -ljson-c - -Afb-daemon automatically includes dependency to json-c. -This is activated through **Requires** keyword in pkg-config. -While almost every plugin replies on **json-c** this is not a must have dependency. - -Internally, afb-daemon relies on **libsystemd** for its event loop, as well -as for its binding to D-Bus. -Plugins developers are encouraged to leverage **libsystemd** when possible. -Nevertheless there is no hard dependency to **libsystemd** if ever -you rather not use it, feel free to do so. - -> Afb-daemon plugin are fully self contain. They do not enforce dependency on any libraries from the application framework. -> Afb-daemon dependencies requirer to run AGL plugins are given at runtime through pointers leveraging read-only -> memory feature. - -Header files to include ------------------------ - -Plugin *tictactoe* has following includes: - -```C -#define _GNU_SOURCE -#include <stdio.h> -#include <string.h> -#include <json-c/json.h> -#include <afb/afb-plugin.h> -``` - -Header *afb/afb-plugin.h* is the only hard dependency, it includes all features -that a plugin MUST HAVE. Outside of includes used to support application logic, -common external headers used within plugins are: - -- *json-c/json.h*: should be include to handle json objects; -- *systemd/sd-event.h*: should be include to access event main loop; -- *systemd/sd-bus.h*: should be include for dbus connections. - -The *tictactoe* plugin does not leverage systemd features, also only json.h -is used on top of mandatory afb/afb-plugin.h. - -When including *afb/afb-plugin.h*, the macro **_GNU_SOURCE** MUST be -defined. - -Choosing names --------------- - -Designers of plugins should define a unique name for every API plugin -as well as for methods VERBs. They should also define names for request -arguments passed as name/value pair in URI. - -While forging names, designers should respect few rules to -ensure that created names are valid and easy to use across platforms. - -All names and strings are UTF-8 encoded. - -### Names for API (plugin) - -Plugin API name are checked. -All characters are authorised except: - -- the control characters (\u0000 .. \u001f) -- the characters of the set { ' ', '"', '#', '%', '&', - '\'', '/', '?', '`', '\x7f' } - -In other words the set of forbidden characters is -{ \u0000..\u0020, \u0022, \u0023, \u0025..\u0027, - \u002f, \u003f, \u0060, \u007f }. - -Afb-daemon makes no distinction between lower case -and upper case when searching for API/VERB. - -### Names for methods - -The names of methods VERBs are totally free and not checked. - -However, the validity rules for method's VERB name are the -same as for Plugin API name except that the dot(.) character -is forbidden. - -Afb-daemon makes no case distinction when searching for an API by name. - -### Names for arguments - -Argument's name are not restricted and can be everything you wish. - -> Warning arguments search is case sensitive and "index" and "Index" -> are not two different arguments. - -### Forging names widely available - -The key names of javascript object can be almost -anything using the arrayed notation: - - object[key] = value - -Nevertheless this is not the case with javascript dot notation: - - object.key = value - -Using the dot notation, the key must be a valid javascript -identifier and dash(-) as well as few other reserved characters cannot be used. - -For this reason, we advise developper to chose name compatible with both javascript and HTML notation. - -It is a good practice, even for arguments not to rely on case sensitivity. -This may reduce headache strength at debug time, especially with interpreted language like -javascript that may not warn you that a variable was not defined. - -Writing a synchronous method implementation ------------------------------------------ - -The method **tictactoe/board** is a synchronous implementation. -Here is its listing: - -```C -/* - * get the board - */ -static void board(struct afb_req req) -{ - struct board *board; - struct json_object *description; - - /* retrieves the context for the session */ - board = board_of_req(req); - INFO(afbitf, "method 'board' called for boardid %d", board->id); - - /* describe the board */ - description = describe(board); - - /* send the board's description */ - afb_req_success(req, description, NULL); -} -``` - -This example shows many aspects of a synchronous -method implementation. Let summarise it: - -1. The function **board_of_req** retrieves the context stored -for the plugin: the board. - -2. The macro **INFO** sends a message of kind *INFO* -to the logging system. The global variable named **afbitf** -used represents the interface to afb-daemon. - -3. The function **describe** creates a json_object representing -the board. - -4. The function **afb_req_success** sends the reply, attaching to -it the object *description*. - -### The incoming request - -For any implementation, the request is received by a structure of type -**struct afb_req**. - -> Note that this is a PLAIN structure, not a pointer to a structure. - -The definition of **struct afb_req** is: - -```C -/* - * Describes the request by plugins from afb-daemon - */ -struct afb_req { - const struct afb_req_itf *itf; /* the interfacing functions */ - void *closure; /* the closure for functions */ -}; -``` - -It contains two pointers: first one *itf*, points to functions used -to handle internal request. Second one *closure* point onto function closure. - -> The structure must never be used directly. -> Instead developer should use the intended functions provided -> by afb-daemon as described here after. - -*req* is used to get arguments of the request, to send -answer, to store session data. - -This object and its interface is defined and documented -in the file names *afb/afb-req-itf.h* - -The above example uses twice *req* object request. - -The first time, to retrieve the board attached to the session of the request. - -The second time, to send the reply: an object that describes the current board. - -### Associating a client context to a session - -When *tic-tac-toe* plugin receives a request, it musts get -the board describing the game associated to the session. - -For a plugin, having data associated to a session is common. -This data is called "plugin context" for the session. -Within *tic-tac-toe* plugin the context is the board. - -Requests *afb_req* offer four functions for storing and retrieving session associated context. - -These functions are: - -- **afb_req_context_get**: - retrieves context data stored for current plugin. - -- **afb_req_context_set**: - store context data of current plugin. - -- **afb_req_context**: - if exist retrieves context data of current plugin. - if context does not yet exist, creates a new context and store it. - -- **afb_req_context_clear**: - reset the stored context data. - -The plugin *tictactoe* use a convenient function to retrieve -its context: the board. This function is *board_of_req*: - -```C -/* - * retrieves the board of the request - */ -static inline struct board *board_of_req(struct afb_req req) -{ - return afb_req_context(req, (void*)get_new_board, (void*)release_board); -} -``` - -The function **afb_req_context** ensures an existing context -for the session of the request. -Its two last arguments are functions to allocate and free context. -Note function type casts to avoid compilation warnings. - -Here is the definition of the function **afb_req_context** - -```C -/* - * Gets the pointer stored by the plugin for the session of 'req'. - * If the stored pointer is NULL, indicating that no pointer was - * already stored, afb_req_context creates a new context by calling - * the function 'create_context' and stores it with the freeing function - * 'free_context'. - */ -static inline void *afb_req_context(struct afb_req req, void *(*create_context)(), void (*free_context)(void*)) -{ - void *result = afb_req_context_get(req); - if (result == NULL) { - result = create_context(); - afb_req_context_set(req, result, free_context); - } - return result; -} -``` - -The second argument if the function that creates the context. -For plugin *tic-tac-toe* (function **get_new_board**). -The function **get_new_board** creates a new board and set usage its count to 1. -The boards are checking usage count to free resources when not used. - -The third argument is a function that frees context resources. -For plugin *tic-tac-toe* (function **release_board**). -The function **release_board** decrease usage count of the board passed in argument. -When usage count falls to zero, data board are freed. - -Definition of other functions dealing with contexts: - -```C -/* - * Gets the pointer stored by the plugin for the session of 'req'. - * When the plugin has not yet recorded a pointer, NULL is returned. - */ -void *afb_req_context_get(struct afb_req req); - -/* - * Stores for the plugin the pointer 'context' to the session of 'req'. - * The function 'free_context' will be called when the session is closed - * or if plugin stores an other pointer. - */ -void afb_req_context_set(struct afb_req req, void *context, void (*free_context)(void*)); - -/* - * Frees the pointer stored by the plugin for the session of 'req' - * and sets it to NULL. - * - * Shortcut for: afb_req_context_set(req, NULL, NULL) - */ -static inline void afb_req_context_clear(struct afb_req req) -{ - afb_req_context_set(req, NULL, NULL); -} -``` - -### Sending reply to a request - -Two kinds of replies: successful or failure. - -> Sending a reply to a request MUST be done once and only once. - -It exists two functions for "success" replies: **afb_req_success** and **afb_req_success_f**. - -```C -/* - * Sends a reply of kind success to the request 'req'. - * The status of the reply is automatically set to "success". - * Its send the object 'obj' (can be NULL) with an - * informationnal comment 'info (can also be NULL). - * - * For conveniency, the function calls 'json_object_put' for 'obj'. - * Thus, in the case where 'obj' should remain available after - * the function returns, the function 'json_object_get' shall be used. - */ -void afb_req_success(struct afb_req req, struct json_object *obj, const char *info); - -/* - * Same as 'afb_req_success' but the 'info' is a formatting - * string followed by arguments. - * - * For conveniency, the function calls 'json_object_put' for 'obj'. - * Thus, in the case where 'obj' should remain available after - * the function returns, the function 'json_object_get' shall be used. - */ -void afb_req_success_f(struct afb_req req, struct json_object *obj, const char *info, ...); -``` - -It exists two functions for "failure" replies: **afb_req_fail** and **afb_req_fail_f**. - -```C -/* - * Sends a reply of kind failure to the request 'req'. - * The status of the reply is set to 'status' and an - * informational comment 'info' (can also be NULL) can be added. - * - * Note that calling afb_req_fail("success", info) is equivalent - * to call afb_req_success(NULL, info). Thus even if possible it - * is strongly recommended to NEVER use "success" for status. - * - * For conveniency, the function calls 'json_object_put' for 'obj'. - * Thus, in the case where 'obj' should remain available after - * the function returns, the function 'json_object_get' shall be used. - */ -void afb_req_fail(struct afb_req req, const char *status, const char *info); - -/* - * Same as 'afb_req_fail' but the 'info' is a formatting - * string followed by arguments. - * - * For conveniency, the function calls 'json_object_put' for 'obj'. - * Thus, in the case where 'obj' should remain available after - * the function returns, the function 'json_object_get' shall be used. - */ -void afb_req_fail_f(struct afb_req req, const char *status, const char *info, ...); -``` - -> For conveniency, these functions automatically call **json_object_put** to release **obj**. -> Because **obj** usage count is null after being passed to a reply function, it SHOULD not be used anymore. -> If exceptionally **obj** needs to remain usable after reply function then using **json_object_get** on **obj** -> to increase usage count and cancels the effect the **json_object_put** is possible. - -Getting argument of invocation ------------------------------- - -Many methods expect arguments. Afb-daemon's plugins -retrieve arguments by name and not by position. - -Arguments are passed by requests through either HTTP -or WebSockets. - -For example, the method **join** of plugin **tic-tac-toe** -expects one argument: the *boardid* to join. Here is an extract: - -```C -/* - * Join a board - */ -static void join(struct afb_req req) -{ - struct board *board, *new_board; - const char *id; - - /* retrieves the context for the session */ - board = board_of_req(req); - INFO(afbitf, "method 'join' called for boardid %d", board->id); - - /* retrieves the argument */ - id = afb_req_value(req, "boardid"); - if (id == NULL) - goto bad_request; - ... -``` - -The function **afb_req_value** searches in the request *req* -for argument name passed in the second argument. When argument name -is not passed, **afb_req_value** returns NULL. - -> The search is case sensitive and *boardid* is not equivalent to *BoardId*. -> Nevertheless having argument names that only differ by name case is not a good idea. - -### Basic functions for querying arguments - -The function **afb_req_value** is defined here after: - -```C -/* - * Gets from the request 'req' the string value of the argument of 'name'. - * Returns NULL if when there is no argument of 'name'. - * Returns the value of the argument of 'name' otherwise. - * - * Shortcut for: afb_req_get(req, name).value - */ -static inline const char *afb_req_value(struct afb_req req, const char *name) -{ - return afb_req_get(req, name).value; -} -``` - -It is defined as a shortcut to call the function **afb_req_get**. -That function is defined here after: - -```C -/* - * Gets from the request 'req' the argument of 'name'. - * Returns a PLAIN structure of type 'struct afb_arg'. - * When the argument of 'name' is not found, all fields of result are set to NULL. - * When the argument of 'name' is found, the fields are filled, - * in particular, the field 'result.name' is set to 'name'. - * - * There is a special name value: the empty string. - * The argument of name "" is defined only if the request was made using - * an HTTP POST of Content-Type "application/json". In that case, the - * argument of name "" receives the value of the body of the HTTP request. - */ -struct afb_arg afb_req_get(struct afb_req req, const char *name); -``` - -That function takes 2 parameters: the request and the name -of the argument to retrieve. It returns a PLAIN structure of -type **struct afb_arg**. - -There is a special name that is defined when the request is -of type HTTP/POST with a Content-Type being application/json. -This name is **""** (the empty string). In that case, the value -of this argument of empty name is the string received as a body -of the post and is supposed to be a JSON string. - -The definition of **struct afb_arg** is: - -```C -/* - * Describes an argument (or parameter) of a request - */ -struct afb_arg { - const char *name; /* name of the argument or NULL if invalid */ - const char *value; /* string representation of the value of the argument */ - /* original filename of the argument if path != NULL */ - const char *path; /* if not NULL, path of the received file for the argument */ - /* when the request is finalized this file is removed */ -}; -``` - -The structure returns the data arguments that are known for the -request. This data include a field named **path**. This **path** -can be accessed using the function **afb_req_path** defined here after: - -```C -/* - * Gets from the request 'req' the path for file attached to the argument of 'name'. - * Returns NULL if when there is no argument of 'name' or when there is no file. - * Returns the path of the argument of 'name' otherwise. - * - * Shortcut for: afb_req_get(req, name).path - */ -static inline const char *afb_req_path(struct afb_req req, const char *name) -{ - return afb_req_get(req, name).path; -} -``` - -The path is only defined for HTTP/POST requests that send file. - -### Arguments for received files - -As it is explained above, clients can send files using HTTP/POST requests. - -Received files are attached to "file" argument name. For example, the -following HTTP fragment (from test/sample-post.html) -will send an HTTP/POST request to the method -**post/upload-image** with 2 arguments named *file* and -*hidden*. - -```html -<h2>Sample Post File</h2> -<form enctype="multipart/form-data"> - <input type="file" name="file" /> - <input type="hidden" name="hidden" value="bollobollo" /> - <br> - <button formmethod="POST" formaction="api/post/upload-image">Post File</button> -</form> -``` - -Argument named **file** should have both its value and path defined. - -The value is the name of the file as it was set by the HTTP client. -Generally it is the filename on client side. - -The path is the effective path of saved file on the temporary local storage -area of the application. This is a randomly generated and unique filename. -It is not linked with the original filename as used on client side. - -After success the plugin can use the uploaded file directly from local storage path with no restriction: -read, write, remove, copy, rename... -Nevertheless when request reply is set and query terminated, the uploaded temporary file at -path is destroyed. - -### Arguments as a JSON object - -Plugins may also request every arguments of a given call as one single object. -This feature is provided by the function **afb_req_json** defined here after: - -```C -/* - * Gets from the request 'req' the json object hashing the arguments. - * The returned object must not be released using 'json_object_put'. - */ -struct json_object *afb_req_json(struct afb_req req); -``` - -It returns a json object. This object depends on how the request was built: - -- For HTTP requests, this json object uses key names mapped on argument name. -Values are either string for common arguments or object ie: { "file": "...", "path": "..." } - -- For WebSockets requests, returned directly the object as provided by the client. - -> In fact, for Websockets requests, the function **afb_req_value** -> can be seen as a shortcut to -> ***json_object_get_string(json_object_object_get(afb_req_json(req), name))*** - -Initialisation of the plugin and declaration of methods ------------------------------------------------------ - -To be active, plugin's methods should be declared to -afb-daemon. Furthermore, the plugin itself must be recorded. - -The registration mechanism is very basic: when afb-need starts, -it loads all plugins listed in: command line or configuration file. - -Loading a plugin follows the following steps: - -1. Afb-daemon loads the plugin with *dlopen*. - -2. Afb-daemon searches for a symbol named **pluginAfbV1Register** using *dlsym*. -This symbol is assumed to be the exported initialisation function of the plugin. - -3. Afb-daemon builds an interface object for the plugin. - -4. Afb-daemon calls the found function **pluginAfbV1Register** with interface pointer -as parameter. - -5. Function **pluginAfbV1Register** setups the plugin and initialises it. - -6. Function **pluginAfbV1Register** returns the pointer to a structure -describing the plugin: version, name (prefix or API name), and list of methods. - -7. Afb-daemon checks that the returned version and name can be managed. -If so, plugin and its methods are register to become usable as soon as -afb-daemon initialisation is finished. - -Here after the code used for **pluginAfbV1Register** from plugin *tic-tac-toe*: - -```C -/* - * activation function for registering the plugin called by afb-daemon - */ -const struct AFB_plugin *pluginAfbV1Register(const struct AFB_interface *itf) -{ - afbitf = itf; // records the interface for accessing afb-daemon - return &plugin_description; // returns the description of the plugin -} -``` - -It is a very minimal initialisation function because *tic-tac-toe* plugin doesn't -have any application related initialisation step. It merely record daemon's interface -and returns its description. - -The variable **afbitf** is a plugin global variable. It keeps the -interface to afb-daemon that should be used for logging and pushing events. -Here is its declaration: - -```C -/* - * the interface to afb-daemon - */ -const struct AFB_interface *afbitf; -``` - -The description of the plugin is defined here after. - -```C -/* - * array of the methods exported to afb-daemon - */ -static const struct AFB_method_desc_v1 plugin_methods[] = { - /* VERB'S NAME SESSION MANAGEMENT FUNCTION TO CALL SHORT DESCRIPTION */ - { .name= "new", .session= AFB_SESSION_NONE, .callback= new, .info= "Starts a new game" }, - { .name= "play", .session= AFB_SESSION_NONE, .callback= play, .info= "Asks the server to play" }, - { .name= "move", .session= AFB_SESSION_NONE, .callback= move, .info= "Tells the client move" }, - { .name= "board", .session= AFB_SESSION_NONE, .callback= board, .info= "Get the current board" }, - { .name= "level", .session= AFB_SESSION_NONE, .callback= level, .info= "Set the server level" }, - { .name= "join", .session= AFB_SESSION_CHECK,.callback= join, .info= "Join a board" }, - { .name= "undo", .session= AFB_SESSION_NONE, .callback= undo, .info= "Undo the last move" }, - { .name= "wait", .session= AFB_SESSION_NONE, .callback= wait, .info= "Wait for a change" }, - { .name= NULL } /* marker for end of the array */ -}; - -/* - * description of the plugin for afb-daemon - */ -static const struct AFB_plugin plugin_description = -{ - /* description conforms to VERSION 1 */ - .type= AFB_PLUGIN_VERSION_1, - .v1= { /* fills the v1 field of the union when AFB_PLUGIN_VERSION_1 */ - .prefix= "tictactoe", /* the API name (or plugin name or prefix) */ - .info= "Sample tac-tac-toe game", /* short description of of the plugin */ - .methods = plugin_methods /* the array describing the methods of the API */ - } -}; -``` - -The structure **plugin_description** describes the plugin. -It declares the type and version of the plugin, its name, a short description -and its methods list. - -The list of methods is an array of structures describing the methods and terminated by a NULL marker. - -In version one of afb-damon plugin, a method description contains 4 fields: - -- the name of the method, - -- the session management flags, - -- the implementation function to be call for the method, - -- a short description. - -The structure describing methods is defined as follows: - -```C -/* - * Description of one method of the API provided by the plugin - * This enumeration is valid for plugins of type 1 - */ -struct AFB_method_desc_v1 -{ - const char *name; /* name of the method */ - enum AFB_session_v1 session; /* authorisation and session requirements of the method */ - void (*callback)(struct afb_req req); /* callback function implementing the method */ - const char *info; /* textual description of the method */ -}; -``` - -For technical reasons, the enumeration **enum AFB_session_v1** is not exactly an -enumeration but the wrapper of constant definitions that can be mixed using bitwise or -(the C operator |). - -The constants that can bit mixed are: - -Constant name | Meaning --------------------------|------------------------------------------------------------- -**AFB_SESSION_CREATE** | Equals to AFB_SESSION_LOA_EQ_0|AFB_SESSION_RENEW -**AFB_SESSION_CLOSE** | Closes the session after the reply and set the LOA to 0 -**AFB_SESSION_RENEW** | Refreshes the token of authentification -**AFB_SESSION_CHECK** | Just requires the token authentification -**AFB_SESSION_LOA_LE_0** | Requires the current LOA to be lesser then or equal to 0 -**AFB_SESSION_LOA_LE_1** | Requires the current LOA to be lesser then or equal to 1 -**AFB_SESSION_LOA_LE_2** | Requires the current LOA to be lesser then or equal to 2 -**AFB_SESSION_LOA_LE_3** | Requires the current LOA to be lesser then or equal to 3 -**AFB_SESSION_LOA_GE_0** | Requires the current LOA to be greater then or equal to 0 -**AFB_SESSION_LOA_GE_1** | Requires the current LOA to be greater then or equal to 1 -**AFB_SESSION_LOA_GE_2** | Requires the current LOA to be greater then or equal to 2 -**AFB_SESSION_LOA_GE_3** | Requires the current LOA to be greater then or equal to 3 -**AFB_SESSION_LOA_EQ_0** | Requires the current LOA to be equal to 0 -**AFB_SESSION_LOA_EQ_1** | Requires the current LOA to be equal to 1 -**AFB_SESSION_LOA_EQ_2** | Requires the current LOA to be equal to 2 -**AFB_SESSION_LOA_EQ_3** | Requires the current LOA to be equal to 3 - -If any of this flag is set, afb-daemon requires an authentication token -as if **AFB_SESSION_CHECK** flag was also set. - -The special value **AFB_SESSION_NONE** is zero and can be used to bypass token check. - -> Note that **AFB_SESSION_CREATE** and **AFB_SESSION_CLOSE** might be removed in later versions. - -Sending messages to the log system ----------------------------------- - -Afb-daemon provides 4 levels of verbosity and 5 methods for logging messages. - -The verbosity is managed. Options allow the change the verbosity of afb-daemon -and the verbosity of the plugins can be set plugin by plugin. - -The methods for logging messages are defined as macros that test the -verbosity level and that call the real logging function only if the -message must be output. This avoid evaluation of arguments of the -formatting messages if the message must not be output. - -### Verbs for logging messages - -The 5 logging methods are: - -Macro | Verbosity | Meaning | syslog level ---------|:---------:|-----------------------------------|:-----------: -ERROR | 0 | Error conditions | 3 -WARNING | 1 | Warning conditions | 4 -NOTICE | 1 | Normal but significant condition | 5 -INFO | 2 | Informational | 6 -DEBUG | 3 | Debug-level messages | 7 - -You can note that the 2 methods **WARNING** and **INFO** have the same level -of verbosity. But they don't have the same *syslog level*. It means that -they are output with a different level on the logging system. - -All of these methods have the same signature: - -```C -void ERROR(const struct AFB_interface *afbitf, const char *message, ...); -``` - -The first argument **afbitf** is the interface to afb daemon that the -plugin received at initialisation time when **pluginAfbV1Register** is called. - -The second argument **message** is a formatting string compatible with printf/sprintf. - -The remaining arguments are arguments of the formating message like with printf. - -### Managing verbosity - -Depending on the level of verbosity, the messages are output or not. -The following table explains what messages will be output depending -ont the verbosity level. - -Level of verbosity | Outputed macro -:-----------------:|-------------------------- -0 | ERROR -1 | ERROR + WARNING + NOTICE -2 | ERROR + WARNING + NOTICE + INFO -3 | ERROR + WARNING + NOTICE + INFO + DEBUG - -### Output format and destination - -The syslog level is used for forging a prefix to the message. -The prefixes are: - -syslog level | prefix -:-----------:|--------------- -0 | <0> EMERGENCY -1 | <1> ALERT -2 | <2> CRITICAL -3 | <3> ERROR -4 | <4> WARNING -5 | <5> NOTICE -6 | <6> INFO -7 | <7> DEBUG - - -The message is pushed to standard error. -The final destination of the message depends on how systemd service -was configured through its variable **StandardError**. It can be -journal, syslog or kmsg. (See man sd-daemon). - -Sending events --------------- - -Since version 0.5, plugins can broadcast events to any potential listener. -As today only unattended even are supported. Targeted events are expected for next -coming version. - -The plugin *tic-tac-toe* broadcasts events when the board changes. -This is done in the function **changed**: - -```C -/* - * signals a change of the board - */ -static void changed(struct board *board, const char *reason) -{ - ... - struct json_object *description; - - /* get the description */ - description = describe(board); - - ... - - afb_daemon_broadcast_event(afbitf->daemon, reason, description); -} -``` - -The description of the changed board is pushed via the daemon interface. - -Within plugin *tic-tac-toe*, *reason* indicates the origin of -the change. In function **afb_daemon_broadcast_event** the second -parameter is the name of broadcasted event. The third argument is the -object that is transmitted with the event. - -Function **afb_daemon_broadcast_event** is defined here after: - -```C -/* - * Broadcasts widely the event of 'name' with the data 'object'. - * 'object' can be NULL. - * 'daemon' MUST be the daemon given in interface when activating the plugin. - * - * For conveniency, the function calls 'json_object_put' for 'object'. - * Thus, in the case where 'object' should remain available after - * the function returns, the function 'json_object_get' shall be used. - */ -void afb_daemon_broadcast_event(struct afb_daemon daemon, const char *name, struct json_object *object); -``` - -> Be aware, as with reply functions **object** is automatically released using -> **json_object_put** when using this function. Call **json_object_get** before -> calling **afb_daemon_broadcast_event** to keep **object** available -> after function returns. - -Event name received by listeners is prefixed with plugin name. -So when a change occurs after a move, the reason is **move** and every clients -receive an event **tictactoe/move**. - -> Note that nothing is said about case sensitivity of event names. -> However, the event is always prefixed with the name that the plugin -> declared, with the same case, followed with a slash /. -> Thus it is safe to compare event using a case sensitive comparison. - - - -Writing an asynchronous method implementation -------------------------------------------- - -The *tic-tac-toe* example allows two clients or more to share the same board. -This is implemented by the method **join** that illustrated partly how to -retrieve arguments. - -When two or more clients are sharing a same board, one of them can wait -until the state of the board changes, but this could also be implemented using -events because an even is generated each time the board changes. - -In this case, the reply to the wait is sent only when the board changes. -See the diagram below: - - CLIENT A CLIENT B TIC-TAC-TOE - | | | - +--------------|----------------->| wait . . . . . . . . - | | | . - : : : . - : : : . - | | | . - | +----------------->| move . . . . - | | | V . - | |<-----------------+ success of move . - | | | . - |<-------------|------------------+ success of wait < - -Here, this is an invocation of the plugin by an other client that -unblock the suspended *wait* call. -Nevertheless in most case this should be a timer, a hardware event, a sync with -a concurrent process or thread, ... - -Common case of an asynchronous implementation. - -Here is the listing of the function **wait**: - -```C -static void wait(struct afb_req req) -{ - struct board *board; - struct waiter *waiter; - - /* retrieves the context for the session */ - board = board_of_req(req); - INFO(afbitf, "method 'wait' called for boardid %d", board->id); - - /* creates the waiter and enqueues it */ - waiter = calloc(1, sizeof *waiter); - waiter->req = req; - waiter->next = board->waiters; - afb_req_addref(req); - board->waiters = waiter; -} -``` - -After retrieving the board, the function adds a new waiter to -waiters list and returns without setting a reply. - -Before returning, it increases **req** request's reference count using **afb_req_addref** function. - -> When a method returns without setting a reply, -> it **MUST** increment request's reference count -> using **afb_req_addref**. If unpredictable behaviour may pop up. - -Later, when a board changes, it calls *tic-tac-toe* **changed** function -with reason of change in parameter. - -Here is the full listing of the function **changed**: - -```C -/* - * signals a change of the board - */ -static void changed(struct board *board, const char *reason) -{ - struct waiter *waiter, *next; - struct json_object *description; - - /* get the description */ - description = describe(board); - - waiter = board->waiters; - board->waiters = NULL; - while (waiter != NULL) { - next = waiter->next; - afb_req_success(waiter->req, json_object_get(description), reason); - afb_req_unref(waiter->req); - free(waiter); - waiter = next; - } - - afb_event_sender_push(afb_daemon_get_event_sender(afbitf->daemon), reason, description); -} -``` - -The list of waiters is walked and a reply is sent to each waiter. -After sending the reply, the reference count of the request -is decremented using **afb_req_unref** to allow resources to be freed. - -> The reference count **MUST** be decremented using **afb_req_unref** to free -> resources and avoid memory leaks. -> This usage count decrement should happen **AFTER** setting reply or -> bad things may happen. - -How to build a plugin ---------------------- - -Afb-daemon provides a *pkg-config* configuration file that can be -queried by providing **afb-daemon** in command line arguments. -This configuration file provides data that should be used -for plugins compilation. Examples: - -```bash -$ pkg-config --cflags afb-daemon -$ pkg-config --libs afb-daemon -``` - -### Example for cmake meta build system - -This example is the extract for building the plugin *afm-main* using *CMAKE*. - -```cmake -pkg_check_modules(afb afb-daemon) -if(afb_FOUND) - message(STATUS "Creation afm-main-plugin for AFB-DAEMON") - add_library(afm-main-plugin MODULE afm-main-plugin.c) - target_compile_options(afm-main-plugin PRIVATE ${afb_CFLAGS}) - target_include_directories(afm-main-plugin PRIVATE ${afb_INCLUDE_DIRS}) - target_link_libraries(afm-main-plugin utils ${afb_LIBRARIES}) - set_target_properties(afm-main-plugin PROPERTIES - PREFIX "" - LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/afm-main-plugin.export-map" - ) - install(TARGETS afm-main-plugin LIBRARY DESTINATION ${plugin_dir}) -else() - message(STATUS "Not creating the plugin for AFB-DAEMON") -endif() -``` - -Let now describe some of these lines. - -```cmake -pkg_check_modules(afb afb-daemon) -``` - -This first lines searches to the *pkg-config* configuration file for -**afb-daemon**. Resulting data are stored in the following variables: - -Variable | Meaning -------------------|------------------------------------------------ -afb_FOUND | Set to 1 if afb-daemon plugin development files exist -afb_LIBRARIES | Only the libraries (w/o the '-l') for compiling afb-daemon plugins -afb_LIBRARY_DIRS | The paths of the libraries (w/o the '-L') for compiling afb-daemon plugins -afb_LDFLAGS | All required linker flags for compiling afb-daemon plugins -afb_INCLUDE_DIRS | The '-I' preprocessor flags (w/o the '-I') for compiling afb-daemon plugins -afb_CFLAGS | All required cflags for compiling afb-daemon plugins - -If development files are found, the plugin can be added to the set of -target to build. - -```cmake -add_library(afm-main-plugin MODULE afm-main-plugin.c) -``` - -This line asks to create a shared library having a single -source file named afm-main-plugin.c to be compiled. -The default name of the created shared object is -**libafm-main-plugin.so**. - -```cmake -set_target_properties(afm-main-plugin PROPERTIES - PREFIX "" - LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/afm-main-plugin.export-map" -) -``` - -This lines are doing two things: - -1. It renames the built library from **libafm-main-plugin.so** to **afm-main-plugin.so** -by removing the implicitly added prefix *lib*. This step is not mandatory -because afb-daemon doesn't check names of files at load time. -The only filename convention used by afb-daemon relates to **.so** termination. -*.so pattern is used when afb-daemon automatically discovers plugin from a directory hierarchy. - -2. It applies a version script at link time to only export the reserved name -**pluginAfbV1Register** for registration entry point. By default, when building -a shared library linker exports all the public symbols (C functions that are not **static**). - -Next line are: - -```cmake -target_include_directories(afm-main-plugin PRIVATE ${afb_INCLUDE_DIRS}) -target_link_libraries(afm-main-plugin utils ${afb_LIBRARIES}) -``` - -As you can see it uses the variables computed by ***pkg_check_modules(afb afb-daemon)*** -to configure the compiler and the linker. - -### Exporting the function pluginAfbV1Register - -The function **pluginAfbV1Register** MUST be exported. This can be achieved -using a version script at link time. Here after is a version script used for -*tic-tac-toe* (plugins/samples/export.map). - - { global: pluginAfbV1Register; local: *; }; - -This sample [version script](https://sourceware.org/binutils/docs-2.26/ld/VERSION.html#VERSION) -exports as global the symbol *pluginAfbV1Register* and hides any -other symbols. - -This version script is added to the link options using the -option **--version-script=export.map** is given directly to the -linker or using the option **-Wl,--version-script=export.map** -when the option is given to the C compiler. - -### Building within yocto - -Adding a dependency to afb-daemon is enough. See below: - - DEPENDS += " afb-daemon " - |