From cf81979fcb253a7674cc2433a51e09a1dc594844 Mon Sep 17 00:00:00 2001 From: Jose Bollo Date: Mon, 27 May 2019 16:58:07 +0200 Subject: wip Change-Id: I8866607b734f001d39af7748bcf5a1441e28c55b Signed-off-by: Jose Bollo --- src/devtools/example-api-gps.yml | 156 +++++++++++++++++ src/devtools/idl-monitor.json | 56 +++--- src/devtools/main-genskel.c | 26 +-- src/devtools/spec-afbidl.md | 365 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 561 insertions(+), 42 deletions(-) create mode 100644 src/devtools/example-api-gps.yml create mode 100644 src/devtools/spec-afbidl.md diff --git a/src/devtools/example-api-gps.yml b/src/devtools/example-api-gps.yml new file mode 100644 index 00000000..1a530bb9 --- /dev/null +++ b/src/devtools/example-api-gps.yml @@ -0,0 +1,156 @@ +%YAML 1.3 +--- +afbidl: "0.1" + +info: + apiname: gps + title: Service for geolocation + description: + GPS service reports current WGS84 coordinates from GNSS devices + via the gpsd application. + version: "0.1" + author: AGL + maintainer: Scott Rifenbark + homepage: https://doc.automotivelinux.org/... + +tools: + + afb-genskel: + scope: static + prefix: req_ + postfix: _cb + init: init_gps + + doc: + id: gps-api + keywords: gps + author: + version: + src_prefix: api-gps + chapters: + - name: Abstract + url: abstract.md + - name: User Guide + url: userguide.md + +verbs: + + subscribe: + description: subscribe to gps/gnss events + request: $/schemas/subscription-desc + reply: + success: + schema: $/schemas/none + set-state: + listening: yes + + unsubscribe: + description: unsubscribe to gps/gnss events + request: $/schemas/subscription-desc + reply: + success: + schema: $/schemas/none + set-state: + listening: no + + location: + description: get current gps/gnss coordinates + request: $/schemas/none + reply: + success: $/schemas/location + _: An error can be returned when the service isn't ready + + record: + description: | + Entering *record* mode you must send **{"state": "on"}** with the **record** + verb which will have a JSON response of **{"filename": "gps_YYYYMMDD_hhmm.log"}** + pointing to log under *app-data/agl-service-gps* + + Now to enter *replaying* mode you must symlink or copy a GPS dump to + *app-data/agl-service-gps/recording.log* and restart the service. + From then on out the previously recorded GPS data will loop infinitely + which is useful for testing or demonstration purposes. + request: $/schemas/record/request + reply: + success: + schema: $/schemas/record/reply + set-state: + recording: yes + _: An error can be returned when the service isn't ready + +events: + location: + schema: $/schemas/location + when-state: + listening: yes + +state-machines: + listening: + states: [ no, yes ] + initial: no + recording: + states: [ no, yes ] + initial: no + +# Follow JsonSchema specification (https://json-schema.org/) +schemas: + subscription-desc: + title: Description of the event subscribed or unsubscribed + type: object + properties: + value: { enum: [ location ] } + required: [ value ] + + location: + title: the location + type: object + properties: + altitude: + title: the altitude in meters above the normal geoide + type: number + minimum: -20000 + maximum: 20000 + latitude: + title: the latitude in degrees + type: number + minimum: -90 + maximum: 90 + longitude: + title: the longitude in degrees + type: number + minimum: -180 + maximum: 180 + speed: + title: the speed in meter per seconds m/s + type: number + minimum: 0 + maximum: 6000 + track: + title: the heading in degrees + type: number + minimum: 0 + maximum: 360 + timestamp: + title: time stamp of the location as a ISO8601 date + type: string #ISO8601 + pattern: \d{4,}-[01][0-9]-[0-3][0-9]T[012][0-9]:[0-5][0-9]:[0-5][0-9].* + + record: + request: + type: object + properties: + state: { const: "on" } + required: [ state ] + + reply: + type: object + properties: + filename: + title: the name of the file that records the data of format gps_YYYYMMDD_hhmm.log + type: string + pattern: gps_\d{4}\d{2}\d{2}_\d{2}\d{2}.log + required: [ filename ] + + none: + title: no value, just null + const: null diff --git a/src/devtools/idl-monitor.json b/src/devtools/idl-monitor.json index 5afc29d0..6fc6c900 100644 --- a/src/devtools/idl-monitor.json +++ b/src/devtools/idl-monitor.json @@ -1,11 +1,12 @@ { "afbidl": "0.1", "info": { + "apiname": "monitor", "description": "monitoring of bindings and internals", "title": "monitor", "version": "1.0" }, - "generator": { + "tools": { "genskel": { "version": 2, "prefix": "f_", @@ -17,33 +18,30 @@ "private": true } }, - "api": { - "name": "monitor", - "verbs": { - "get": { - "description": "Get monitoring data.", - "permissions": { "session": "check" }, - "request": { "$ref": "#/schemas/get-request" }, - "reply": { "$ref": "#/schemas/get-reply" } - }, - "set": { - "description": "Set monitoring actions.", - "permissions": { "session": "check" }, - "request": { "$ref": "#/schemas/set-request" }, - "reply": { "$ref": "#/schemas/any" } - }, - "trace": { - "description": "Set monitoring actions.", - "permissions": { "session": "check" }, - "request": { "$ref": "#/schemas/trace-request" }, - "reply": { "$ref": "#/schemas/any" } - }, - "session": { - "description": "describes the session.", - "permissions": { "session": "check" }, - "request": { "$ref": "#/schemas/session-request" }, - "reply": { "$ref": "#/schemas/any" } - } + "verbs": { + "get": { + "description": "Get monitoring data.", + "permissions": { "session": "check" }, + "request": { "$ref": "#/schemas/get-request" }, + "reply": { "$ref": "#/schemas/get-reply" } + }, + "set": { + "description": "Set monitoring actions.", + "permissions": { "session": "check" }, + "request": { "$ref": "#/schemas/set-request" }, + "reply": { "$ref": "#/schemas/any" } + }, + "trace": { + "description": "Set monitoring actions.", + "permissions": { "session": "check" }, + "request": { "$ref": "#/schemas/trace-request" }, + "reply": { "$ref": "#/schemas/any" } + }, + "session": { + "description": "describes the session.", + "permissions": { "session": "check" }, + "request": { "$ref": "#/schemas/session-request" }, + "reply": { "$ref": "#/schemas/any" } } }, "schemas": { @@ -162,7 +160,7 @@ "json", "life", "ref", - "reply", + "reply", "result", "session", "session_close", diff --git a/src/devtools/main-genskel.c b/src/devtools/main-genskel.c index bad7ee97..718452f1 100644 --- a/src/devtools/main-genskel.c +++ b/src/devtools/main-genskel.c @@ -464,18 +464,18 @@ void openapi_enum_verbs(void (*func)(const char *name, struct json_object *obj)) void afbidl_getvars() { - getvar(&preinit, "#/generator/genskel/preinit", NULL); - getvar(&init, "#/generator/genskel/init", NULL); - getvar(&onevent, "#/generator/genskel/onevent", NULL); - getvar(&scope, "#/generator/genskel/scope", "static"); - getvar(&prefix, "#/generator/genskel/prefix", "afb_verb_"); - getvar(&postfix, "#/generator/genskel/postfix", "_cb"); - getvar(&provideclass, "#/generator/genskel/provide-class", NULL); - getvar(&requireclass, "#/generator/genskel/require-class", NULL); - getvar(&requireapi, "#/generator/genskel/require-api", NULL); - getvarbool(&priv, "#/generator/genskel/private", 0); - getvarbool(&noconc, "#/generator/genskel/noconcurrency", 0); - getvar(&api, "#/api/name", NULL); + getvar(&preinit, "#/tools/afb-genskel/preinit", NULL); + getvar(&init, "#/tools/afb-genskel/init", NULL); + getvar(&onevent, "#/tools/afb-genskel/onevent", NULL); + getvar(&scope, "#/tools/afb-genskel/scope", "static"); + getvar(&prefix, "#/tools/afb-genskel/prefix", "afb_verb_"); + getvar(&postfix, "#/tools/afb-genskel/postfix", "_cb"); + getvar(&provideclass, "#/tools/afb-genskel/provide-class", NULL); + getvar(&requireclass, "#/tools/afb-genskel/require-class", NULL); + getvar(&requireapi, "#/tools/afb-genskel/require-api", NULL); + getvarbool(&priv, "#/tools/afb-genskel/private", 0); + getvarbool(&noconc, "#/tools/afb-genskel/noconcurrency", 0); + getvar(&api, "#/info/apiname", NULL); getvar(&api, "#/info/title", "?"); getvar(&info, "#/info/description", NULL); } @@ -487,7 +487,7 @@ void afbidl_enum_verbs(void (*func)(const char *name, struct json_object *obj)) const char *name; /* search the verbs */ - verbs = get$ref(root, "#/api/verbs"); + verbs = get$ref(root, "#/verbs"); if (!verbs) return; diff --git a/src/devtools/spec-afbidl.md b/src/devtools/spec-afbidl.md new file mode 100644 index 00000000..74256659 --- /dev/null +++ b/src/devtools/spec-afbidl.md @@ -0,0 +1,365 @@ +API specification for AGL +========================= + +Micro service architectures on web mostly use OpenAPI +for specifying and documenting their API (Application +Programming Interface). +Following that use, AGL's binder provides a tool to +translate OpenAPI specifications to a code skeleton +written either in C or C++ languages. + +However, the API descritpion language OpenAPI never +provided some the requirement that AGL expected for +a such tool: + + - specify API that throws events + - describe the permission based security model + +Using OpenAPI also had the disavantage of implying +some twist of the model and then some uglyness +verbosity. + +Unfortunately, search for a replacement of OpenAPI +that would fullfil below requirements failed. + + - Describe JSON data of APIs + - Describe events + - Describe permission based security (even as extension) + - Suitable for generating: + * Documentation + * Code skeleton with/without parameter validation + * Automated test + +Consequently, a new API specification formalism has +to be proposed. + +This document is the proposal for that new formalism. +For the best, that proposal includes advanced designs +introduced after discussions with Joël Champeau and +Philippe Dhaussy, researchers at ENSTA Bretagne (National +Institute of Advanced Technologies of Brittany) specialized +in system modeling and formal verification. + + +The goals of specifying APIs +---------------------------- + +The micro service architecture of AGL and its flexible +IPC mechanism emphasis the decomposition of services or +applications in tiny cooperative parts. +This has big advantages in terms of flexibility and +development process but, conversely, implies to +correctly document interfaces. +Documenting or specifying API are the same thing, except +that, traditionnaly, specifying comes forward and +documenting afterward. + +Specifying API can be done using simple text documents. +Using text documents is great for humans but not for +computers. +For this reason, because machines can't exploit human +structured texts, the use of simple text documents +should be avoided as much as possible. +In effect, here is the list of all items that computers +can do based on API specifications: + + - Automatic generation of documentation + in many formats and using customizable styles + + - Automatic generation of code either + minimal or elaborate + + - Automatic adaptation to tansport backend or + protocol + + - Automatic generation of test cases + + - Integration of advanced tools like supervision, + tracing, spying + + - Proof of system properties, in particular + when assembling many API together + +Many IDL (Interface Description Language) exist +but it rarely fit all that requirements. +First of all, they generally are "languages", +meaning that they are difficult to parse, to +generate and to manipulate by tools. + +OpenAPI is not a language. It is a specification +made by a data structure of JSON format [1][json-org], +[2][json-rfc]. +Using JSON has the advantage that no parser has +to be written because a standard one exists. +JSON is not human friendly but its data model +is quasi-isomorph with the one of YAML [3][yaml] +that is more human friendly. + +For this reasons, the below proposal describes +an API specification format based on YAML. + +Nevertheless, for specifying the values expected +by APIs the format will use the JSON Schema formalism +[4][json-schema]. This allows to describe complex +values and their constraints using a format easy to +integrate in tools. + +[json-org]: http://json.org/ "JSON format" +[json-rfc]: https://tools.ietf.org/html/rfc8259 "JSON format RFC" +[yaml]: https://yaml.org/ "YAML format" +[json-schema]: https://json-schema.org/ "JSON Schema" + +Content of API specifications +----------------------------- + +### Top level structure + +An API specification has the following structure: + +```yaml +%YAML 1.3 +--- +afbidl: "0.1" +info: # description the content of the specification +tools: # items for tools (doc, afb-genskel, ..) +verbs: # description of verbs of the API +events: # description of the events emitted by the API +state-machines: # description of the state machines of the API +examples: # examples of usage with or without timings +schemas: # place holder for description of the types of items +``` + +The specification is designed to describe only one API. +If needed (example: simulation of a complex system), the aggregation +of multiple API descriptions can be done but externally with some +other description. + +The heading line *%YAML 1.3* is recommended but not mandatory. + +The main item of the description is an object. Its fields are: + + - afbidl: this field indicates that the description follows that + specification and the value precise what version of the specification + is used. Current version is 0.1 + + - info: this field contains an object that give informations about the + API. Its mandatory fields are: + + - apiname: name of the API + - title: short explanation of the API + - description: long description of the API + - version: version of the API + + Other fields are accepted, example: author, maintainer, homepage, + site, copyright, license, ... + + - tools: this fields contains an object that can set properties for + processing tools. The fields are the names of tool to setup. + + - verbs: this field contains an object whose fields are the names + of the verbs of the API. For each verb the value attached to the + field of the verb is the description of the verb. + + - events: this field contains an object whose fields are the names + of the events thrown by the API. For each event the value attached + to the field describes the event. + + - state-machines: this field contains an object whose fields are the + names of the state-machines of the API. For each state-machine the + value attached to the field describes the state machine. + + - examples: TO BE SPECIFIED - object of named sequences/scenarii - + + - schemas: this optionnal field is intended to group the schema of + the common types used by API. + + +### Describing verbs + +The verbs are described using an object containing the fields + +```yaml +title: # short explanation of the verb +description: # detailed description of the verb +permissions: # required permissions +request: # schema of the request parameters +reply: # describe the reply +``` + +### Describing events + +The events are described using an object containing the fields + +```yaml +schema: # description of the data associated with the event +when-state: # condition of emiting the event +set-state: # when the event is associated to a state change +``` + +### Describing state machine + + + + +Example of the API gps +---------------------- + +```yaml +%YAML 1.3 +--- +afbidl: "0.1" + +info: + apiname: gps + title: Service for geolocation + description: + GPS service reports current WGS84 coordinates from GNSS devices + via the gpsd application. + version: "0.1" + author: AGL + maintainer: John Difool + homepage: https://doc.automotivelinux.org/... + +tools: + + afb-genskel: + scope: static + prefix: req_ + postfix: _cb + init: init_gps + + doc: + id: gps-api + keywords: gps + author: + version: + src_prefix: api-gps + chapters: + - name: Abstract + url: abstract.md + - name: User Guide + url: userguide.md + +verbs: + + subscribe: + description: subscribe to gps/gnss events + request: $/schemas/subscription-desc + reply: + success: + schema: $/schemas/none + set-state: + listening: yes + + unsubscribe: + description: unsubscribe to gps/gnss events + request: $/schemas/subscription-desc + reply: + success: + schema: $/schemas/none + set-state: + listening: no + + location: + description: get current gps/gnss coordinates + request: $/schemas/none + reply: + success: $/schemas/location + _: An error can be returned when the service isn't ready + + record: + description: | + Entering *record* mode you must send **{"state": "on"}** with the **record** + verb which will have a JSON response of **{"filename": "gps_YYYYMMDD_hhmm.log"}** + pointing to log under *app-data/agl-service-gps* + + Now to enter *replaying* mode you must symlink or copy a GPS dump to + *app-data/agl-service-gps/recording.log* and restart the service. + From then on out the previously recorded GPS data will loop infinitely + which is useful for testing or demonstration purposes. + request: $/schemas/record/request + reply: + success: + schema: $/schemas/record/reply + set-state: + recording: yes + _: An error can be returned when the service isn't ready + +events: + location: + schema: $/schemas/location + when-state: + listening: yes + +state-machines: + listening: + states: [ no, yes ] + initial: no + recording: + states: [ no, yes ] + initial: no + +# Follow JsonSchema specification (https://json-schema.org/) +schemas: + subscription-desc: + title: Description of the event subscribed or unsubscribed + type: object + properties: + value: { enum: [ location ] } + required: [ value ] + + location: + title: the location + type: object + properties: + altitude: + title: the altitude in meters above the normal geoide + type: number + minimum: -20000 + maximum: 20000 + latitude: + title: the latitude in degrees + type: number + minimum: -90 + maximum: 90 + longitude: + title: the longitude in degrees + type: number + minimum: -180 + maximum: 180 + speed: + title: the speed in meter per seconds m/s + type: number + minimum: 0 + maximum: 6000 + track: + title: the heading in degrees + type: number + minimum: 0 + maximum: 360 + timestamp: + title: time stamp of the location as a ISO8601 date + type: string #ISO8601 + pattern: \d{4,}-[01][0-9]-[0-3][0-9]T[012][0-9]:[0-5][0-9]:[0-5][0-9].* + + record: + request: + type: object + properties: + state: { const: "on" } + required: [ state ] + + reply: + type: object + properties: + filename: + title: the name of the file that records the data of format gps_YYYYMMDD_hhmm.log + type: string + pattern: gps_\d{4}\d{2}\d{2}_\d{2}\d{2}.log + required: [ filename ] + + none: + title: no value, just null + const: null +``` + -- cgit 1.2.3-korg