X-Git-Url: https://gerrit.automotivelinux.org/gerrit/gitweb?a=blobdiff_plain;f=doc%2Fafb-plugin-writing.md;h=57b2609515f7b5f86565df65716a2d8dbe3b0560;hb=6914f7781b42972263a417484bbeb179efe66e78;hp=7861021bbf5423f9af80bd0f25512f253becb8b0;hpb=d96d0533b8326570db57d13b8f808bc62d1a7fa4;p=src%2Fapp-framework-binder.git diff --git a/doc/afb-plugin-writing.md b/doc/afb-plugin-writing.md index 7861021b..57b26095 100644 --- a/doc/afb-plugin-writing.md +++ b/doc/afb-plugin-writing.md @@ -1,7 +1,7 @@ HOWTO WRITE a PLUGIN for AFB-DAEMON =================================== version: 1 - Date: 27 mai 2016 + Date: 29 mai 2016 Author: José Bollo TABLE-OF-CONTENT-HERE @@ -9,95 +9,84 @@ TABLE-OF-CONTENT-HERE Summary ------- -The binder afb-daemon serves files through -the HTTP protocol and offers access to API's through +The binder afb-daemon serves files through HTTP protocol +and offers to developers the capability to expose application APIs through HTTP or WebSocket protocol. -The plugins are used to add API's to afb-daemon. +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 part is intended to be read -by developpers. +by developers. -Before going into details, through a tiny example, -a short overview plugins basis is needed. +Before moving further through an example, here after +a short overview of binder plugins fundamentals. ### Nature of a plugin -A plugin is a separate piece of code made of a shared library. -The plugin is loaded and activated by afb-daemon when afb-daemon -starts. +A plugin is an independent piece of software, self contain and expose as a dynamically loadable library. +A plugin is loaded by afb-daemon that exposes contained API dynamically at runtime. -Technically, a plugin is not linked to any library of afb-daemon. +Technically, a binder plugins does not reference and is not linked with any library from afb-daemon. -### Kinds of plugins +### Class of plugins -There is two kinds of plugins: application plugins and service -plugins. +Application binder supports two kinds of plugins: application plugins and service +plugins. Technically both class of plugin are equivalent and coding API is shared. Only sharing mode and security context diverge. -#### Application plugins +#### Application-plugins -Application plugins are intended to be instanciated for each -application: when an application using that plugin is started, -its binder starts a new instance of the plugin. +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 AGL application framework, a dedicate binder is started that loads/activates application plugin(s). +The API expose by application-plugin are executed within corresponding application security context. -It means that the application plugins mainly have only one -context to manage for one client. +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 this "shared" plugin. -#### Service plugins +#### Service-plugins -Service plugins are intended to be instanciated only one time -only and connected to many clients. +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 that is shared in between multiple clients. -So either it does not manage context at all or otherwise, -if it manages context, it should be able to manage one context -per client. +Service-plugins can either be stateless or manage client context. When managing context each client get a private context. -In details, it may be useful to have service plugins at a user -level. +Sharing may either be global to the platform (ie: GPS service) or dedicated to a given user (ie: preference management) -### Live cycle of a plugin within afb-daemon +### Live cycle of plugins within afb-daemon -The plugins are loaded and activated when afb-daemon starts. +Application and service plugins are loaded and activated each time a new afb-daemon is started. -At start, the plugin initialise itself. -If it fails to initialise then afb-daemon stops. +At launch time, every loaded plugin initialise itself. +If a single plugin initialisation fail corresponding instance of afb-daemon self aborts. -Conversely, if it success to initialize, it must declare -a name, that must be unique, and a list of API's verbs. +Conversely, when plugin initialisation succeeds, it should register +its unique name and the list of API verbs it exposes. -When initialized, the functions implementing the API's verbs -of the plugin are activated on call. +When initialised, on request from clients plugin's function corresponding to expose API verbs +are activated by the afb-daemon instance attached to the application or service. -At the end, nothing special is done by afb-daemon. -Consequently, developpers of plugins should use 'atexit' -or 'on_exit' during initialisation if they need to -perform specific actions when stopping. +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. -### Content of a plugin +### Plugin Contend -For afb-daemon, a plugin contains 2 different -things: names and functions. +Afb-daemon's plugin register two classes of objects: names and functions. -There is two kind of names: - - the name of the plugin, - - the names of the verbs. +Plugins declare categories of names: + - A unique plugin name, + - Multiple API verb's names. -There is two kind of functions: - - the initialisation function - - functions implementing verbs +Plugins declare two categories of functions: + - initialisation function + - API functions implementing verbs -Afb-daemon translates the name of the method that is -invoked to a pair of API and verb names. For example, -the method named **foo/bar** translated to the API -name **foo** and the verb name **bar**. -To serve it, afb-daemon search the plugin that record -the name **foo** and if it also recorded the verb **bar**, -it calls the implementation function declared for this verb. +Afb-daemon parses URI requests to extract plugin name and API verb. +As an example, URI **foo/bar** translates to API verb named **bar** within plugin named **foo**. +To serve such a request, afb-daemon looks for an active plugin named **foo** and then within this plugin for an API verb named **bar**. +When find afb-daemon calls corresponding function with attached parameter if any. -Afb-daemon make no distinction between lower case -and upper case when searching for a method. -Thus, The names **TicTacToe/Board** and **tictactoe/borad** -are equals. +Afb-daemon ignores letter case when parsing URI. Thus **TicTacToe/Board** and **tictactoe/board** are equivalent. #### The name of the plugin @@ -271,7 +260,7 @@ and upper case when searching for an API by its name. The names of the verbs are not checked. However, the validity rules for verb's names are the -same as for API's names except that the dot (.) character +same as for API names except that the dot (.) character is forbidden. Afb-daemon make no distinction between lower case @@ -501,12 +490,20 @@ The two functions to send a reply of kind "success" are * 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, ...); @@ -521,15 +518,28 @@ The two functions to send a reply of kind "failure" are * Note that calling afb_req_fail("success", info) is equivalent * to call afb_req_success(NULL, info). Thus even if possible it * is strongly recommanded 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 call **json_object_put** to release the object **obj** +> that they send. Then **obj** can not be used after calling one of these reply functions. +> When it is not the expected behaviour, calling the function **json_object_get** on the object **obj** +> before cancels the effect of **json_object_put**. + Getting argument of invocation ------------------------------ @@ -968,9 +978,18 @@ The function **afb_daemon_broadcast_event** is defined as below: * 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 for reply functions, the **object** is automatically released using +> **json_object_put** by the function. Then call **json_object_get** before +> calling **afb_daemon_broadcast_event** to keep **object** available +> after the returning of the function. + In fact the event name received by the listener is prefixed with the name of the plugin. So when the change occurs after a move, the reason is **move** and then the clients receive the event **tictactoe/move**. @@ -981,32 +1000,107 @@ reason is **move** and then the clients receive the event **tictactoe/move**. > Thus it is safe to compare event using a case sensitive comparison. + Writing an asynchronous verb implementation ------------------------------------------- -/* - * 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; +The *tic-tac-toe* example allows two clients or more to share the same board. +This is implemented by the verb **join** that illustrated partly the 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. (This coulded 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. +But in general, this will be a timer, a hardware event, the sync with +a concurrent process or thread, ... + +So the case is common, this is an asynchronous implementation. + +Here is the listing of the function **wait**: + + 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; } - afb_event_sender_push(afb_daemon_get_event_sender(afbitf->daemon), reason, description); -} +After retrieving the board, the function adds a new waiter to the +current list of waiters and returns without sending a reply. + +Before returning, it increases the reference count of the +request **req** using the function **afb_req_addref**. + +> When the implentation of a verb returns without sending a reply, +> it **MUST** increment the reference count of the request +> using **afb_req_addref**. If it doesn't bad things can happen. + +Later, when the board changes, it calls the function **changed** +of *tic-tac-toe* with the reason of the change. + +Here is the full listing of the function **changed**: + + /* + * 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 the sending the reply, the reference count of the request +is decremented using **afb_req_unref** to allow its resources to be freed. + +> The reference count **MUST** be decremented using **afb_req_unref** because, +> otherwise, there is a leak of resources. +> It must be decremented **AFTER** the sending of the reply, because, otherwise, +> bad things may happen. How to build a plugin ---------------------