From 1d4de11a907e41c06063a2cd5028dc4101690f50 Mon Sep 17 00:00:00 2001
From: =?utf8?q?Jos=C3=A9=20Bollo?= <jose.bollo@iot.bzh>
Date: Tue, 11 Oct 2016 17:07:16 +0200
Subject: [PATCH] Prepare the Integration with systemd
MIME-Version: 1.0
Content-Type: text/plain; charset=utf8
Content-Transfer-Encoding: 8bit

This is an intermediate commit providing
basic functionnalities for setting up
integration of the framework with systemd.

 - file afm-unit.conf is a mustache template
 - translation of config.xml to json object
 - mustache (extended) application of the json to the template
 - post processing of the result for extracting unit files

This processing is currently available as a test
(and a tool) and will be integrated after more
developement, test and validation.

Signed-off-by: José Bollo <jose.bollo@iot.bzh>
---
 CMakeLists.txt                     |   2 +
 conf/CMakeLists.txt                |   5 +-
 conf/afm-unit.conf                 | 168 +++++++++++++
 docs/config.xml.md                 | 303 +++++++++++++++++++++++
 docs/permissions.md                |  61 +++++
 docs/widgets.md                    | 238 +-----------------
 mkdocs.yml                         |   4 +-
 src/CMakeLists.txt                 |  10 +
 src/mustach.c                      | 257 ++++++++++++++++++++
 src/mustach.h                      | 112 +++++++++
 src/tests/CMakeLists.txt           |  20 ++
 src/tests/test-unit/CMakeLists.txt |  22 ++
 src/tests/test-unit/config.xml     |  46 ++++
 src/tests/test-unit/sample.unit    |  94 ++++++++
 src/tests/test-unit/test-unit.c    |  89 +++++++
 src/wgt-json.c                     | 482 +++++++++++++++++++++++++++++++++++++
 src/wgt-json.h                     |  27 +++
 src/wgt-strings.c                  |  28 ++-
 src/wgt-strings.h                  |  20 +-
 src/wgtpkg-mustach.c               | 264 ++++++++++++++++++++
 src/wgtpkg-mustach.h               |  24 ++
 src/wgtpkg-unit.c                  | 254 +++++++++++++++++++
 src/wgtpkg-unit.h                  |  45 ++++
 23 files changed, 2330 insertions(+), 245 deletions(-)
 create mode 100644 conf/afm-unit.conf
 create mode 100644 docs/config.xml.md
 create mode 100644 docs/permissions.md
 create mode 100644 src/mustach.c
 create mode 100644 src/mustach.h
 create mode 100644 src/tests/CMakeLists.txt
 create mode 100644 src/tests/test-unit/CMakeLists.txt
 create mode 100644 src/tests/test-unit/config.xml
 create mode 100644 src/tests/test-unit/sample.unit
 create mode 100644 src/tests/test-unit/test-unit.c
 create mode 100644 src/wgt-json.c
 create mode 100644 src/wgt-json.h
 create mode 100644 src/wgtpkg-mustach.c
 create mode 100644 src/wgtpkg-mustach.h
 create mode 100644 src/wgtpkg-unit.c
 create mode 100644 src/wgtpkg-unit.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5796837..83e0c39 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -21,6 +21,7 @@ project(afm-main C)
 cmake_minimum_required(VERSION 2.8)
 
 include(GNUInstallDirs)
+include(CTest)
 
 set(PROJECT_NAME "AFM Main")
 set(PROJECT_PRETTY_NAME "Application Framework Main")
@@ -59,6 +60,7 @@ add_definitions(
 	-DFWK_USER_APP_DIR="${afm_user_appdir}"
 	-DWGTPKG_TRUSTED_CERT_DIR="${wgtpkg_trusted_cert_dir}"
 	-DFWK_LAUNCH_CONF="${afm_confdir}/afm-launch.conf"
+	-DFWK_UNIT_CONF="${afm_confdir}/afm-unit.conf"
 	-DFWK_USER_APP_DIR_LABEL="${afm_user_appdir_label}"
 )
 
diff --git a/conf/CMakeLists.txt b/conf/CMakeLists.txt
index 32a0cc4..d8611e0 100644
--- a/conf/CMakeLists.txt
+++ b/conf/CMakeLists.txt
@@ -1,5 +1,5 @@
 ###########################################################################
-# Copyright 2015 IoT.bzh
+# Copyright 2015, 2016, 2017 IoT.bzh
 #
 # author: José Bollo <jose.bollo@iot.bzh>
 #
@@ -30,6 +30,7 @@ if(NOT USE_SDK)
 	install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/afm-system-daemon.service DESTINATION ${UNITDIR_SYSTEM})
 	install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/afm-user-daemon.conf      DESTINATION ${SYSCONFDIR_DBUS_USER})
 	install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/afm-user-daemon.service   DESTINATION ${UNITDIR_USER})
-	install(FILES ${CMAKE_CURRENT_BINARY_DIR}/afm-launch.conf	    DESTINATION ${afm_confdir})
+	install(FILES ${CMAKE_CURRENT_BINARY_DIR}/afm-launch.conf           DESTINATION ${afm_confdir})
+	install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/afm-unit.conf             DESTINATION ${afm_confdir})
 endif()
 
diff --git a/conf/afm-unit.conf b/conf/afm-unit.conf
new file mode 100644
index 0000000..290c3e1
--- /dev/null
+++ b/conf/afm-unit.conf
@@ -0,0 +1,168 @@
+;---------------------------------------------------------------------------------
+; File:
+;
+;    afm-unit.conf
+;
+; Role:
+;
+;    Configure how installation of widget produces unit files for systemd
+;
+; Processing and format:
+;
+;    1. File load
+;
+;           Lines beginning with ; are firstly removed
+;
+;    2. File instanciation
+;
+;           Mustache (extended) substitutions are applied using JSON
+;           data deduced from config.xml file of the widget.
+;
+;    3. Extraction of units
+;
+;           Extract produced units, pack it (remove empty lines and directives)
+;
+; Directives:
+;
+;    All directive occopy one whole line starting with %
+;
+;     - %nl
+;
+;             produce an empty line at the end
+;
+;     - %begin systemd-unit
+;     - %end systemd-unit
+;
+;             delimit the produced unit
+;
+;     - %systemd-unit user
+;     - %systemd-unit system
+;
+;             tells the kind of unit (user/system)
+;
+;     - %systemd-unit service NAME
+;     - %systemd-unit socket NAME
+;
+;             gives the name and type of the unit
+;
+;---------------------------------------------------------------------------------
+{{#targets}}
+%begin systemd-unit
+
+# auto generated by wgtpkg-unit for {{id}} version {{version}} target {{:#target}}
+%nl
+
+[unit]
+Description={{description}}
+X-AGL-Name={{name.content}}
+X-AGL-Name-Short={{name.short}}
+X-AGL-Id={{id}}
+X-AGL-Idaver={{idaver}}
+X-AGL-Target-Name={{:#target}}
+X-AGL-Author={{author.content}}
+X-AGL-Author-email={{author.email}}
+X-AGL-HTTP-port={{:#metadata.http-port}}
+%nl
+
+# Adds check to smack
+ConditionSecurity=smack
+%nl
+
+# Automatic bound to required bindings
+{{#required-binding}}
+BindsTo=afm-api-{{name}}
+After=afm-api-{{name}}
+{{/required-binding}}
+%nl
+
+[Service]
+SmackProcessLabel=User::App::{{id}}
+
+{{#required-permission}}
+  {{#urn:AGL:permission::platform:no-oom}}      OOMScoreAdjust=-500             {{/urn:AGL:permission::platform:no-oom}}
+  {{#urn:AGL:permission::partner:real-time}}    IOSchedulingClass=realtime      {{/urn:AGL:permission::partner:real-time}}
+  {{^urn:AGL:permission::partner:real-time}}    RestrictRealtime=on             {{/urn:AGL:permission::partner:real-time}}
+  {{#urn:AGL:permission::public:display}}       SupplementaryGroups=display     {{/urn:AGL:permission::public:display}}
+  {{^urn:AGL:permission::public:syscall:clock}} SystemCallFilter=~@clock        {{/urn:AGL:permission::public:syscall:clock}}
+  {{^urn:AGL:permission::public:internet}}      RestrictAddressFamilies=AF_UNIX {{/urn:AGL:permission::public:internet}}
+{{/required-permission}}
+%nl
+
+WorkingDirectory={{&#metadata.app-data-dir}}
+
+;---------------------------------------------------------------------------------
+{{#content.type=text/html}}
+
+%systemd-unit user
+
+%systemd-unit service afm-appli-{{idaver}}{{^#target=main}}@{{:#target}}{{/#target=main}}
+
+ExecStart=/usr/bin/afb-daemon --port={{:#metadata.http-port}} --random-token \
+	--rootdir={{:#metadata.install-dir}} \
+	--workdir={{&#metadata.app-data-dir}} \
+	--roothttp=htdocs \
+	{{#required-permission.urn:AGL:permission::public:applications:read}}\
+		--alias=/icons:{{:#metadata.icons-dir}} \
+	{{/required-permission.urn:AGL:permission::public:applications:read}}\
+	{{#required-binding}}\
+		{{#value=auto}}\
+			--ws-client=unix:%t/apis/ws/{{name}} \
+		{{/value=auto}}\
+		{{#value=ws}}\
+			--ws-client=unix:%t/apis/ws/{{name}} \
+		{{/value=ws}}\
+		{{#value=dbus}}\
+			--dbus-client={{name}} \
+		{{/value=dbus}}\
+		{{#value=link}}\
+			--binding=%t/apis/lib/{{name}} \
+		{{/value=link}}\
+		{{#value=cloud}}\
+			--cloud-client={{name}} \
+		{{/value=cloud}}\
+	{{/required-binding}}\
+	--exec /usr/bin/web-runtime http://localhost:@p/{{content.src}}?token=@t
+
+{{/content.type=text/html}}
+
+;---------------------------------------------------------------------------------
+{{#content.type=application/vnd.agl.service}}
+
+%systemd-unit user
+%systemd-unit service afm-api-{{:#target}}
+
+ExecStart=/usr/bin/afb-daemon \
+	--rootdir={{:#metadata.install-dir}} \
+	--workdir={{&#metadata.install-dir}} \
+	{{^required-permission.urn:AGL:permission::partner:service:no-ws}}\
+		--ws-server=unix:%t/bindings/{{:#target}} \
+	{{/required-permission.urn:AGL:permission::partner:service:no-ws}}\
+	{{^required-permission.urn:AGL:permission::partner:service:no-dbus}}\
+		--dbus-server={{:#target}} \
+	{{/required-permission.urn:AGL:permission::partner:service:no-dbus}}\
+	--no-httpd 
+
+{{^required-permission.urn:AGL:permission::partner:service:no-ws}}
+
+%end systemd-unit
+%begin systemd-unit
+
+# auto generated by wgtpkg-unit for {{id}} version {{version}} target {{:#target}}
+#
+%systemd-unit user
+%systemd-unit socket afm-api-{{:#target}}
+
+
+[socket]
+SmackLabel=*
+ListenStream=%t/bindings/{{:#target}}
+
+{{/required-permission.urn:AGL:permission::partner:service:no-ws}}
+
+{{/content.type=application/vnd.agl.service}}
+
+;---------------------------------------------------------------------------------
+%end systemd-unit
+{{/targets}}
+
+
diff --git a/docs/config.xml.md b/docs/config.xml.md
new file mode 100644
index 0000000..7f413d5
--- /dev/null
+++ b/docs/config.xml.md
@@ -0,0 +1,303 @@
+The configuration file config.xml
+===========
+
+The widgets are described by the W3C's technical recommendations
+[Packaged Web Apps (Widgets)][widgets] and [XML Digital Signatures for Widgets][widgets-digsig]
+ that specifies the configuration file **config.xml**.
+
+Overview
+--------
+
+The file **config.xml** describes important data of the application
+to the framework:
+
+- the unique identifier of the application
+- the name of the application
+- the type of the application
+- the icon of the application
+- the permissions linked to the application
+- the services and dependancies of the application
+
+The file MUST be at the root of the widget and MUST be case sensitively name
+***config.xml***.
+
+The file **config.xml** is a XML file described by the document
+[widgets].
+
+Here is the example of the config file for the QML application SmartHome.
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns="http://www.w3.org/ns/widgets" id="smarthome" version="0.1">
+  <name>SmartHome</name>
+  <icon src="smarthome.png"/>
+  <content src="qml/smarthome/smarthome.qml" type="text/vnd.qt.qml"/>
+  <description>This is the Smarthome QML demo application. It shows some user interfaces for controlling an 
+automated house. The user interface is completely done with QML.</description>
+  <author>Qt team</author>
+  <license>GPL</license>
+</widget>
+```
+
+The most important items are:
+
+- **<widget id="......"\>**: gives the id of the widget. It must be unique.
+
+- **<widget version="......"\>**: gives the version of the widget
+
+- **<icon src="..."\>**: gives a path to the icon of the application
+  (can be repeated with different sizes)
+
+- **<content src="..." type="..."\>**: this indicates the entry point and its type.
+
+Standard elements of "config.xml"
+---------------------------------
+
+### The element widget
+
+#### the attribute id of widget
+
+The attribute *id* is mandatory (for version 2.x, blowfish) and must be unique.
+
+Values for *id* are any non empty string containing only latin letters,
+arabic digits, and the three characters '.' (dot), '-' (dash) and
+'_' (underscore).
+
+Authors can use a mnemonic id or can pick a unique id using
+command **uuid** or **uuidgen**.
+
+### the attribute version of widget
+
+The attribute *version* is mandatory (for version 2.x, blowfish).
+
+Values for *version* are any non empty string containing only latin letters,
+arabic digits, and the three characters '.' (dot), '-' (dash) and
+'_' (underscore).
+
+Version values are dot separated fields MAJOR.MINOR.REVISION.
+Such version would preferabily follow guidelines of
+[semantic versionning][semantic-version].
+
+### The element content
+
+The element *content* is mandatory (for version 2.x, blowfish) and must designate a file
+(subject to localisation) with its attribute *src*.
+
+The content designed depends on its type. See below for the known types.
+
+### The element icon
+
+The element *icon* is mandatory (for version 2.x, blowfish) and must
+be unique. It must designate an image file with its attribute *src*.
+
+AGL features
+------------
+
+The AGL framework uses the feature tag for specifying security and binding
+requirement of the widget.
+
+The current version of AGL (up to 2.0.1, blowfish) has no fully implemented
+features.
+
+The features planned to be implemented are described below.
+
+### feature name="urn:AGL:widget:required-binding"
+
+List of the bindings required by the widget.
+
+Each required binding must be explicited using a <param> entry.
+
+Example:
+```xml
+<feature name="urn:AGL:widget:required-binding">
+  <param name="urn:AGL:permission:A" value="required" />
+  <param name="urn:AGL:permission:B" value="optional" />
+</feature>
+```
+
+This will be *virtually* translated for mustaches to the JSON
+```json
+"required-binding": {
+  "param": [
+      { "name": "urn:AGL:permission:A", "value": "required", "required": true },
+      { "name": "urn:AGL:permission:A", "value": "optional", "optional": true }
+    ],
+  "urn:AGL:permission:A": { "name": "urn:AGL:permission:A", "value": "required", "required": true },
+  "urn:AGL:permission:B": { "name": "urn:AGL:permission:B", "value": "optional", "optional": true }
+}
+```
+
+#### param name="#target"
+
+Declares the name of the component requiring the listed bindings.
+Only one instance of the param "#target" is allowed.
+When there is not instance of the param
+The value is either:
+
+- required: the binding is mandatorily needed except if the feature
+isn't required (required="false") and in that case it is optional.
+- optional: the binding is optional
+
+#### param name=[required binding name]
+
+The value is either:
+
+- required: the binding is mandatorily needed except if the feature
+isn't required (required="false") and in that case it is optional.
+- optional: the binding is optional
+
+### feature name="urn:AGL:widget:required-permission"
+
+List of the permissions required by the widget.
+
+Each required permission must be explicited using a <param> entry.
+
+#### param name=[required permission name]
+
+The value is either:
+
+- required: the permission is mandatorily needed except if the feature
+isn't required (required="false") and in that case it is optional.
+- optional: the permission is optional
+
+### feature name="urn:AGL:widget:provided-binding"
+
+Use this feature for each provided binding of the widget.
+The parameters are:
+
+#### param name="subid"
+
+REQUIRED
+
+The value is the string that must match the binding prefix.
+It must be unique.
+
+#### param name="name"
+
+REQUIRED
+
+The value is the string that must match the binding prefix.
+It must be unique.
+
+#### param name="src"
+
+REQUIRED
+
+The value is the path of the shared library for the binding.
+
+#### param name="type"
+
+REQUIRED
+
+Currently it must be ***application/vnd.agl.binding.v1***.
+
+
+#### param name="scope"
+
+REQUIRED
+
+The value indicate the availability of the binidng:
+
+- private: used only by the widget
+- public: available to allowed clients as a remote service (requires permission+)
+- inline: available to allowed clients inside their binding (unsafe, requires permission+++)
+
+#### param name="needed-binding"
+
+OPTIONAL
+
+The value is a space separated list of binding's names that the binding needs.
+
+### feature name="urn:AGL:widget:defined-permission"
+
+Each required permission must be explicited using a <param> entry.
+
+#### param name=[defined permission name]
+
+The value is the level of the defined permission.
+Standard levels are: 
+
+- system
+- platform
+- partner
+- tiers
+- public
+
+This level defines the level of accreditation required to get the given
+permission. The accreditions are given by signatures of widgets.
+
+Known content types
+-------------------
+
+The configuration file ***/etc/afm/afm-unit.conf*** defines the types
+of widget known and how to launch it.
+
+Known types for the type of content are (for version 2.x, blowfish):
+
+- ***text/html***: 
+   HTML application,
+   content.src designates the home page of the application
+
+- ***application/x-executable***:
+   Native application,
+   content.src designates the relative path of the binary
+
+- ***application/vnd.agl.url***:
+   Internet url,
+   content.src designates the url to be used
+
+- ***application/vnd.agl.service***:
+   AGL service defined as a binder,
+   content.src designates the directory of provided binders,
+   http content, if any, must be put in the subdirectory ***htdocs*** of the widget
+
+- ***application/vnd.agl.native***:
+   Native application with AGL service defined as a binder,
+   content.src designates the relative path of the binary,
+   bindings, if any must be put in the subdirectory ***lib*** of the widget,
+   http content, if any, must be put in the subdirectory ***htdocs*** of the widget
+
+- ***text/vnd.qt.qml***, ***application/vnd.agl.qml***:
+   QML application,
+   content.src designate the relative path of the QML root,
+   imports must be put in the subdirectory ***imports*** of the widget
+
+- ***application/vnd.agl.qml.hybrid***:
+   QML application with bindings,
+   content.src designate the relative path of the QML root,
+   bindings, if any must be put in the subdirectory ***lib*** of the widget,
+   imports must be put in the subdirectory ***imports*** of the widget
+
+- ***application/vnd.agl.html.hybrid***:
+   HTML application,
+   content.src designates the home page of the application,
+   bindings, if any must be put in the subdirectory ***lib*** of the widget,
+   http content must be put in the subdirectory ***htdocs*** of the widget
+
+---
+
+
+[widgets]:          http://www.w3.org/TR/widgets                                    "Packaged Web Apps"
+[widgets-digsig]:   http://www.w3.org/TR/widgets-digsig                             "XML Digital Signatures for Widgets"
+[libxml2]:          http://xmlsoft.org/html/index.html                              "libxml2"
+[app-manifest]:     http://www.w3.org/TR/appmanifest                                "Web App Manifest"
+
+
+[meta-intel]:       https://github.com/01org/meta-intel-iot-security                "A collection of layers providing security technologies"
+[widgets]:          http://www.w3.org/TR/widgets                                    "Packaged Web Apps"
+[widgets-digsig]:   http://www.w3.org/TR/widgets-digsig                             "XML Digital Signatures for Widgets"
+[libxml2]:          http://xmlsoft.org/html/index.html                              "libxml2"
+[openssl]:          https://www.openssl.org                                         "OpenSSL"
+[xmlsec]:           https://www.aleksey.com/xmlsec                                  "XMLSec"
+[json-c]:           https://github.com/json-c/json-c                                "JSON-c"
+[d-bus]:            http://www.freedesktop.org/wiki/Software/dbus                   "D-Bus"
+[libzip]:           http://www.nih.at/libzip                                        "libzip"
+[cmake]:            https://cmake.org                                               "CMake"
+[security-manager]: https://wiki.tizen.org/wiki/Security/Tizen_3.X_Security_Manager "Security-Manager"
+[app-manifest]:     http://www.w3.org/TR/appmanifest                                "Web App Manifest"
+[tizen-security]:   https://wiki.tizen.org/wiki/Security                            "Tizen security home page"
+[tizen-secu-3]:     https://wiki.tizen.org/wiki/Security/Tizen_3.X_Overview         "Tizen 3 security overview"
+[semantic-version]: http://semver.org/                                              "Semantic versionning"
+
+
+
diff --git a/docs/permissions.md b/docs/permissions.md
new file mode 100644
index 0000000..300a719
--- /dev/null
+++ b/docs/permissions.md
@@ -0,0 +1,61 @@
+The permissions
+===============
+
+
+Permission's names
+------------------
+
+The proposal here is to specify a naming scheme for permissions
+that allows the system to be as stateless as possible. The current
+current specification includes in the naming of permissions either
+the name of the bound binding when existing and the level of the
+permission itself. Doing this, there is no real need for the
+framework to keep updated a database of installed permissions.
+
+The permission names are [URN][URN] of the form:
+
+  urn:AGL:permission:<binding>:<level>:<hierarchical-name>
+
+where "AGL" is the NID (the namespace identifier) dedicated to
+AGL (note: a RFC should be produced to standardize this name space).
+
+The permission names are made of NSS (the namespace specific string)
+starting with "permission:" and followed by colon separated
+fields. The 2 first fields are <binding> and <level> and the remaining
+fields are gouped to form the <hierarchical-name>.
+
+	<binding> ::= [ <pname> ]
+	
+	<pname> ::= 1*<pchars>
+	
+	<pchars> ::= <upper> | <lower> | <number> | <extra>
+	
+	<extra> ::= "-" | "." | "_" | "@"
+
+The field <binding> can be made of any valid character for NSS except
+the characters colon and star (:*). This field designate the binding
+providing the permission. It is use to deduce binding requirements
+from permission requirements. The field <binding> can be the empty
+string when the permission is defined by the AGL system itself.
+The field <binding> if starting with the character "@" represents
+a transversal permission not bound to any binding.
+
+	<level> ::= 1*<lower>
+
+The field <level> is made only of letters in lower case.
+The field <level> can only take some predefined values:
+"system", "platform", "partner", "tiers", "owner", "public".
+
+	<hierarchical-name> ::= <pname> 0*(":" <pname>)
+
+The field <hierarchical-name> is made <pname> separated by
+colons. The names at left are hierarchically grouping the
+names at right. This hierarchical behaviour is intended to
+be used to request permissions using hierarchical grouping.
+
+Permission's level
+------------------
+
+
+[URN]: https://tools.ietf.org/rfc/rfc2141.txt "RFC 2141: URN Syntax"
+
diff --git a/docs/widgets.md b/docs/widgets.md
index c23faa8..2acc92a 100644
--- a/docs/widgets.md
+++ b/docs/widgets.md
@@ -1,246 +1,12 @@
 The widgets
 ===========
 
-The widgets are described by the technical recommendations
-[widgets] and [widgets-digsig].
+The widgets are described by the W3C's technical recommendations
+[Packaged Web Apps (Widgets)][widgets] and [XML Digital Signatures for Widgets][widgets-digsig]
 
 In summary, **widgets are ZIP files that can be signed and
 whose content is described by the file <config.xml>**.
 
-The configuration file config.xml
----------------------------------
-
-The file **config.xml** describes important data of the application
-to the framework:
-
-- the unique identifier of the application
-- the name of the application
-- the type of the application
-- the icon of the application
-- the permissions linked to the application
-- the services and dependancies of the application
-
-The file MUST be at the root of the widget and MUST be case sensitively name
-***config.xml***.
-
-The file **config.xml** is a XML file described by the document
-[widgets].
-
-Here is the example of the config file for the QML application SmartHome.
-
-```xml
-<?xml version="1.0" encoding="UTF-8"?>
-<widget xmlns="http://www.w3.org/ns/widgets" id="smarthome" version="0.1">
-  <name>SmartHome</name>
-  <icon src="smarthome.png"/>
-  <content src="qml/smarthome/smarthome.qml" type="text/vnd.qt.qml"/>
-  <description>This is the Smarthome QML demo application. It shows some user interfaces for controlling an 
-automated house. The user interface is completely done with QML.</description>
-  <author>Qt team</author>
-  <license>GPL</license>
-</widget>
-```
-
-The most important items are:
-
-- **\<widget id="......"\>**: gives the id of the widget. It must be unique.
-
-- **\<widget version="......"\>**: gives the version of the widget
-
-- **\<icon src="..."\>**: gives a path to the icon of the application
-  (can be repeated with different sizes)
-
-- **\<content src="..." type="..."\>**: this indicates the entry point and its type.
-  The types handled are set through the file /etc/afm/afm-launch.conf
-
-Further development will add handling of <feature> for requiring and providing
-permissions and services.
-
----
-
-### Standard elements of "config.xml"
-
-#### The element widget
-
-##### the attribute id of widget
-
-The attribute *id* is mandatory (for version 2.x, blowfish) and must be unique.
-
-Values for *id* are any non empty string containing only latin letters,
-arabic digits, and the three characters '.' (dot), '-' (dash) and
-'_' (underscore).
-
-Authors can use a mnemonic id or can pick a unique id using
-command **uuid** or **uuidgen**.
-
-#### the attribute version of widget
-
-The attribute *version* is mandatory (for version 2.x, blowfish).
-
-Values for *id* are any non empty string containing only latin letters,
-arabic digits, and the three characters '.' (dot), '-' (dash) and
-'_' (underscore).
-
-Version values are dot separated fields MAJOR.MINOR.REVISION.
-
-#### The element content
-
-The element *content* is mandatory (for version 2.x, blowfish) and must designate a file
-(subject to localisation) with its attribute *src*.
-
-The content designed depends on its type. See below for the known types.
-
-#### The element icon
-
-The element *icon* is mandatory (for version 2.x, blowfish) and must
-be unique. It must designate an image file with its attribute *src*.
-
-### Known widget types and content
-
-The configuration file ***/etc/afm/afm-launch.conf*** defines the types
-of widget known and how to launch it.
-
-Known types for the type of content are (for version 2.x, blowfish):
-
-- ***text/html***: 
-   HTML application,
-   content.src designates the home page of the application
-
-- ***application/x-executable***:
-   Native application,
-   content.src designates the relative path of the binary
-
-- ***application/vnd.agl.url***:
-   Internet url,
-   content.src designates the url to be used
-
-- ***application/vnd.agl.service***:
-   AGL service defined as a binder,
-   content.src designates the directory of provided binders,
-   http content, if any, must be put in the subdirectory ***htdocs*** of the widget
-
-- ***application/vnd.agl.native***:
-   Native application with AGL service defined as a binder,
-   content.src designates the relative path of the binary,
-   bindings, if any must be put in the subdirectory ***lib*** of the widget,
-   http content, if any, must be put in the subdirectory ***htdocs*** of the widget
-
-- ***text/vnd.qt.qml***, ***application/vnd.agl.qml***:
-   QML application,
-   content.src designate the relative path of the QML root,
-   imports must be put in the subdirectory ***imports*** of the widget
-
-- ***application/vnd.agl.qml.hybrid***:
-   QML application with bindings,
-   content.src designate the relative path of the QML root,
-   bindings, if any must be put in the subdirectory ***lib*** of the widget,
-   imports must be put in the subdirectory ***imports*** of the widget
-
-- ***application/vnd.agl.html.hybrid***:
-   HTML application,
-   content.src designates the home page of the application,
-   bindings, if any must be put in the subdirectory ***lib*** of the widget,
-   http content must be put in the subdirectory ***htdocs*** of the widget
-
----
-
-### AGL features
-
-The AGL framework uses the feature tag for specifying security and binding
-requirement of the widget.
-
-The current version of AGL (up to 2.0.1, blowfish) has no fully implemented
-features.
-
-The features planned to be implemented are described below.
-
-#### feature name="urn:AGL:required-binding"
-
-List of the bindings required by the widget.
-
-Each required binding must be explicited using a <param> entry.
-
-##### param name=[required binding name]
-
-The value is either:
-
-- required: the binding is mandatorily needed except if the feature
-isn't required (required="false") and in that case it is optional.
-- optional: the binding is optional
-
-#### feature name="urn:AGL:required-permission"
-
-List of the permissions required by the widget.
-
-Each required permission must be explicited using a <param> entry.
-
-##### param name=[required permission name]
-
-The value is either:
-
-- required: the permission is mandatorily needed except if the feature
-isn't required (required="false") and in that case it is optional.
-- optional: the permission is optional
-
-#### feature name="urn:AGL:provided-binding"
-
-Use this feature for each provided binding of the widget.
-The parameters are:
-
-##### param name="name"
-
-REQUIRED
-
-The value is the string that must match the binding prefix.
-It must be unique.
-
-##### param name="src"
-
-REQUIRED
-
-The value is the path of the shared library for the binding.
-
-##### param name="type"
-
-REQUIRED
-
-Currently it must be ***application/vnd.agl.binding.v1***.
-
-
-##### param name="scope"
-
-REQUIRED
-
-The value indicate the availability of the binidng:
-
-- private: used only by the widget
-- public: available to allowed clients as a remote service (requires permission+)
-- inline: available to allowed clients inside their binding (unsafe, requires permission+++)
-
-##### param name="needed-binding"
-
-OPTIONAL
-
-The value is a space separated list of binding's names that the binding needs.
-
-#### feature name="urn:AGL:defined-permission"
-
-Each required permission must be explicited using a <param> entry.
-
-##### param name=[defined permission name]
-
-The value is the level of the defined permission.
-Standard levels are: 
-
-- system
-- platform
-- partner
-- public
-
-This level defines the level of accreditation required to get the given
-permission. The accreditions are given by signatures of widgets.
-
-
 Tools for managing widgets
 --------------------------
 
diff --git a/mkdocs.yml b/mkdocs.yml
index 1102944..fc8cb09 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -8,4 +8,6 @@ pages:
       - 'The application framework': 'application-framework.md'
       - 'The security framework': 'security-framework.md'
   - 'The afm daemons' : 'afm-daemons.md'
-  - 'Widgets of the framework' : 'widgets.md'
+  - 'Widgets of the framework' :
+      - 'Overview of widgets': 'widgets.md'
+      - 'The configuration file': 'config.xml.md'
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index f126346..c38dd07 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -86,14 +86,17 @@ add_library(wgtpkg STATIC
 	wgtpkg-digsig.c
 	wgtpkg-files.c
 	wgtpkg-install.c
+	wgtpkg-mustach.c
 	wgtpkg-permissions.c
 	wgtpkg-uninstall.c
+	wgtpkg-unit.c
 	wgtpkg-workdir.c
 	wgtpkg-xmlsec.c
 	wgtpkg-zip.c
 	)
 
 add_library(utils STATIC
+	mustach.c
 	utils-dir.c
 	utils-file.c
 	utils-json.c
@@ -104,6 +107,7 @@ add_library(wgt STATIC
 	wgt-config.c
 	wgt-info.c
 	wgt-strings.c
+	wgt-json.c
 	wgt.c
 	)
 
@@ -189,3 +193,9 @@ if(EXTRA2_FOUND)
 	endif()
 
 endif(EXTRA2_FOUND)
+
+###########################################################################
+# the tests
+
+add_subdirectory(tests)
+
diff --git a/src/mustach.c b/src/mustach.c
new file mode 100644
index 0000000..19df16f
--- /dev/null
+++ b/src/mustach.c
@@ -0,0 +1,257 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+ Author: José Bollo <jose.bollo@iot.bzh>
+
+ https://gitlab.com/jobol/mustach
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "mustach.h"
+
+#define NAME_LENGTH_MAX   1024
+#define DEPTH_MAX         256
+
+static int getpartial(struct mustach_itf *itf, void *closure, const char *name, char **result)
+{
+	int rc;
+	FILE *file;
+	size_t size;
+
+	*result = NULL;
+	file = open_memstream(result, &size);
+	if (file == NULL)
+		rc = MUSTACH_ERROR_SYSTEM;
+	else {
+		rc = itf->put(closure, name, 0, file);
+		if (rc == 0)
+			/* adds terminating null */
+			rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
+		fclose(file);
+		if (rc < 0) {
+			free(result);
+			*result = NULL;
+		}
+	}
+	return rc;
+}
+
+static int process(const char *template, struct mustach_itf *itf, void *closure, FILE *file, const char *opstr, const char *clstr)
+{
+	char name[NAME_LENGTH_MAX + 1], *partial, c;
+	const char *beg, *term;
+	struct { const char *name, *again; size_t length; int emit, entered; } stack[DEPTH_MAX];
+	size_t oplen, cllen, len, l;
+	int depth, rc, emit;
+
+	emit = 1;
+	oplen = strlen(opstr);
+	cllen = strlen(clstr);
+	depth = 0;
+	for(;;) {
+		beg = strstr(template, opstr);
+		if (beg == NULL) {
+			/* no more mustach */
+			if (emit)
+				fwrite(template, strlen(template), 1, file);
+			return depth ? MUSTACH_ERROR_UNEXPECTED_END : 0;
+		}
+		if (emit)
+			fwrite(template, (size_t)(beg - template), 1, file);
+		term = strstr(template, clstr);
+		if (term == NULL)
+			return MUSTACH_ERROR_UNEXPECTED_END;
+		template = term + cllen;
+		beg += oplen;
+		len = (size_t)(term - beg);
+		c = *beg;
+		switch(c) {
+		case '!':
+		case '=':
+			break;
+		case '{':
+			for (l = 0 ; clstr[l] == '}' ; l++);
+			if (clstr[l]) {
+				if (!len || beg[len-1] != '}')
+					return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
+				len--;
+			} else {
+				if (term[l] != '}')
+					return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
+				template++;
+			}
+			c = '&';
+		case '^':
+		case '#':
+		case '/':
+		case '&':
+		case '>':
+#if !defined(NO_EXTENSION_FOR_MUSTACH) && !defined(NO_COLON_EXTENSION_FOR_MUSTACH)
+		case ':':
+#endif
+			beg++; len--;
+		default:
+			while (len && isspace(beg[0])) { beg++; len--; }
+			while (len && isspace(beg[len-1])) len--;
+			if (len == 0)
+				return MUSTACH_ERROR_EMPTY_TAG;
+			if (len > NAME_LENGTH_MAX)
+				return MUSTACH_ERROR_TAG_TOO_LONG;
+			memcpy(name, beg, len);
+			name[len] = 0;
+			break;
+		}
+		switch(c) {
+		case '!':
+			/* comment */
+			/* nothing to do */
+			break;
+		case '=':
+			/* defines separators */
+			if (len < 5 || beg[len - 1] != '=')
+				return MUSTACH_ERROR_BAD_SEPARATORS;
+			beg++;
+			len -= 2;
+			for (l = 0; l < len && !isspace(beg[l]) ; l++);
+			if (l == len)
+				return MUSTACH_ERROR_BAD_SEPARATORS;
+			opstr = strndupa(beg, l);
+			while (l < len && isspace(beg[l])) l++;
+			if (l == len)
+				return MUSTACH_ERROR_BAD_SEPARATORS;
+			clstr = strndupa(beg + l, len - l);
+			oplen = strlen(opstr);
+			cllen = strlen(clstr);
+			break;
+		case '^':
+		case '#':
+			/* begin section */
+			if (depth == DEPTH_MAX)
+				return MUSTACH_ERROR_TOO_DEPTH;
+			rc = emit;
+			if (rc) {
+				rc = itf->enter(closure, name);
+				if (rc < 0)
+					return rc;
+			}
+			stack[depth].name = beg;
+			stack[depth].again = template;
+			stack[depth].length = len;
+			stack[depth].emit = emit;
+			stack[depth].entered = rc;
+			if ((c == '#') == (rc == 0))
+				emit = 0;
+			depth++;
+			break;
+		case '/':
+			/* end section */
+			if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len))
+				return MUSTACH_ERROR_CLOSING;
+			rc = emit && stack[depth].entered ? itf->next(closure) : 0;
+			if (rc < 0)
+				return rc;
+			if (rc) {
+				template = stack[depth++].again;
+			} else {
+				emit = stack[depth].emit;
+				if (emit && stack[depth].entered)
+					itf->leave(closure);
+			}
+			break;
+		case '>':
+			/* partials */
+			if (emit) {
+				rc = getpartial(itf, closure, name, &partial);
+				if (rc == 0) {
+					rc = process(partial, itf, closure, file, opstr, clstr);
+					free(partial);
+				}
+				if (rc < 0)
+					return rc;
+			}
+			break;
+		default:
+			/* replacement */
+			if (emit) {
+				rc = itf->put(closure, name, c != '&', file);
+				if (rc < 0)
+					return rc;
+			}
+			break;
+		}
+	}
+}
+
+int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file)
+{
+	int rc = itf->start ? itf->start(closure) : 0;
+	if (rc == 0)
+		rc = process(template, itf, closure, file, "{{", "}}");
+	return rc;
+}
+
+int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd)
+{
+	int rc;
+	FILE *file;
+
+	file = fdopen(fd, "w");
+	if (file == NULL) {
+		rc = MUSTACH_ERROR_SYSTEM;
+		errno = ENOMEM;
+	} else {
+		rc = fmustach(template, itf, closure, file);
+		fclose(file);
+	}
+	return rc;
+}
+
+int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size)
+{
+	int rc;
+	FILE *file;
+	size_t s;
+
+	*result = NULL;
+	if (size == NULL)
+		size = &s;
+	file = open_memstream(result, size);
+	if (file == NULL) {
+		rc = MUSTACH_ERROR_SYSTEM;
+		errno = ENOMEM;
+	} else {
+		rc = fmustach(template, itf, closure, file);
+		if (rc == 0)
+			/* adds terminating null */
+			rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
+		fclose(file);
+		if (rc >= 0)
+			/* removes terminating null of the length */
+			(*size)--;
+		else {
+			free(*result);
+			*result = NULL;
+			*size = 0;
+		}
+	}
+	return rc;
+}
+
diff --git a/src/mustach.h b/src/mustach.h
new file mode 100644
index 0000000..8196679
--- /dev/null
+++ b/src/mustach.h
@@ -0,0 +1,112 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+ Author: José Bollo <jose.bollo@iot.bzh>
+
+ https://gitlab.com/jobol/mustach
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#ifndef _mustach_h_included_
+#define _mustach_h_included_
+
+/**
+ * mustach_itf - interface for callbacks
+ *
+ * All of this function should return a negative value to stop
+ * the mustache processing. The returned negative value will be
+ * then returned to the caller of mustach as is.
+ *
+ * The functions enter and next should return 0 or 1.
+ *
+ * All other functions should normally return 0.
+ *
+ * @start: Starts the mustach processing of the closure
+ *         'start' is optional (can be NULL)
+ *
+ * @put: Writes the value of 'name' to 'file' with 'escape' or not
+ *
+ * @enter: Enters the section of 'name' if possible.
+ *         Musts return 1 if entered or 0 if not entered.
+ *         When 1 is returned, the function 'leave' will always be called.
+ *         Conversely 'leave' is never called when enter returns 0 or
+ *         a negative value.
+ *         When 1 is returned, the function must activate the first
+ *         item of the section.
+ *
+ * @next: Activates the next item of the section if it exists.
+ *        Musts return 1 when the next item is activated.
+ *        Musts return 0 when there is no item to activate.
+ *
+ * @leave: Leaves the last entered section
+ */
+struct mustach_itf {
+	int (*start)(void *closure);
+	int (*put)(void *closure, const char *name, int escape, FILE *file);
+	int (*enter)(void *closure, const char *name);
+	int (*next)(void *closure);
+	int (*leave)(void *closure);
+};
+
+#define MUSTACH_OK                       0
+#define MUSTACH_ERROR_SYSTEM            -1
+#define MUSTACH_ERROR_UNEXPECTED_END    -2
+#define MUSTACH_ERROR_EMPTY_TAG         -3
+#define MUSTACH_ERROR_TAG_TOO_LONG      -4
+#define MUSTACH_ERROR_BAD_SEPARATORS    -5
+#define MUSTACH_ERROR_TOO_DEPTH         -6
+#define MUSTACH_ERROR_CLOSING           -7
+#define MUSTACH_ERROR_BAD_UNESCAPE_TAG  -8
+
+/**
+ * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instanciate
+ * @itf:      the interface to the functions that mustach calls
+ * @closure:  the closure to pass to functions called
+ * @file:     the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file);
+
+/**
+ * fmustach - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instanciate
+ * @itf:      the interface to the functions that mustach calls
+ * @closure:  the closure to pass to functions called
+ * @fd:       the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd);
+
+/**
+ * fmustach - Renders the mustache 'template' in 'result' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instanciate
+ * @itf:      the interface to the functions that mustach calls
+ * @closure:  the closure to pass to functions called
+ * @result:   the pointer receiving the result when 0 is returned
+ * @size:     the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size);
+
+#endif
+
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
new file mode 100644
index 0000000..09d4d0b
--- /dev/null
+++ b/src/tests/CMakeLists.txt
@@ -0,0 +1,20 @@
+###########################################################################
+# Copyright 2016, 2017 IoT.bzh
+#
+# author: José Bollo <jose.bollo@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+add_subdirectory(test-unit)
+
diff --git a/src/tests/test-unit/CMakeLists.txt b/src/tests/test-unit/CMakeLists.txt
new file mode 100644
index 0000000..3f6db69
--- /dev/null
+++ b/src/tests/test-unit/CMakeLists.txt
@@ -0,0 +1,22 @@
+###########################################################################
+# Copyright 2016, 2017 IoT.bzh
+#
+# author: José Bollo <jose.bollo@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+include_directories(../..)
+add_executable(test-unit test-unit.c)
+target_link_libraries(test-unit wgtpkg wgt utils)
+add_test(NAME test-unit COMMAND test-unit ${CMAKE_CURRENT_SOURCE_DIR}/sample.unit ${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/src/tests/test-unit/config.xml b/src/tests/test-unit/config.xml
new file mode 100644
index 0000000..24a383d
--- /dev/null
+++ b/src/tests/test-unit/config.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns="http://www.w3.org/ns/widgets" id="bzh.iot.geoloc" version="0.0.1-alpha">
+  <name>Sample Service</name>
+  <icon src="service.png"/>
+  <content src="index.html" type="text/html"/>
+  <description>
+	This is the Smarthome QML demo application.
+	It shows some user interfaces for controlling an automated house.
+	The user interface is completely done with QML.
+  </description>
+  <author>Qt team</author>
+  <license>GPL</license>
+  <feature name="urn:AGL:widget:required-binding">
+    <param name="#target" value="main" />
+    <param name="gps" value="ws" />
+    <param name="phone" value="dbus" />
+    <param name="identity" value="link" />
+    <param name="log:https://oic@agl.iot.bzh/cloud/log" value="cloud" />
+  </feature>
+  <feature name="urn:AGL:widget:required-permission">
+    <param name="#target" value="main" />
+    <param name="urn:AGL:permission::platform:no-oom" value="required" />
+    <param name="urn:AGL:permission::partner:real-time" value="required" />
+    <param name="urn:AGL:permission::public:applications:read" value="required" />
+    <param name="urn:AGL:permission::public:display" value="required" />
+  </feature>
+  <feature name="urn:AGL:widget:provided-binding">
+    <param name="#target" value="geoloc" />
+    <param name="description" value="binding of name geoloc" />
+    <param name="content.src" value="libs/binding-geoloc.so" />
+    <param name="content.type" value="application/vnd.agl.service" />
+  </feature>
+  <feature name="urn:AGL:widget:required-binding">
+    <param name="#target" value="geoloc" />
+    <param name="identity" value="auto" />
+  </feature>
+  <feature name="urn:AGL:widget:required-permission">
+    <param name="#target" value="geoloc" />
+    <param name="urn:AGL:permission:real-time" value="required" />
+    <param name="urn:AGL:permission:syscall:*" value="required" />
+  </feature>
+  <feature name="urn:AGL:widget:defined-permission">
+    <param name="urn:AGL:permission:geoloc:public:settings" value="public" />
+  </feature>
+</widget>
+
diff --git a/src/tests/test-unit/sample.unit b/src/tests/test-unit/sample.unit
new file mode 100644
index 0000000..2ee2a2a
--- /dev/null
+++ b/src/tests/test-unit/sample.unit
@@ -0,0 +1,94 @@
+{{#targets.list}}
+
+%begin systemd-unit
+
+# auto generated by wgtpkg-unit for {{id}} version {{version}} target {{:#target}}
+%nl
+
+[unit]
+Description={{description}}
+X-Name={{name.content}}
+X-Name-Short={{name.short}}
+X-Id={{id}}
+X-Idaver={{idaver}}
+X-Target-Name={{:#target}}
+X-Author={{{author.content}}}
+X-Author-email={{author.email}}
+%nl
+
+# Adds check to smack
+ConditionSecurity=smack
+%nl
+
+# Automatic bound to required bindings
+{{#required-binding.list}}
+BindsTo=afm-binding-{{name}}
+After=afm-binding-{{name}}
+{{/required-binding.list}}
+%nl
+
+[Service]
+SmackProcessLabel=User::App::{{id}}
+
+{{#required-permission.dict}}
+  {{#urn:AGL:permission::platform:no-oom}}      OOMScoreAdjust=-500             {{/urn:AGL:permission::platform:no-oom}}
+  {{#urn:AGL:permission::partner:real-time}}    IOSchedulingClass=realtime      {{/urn:AGL:permission::partner:real-time}}
+  {{^urn:AGL:permission::partner:real-time}}    RestrictRealtime=on             {{/urn:AGL:permission::partner:real-time}}
+  {{#urn:AGL:permission::public:display}}       SupplementaryGroups=display     {{/urn:AGL:permission::public:display}}
+  {{^urn:AGL:permission::public:syscall:clock}} SystemCallFilter=~@clock        {{/urn:AGL:permission::public:syscall:clock}}
+  {{^urn:AGL:permission::public:internet}}      RestrictAddressFamilies=AF_UNIX {{/urn:AGL:permission::public:internet}}
+{{/required-permission.dict}}
+%nl
+
+WorkingDirectory={{widget-app-data-dir}}
+
+{{#content.type=text/html}}
+
+%systemd-unit user
+
+%systemd-unit service afm-appli-{{idaver}}{{^#target=main}}@{{:#target}}{{/#target=main}}
+
+ExecStart=/usr/bin/afb-daemon --port=%P --random-token \
+	--rootdir={{widget-install-dir}} \
+	--workdir={{{widget-app-data-dir}}} \
+	--roothttp=htdocs \
+	{{#required-permission.dict.urn:AGL:permission::public:applications:read}}\
+	--alias=/icons:{{widget-icons-dir}} \
+	\{{/required-permission.dict.urn:AGL:permission::public:applications:read}}
+	{{#required-binding}}\
+	--ws-client=unix:%t/bindings/{{:#target}}
+	\{{/required-binding}}
+	--exec /usr/bin/web-runtime http://localhost:@p/{{content.src}}?token=@t
+
+{{/content.type=text/html}}
+
+{{#content.type=application/vnd.agl.service}}
+
+%systemd-unit user
+%systemd-unit service afm-binding-{{:#target}}
+
+ExecStart=/usr/bin/afb-daemon \
+	--rootdir={{widget-install-dir}} \
+	--workdir={{{widget-app-data-dir}}} \
+	--no-httpd \
+	--ws-server=unix:%t/bindings/{{:#target}}
+
+%end systemd-unit
+%begin systemd-unit
+
+# auto generated by wgtpkg-unit for {{id}} version {{version}} target {{:#target}}
+#
+%systemd-unit user
+%systemd-unit socket afm-binding-{{:#target}}
+
+
+[socket]
+SmackLabel=*
+ListenStream=%t/bindings/{{:#target}}
+
+{{/content.type=application/vnd.agl.service}}
+
+%end systemd-unit
+
+{{/targets.list}}
+
diff --git a/src/tests/test-unit/test-unit.c b/src/tests/test-unit/test-unit.c
new file mode 100644
index 0000000..09b7c86
--- /dev/null
+++ b/src/tests/test-unit/test-unit.c
@@ -0,0 +1,89 @@
+/*
+ Copyright 2016, 2017 IoT.bzh
+
+ author: José Bollo <jose.bollo@iot.bzh>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <json-c/json.h>
+
+#include <wgt.h>
+#include <utils-json.h>
+#include <wgt-json.h>
+#include <wgtpkg-mustach.h>
+#include <wgtpkg-unit.h>
+
+
+#define error(...) fprintf(stderr,__VA_ARGS__),exit(1)
+
+
+
+static int process1(const struct unitdesc *desc)
+{
+	int isuser = desc->scope == unitscope_user;
+	int issystem = desc->scope == unitscope_system;
+	int issock = desc->type == unittype_socket;
+	int isserv = desc->type == unittype_service;
+	const char *name = desc->name;
+	const char *content = desc->content;
+
+printf("\n##########################################################");
+printf("\n### usr=%d sys=%d soc=%d srv=%d    name  %s%s", isuser, issystem, issock, isserv, name?:"?", issock?".socket":isserv?".service":"");
+printf("\n##########################################################");
+printf("\n%s\n\n",content);
+	return 0;
+}
+
+static int process(void *closure, const struct unitdesc descs[], unsigned count)
+{
+	while (count--)
+		process1(descs++);
+	return 0;
+}
+
+int main(int ac, char **av)
+{
+	struct json_object *obj;
+	int rc;
+
+	rc = unit_generator_on(*++av);
+	if (rc < 0)
+		error("can't read template %s: %m",*av);
+	while(*++av) {
+		obj = wgt_path_to_json(*av);
+		if (!obj)
+			error("can't read widget config at %s: %m",*av);
+
+		j_add_string_m(obj, "#metadata.install-dir", "INSTALL-DIR");
+		j_add_string_m(obj, "#metadata.app-data-dir", "%h/app-data");
+		j_add_string_m(obj, "#metadata.icons-dir", "ICONS-DIR");
+		j_add_string_m(obj, "#metadata.http-port", "HTTP-PORT");
+
+		puts(json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PRETTY));
+		rc = unit_generator_process(obj, process, NULL);
+		if (rc)
+			error("can't apply generate units, error %d",rc);
+		json_object_put(obj);
+	}
+	return 0;
+}
+
+
diff --git a/src/wgt-json.c b/src/wgt-json.c
new file mode 100644
index 0000000..cc5ee93
--- /dev/null
+++ b/src/wgt-json.c
@@ -0,0 +1,482 @@
+/*
+ Copyright 2015, 2016, 2017 IoT.bzh
+
+ author: José Bollo <jose.bollo@iot.bzh>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <sys/types.h>
+
+#include <json-c/json.h>
+
+#include "utils-json.h"
+#include "wgt-info.h"
+#include "wgt-json.h"
+#include "wgt-strings.h"
+#include "verbose.h"
+
+/*
+{
+  permissions: {
+	dict: {
+		ID: { name: ID, level: LEVEL, index: INDEX },
+		...
+	},
+	list: [
+		{ name: ID, level: LEVEL, index: 0 },
+		...
+	}
+  },
+  targets: [
+		{ name: ID, level: LEVEL, index: 0 },
+		...
+	]
+  }
+}
+*/
+
+struct paramaction
+{
+	const char *name;
+	int (*action)(struct json_object *obj, const struct wgt_desc_param *param, void *closure);
+	void *closure;
+};
+
+
+/* apply params */
+static int apply_params(struct json_object *obj, const struct wgt_desc_param *param, const struct paramaction *actions)
+{
+	int rc;
+	const struct paramaction *a;
+
+	if (!obj)
+		rc = -1;
+	else {
+		rc = 0;
+		while(param) {
+			for (a = actions ; a->name && strcmp(a->name, param->name) ; a++);
+			if (a->action && a->action(obj, param, a->closure) < 0)
+				rc = -1;
+			param = param->next;
+		}
+	}
+	return rc;
+}
+
+static int get_array(struct json_object **result, struct json_object *obj, const char *key, int create)
+{
+	int rc = j_enter_m(&obj, &key, 1);
+	if (rc < 0)
+		*result = NULL;
+	else if (!json_object_object_get_ex(obj, key, result)) {
+		if (!create) {
+			*result = NULL;
+			rc = -ENOENT;
+		} else {
+			if (!(*result = j_add_new_array(obj, key)))
+				rc = -ENOMEM;
+		}
+	}
+	return rc;
+}
+
+/* get the param of 'name' for the feature 'feat' */
+static const char *get_target_name(const struct wgt_desc_feature *feat, const char *defval)
+{
+	const struct wgt_desc_param *param = feat->params;
+	while (param && strcmp(param->name, string_sharp_target))
+		param = param->next;
+	if (param) {
+		defval = param->value;
+		param = param->next;
+		while (param) {
+			if (!strcmp(param->name, string_sharp_target)) {
+				defval = NULL;
+				break;
+			}
+			param = param->next;
+		}
+	}
+	return defval;
+}
+
+/* get the target */
+static struct json_object *get_array_item_by_key(struct json_object *array, const char *key, const char *val)
+{
+	struct json_object *result, *k;
+	int i, n;
+
+	n = json_object_array_length(array);
+	for (i = 0 ; i < n ; i++) {
+		result = json_object_array_get_idx(array, i);
+		if (result && json_object_object_get_ex(result, key, &k)
+			&& json_object_get_type(k) == json_type_string
+			&& !strcmp(json_object_get_string(k), val))
+			return result;
+	}
+	return NULL;
+}
+
+static int get_target(struct json_object **result, struct json_object *targets, const char *name, int create)
+{
+	int rc;
+	struct json_object *t;
+
+	t = get_array_item_by_key(targets, string_sharp_target, name);
+	if (t) {
+		if (!create)
+			rc = 0;
+		else {
+			ERROR("duplicated target name: %s", name);
+			t = NULL;
+			rc = -EEXIST;
+		}
+	} else {
+		if (!create) {
+			ERROR("target name not found: %s", name);
+			rc = -ENOENT;
+		} else {
+			t = j_add_new_object(targets, NULL);
+			if (t && j_add_string(t, string_sharp_target, name))
+				rc = 0;
+			else {
+				json_object_put(t);
+				t = NULL;
+				rc = -ENOMEM;
+			}
+		}
+	}
+	*result = t;
+	return rc;
+}
+
+static int make_target(struct json_object *targets, const struct wgt_desc_feature *feat)
+{
+	const char *id;
+	struct json_object *target;
+
+	id = get_target_name(feat, NULL);
+	if (id == NULL) {
+		ERROR("target of feature %s is missing or repeated", feat->name);
+		return -EINVAL;
+	}
+
+	return get_target(&target, targets, id, 1);
+}
+
+static int add_icon(struct json_object *target, const char *src, int width, int height)
+{
+	int rc;
+	struct json_object *icon, *array;
+
+	rc = get_array(&array, target, string_icon, 1);
+	if (rc >= 0) {
+		icon = json_object_new_object();
+		if(!icon
+		|| !j_add_string(icon, string_src, src)
+		|| (width > 0 && !j_add_integer(icon, string_width, width))
+		|| (height > 0 && !j_add_integer(icon, string_height, height))
+		|| !j_add(array, NULL, icon)) {
+			json_object_put(icon);
+			rc = -ENOMEM;
+		}
+	}
+	return rc;
+}
+
+static int make_main_target(struct json_object *targets, const struct wgt_desc *desc)
+{
+	int rc;
+	struct wgt_desc_icon *icon;
+	struct json_object *target;
+
+	/* create the target 'main' */
+	rc = get_target(&target, targets, string_main, 1);
+
+	/* adds icons if any */
+	icon = desc->icons;
+	while (rc >= 0 && icon) {
+		rc = add_icon(target, icon->src, icon->width, icon->height);
+		icon = icon->next;
+	}
+
+	if (rc >= 0)
+		rc = j_add_many_strings_m(target,
+			"content.src", desc->content_src,
+			"content.type", desc->content_type,
+			"content.encoding", desc->content_encoding,
+			NULL) ? 0 : -errno;
+
+	if (rc >= 0)
+		if((desc->width && !j_add_integer(target, string_width, desc->width))
+		|| (desc->height && !j_add_integer(target, string_height, desc->height)))
+			rc = -ENOMEM;
+
+	return rc;
+}
+
+/***********************************************************************************************************/
+
+static struct json_object *object_of_param(const struct wgt_desc_param *param)
+{
+	struct json_object *value;
+
+	value = json_object_new_object();
+	if (value
+	 && j_add_string(value, string_name, param->name)
+	 && j_add_string(value, string_value, param->value))
+		return value;
+
+	json_object_put(value);
+	return NULL;
+}
+
+static int add_param_simple(struct json_object *obj, const struct wgt_desc_param *param, void *closure)
+{
+	return j_add_string_m(obj, param->name, param->value);
+}
+
+static int add_param_array(struct json_object *obj, const struct wgt_desc_param *param, void *closure)
+{
+	struct json_object *array, *value;
+
+	if (!closure)
+		array = obj;
+	else if (!json_object_object_get_ex(obj, closure, &array)) {
+		array = j_add_new_array(obj, closure);
+		if (!array)
+			return -ENOMEM;
+	}
+	value = object_of_param(param);
+	if (value && j_add(array, NULL, value))
+		return 0;
+
+	json_object_put(value);
+	return -ENOMEM;
+}
+
+static int add_param_object(struct json_object *obj, const struct wgt_desc_param *param, void *closure)
+{
+	struct json_object *object, *value;
+
+	if (!closure)
+		object = obj;
+	else if (!json_object_object_get_ex(obj, closure, &object)) {
+		object = j_add_new_object(obj, closure);
+		if (!object)
+			return -ENOMEM;
+	}
+	value = object_of_param(param);
+	if (value && j_add(object, param->name, value))
+		return 0;
+
+	json_object_put(value);
+	return -ENOMEM;
+}
+
+static int add_targeted_params(struct json_object *targets, const struct wgt_desc_feature *feat, struct paramaction actions[])
+{
+	int rc;
+	const char *id;
+	struct json_object *obj;
+
+	id = get_target_name(feat, string_main);
+	rc = get_target(&obj, targets, id, 0);
+	return rc < 0 ? rc : apply_params(obj, feat->params, actions);
+}
+
+static int add_provided(struct json_object *targets, const struct wgt_desc_feature *feat)
+{
+	static struct paramaction actions[] = {
+		{ .name = string_sharp_target, .action = NULL, .closure = NULL },
+		{ .name = NULL, .action = add_param_simple, .closure = NULL }
+	};
+	return add_targeted_params(targets, feat, actions);
+}
+
+static int add_required_binding(struct json_object *targets, const struct wgt_desc_feature *feat)
+{
+	static struct paramaction actions[] = {
+		{ .name = string_sharp_target, .action = NULL, .closure = NULL },
+		{ .name = NULL, .action = add_param_array, .closure = (void*)string_required_binding }
+	};
+	return add_targeted_params(targets, feat, actions);
+}
+
+
+static int add_required_permission(struct json_object *targets, const struct wgt_desc_feature *feat)
+{
+	static struct paramaction actions[] = {
+		{ .name = string_sharp_target, .action = NULL, .closure = NULL },
+		{ .name = NULL, .action = add_param_object, .closure = (void*)string_required_permission }
+	};
+	return add_targeted_params(targets, feat, actions);
+}
+
+static int add_defined_permission(struct json_object *defperm, const struct wgt_desc_feature *feat)
+{
+	static struct paramaction actions[] = {
+		{ .name = NULL, .action = add_param_array, .closure = NULL }
+	};
+	return apply_params(defperm, feat->params, actions);
+}
+
+/***********************************************************************************************************/
+
+static struct json_object *to_json(const struct wgt_desc *desc)
+{
+	size_t prefixlen;
+	const struct wgt_desc_feature *feat;
+	const char *featname;
+	struct json_object *result, *targets, *permissions;
+	int rc, rc2;
+
+	/* create the application structure */
+	if(!(result = json_object_new_object())
+	|| !(targets = j_add_new_array(result, string_targets))
+	|| !(permissions = j_add_new_array(result, string_defined_permission))
+	)
+		goto error;
+
+	/* first pass: declarations */
+	rc = make_main_target(targets, desc);
+	prefixlen = strlen(string_AGL_widget_prefix);
+	for (feat = desc->features ; feat ; feat = feat->next) {
+		featname = feat->name;
+		if (!memcmp(featname, string_AGL_widget_prefix, prefixlen)) {
+			if (!feat->required) {
+				ERROR("feature %s can't be optional", featname);
+				if (!rc)
+					rc = -EINVAL;
+			}
+			featname += prefixlen;
+			if (!strcmp(featname, string_provided_binding)
+			||  !strcmp(featname, string_provided_application)) {
+				rc2 = make_target(targets, feat);
+				if (rc2 < 0 && !rc)
+					rc = rc2;
+			}
+		}
+	}
+
+	/* second pass: definitions */
+	for (feat = desc->features ; feat ; feat = feat->next) {
+		featname = feat->name;
+		if (!memcmp(featname, string_AGL_widget_prefix, prefixlen)) {
+			featname += prefixlen;
+			if (!strcmp(featname, string_defined_permission)) {
+				rc2 = add_defined_permission(permissions, feat);
+				if (rc2 < 0 && !rc)
+					rc = rc2;
+			}
+			else if (!strcmp(featname, string_provided_application)
+				|| !strcmp(featname, string_provided_binding)) {
+				rc2 = add_provided(targets, feat);
+				if (rc2 < 0 && !rc)
+					rc = rc2;
+			}
+			else if (!strcmp(featname, string_required_binding)) {
+				rc2 = add_required_binding(targets, feat);
+				if (rc2 < 0 && !rc)
+					rc = rc2;
+			}
+			else if (!strcmp(featname, string_required_permission)) {
+				rc2 = add_required_permission(targets, feat);
+				if (rc2 < 0 && !rc)
+					rc = rc2;
+			}
+		}
+	}
+
+	/* fills the main */
+	rc2 = j_add_many_strings_m(result,
+		string_id, desc->id,
+		string_idaver, desc->idaver,
+		string_version, desc->version,
+		"ver", desc->ver,
+		"author.content", desc->author,
+		"author.href", desc->author_href,
+		"author.email", desc->author_email,
+		"license.content", desc->license,
+		"license.href", desc->license_href,
+		"defaultlocale", desc->defaultlocale,
+		"name.content", desc->name,
+		"name.short", desc->name_short,
+		"description", desc->description,
+		NULL) ? 0 : -errno;
+	if (rc2 < 0 && !rc)
+		rc = rc2;
+
+	/* */
+
+	/* */
+	if (!rc) {
+		return result;
+	}
+
+error:
+	json_object_put(result);
+	return NULL;
+}
+
+struct json_object *wgt_info_to_json(struct wgt_info *info)
+{
+	return to_json(wgt_info_desc(info));
+}
+
+struct json_object *wgt_to_json(struct wgt *wgt)
+{
+	struct json_object *result;
+	struct wgt_info *info;
+
+	info = wgt_info_create(wgt, 1, 1, 1);
+	if (info == NULL)
+		result = NULL;
+	else {
+		result = wgt_info_to_json(info);
+		wgt_info_unref(info);
+	}
+	return result;
+}
+
+struct json_object *wgt_path_at_to_json(int dfd, const char *path)
+{
+	struct json_object *result;
+	struct wgt_info *info;
+
+	info = wgt_info_createat(dfd, path, 1, 1, 1);
+	if (info == NULL)
+		result = NULL;
+	else {
+		result = wgt_info_to_json(info);
+		wgt_info_unref(info);
+	}
+	return result;
+}
+
+struct json_object *wgt_path_to_json(const char *path)
+{
+	return wgt_path_at_to_json(AT_FDCWD, path);
+}
+
+
diff --git a/src/wgt-json.h b/src/wgt-json.h
new file mode 100644
index 0000000..11c4e41
--- /dev/null
+++ b/src/wgt-json.h
@@ -0,0 +1,27 @@
+/*
+ Copyright 2015, 2016, 2017 IoT.bzh
+
+ author: José Bollo <jose.bollo@iot.bzh>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+struct wgt;
+struct wgt_info;
+struct json_object;
+
+extern struct json_object *wgt_info_to_json(struct wgt_info *info);
+extern struct json_object *wgt_to_json(struct wgt *wgt);
+extern struct json_object *wgt_path_at_to_json(int dfd, const char *path);
+extern struct json_object *wgt_path_to_json(const char *path);
+
diff --git a/src/wgt-strings.c b/src/wgt-strings.c
index e3d45a0..a359880 100644
--- a/src/wgt-strings.c
+++ b/src/wgt-strings.c
@@ -1,5 +1,5 @@
 /*
- Copyright 2016 IoT.bzh
+ Copyright 2016, 2017 IoT.bzh
 
  author: José Bollo <jose.bollo@iot.bzh>
 
@@ -16,6 +16,7 @@
  limitations under the License.
 */
 
+/* defined by/for config.xml */
 const char string_author[] = "author";
 const char string_content[] = "content";
 const char string_defaultlocale[] = "defaultlocale";
@@ -29,7 +30,6 @@ const char string_icon[] = "icon";
 const char string_id[] = "id";
 const char string_license[] = "license";
 const char string_name[] = "name";
-const char string_optional[] = "optional";
 const char string_param[] = "param";
 const char string_preference[] = "preference";
 const char string_readonly[] = "readonly";
@@ -44,7 +44,27 @@ const char string_widget[] = "widget";
 const char string_width[] = "width";
 const char string_config_dot_xml[] = "config.xml";
 
-const char feature_required_binding[] = FWK_PREFIX "required-binding";
-const char feature_required_permission[] = FWK_PREFIX "required-permission";
+/* other strings */
+const char string_AGL_prefix[] = FWK_PREFIX;
+const char string_AGL_permission_prefix[] = FWK_PREFIX "permission:";
+const char string_AGL_widget_prefix[] = FWK_PREFIX "widget:";
+const char string_defined_permission[] = "defined-permission";
+const char string_dict[] = "dict";
+const char string_idaver[] = "idaver";
+const char string_index[] = "index";
+const char string_level[] = "level";
+const char string_list[] = "list";
+const char string_main[] = "main";
+const char string_optional[] = "optional";
+const char string_provided_application[] = "provided-application";
+const char string_provided_binding[] = "provided-binding";
+const char string_required_binding[] = "required-binding";
+const char string_required_permission[] = "required-permission";
+const char string_targets[] = "targets";
+const char string_sharp_target[] = "#target";
+
+
+const char feature_required_binding[] = FWK_PREFIX "widget:required-binding";
+const char feature_required_permission[] = FWK_PREFIX "widget:required-permission";
 
 
diff --git a/src/wgt-strings.h b/src/wgt-strings.h
index b2befcc..7609723 100644
--- a/src/wgt-strings.h
+++ b/src/wgt-strings.h
@@ -1,5 +1,5 @@
 /*
- Copyright 2016 IoT.bzh
+ Copyright 2016, 2017 IoT.bzh
 
  author: José Bollo <jose.bollo@iot.bzh>
 
@@ -16,6 +16,7 @@
  limitations under the License.
 */
 
+/* defined by/for config.xml */
 extern const char string_author[];
 extern const char string_content[];
 extern const char string_defaultlocale[];
@@ -29,7 +30,6 @@ extern const char string_icon[];
 extern const char string_id[];
 extern const char string_license[];
 extern const char string_name[];
-extern const char string_optional[];
 extern const char string_param[];
 extern const char string_preference[];
 extern const char string_readonly[];
@@ -44,6 +44,22 @@ extern const char string_widget[];
 extern const char string_width[];
 extern const char string_config_dot_xml[];
 
+/* other strings */
+extern const char string_AGL_widget_prefix[];
+extern const char string_defined_permission[];
+extern const char string_dict[];
+extern const char string_idaver[];
+extern const char string_index[];
+extern const char string_level[];
+extern const char string_list[];
+extern const char string_main[];
+extern const char string_optional[];
+extern const char string_provided_application[];
+extern const char string_provided_binding[];
+extern const char string_required_binding[];
+extern const char string_required_permission[];
+extern const char string_sharp_target[];
+extern const char string_targets[];
 
 extern const char feature_required_binding[];
 extern const char feature_required_permission[];
diff --git a/src/wgtpkg-mustach.c b/src/wgtpkg-mustach.c
new file mode 100644
index 0000000..4b05bc2
--- /dev/null
+++ b/src/wgtpkg-mustach.c
@@ -0,0 +1,264 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+ Author: José Bollo <jose.bollo@iot.bzh>
+
+ https://gitlab.com/jobol/mustach
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <string.h>
+
+#include <json-c/json.h>
+
+#include "mustach.h"
+
+#define MAX_DEPTH 256
+
+
+struct expl {
+	struct json_object *root;
+	int depth;
+	struct {
+		struct json_object *cont;
+		struct json_object *obj;
+		int index, count;
+	} stack[MAX_DEPTH];
+};
+
+/*
+ * Scan a key=val text.
+ * If the sign = is found, drop it and returns a pointer to the value.
+ * If the sign = is not here, returns NULL.
+ * Replace any \= of the key by its unescaped version =.
+ */
+static char *keyval(char *head)
+{
+	char *w, c;
+
+	c = *(w = head);
+	while (c && c != '=') {
+		if (c == '\\') {
+			switch (head[1]) {
+			case '\\': *w++ = c;
+			case '=': c = *++head;
+			default: break;
+			}
+		}
+		*w++ = c;
+		c = *++head;
+	}
+	*w = 0;
+	return c == '=' ? ++head : NULL;
+}
+
+/*
+ * Returns the unescaped version of the first component
+ * and update 'name' to point the next components if any.
+ */
+static char *first(char **name)
+{
+	char *r, *i, *w, c;
+
+	c = *(i = *name);
+	if (!c)
+		r = NULL;
+	else {
+		r = w = i;
+		while (c && c != '.') {
+			if (c == '\\' && (i[1] == '.' || i[1] == '\\'))
+				c = *++i;
+			*w++ = c;
+			c = *++i;
+		}
+		*w = 0;
+		*name = i + !!c;
+	}
+	return r;
+}
+
+/*
+ * Replace the last occurence of ':' followed by
+ * any character not being '*' by ':*', the
+ * globalisation of the key.
+ * Returns NULL if no globalisation is done
+ * or else the key globalized.
+ */
+static char *globalize(char *key)
+{
+	char *f, *r;
+
+	f = NULL;
+	for (r = key; *r ; r++) {
+		if (r[0] == ':' && r[1] && r[1] != '*')
+			f = r;
+	}
+	if (f) {
+		f[1] = '*';
+		f[2] = 0;
+		f = key;
+	}
+	return f;
+}
+
+/*
+ * find the object of 'name'
+ */
+static struct json_object *find(struct expl *e, const char *name)
+{
+	int i;
+	struct json_object *o, *r;
+	char *n, *c, *v;
+
+	/* get a local key */
+	n = strdupa(name);
+
+	/* extract its value */
+	v = keyval(n);
+
+	/* search the first component for each valid globalisation */
+	i = e->depth;
+	c = first(&n);
+	while (c) {
+		if (i < 0) {
+			/* next globalisation */
+			i = e->depth;
+			c = globalize(c);
+		}
+		else if (json_object_object_get_ex(e->stack[i].obj, c, &o)) {
+
+			/* found the root, search the subcomponents */
+			c = first(&n);
+			while(c) {
+				while (!json_object_object_get_ex(o, c, &r)) {
+					c = globalize(c);
+					if (!c)
+						return NULL;
+				}
+				o = r;
+				c = first(&n);
+			}
+
+			/* check the value if requested */
+			if (v) {
+				i = v[0] == '!';
+				if (i == !strcmp(&v[i], json_object_get_string(o)))
+					o = NULL;
+			}
+			return o;
+		}
+		i--;
+	}
+	return NULL;
+}
+
+static int start(void *closure)
+{
+	struct expl *e = closure;
+	e->depth = 0;
+	e->stack[0].cont = NULL;
+	e->stack[0].obj = e->root;
+	e->stack[0].index = 0;
+	e->stack[0].count = 1;
+	return 0;
+}
+
+static void print(FILE *file, const char *string, int escape)
+{
+	if (!escape)
+		fputs(string, file);
+	else if (*string)
+		do {
+			switch(*string) {
+			case '%': fputs("%%", file); break;
+			case '\n': fputs("\\n\\\n", file); break;
+			default: putc(*string, file); break;
+			}
+		} while(*++string);
+}
+
+static int put(void *closure, const char *name, int escape, FILE *file)
+{
+	struct expl *e = closure;
+	struct json_object *o = find(e, name);
+	if (o)
+		print(file, json_object_get_string(o), escape);
+	return 0;
+}
+
+static int enter(void *closure, const char *name)
+{
+	struct expl *e = closure;
+	struct json_object *o = find(e, name);
+	if (++e->depth >= MAX_DEPTH)
+		return MUSTACH_ERROR_TOO_DEPTH;
+	if (json_object_is_type(o, json_type_array)) {
+		e->stack[e->depth].count = json_object_array_length(o);
+		if (e->stack[e->depth].count == 0) {
+			e->depth--;
+			return 0;
+		}
+		e->stack[e->depth].cont = o;
+		e->stack[e->depth].obj = json_object_array_get_idx(o, 0);
+		e->stack[e->depth].index = 0;
+	} else if (json_object_is_type(o, json_type_object) || json_object_get_boolean(o)) {
+		e->stack[e->depth].count = 1;
+		e->stack[e->depth].cont = NULL;
+		e->stack[e->depth].obj = o;
+		e->stack[e->depth].index = 0;
+	} else {
+		e->depth--;
+		return 0;
+	}
+	return 1;
+}
+
+static int next(void *closure)
+{
+	struct expl *e = closure;
+	if (e->depth <= 0)
+		return MUSTACH_ERROR_CLOSING;
+	e->stack[e->depth].index++;
+	if (e->stack[e->depth].index >= e->stack[e->depth].count)
+		return 0;
+	e->stack[e->depth].obj = json_object_array_get_idx(e->stack[e->depth].cont, e->stack[e->depth].index);
+	return 1;
+}
+
+static int leave(void *closure)
+{
+	struct expl *e = closure;
+	if (e->depth <= 0)
+		return MUSTACH_ERROR_CLOSING;
+	e->depth--;
+	return 0;
+}
+
+static struct mustach_itf itf = {
+	.start = start,
+	.put = put,
+	.enter = enter,
+	.next = next,
+	.leave = leave
+};
+
+int apply_mustach(const char *template, struct json_object *root, char **result, size_t *size)
+{
+	struct expl e;
+	e.root = root;
+	return mustach(template, &itf, &e, result, size);
+}
+
diff --git a/src/wgtpkg-mustach.h b/src/wgtpkg-mustach.h
new file mode 100644
index 0000000..c4ef6e1
--- /dev/null
+++ b/src/wgtpkg-mustach.h
@@ -0,0 +1,24 @@
+/*
+ Author: José Bollo <jose.bollo@iot.bzh>
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+struct json_object;
+
+extern int apply_mustach(const char *template, struct json_object *root, char **result, size_t *size);
+
+
diff --git a/src/wgtpkg-unit.c b/src/wgtpkg-unit.c
new file mode 100644
index 0000000..8569c5a
--- /dev/null
+++ b/src/wgtpkg-unit.c
@@ -0,0 +1,254 @@
+/*
+ Copyright 2016, 2017 IoT.bzh
+
+ author: José Bollo <jose.bollo@iot.bzh>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "verbose.h"
+#include "utils-file.h"
+
+#include "wgtpkg-mustach.h"
+#include "wgtpkg-unit.h"
+
+static char *template;
+
+/*
+ * Pack a null terminated 'text' by removing empty lines,
+ * lines made of blanks and terminated with \, lines
+ * starting with the 'purge' character (can be null).
+ * Lines made of the 'purge' character followed with
+ * "nl" exactly (without quotes ") are replaced with
+ * an empty line.
+ *
+ * Returns the pointer to the ending null.
+ */
+static char *pack(char *text, char purge)
+{
+	char *read, *write, *begin, *start, c, emit, cont, nextcont;
+
+	cont = 0;
+	c = *(begin = write = read = text);
+	while (c) {
+		emit = nextcont = 0;
+		start = NULL;
+		begin = read;
+		while (c && c != '\n') {
+			if (c != ' ' && c != '\t') {
+				if (c == '\\' && read[1] == '\n')
+					nextcont = 1;
+				else {
+					emit = 1;
+					if (!start)
+						start = read;
+				}
+			}
+			c = *++read;
+		}
+		if (c)
+			c = *++read;
+		if (emit) {
+			if (!cont && start)
+				begin = start;
+			if (purge && *begin == purge) {
+				if (!strncmp(begin+1, "nl\n",3))
+					*write++ = '\n';
+			} else {
+				while (begin != read)
+					*write++ = *begin++;
+			}
+		}
+		cont = nextcont;
+	}
+	*write = 0;
+	return write;
+}
+
+/*
+ * Searchs the first character of the next line
+ * of the 'text' and returns its address
+ * Returns NULL if there is no next line.
+ */
+static inline char *nextline(char *text)
+{
+	char *result = strchr(text, '\n');
+	return result + !!result;
+}
+
+/*
+ * Search in 'text' the offset of a line beginning with the 'pattern'
+ * Returns NULL if not found or the address of the line contning the pattern
+ * If args isn't NULL and the pattern is found, the pointed pattern is
+ * updated with the address of the character following the found pattern.
+ */
+static char *offset(char *text, const char *pattern, char **args)
+{
+	size_t len;
+
+	if (text) {
+		len = strlen(pattern);
+		do {
+			if (strncmp(text, pattern, len))
+				text = nextline(text);
+			else {
+				if (args)
+					*args = &text[len];
+				break;
+			}
+		} while (text);
+	}
+	return text;
+}
+
+/*
+ * process one unit
+ */
+
+static int process_one_unit(char *spec, struct unitdesc *desc)
+{
+	char *nsoc, *nsrv;
+	int isuser, issystem, issock, isserv;
+
+	/* found the configuration directive of the unit */
+	isuser = !!offset(spec, "%systemd-unit user\n", NULL);
+	issystem = !!offset(spec, "%systemd-unit system\n", NULL);
+	issock  = !!offset(spec, "%systemd-unit socket ", &nsoc);
+	isserv  = !!offset(spec, "%systemd-unit service ", &nsrv);
+
+	if (isuser ^ issystem) {
+		desc->scope = isuser ? unitscope_user : unitscope_system;
+	} else {
+		desc->scope = unitscope_unknown;
+	}
+
+	if (issock ^ isserv) {
+		if (issock) {
+			desc->type = unittype_socket;
+			desc->name = nsoc;
+		} else {
+			desc->type = unittype_service;
+			desc->name = nsrv;
+		}
+		desc->name_length = (size_t)(strchrnul(desc->name, '\n') - desc->name);
+		desc->name = strndup(desc->name, desc->name_length);
+	} else {
+		desc->type = unittype_unknown;
+		desc->name = NULL;
+		desc->name_length = 0;
+	}
+
+	desc->content = spec;
+	desc->content_length = (size_t)(pack(spec, '%') - spec);
+
+	return 0;
+}
+
+/*
+ */
+static int process_all_units(char *spec, int (*process)(void *closure, const struct unitdesc descs[], unsigned count), void *closure)
+{
+	int rc, rc2;
+	unsigned n;
+	char *beg, *end, *after;
+	struct unitdesc *descs, *d;
+
+	descs = NULL;
+	n = 0;
+	rc = 0;
+	beg = offset(spec, "%begin systemd-unit\n", NULL);
+	while(beg) {
+		beg = nextline(beg);
+		end = offset(beg, "%end systemd-unit\n", &after);
+		if (!end) {
+			/* unterminated unit !! */
+			ERROR("unterminated unit description!! %s", beg);
+			break;
+		}
+		*end = 0;
+
+		d = realloc(descs, (n + 1) * sizeof *descs);
+		if (d == NULL)
+			rc2 = -ENOMEM;
+		else {
+			memset(&d[n], 0, sizeof *d);
+			descs = d;
+			rc2 = process_one_unit(beg, &descs[n]);
+		}
+
+		if (rc2 >= 0)
+			n++;
+		else if (rc == 0)
+			rc = rc2;
+		beg = offset(after, "%begin systemd-unit\n", NULL);
+	}
+
+	if (rc == 0 && process)
+		rc = process(closure, descs, n);
+	while(n)
+		free((char *)(descs[--n].name));
+	free(descs);
+
+	return rc;
+}
+
+int unit_generator_on(const char *filename)
+{
+	size_t size;
+	char *tmp;
+	int rc;
+
+	rc = getfile(filename ? : FWK_UNIT_CONF, &template, NULL);
+	if (!rc) {
+		size = (size_t)(pack(template, ';') - template);
+		tmp = realloc(template, 1 + size);
+		if (tmp)
+			template = tmp;
+	}
+	return rc;
+}
+
+void unit_generator_off()
+{
+	free(template);
+	template = NULL;
+}
+
+int unit_generator_process(struct json_object *jdesc, int (*process)(void *closure, const struct unitdesc descs[], unsigned count), void *closure)
+{
+	int rc;
+	size_t size;
+	char *instance;
+
+	rc = template ? 0 : unit_generator_on(NULL);
+	if (!rc) {
+		instance = NULL;
+		rc = apply_mustach(template, jdesc, &instance, &size);
+		if (!rc) {
+			rc = process_all_units(instance, process, closure);
+		}
+		free(instance);
+	}
+	return rc;
+}
+
diff --git a/src/wgtpkg-unit.h b/src/wgtpkg-unit.h
new file mode 100644
index 0000000..4843685
--- /dev/null
+++ b/src/wgtpkg-unit.h
@@ -0,0 +1,45 @@
+/*
+ Copyright 2016, 2017 IoT.bzh
+
+ author: José Bollo <jose.bollo@iot.bzh>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+
+struct json_object;
+
+enum unitscope {
+	unitscope_unknown = 0,
+	unitscope_system,
+	unitscope_user
+};
+
+enum unittype {
+	unittype_unknown = 0,
+	unittype_service,
+	unittype_socket
+};
+
+struct unitdesc {
+	enum unitscope scope;
+	enum unittype type;
+	const char *name;
+	size_t name_length;
+	const char *content;
+	size_t content_length;
+};
+
+extern int unit_generator_on(const char *filename);
+extern void unit_generator_off();
+extern int unit_generator_process(struct json_object *jdesc, int (*process)(void *closure, const struct unitdesc descs[], unsigned count), void *closure);
-- 
2.16.6