โŒ About FreshRSS

Normal view

There are new articles available, click to refresh the page.
Before yesterdayblog.vacs.fr - Tag Ada

Reentrant scanner and parser with Aflex and Ayacc

14 May 2023 at 19:02
[Aflex and Ayacc](Ada/Aflex-Ayacc-code.jpg)
    1. What's new in Aflex 1.6

- Support the flex options `%option output`, `%option nooutput`, `%option yywrap`, `%option noinput`,

 `%option noyywrap`, `%option unput`, `%option nounput`, `%option bufsize=NNN` to better control the
 generated `_IO` package.
- Aflex(https://github.com/Ada-France/aflex) templates provide more control for tuning the code generation and
 they are embedded with [Advanced Resource Embedder](https://gitlab.com/stcarrez/resource-embedder)
- Support to define Ada code block in the scanner that is inserted in the generated scanner - New option -P to generate a private Ada package for DFA and IO - New directive `%option reentrant` and `%yyvar` to generate a recursive scanner - New directive `%yydecl` to allow passing parameters to `YYLex`
 or change the default function name

Example of `%option` directives to tell Aflex(https://github.com/Ada-France/aflex) to avoid generating several function or procedures and customize the buffer size.

```Ada %option nounput %option noinput %option nooutput %option noyywrap %option bufsize=1024 ```

The tool supports some code block injection at various places in the generated scanner. The code block has the following syntax where `<block-name>` is the name of the code block:

```Ada %<block-name> {

 -- Put Ada code here

} ```

The `%yytype` code block can contain type declaration, function and procedure declarations. It is injected within the `YYLex` function in the declaration part. The `%yyinit` code block can contain statements that are executed at beginning of the `YYLex` function. The `%yyaction` code block can contain statements that are executed before running any action. The `%yywrap` code block can contain statements which are executed when the end of current file is reached to start parsing a next input.

    1. What's new in Ayacc 1.4

- Support the Bison `%define variable value` option to configure the parser generator - Support the Bison `%code name { ... }` directive to insert code verbatim into the output parser - Recognize some Bison variables `api.pure`, `api.private`, `parse.error`, `parse.stacksize`,

 `parse.name`, `parse.params`, `parse.yyclearin`, `parse.yyerrok`, `parse.error`
- New option `-S skeleton` to allow using an external skeleton file for the parser generator - Ayacc(https://github.com/Ada-France/ayacc) templates provide more control for tuning the code generation and
 they are embedded with [Advanced Resource Embedder](https://gitlab.com/stcarrez/resource-embedder)
- New option `-P` to generate a private Ada package for the tokens package - Improvement to allow passing parameters to `YYParse` for the grammar rules - New `%lex` directive to control the call of `YYLex` function - Fix #6: ayacc gets stuck creating an infinitely large file after encountering a comment in an action

The generator supports two code block injections, the first one `decl` is injected in the `YYParse` procedure declaration and the `init` is injected as first statements to be executed only once when the procedure is called. The syntax is borrowed from the Bison parser:

```Ada %code decl {

  -- Put Ada declarations

} %code init {

  -- Put Ada statements

} ```

Some other Bison like improvements have been introduced to control the generation of the parser code.

``` %define parse.error true %define parse.stacksize 256 %define parse.yyclearin false %define parse.yyerrok false %define parse.name MyParser ```

    1. How to use

The easiest way to use Ayacc(https://github.com/Ada-France/ayacc) and Aflex(https://github.com/Ada-France/aflex) is to use Alire(https://github.com/alire-project/alire), get the sources, build them and install them. You can do this as follows:

``` alr get aflex cd aflex_1.6.0_b3c21d99 alr build alr install alr get ayacc cd ayacc_1.4.0_c06f997f alr build alr install ```

  • UPDATE*: the `alr install` command is available only with Alire(https://github.com/alire-project/alire) 2.0.

Using these tools is done in two steps:

1. a first step to call `aflex` or `ayacc` command with the scanner file or grammar file, 2. a second step to call `gnatchop` to split the generated file in separate Ada files

For example, with a `calc_lex.l` scanner file, you would use:

``` aflex calc_lex.l gnatchop -w calc_lex.ada ```

And with a `calc.y` grammar file:

``` ayacc calc.y gnatchop -w calc.ada ```

To know more about how to write a scanner file or grammar file, have a look at Aflex 1.5 and Ayacc 1.3.0(https://blog.vacs.fr/vacs/blogs/post.html?post=2021/12/18/Aflex-1.5-and-Ayacc-1.3.0) which explains more into details some of these aspects.

    1. Highlight on reentrancy

By default Aflex(https://github.com/Ada-France/aflex) and Ayacc(https://github.com/Ada-France/ayacc) generate a scanner and a parser which use global variables declared in a generated Ada package. These global variables contain some state about the scanner such as the current file being scanned. The Ayacc(https://github.com/Ada-France/ayacc) parser generates on its side two global variables `YYLVal` and `YYVal`.

Using global variables creates some strong restrictions when using the generated scanner and parser: we can scan and parse only one file at a time. It cannot be used in a multi-thread environment unless the scan and parse is protected from concurrent access. We cannot use easily some grammars that need to recurse and parse another file such as an included file.

      1. Reentrant scanner

The reentrant scanner is created by using the `-R` option or the `%option reentrant` directive. The scanner will then need a specific declaration with a context parameter that will hold the scanner state and variables. The context parameter has its type generated in the `Lexer_IO` package. The `%yydecl` directive in the scanner file must be used to declare the `YYLex` function with its parameters. By default the name of the context variable is `Context` but you can decide to customize and change it to another name by using the `%yyvar` directive.

``` %option reentrant %yyvar Context %yydecl function YYLex (Context : in out Lexer_IO.Context_Type) return Token ```

When the `reentrant` option is activated, Aflex(https://github.com/Ada-France/aflex) will generate a first `Context_Type` limited type in the `Lexer_DFA` package and another one in the `Lexer_IO` package. The generator can probably be improved in the future to provide a single package with a single type declaration. The `Lexer_DFA` package contains the internal data structures for the scanner to maintain its state and the `Lexer_IO` package holds the input file as well as the `YYLVal` and `YYVal` values.

      1. Reentrant parser

On its side, Ayacc(https://github.com/Ada-France/ayacc) uses the `YYLVal` and `YYVal` variables. By default, it generates them in the `_tokens` package that contains the list of parser symbols. It must not generate them and it must now use the scanner `Context_Type` to hold them as well as the scanner internal state. The setup requires several steps:

1. The reentrant parser is activated by using the `%define api.pure`

  directive similar to the [bison %define](https://www.gnu.org/software/bison/manual/html_node/_0025define-Summary.html).

2. The `%lex` directive must be used to define how the `YYLex` function must be called since it now has some

  context parameter.

3. The scanner context variable must be declared somewhere, either as parameter to the `YYParse`

  procedure or as a local variable to `YYParse`.  This is done using the new `%code decl` directive
  and allows to customize the local declaration part of the `YYParse` generated procedure.

4. We must give visibility of the `YYLVal` and `YYVal` values defined in the scanner context variable.

  Again, we can do this within the `%code decl` directive.

A simple reentrant parser could be defined by using:

```Ada %define api.pure true %lex YYLex (Scanner) %code decl {

     Scanner : Lexer_IO.Context_Type;
     YYLVal  : YYSType renames Scanner.YYLVal;
     YYVal   : YYSType renames Scanner.YYVal;

} ```

However, this simple form is not really useful as you may need to open the file and setup the scanner to read from it. It is probably better to pass the scanner context as parameter to the `YYParse` procedure. For this, we can use the `%define parse.params` directive to control the procedure parameters. The reentrant parser is declared as follows:

```Ada %lex YYLex (Scanner) %define api.pure true %define parse.params "Scanner : in out Lexer_IO.Context_Type" %code decl {

     YYLVal : YYSType renames Scanner.YYLVal;
     YYVal  : YYSType renames Scanner.YYVal;

} ```

To use the reentrant parser and scanner, we only need to declare the scanner context, open the file by using the `Lexer_IO.Open_Input` procedure and call the `YYParse` procedure as follows:

```Ada

 Scanner : Lexer_IO.Context_Type;
 ...
   Lexer_IO.Open_Input (Scanner, "file-to-scan");
   YYParse (Scanner);

```

      1. Grammar examples:

To have a more complete example of a reentrant parser, you may have a look at the following files:

AWA 2.4.0

7 August 2022 at 21:12

The framework provides several ready to use and extendable modules that are common to many web applications. This includes the login, authentication, users, permissions, managing comments, tags, votes, documents, images. It provides a complete blog, question and answers and a wiki module.

AWA simplifies the Web Application development by taking care of user management authentication and by providing the foundations on top of which you can construct your own application. AWA provides a powerful permission management that gives flexibility to applications to grant access and protect your user's resources.

A typical architecture of an AWA application is represented by the picture below:

[Ada Web Application architecture](Ada/awa_architecture_overview.png)
    1. Using AWA with Alire

To use AWA with Alire, you should first register a new index to get access to the AWA crates and its dependencies.

``` alr index add git+https://github.com/stcarrez/awa-alire-index name awa ```

After this command, the `alr` command should give you access to the AWA crates and you can check that with the command:

``` alr search awa ```

Once you have setup the Alire index, you can import the `awa` framework and the `dynamo` associated tool by using the following commands:

``` alr init demo cd demo alr with awa alr with dynamo alr with servletada_aws alr build ```

Note, if you are using FreeBSD, you should probably use the following command to build (because the MariaDB shared libraries are installed in `/usr/local/lib/mariadb` directory):

``` alr build -- -largs -L/usr/local/lib/mariadb ```

Once the `dynamo` tool is built, you can use it to setup your project by using the `create-project` command. To run the `dynamo` tool, you must run it by using the `alr exec` command because Alire will setup some environment variables and `PATH` that gives access to Dynamo and AWA configuration files.

``` alr exec dynamo create-project -l apache web demo [email protected] ```

Change the `demo.gpr` GNAT project that was generated by `alr` and replace `demo.adb` by `demo-server.adb`. That later source has been generated by `dynamo` and it contains everything to setup, prepare and run the Web server. Once this is done, build the application:

``` alr build ```

The Atlas AWA Demonstrator(https://github.com/stcarrez/atlas) is also available as Alire crates. Two versions are available, the `atlas` crate is using the Ada Web Server and the `atlas_ews` is using the Embedded Web Server. To play to Atlas, you can try one of the following commands:

``` alr get atlas

  1. or

alr get altas_ews ```

    1. Debian packages

Debian packages are also available for Ubuntu 20, Ubuntu 22 and Debian 11. The repository also includes a Debian package for Alire 1.2. Choose **one** of the following configuration and add it to your `/etc/apt/sources.list` configuration.

``` deb https://apt.vacs.fr/ubuntu-focal focal main

  1. or

deb https://apt.vacs.fr/ubuntu-jammy jammy main

  1. or

deb https://apt.vacs.fr/debian-bullseye bullseye main ```

And you can run the following command to accept the signed packages:

``` wget -O - https://apt.vacs.fr/apt.vacs.fr.gpg.key | sudo apt-key add - ```

      1. AWA 2.4.0(https://github.com/stcarrez/ada-awa/releases/tag/2.4.0) Download: awa-2.4.0.tar.gz(http://download.vacs.fr/ada-awa/awa-all-2.4.0.tar.gz)
 - Add support for SQL queries embedded in applications with ARE
 - Fix #20: Do not use git:// protocol
 - New EasyMDE plugin to integrate the Easy Markdown Editor
 - Update AWA blog and AWA wiki to use the EasyMDE editor for Markdown
 - Use Dynamo 1.3.0, Ada Server Faces 1.5.0, Ada Servlet 1.6.0, OpenAPI Ada 0.6.0
 - Use Ada Wiki 1.4.0, Ada Database Objects 2.3.0
 - Use Ada Keystore 1.3.3, Ada EL 1.8.5, Ada Utility Library 2.5.0
      1. Dynamo 1.3.0(https://github.com/stcarrez/dynamo/releases/tag/1.3.0) Download: dynamo-1.3.0.tar.gz(http://download.vacs.fr/dynamo/dynamo-1.3.0.tar.gz)
 - Fix #5: Generated body does not compile when an enumeration of another UML package is used
 - Fix #7: No default type for SQL generation of a column that uses an enumeration
 - Fix #9: Option or configuration to disable some SQL generation
 - Fix #10: Definition of an UML datatype with a tagged value raises an exception
 - Fix #12: Avoid emitting a full qualified type name for types declared in the current package
 - Fix #16: Improvement in Markdown documentation generator
 - Fix #17: YAML parser: accessibility check failure
 - Fix #18: Generate database operation to reload an object
 - Fix #19: Add dynamo configuration through environment support
 - Fix #20: Give access to the man page from alire
 - Fix $21: Generated procedure Create is missing overriding keyword
      1. OpenAPI Ada 0.6.0(https://github.com/stcarrez/swagger-ada/releases/tag/0.6.0) Download: openapi-ada-0.6.0.tar.gz(http://download.vacs.fr/openapi-ada/openapi-ada-0.6.0.tar.gz)
 - Rename Swagger package into OpenAPI and provide a Swagger package for compatibility
 - Update the openapi generator to version 6.0.0
 - Add support for text/plain response
 - Add support to use external Ada types in OpenAPI definitions
 - Add support for multiple response types
 - Add support for binary responses
 - Add support for Ada enum generation for OpenAPI enums
 - Integrate Swagger UI v4.13.0
      1. Ada Server Faces 1.5.0(https://github.com/stcarrez/ada-asf/releases/tag/1.5.0) Download: ada-asf-1.5.0.tar.gz(http://download.vacs.fr/ada-asf/ada-asf-1.5.0.tar.gz)

- New widget <w:progress> to display horizontal/vertical progress bars

      1. Ada Servlet 1.6.0(https://github.com/stcarrez/ada-servlet/releases/tag/1.6.0) Download: ada-servlet-1.6.0.tar.gz(http://download.vacs.fr/ada-servlet/ada-servlet-1.6.0.tar.gz)
      2.  - Fix #4: Alire servletada_aws GNAT project fails due to missing Naming rule
         - Fix #5: The Input_Line_Size_Limit parameter is not taken into account
         - Fix #6: GNAT configuration project is not correct to build with debugging
         - Fix #7: Constraint error raised when matching empty path routes
         - Fix #11: Support for Embedded Web Server
         - Fix #12: Support for multiple response types in REST operations
        
      1. Ada Security 1.4.1(https://github.com/stcarrez/ada-security/releases/tag/1.4.1) Download: ada-security-1.4.1.tar.gz(http://download.vacs.fr/ada-security/ada-security-1.4.1.tar.gz)
 - Fix Alire GNAT project to build in debug mode
 - Fix Security.Random that generates shorter random string
      1. Ada Database Objects 2.3.0(https://github.com/stcarrez/ada-ado/releases/tag/2.3.0) Download: ada-ado-2.3.0.tar.gz(http://download.vacs.fr/ada-ado/ada-ado-2.3.0.tar.gz)
 - Fix #4: Is_Loaded predicate operation is false when an object is not yet inserted in the database
 - Fix #5: Exception raised when SQLite Query_Statement is finalized if the SQL query was invalid
 - Fix #7: Update SQLite support to 3.31.1
 - Fix #8: Add SQlite busy handler to handle the SQLITE_BUSY error
 - Fix #9: Better handling of SQLITE_BUSY error
 - Fix #10: Error 'XML query file does not exist' when the query is loaded from a static embedded loader
      1. Ada Wiki Engine 1.5.0(https://github.com/stcarrez/ada-wiki/releases/tag/1.5.0) Download: ada-wiki-1.5.0.tar.gz(http://download.vacs.fr/ada-wiki/ada-wiki-1.5.0.tar.gz)
      2.  - Add support for Textile markup language
         - Rewrote the Markdown parser to better follow the Common Mark Specification
        
      1. Ada Utility Library 2.5.0(https://github.com/stcarrez/ada-util/releases/tag/2.5.0) Download: ada-util-2.5.0.tar.gz(http://download.vacs.fr/ada-util/ada-util-2.5.0.tar.gz)
 - New examples to illustrate the IO stream composition
 - New examples for JSON parser and Util.Beans.Objects
 - Add support to set environment variables when launching a process (without changing the current process environment!)
 - Add support to indent XML output streams
 - New package Util.Files.Rolling to provide a rolling file manager
 - New package Util.Beans.Objects.Iterators to easily iterate over objects
 - Add a new log appender to support rolling log files with size and time based policies
 - New operation Util.Files.Delete_Tree to delete a directory tree and work arround
   for GNAT bug gcc/63222 and gcc/56055
 - New operation Util.Files.Realpath to find the canonicalized absolute path of a file
 - New package Util.Blobs to hold binary content with reference counting
 - New package Util.Http.Headers to provide some HTTP helper operations
 - Add support for Blob in bean objects
 - Fix compilation on NetBSD 9.2
 - Fix compilation with AWS >= 22.0

Advanced Resource Embedder for Ada, C and Go

11 June 2021 at 12:43

The tool is able to apply some transformations on the collected files:

  • it can run a Javascript minifier such as `closure`,
  • it can compress CSS files by running `yui-compressor`,
  • it can compress files by running `gzip` or another compression tool.

Once these transformations are executed, it invokes a target generator to produce a source file either in C, Ada or Go language. The generated source file can then be used in the final program and taken into account during the compilation process of that program. At the end, the binary will contain the embedded files with their optional transformations.

[Resource Embedder Overview](Ada/resource-embedder.png)

The process to use ARE is simple:

  • You describe the resources that you want to embed.
 The description is either made on command line arguments or by writing an XML file.
 The XML description gives more flexibility as it allows to define a transformation rule that
 must be executed on the original file before being embedded.  This allows to minify a Javascript
 or CSS file, compress some files and even encrypt a file before its integration.
  • You run the ARE command with your target language and rule description and you give the tool
  •  a list of directories that must be scanned to identify the files that must be collected.
     The ARE tool scan the directories according to the patterns that you have given either on
     the command line or in the XML rule description.  After identifying the files, the tool applies
     the rules and execute the transformations.
     The ARE tool then invokes the target language generator that writes one or several files depending
     on the list of resources.
    
  • Once the files are generated, you use them in your program and add them in your build process
  •  as they are now part of your sources.  After building your program, it now embeds the
     resource files that were collected and optionally transformed.
    
      1. Defining resources to embed

    The first step is to write some `package.xml` file which describes a list of resources with their content. The root XML element is *`package`* and each resource is described by a `resource` XML element. The resource is assigned a name that will be used by the code generator. The C generator will use the name as a prefix for the C function and C type declaration. The Ada and Go generator will use that name for the Ada or Go package.

    The following resource definition declares the `Help` resource. It contains an installation rule that will copy the files under the `help` directory in the resource set. Only files matching the `.txt` pattern will be taken into account.

    ```XML <package>

     <resource name='Help'>
       <install mode='copy'>
         <fileset dir="help">
           <include name="**/*.txt"/>
         </fileset>
       </install>
     </resource>
     ...
    

    </package> ```

    The next resource definition will run an external program to get the content that must be embedded. The `man` directory is scanned and it will execute the command `man #{name}` on each filename found. That directory contains the empty files `ls`, `pwd` and `sh` and this will run and embed the man page for these commands.

    ```XML <package>

      ...
     <resource name='Man'>
       <install mode='exec'>
         <command output='#{dst}'>man #{name}</command>
         <fileset dir="man">
           <include name="*"/>
         </fileset>
       </install>
     </resource>
    

    </package> ```

    You may run other commands such as:

    ``` <command>closure charset UTF-8 #{src} js_output_file #{dst}</command> <command>yui-compressor type css charset UTF-8 -o #{dst} #{src}</command> <command output="#{dst}">gzip --no-name -c #{src}</command> ```

      1. Running the Advanced Resource Embedder

    The tool has several options that allows you to control the target code generator and tune the generation according to your needs. Assuming that the files to embed are located in the current directory, you would use the following command to generate C source files in the `src` directory:

    ``` are lang=c -o src rule=package.xml list-access name-access . ```

    For C, this would generate a `src/man.h`, `src/man.c`, `src/help.h` and `src/help.c`. You now have to include these files in the compilation of your program.

    You would use the following command for Go:

    ``` are lang=go rule=package.xml list-access name-access . ```

    and it would generate in `man/man.go` and `help/help.go` the Go source files.

    You would use the following command for Ada:

    ``` are lang=Ada -o src rule=package.xml list-access name-access . ```

    and it would generate `src/man.ads`, `src/man.adb`, `src/help.ads` and `src/help.adb`.

      1. Using the resource

    The code generator emits by default a set of type and function declaration that give access to the resource. For C, the following structure and declaration are generated in the header for the `man` resource:

    ```C struct man_content {

     const unsigned char *content;
     size_t size;
     time_t modtime;
     int format;
    

    };

    extern const struct man_content* man_get_content(const char* name); ```

    But for the Go language, the `man` generated package declares:

    ```Go type Content struct {

       Content  []byte
       Size  int64
       Modtime  int64
       Format   int
    

    } ... func Get_content(name string) (*Content) {...} ```

    And for Ada, it will generate:

    ```Ada package Resources.Man is

      type Content_Access is access constant Ada.Streams.Stream_Element_Array;
      type Name_Access is access constant String;
      type Format_Type is (FILE_RAW, FILE_GZIP);
      type Content_Type is record
         Name    : Name_Access;
         Content : Content_Access;
         Modtime : Interfaces.C.long := 0;
         Format  : Format_Type := FILE_RAW;
      end record;
      Null_Content : constant Content_Type;
    
      type Name_Array is array (Natural range <>) of Name_Access;
      Names : constant Name_Array;
    
      function Get_Content (Name : String) return Content_Type;
    

    private

      ...
    

    end Resources.Man; ```

    You can avoid the type declaration by using the `--no-type-declaration` option and in that case you have to make available these types somehow. In Ada, this can easily be made by providing these types in a parent Ada package (see the Embedding help and documentation in Ada(https://gitlab.com/stcarrez/resource-embedder/tree/master/examples/ada-help) for the example).

    Now with the generated code, you only need to call the generated get content method to obtain the embedded content. Simple!

      1. Examples

    The project proposes several detailed examples to illustrate various integrations.

    This first set of example shows how to you can embed configuration files in a C, Ada or Go program. The Advance Resource Embedder simply puts the configuration files in an array of bytes that can easily be retrieved by a generated function.

    A second set of example is more advanced by the use of an XML file that describes what must be embedded with the transformations that must be made. It creates two distinct resource sets `help` and `man`. The `help` resource set is composed of a set of fixed documentation files provided in the example. The `man` resource set is created by running the `man` Unix command on various names to embed the man page of `ls`, `pwd` and `sh`.

    More specific examples show how to make specific transformations on the files before integrating them:

      1. License considerations

    The generated code produced by Advance Resource Embedder(https://gitlab.com/stcarrez/resource-embedder) is not covered by any license. However, when you integrate some resource files with the tool, the generated code will contain the original file in some binary form and therefore it may be covered by the license associated with these resource files.

    For example, if you include a file covered by the GNU General Public license, then the generated file is covered by the GPL.

      1. How can you get Advanced Resource Embedder?
        1. Use the source Luke!

    Quick instructions to build the tool:

    ``` git clone --recursive https://gitlab.com/stcarrez/resource-embedder.git cd resource-embedder make build test install ```

        1. Debian Packages for x86_64

    You can install ARE by using the Debian 10 and Ubuntu 20.04 or 18.04 packages. First, setup to accept the signed packages:

    ``` wget -O - https://apt.vacs.fr/apt.vacs.fr.gpg.key | sudo apt-key add - ```

    and choose one of the `echo` command according to your Linux distribution:

    Ubuntu 20.04 ``` echo "deb https://apt.vacs.fr/ubuntu-focal focal main" | sudo tee -a /etc/apt/sources.list.d/vacs.list ```

    Ubuntu 18.04 ``` echo "deb https://apt.vacs.fr/ubuntu-bionic bionic main" | sudo tee -a /etc/apt/sources.list.d/vacs.list ```

    Debian 10 ``` echo "deb https://apt.vacs.fr/debian-buster buster main" | sudo tee -a /etc/apt/sources.list.d/vacs.list ```

    Then, launch the apt update command:

    ``` sudo apt-get update ```

    and install the tool using:

    ``` sudo apt-get install -y are ```

      1. Conclusion

    Embedding files, scripts, documentation and other contents in C and Ada is easily done by using the Advance Resource Embedder(https://gitlab.com/stcarrez/resource-embedder). Go developers are already familiar with these mechanisms thanks to the `go:embed` directive. But the tool provides custom transformations that gives more flexibility for the integration.

    Some benefits in embedding contents in final binaries:

    • the content is embedded within the readonly `.rodata` section,
    • you avoid at least 3 system calls to access the content: an `open`, a `read` and a `close`,
    • you reduce the size of your application on the disk: contents are contiguous within the `.rodata` section
     whereas written on a file system they each require full dedicated data blocks (4K in most cases),
    
  • you get a portable `mmap` of your files for free and without pain.
  • If you want to know more about the tool, have a look at its documentation:

    • Resource Embedder Guide(https://resource-embedder.readthedocs.io/en/latest/) PDF(https://gitlab.com/stcarrez/resource-embedder/blob/master/docs/are-book.pdf)
    • Man page: are (1)(https://gitlab.com/stcarrez/resource-embedder/blob/master/docs/are.md)

    and if you have ideas for improvements, fork the project and submit a pull request!

    New release of AKT with Fuse support

    7 March 2021 at 19:08

    Data are stored in secure wallets protected and signed by their own AES-256 key.

    Wallets are protected by a master key using AES-256-CBC and the wallet master key is protected by a user password. The wallet defines up to 7 slots that identify a password key that is able to unlock the master key. To open a wallet, it is necessary to unlock one of these 7 slots by providing the correct password. Wallet key slots are protected by the user's password and the PBKDF2-HMAC-256 algorithm, a random salt, a random counter and they are encrypted using AES-256-CBC. A wallet key slot can also be protected by a GPG private key.

    Data stored in the wallet are protected by their own encryption keys using AES-256-CBC. Big contents are stored in several data blocks and each data block is encrypted by using its own AES-256-CBC encryption key. Encryption keys are protected by the master key using AES-256-CBC. Data blocks are then signed using HMAC-256.

    A wallet can contain another wallet which is then protected by its own encryption keys and passwords (with 7 independent slots). Because the child wallet has its own master key, it is necessary to known the primary password and the child password to unlock the parent wallet first and then the child wallet.

    Installation

    You can install AKT by using the Debian 10 and Ubuntu 20.04 or 18.04 packages. First, setup to accept the signed packages:

    wget -O - https://apt.vacs.fr/apt.vacs.fr.gpg.key | sudo apt-key add -
    

    and choose one of the echo command according to your Linux distribution:

    # Ubuntu 20.04
    # echo "deb https://apt.vacs.fr/ubuntu-focal focal main" | sudo tee -a /etc/apt/sources.list.d/vacs.list
    # Ubuntu 18.04
    # echo "deb https://apt.vacs.fr/ubuntu-bionic bionic main" | sudo tee -a /etc/apt/sources.list.d/vacs.list
    # Debian 10
    # echo "deb https://apt.vacs.fr/debian-buster buster main" | sudo tee -a /etc/apt/sources.list.d/vacs.list
    

    Then, launch the apt update command:

    sudo apt-get update
    

    and install the tool using:

    sudo apt-get install -y akt
    

    You can also build from the source by using the following commands:

    sudo apt-get install -y make gnat-7 gprbuild git gnupg2 libfuse-dev
    git clone --recursive https://github.com/stcarrez/ada-keystore.git
    cd ada-keystore
    ./configure --enable-fuse
    

    Using AKT

    To setup a keystore file and protect it with your GPG key, use the following command:

    akt create secure.akt --gpg your-gpg-key-id
    

    The keystore content can be mounted as a filesystem so that the encrypted content are directly available to existing applications as regular files. The mount command starts the fuse daemon and puts the command as a background fuse daemon to serve as a fuse filesystem and make the content visible through the mount point.

    akt mount secure.akt /mnt
    

    After the mount is successful, you can copy or access any file stored in the secure keystore. By default the Fuse filesystem is configured to ask the kernel to avoid caching the decrypted content. This has an impact on the performance since every access requires to decrypt the content.

    To unmount the filesystem, you must use the umount command.

    umount /mnt
    

    Sometimes, you may have to force the umount by running:

    sudo umount /mnt
    

    Ada Stemmer Library

    16 May 2020 at 07:55

    Stemming is not new as it was first introduced in 1968 by Julie Beth Lovis who was a computational linguist that created the first algorithm known today as the Lovins Stemming algorithm. Her algorithm has significantly influenced other algorithms such as the Porter Stemmer algorithm which is now a common stemming algorithm for English words. These algorithms are specific to the English language and will not work for French, Greek or Russian.

    To support several natural languages, it is necessary to have several algorithms. The Snowball stemming algorithms project provides such support through a specific string processing language, a compiler and a set of algorithms for various natural languages. The Snowball compiler has been adapted to generate Ada code (See Snowball Ada on GitHub).

    The Ada Stemmer Library integrates stemming algorithms for: English, Danish, Dutch, French, German, Greek, Italian, Serbian, Spanish, Swedish, Russian. The Snowball compiler provides several other algorithms but they are not integrated yet: their integration is left as an exercise to the reader.

    Stemmer Overview

    Snowball is a small string processing language designed for creating stemming algorithms for use in Information Retrieval. A Snowball script describes a set of rules which are applied and checked on an input word or some portion of it in order to eliminate or replace some terms. The stemmer will usually transform a plural into a singular form, it will reduce the multiple forms of a verb, find the noun from an adverb and so on. Romance languages, Germanic languages, Scandinavian languages share some common rules but each language will need its own snowball algorithm. The Snowball compiler provides a detailed list of several stemming algorithms for various natural languages. This list is available on: https://snowballstem.org/algorithms/

    C

    The Snowball compiler reads the Snowball script and generates the stemmer implementation for a given target programming language such as Ada, C, C#, Java, JavaScript, Go, Python, Rust. The Ada Stemmer Library contains the generated algorithms for several natural languages. The generated stemmers are not able to recognize the natural language and it is necessary to tell the stemmer library which natural language you wish to use.

    The Ada Stemmer Library supports only UTF-8 strings which simplifies both the implementation and the API. The library only uses the Ada String type to handle strings.

    Setup

    To use the library, you should run the following commands:

      git clone https://github.com/stcarrez/ada-stemmer.git
      cd ada-stemmer
      make build install
    

    This will fetch, compile and install the library. You can then add the following line in your GNAT project file:

      with "stemmer";
    

    Stemming examples

    Each stemmer algorithm works on a single word at a time. The Ada Stemmer Library does not split words. You have to give it one word at a time to stem and it returns either the word itself or its stem. The Stemmer.Factory is the multi-language entry point. The stemmer algorithm is created for each call. The following simple code:

      with Stemmer.Factory; use Stemmer.Factory;
      with Ada.Text_IO; use Ada.Text_IO;
        ...
        Put_Line (Stem (L_FRENCH, "chienne"));
    

    will print the string:

     chien
    

    When multiple words must be stemmed, it may be better to declare the instance of the stemmer and use the same instance to stem several words. The Stem_Word procedure can be called with each word and it returns a boolean that indicates whether the word was stemmed or not. The result is obtained by calling the Get_Result function. For exemple,

      with Stemmer.English;
      with Ada.Text_IO; use Ada.Text_IO;
      ..
        Ctx : Stemmer.English.Context_Type;
        Stemmed : Boolean;
        ..
        Ctx.Stem_Word ("zealously", Stemmed);
        if Stemmed then
           Put_Line (Ctx.Get_Result);
        end if;
    

    Integrating a new Stemming algorithm

    Integration of a new stemming algorithm is quite easy but requires to install the Snowball Ada compiler.

      git clone --branch ada-support https://github.com/stcarrez/snowball
      cd snowball
      make
    

    The Snowball compiler needs the path of the stemming algorithm, the target programming language, the name of the Ada child package that will contain the generated algorithm and the target path. For example, to generate the Lithuanian stemmer, the following command can be used:

      ./snowball algorithms/lithuanian.sbl -ada -P Lithuanian -o stemmer-lithuanian
    

    You will then get two files: stemmer-lithuanian.ads and stemmer-lithuanian.adb. After integration of the generated files in your project, you can access the generated stemmer with:

      with Stemmer.Lithuanian;
      ..
        Ctx : Stemmer.Lithuanian.Context_Type;
    

    Conclusion

    Thanks to the Snowball compiler and its algorithms, it is possible to do some natural language analysis. Version 1.0 of the Ada Stemmer Library being available on GitHub, it is now possible to start doing some natural language analysis in Ada!

    Writing an Ada programmer's guide with Dynamo, Pandoc and Read the Docs

    18 February 2018 at 09:17

    user-guide-generation.png

    Writing user's guide in Ada specification

    Since I often forget to update some external documentation, I've found convenient to have it close to the implementation within the code. I'm not speaking about a reference documentation that explains every type, function or procedure provided by an Ada package. I'm talking about a programmer's guide.

    The solution I've used is to write a small programmer's guide within some Ada package specification. The programmer's guide is written within comments before the package declaration. It is then extracted and merged with other package documentation to create the final programmer's guide. One benefit in having such small programmer's guide in the package specification is that it also brings some piece of documentation to developers: the user's guide is close to the specification.

    The documentation is written using Markdown syntax and put before the package declaration. The extraction tool recognizes a number of formatting patterns and commands that help in merging the different pieces in one or several files.

    Section headers

    First the small programmer's guide must start with a section header introduced by at least one = (equal) sign. The programmer's guide documentation ends with the start of the Ada package declaration. Unlike AdaBrowse and AdaDoc, the package specification is not parsed and not used.

    --  = Streams =
    --  The `Util.Streams` package provides several types and operations to allow the
    --  composition of input and output streams.  Input streams can be chained together so that
    --  ...
    ...
    package Util.Streams is ...
    

    When an Ada package specification includes such comment, a documentation file is created. The generated file name is derived from the package name found after the package keyword. The . (dot) are replaced by _ (underscore) and the .md extension is added. In this example, the generated file is Util_Streams.md.

    Merging with @include <file>

    The @include command indicates that the small programmer's guide from the given file must be included. For example, the Streams support of Ada Utility Library is provided by several packages, each being a child of the Util.Streams package. The different pieces of the programmer's guide are merged together by using the following comments:

    --  @include util-streams-buffered.ads
    --  @include util-streams-texts.ads
    --  @include util-streams-files.ads
    --  @include util-streams-pipes.ads
    --  @include util-streams-sockets.ads
    --  @include util-streams-raw.ads
    --  @include util-streams-buffered-encoders.ads
    

    Autolink

    To avoid having links in the Ada comments, an auto-link feature is used so that some words or short sentences can be turned into links automatically. The auto-link feature works by using a simple text file that indicates words or sequence or words that should be changed to a link. The text file contains one link definition per line, composed of a set of words that must match and the associated link.

    Java Bean                  https://en.wikipedia.org/wiki/JavaBean
    Java Log4j                 https://logging.apache.org/log4j/2.x/
    Log4cxx                    https://logging.apache.org/log4cxx/latest_stable/index.html
    RFC7231                    https://tools.ietf.org/html/rfc7231
    

    The auto-link feature is very basic. To match a link, a sequence of several words must be present on the same comment line. For example, the following documentation extract:

    --  = Logging =
    --  The `Util.Log` package and children provide a simple logging framework inspired
    --  from the Java Log4j library.  It is intended to provide a subset of logging features
    

    will generate the following Markdown extract with a link for the "Java Log4j" word sequence:

    # Logging
    The `Util.Log` package and children provide a simple logging framework inspired
    from the [Java Log4j](https://logging.apache.org/log4j/2.x/) library...
    

    Code extract

    Having code examples is important for a programmer's guide and I've made the choice to have them as part of the comment. The extraction tool recognizes them by assuming that they are introduced by an empty line and indented by at least 4 spaces. The code extractor will use the Markdown fenced code block (```) to enclose them.

    --  is free but using the full package name is helpful to control precisely the logs.
    --
    --    with Util.Log.Loggers;
    --    package body X.Y is
    --      Log : constant Util.Log.Loggers := Util.Log.Loggers.Create ("X.Y");
    --    end X.Y;
    --
    --  == Logger Messages ==
    

    Extracting documentation from Ada specification

    Once the documentation is written, the Dynamo command is used to extract, merge and generate the documentation. The build-doc sub-command scans the project files, reading the Ada specification, some project XML files and generates the documentation in Markdown format. The -pandoc option tells the documentation generator to write the documentation for a book oriented organization formatted with Pandoc. It will generate them in the docs directory.

    dynamo build-doc -pandoc docs
    

    Putting it all together

    Pandoc being a versatile document converter, it allows to format all the generated documentation with some other files and produce a final PDF document. Several files are not generated by Dynamo and they are written either as LaTeX (pagebreak.tex) or Markdown, for example the cover page, the introduction and installation chapters.

    By using a custom LaTeX template (eisvogel.tex) and using several configuration option some nice PDF is generated.

    pandoc -f markdown -o util-book.pdf --listings --number-sections \
      --toc --template=./docs/eisvogel.tex docs/title.md docs/pagebreak.tex \
      docs/intro.md docs/pagebreak.tex docs/Installation.md docs/pagebreak.tex \
      docs/Util_Log.md docs/pagebreak.tex docs/Util_Properties.md docs/pagebreak.tex \
      docs/Util_Dates.md doc/pagebreak.tex docs/Util_Beans.md docs/pagebreak.tex \
      docs/Util_Http.md docs/pagebreak.tex docs/Util_Streams.md docs/pagebreak.tex \
      docs/Util_Encoders.md docs/pagebreak.tex docs/Util_Events_Timers.md \
      docs/pagebreak.tex docs/Util_Measures.md
    

    Here is the final PDF file: Ada Utility Library Programmer's Guide

    Publishing the programmer's guide

    Read the Docs offers a free documentation hosting with a complete build and publication environment. They are able to connect to a GitHub repository and be notified each time some changes are pushed to build automatically the documentation.

    The documentation is produced by MkDocs and the mkdocs.yml configuration file allows to configure how the documentation is built, organized and presented:

    site_name: Ada Utility Library
    docs_dir: doc
    pages:
      - Introduction: index.md
      - Installation: Installation.md
      - Log: Util_Log.md
      - Properties: Util_Properties.md
      - Dates: Util_Dates.md
      - Ada Beans: Util_Beans.md
      - HTTP: Util_Http.md
      - Streams: Util_Streams.md
      - Encoders: Util_Encoders.md
      - Measures: Util_Measures.md
      - Timers: Util_Events_Timers.md
    theme: readthedocs
    

    Here is the final programmer's guide: Ada Utility Library Users' Guide.

    Conclusion

    I've found quite useful to write the programmer's guide within the Ada specification. Doing so also helps during the design of a new package because it forces to think a little bit on how the package is going to be used. There are some drawbacks in using this model:

    • Each time the documentation must be fixed, the Ada specification file is modified,
    • The layout of a programmer's guide does not always follow a package organization,
    • Merging the documentation from different parts could bring some complexity when you have to find out where some documentation actually comes from.

    Generating a REST Ada client with OpenAPI and Swagger Codegen

    8 October 2017 at 18:32

    swagger-ada-generator.png

    Writing an OpenAPI document

    The OpenAPI document is either a JSON or a YAML file that describes the REST API operations. The document can be used both for the documentation of the API and for the code generation in several programming language. We will see briefly through the Petstore example how the OpenAPI document is organized. The full OpenAPI document is available in petstore.yaml.

    General description

    A first part of the OpenAPI document provides a general description of the API. This includes the general description, the terms of service, the license and some contact information.

    swagger: '2.0'
    info:
      description: 'This is a sample server Petstore server.  You can find out more about Swagger at [http://swagger.io](http://s
    wagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).  For this sample, you can use the api key `special-key
    ` to test the authorization filters.'
      version: 1.0.0
      title: Swagger Petstore
      termsOfService: 'http://swagger.io/terms/'
      contact:
        email: [email protected]
      license:
        name: Apache 2.0
        url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
    host: petstore.swagger.io
    basePath: /v2
    

    Type description

    The OpenAPI document can also describe types which are used by the REST operations. These types provide a description of how the data is organized and passed through the API operations.

    It is possible to describe almost all possible types from simple properties, group of properties up to complex types including arrays. For example a Pet type is made of several properties each of them having a name, a type and other information to describe how the type is serialized.

    definitions:
      Pet:
        title: a Pet
        description: A pet for sale in the pet store
        type: object
        required:
          - name
          - photoUrls
        properties:
          id:
            type: integer
            format: int64
          category:
            $ref: '#/definitions/Category'
          name:
            type: string
            example: doggie
          photoUrls:
            type: array
            xml:
              name: photoUrl
              wrapped: true
            items:
              type: string
          tags:
            type: array
            xml:
              name: tag
              wrapped: true
            items:
              $ref: '#/definitions/Tag'
          status:
            type: string
            description: pet status in the store
            enum:
              - available
              - pending
              - sold
        xml:
          name: Pet
    

    In this example, the Pet type contains 6 properties (id, category, name, photoUrls, tags, status) and refers to two other types Category and Tag.

    Operation description

    Operations are introduced by the paths object in the OpenAPI document. This section describes the possible paths that can be used by URL and the associated operation. Some operations receive their parameter within the path and this is represented by the {name} notation.

    The operation description indicates the HTTP method that is used get, post, put or delete.

    The following definition describes the getPetById operation.

    paths:
      '/pet/{petId}':
        get:
          tags:
            - pet
          summary: Find pet by ID
          description: Returns a single pet
          operationId: getPetById
          produces:
            - application/xml
            - application/json
          parameters:
            - name: petId
              in: path
              description: ID of pet to return
              required: true
              type: integer
              format: int64
          responses:
            '200':
              description: successful operation
              schema:
                $ref: '#/definitions/Pet'
            '400':
              description: Invalid ID supplied
            '404':
              description: Pet not found
          security:
            - api_key: []
    

    The summary and description are used for the documentation purposes. The operationId is used by code generators to provide an operation name that a target programming language can use. The produces section indicates the media types that are supported by the operation and which are generated for the response. The parameters section represents all the operation parameters. Some parameters can be extracted from the path (which is the case for the petId parameter) and some others can be passed as query parameter.

    The responses section describes the possible responses for the operation as well as the format used by the response. In this example, the operation returns an object described by the Pet type.

    Using Swagger Codegen

    The documentation and the Ada client are generated from the OpenAPI document by using the Swagger Codegen generator. The generator is a Java program that is packaged within a jar file. It must be launched by the Java 7 or Java 8 runtime.

    Generating the documentation

    The HTML documentation is generated from the OpenAPI document by using the following command:

     java -jar swagger-codegen-cli.jar generate -l html -i petstore.yaml -o doc
    

    Generating the Ada client

    To generate the Ada client, you will use the -l ada option to use the Ada code generator. The OpenAPI document is passed with the -i option.

     java -jar swagger-codegen-cli.jar generate -l ada -i petstore.yaml -o client \
           -DprojectName=Petstore --model-package Samples.Petstore
    

    The Ada generator uses two options to control the generation. The -DprojectName=Petstore option allows to control the name of the generated GNAT project and the --model-package option controls the name of the Ada package for the generated code.

    The Ada generator will create the following Ada packages:

    • Samples.Petstore.Models is the package that contains all the types described in the OpenAPI document. Each OpenAPI type is represented by an Ada record and it

    is also completed by an instantiation of the Ada.Containers.Vectors package for the representation of arrays of the given type. The Models package also provides Serialize and Deserialize procedures for the serialization and deserialization of the data over JSON or XML streams.

    • Samples.Petstore.Clients is the package that declares the Client_Type tagged record which provides all the operations for the OpenAPI document.

    For the Pet type describe previously, the Ada generator produces the following code extract:

    package Samples.Petstore.Models is
       ...
       type Pet_Type is
         record
           Id : Swagger.Long;
           Category : Samples.Petstore.Models.Category_Type;
           Name : Swagger.UString;
           Photo_Urls : Swagger.UString_Vectors.Vector;
           Tags : Samples.Petstore.Models.Tag_Type_Vectors.Vector;
           Status : Swagger.UString;
         end record;
         ...
    end Samples.Petstore.Models;
    

    and for the operation it generates the following code:

    package Samples.Petstore.Clients is
       ...
       type Client_Type is new Swagger.Clients.Client_Type with null record;
       procedure Get_Pet_By_Id
          (Client : in out Client_Type;
           Pet_Id : in Swagger.Long;
           Result : out Samples.Petstore.Models.Pet_Type);
       ...
    end Samples.Petstore.Clients;
    

    Using the REST Ada client

    Initialization

    The HTTP/REST support is provided by Ada Util and encapsulated by Swagger Ada. The Ada Util library also takes care of the JSON and XML serialization and deserialization. If you want to use Curl, you should initialize with the following:

    with Util.Http.Clients.Curl;
    ...
       Util.Http.Clients.Curl.Register;
    

    But if you want to use AWS, you will initialize with:

    with Util.Http.Clients.Web;
    ...
       Util.Http.Clients.Web.Register;
    

    After the initialization is done, you will declare a client instance to access the API operations:

    with Samples.Petstore.Clients;
    ...
       C : Samples.Petstore.Clients.Client_Type;
    

    And you should initialize the server base URL you want to connect to. To use the live Swagger Petstore service you can set the server base URL as follows:

      C.Set_Server ("http://petstore.swagger.io/v2");
    

    At this stage, you can use the generated operation by calling operations on the client.

    Calling a REST operation

    Let's retrieve some pet information by calling the Get_Pet_By_Id operation described previously. This operation needs an integer as input parameter and returns a Pet_Type object that contains all the pet information. You will first declare the pet instance as follows:

    with Samples.Petstore.Models;
    ...
      Pet  : Samples.Petstore.Models.Pet_Type;
    

    And then call the Get_Pet_By_Id operation:

      C.Get_Pet_By_Id (768, Pet);
    

    At this stage, you can access information from the Pet instance:

    with Ada.Text_IO;
    ...
      Ada.Text_IO.Put_Line ("Id      : " & Swagger.Long'Image (Pet.Id));
      Ada.Text_IO.Put_Line ("Name    : " & Swagger.To_String (Pet.Name));
      Ada.Text_IO.Put_Line ("Status  : " & Swagger.To_String (Pet.Status));
    

    The Swagger Ada Petstore illustrates other uses of the generated operations. It allows to list the inventory, list the pets with a given status, add a pet and so on...

    Conclusion and references

    The OpenAPI Specification provides a standard way to describe REST operations. The Swagger Codegen is the generator to be used to simplify the implementation of REST clients in many programming languages and to generate the documentation of the API. The Ada code generator only supports the client side but the server code generation is under work.

    The sources of the petstore samples are available:

    The APIs.guru lists more than 550 API descriptions from various providers such as Amazon, Google, Microsoft and many other online services. They are now available to the Ada community!

    Simple UDP Echo Server on STM32F746

    4 December 2016 at 23:01

    Overview

    The Echo server listens to the UDP port 7 on the Ethernet network and it sends back the received packet to the sender: this is the RFC 862 Echo protocol. Our application follows that RFC but it also maintains a list of the last 10 messages that have been received. The list is then displayed on the STM32 display so that we get a visual feedback of the received messages.

    The Echo server uses the DHCP client to get and IPv4 address and the default gateway. We will see how that DHCP client is integrated in the application.

    The application has two tasks. The main task loops to manage the refresh of the STM32 display and also to perform some network housekeeping such as the DHCP client management and ARP table management. The second task is responsible for waiting Ethernet packets, analyzing them to handle ARP, ICMP and UDP packets.

    Through this article, you will see:

    1. How the STM32 board and network stack are initialized,
    2. How the board gets an IPv4 address using DHCP,
    3. How to implement the UDP echo server,
    4. How to build and test the echo server.

    Initialization

    STM32 Board Initialization

    First of all, the STM32 board must be initialized. There is no random generator available in the Ada Ravenscar profile and we need one for the DHCP protocol for the XID generation. The STM32 provides a hardware random generator that we are going to use. The Initialize_RNG must be called once during the startup and before any network operation is called.

    We will use the display to list the messages that we have received. The Display instance must be initialized and the layer configured.

    
    with HAL.Bitmap;
    with STM32.RNG.Interrupts;
    with STM32.Board;
    ...
       STM32.RNG.Interrupts.Initialize_RNG;
       STM32.Board.Display.Initialize;
       STM32.Board.Display.Initialize_Layer (1, HAL.Bitmap.ARGB_1555);
    
    Network stack initialization

    The network stack will need some memory to receive and send network packets. As described in Using the Ada Embedded Network STM32 Ethernet Driver, we allocate the memory by using the SDRAM.Reserve function and the Add_Region procedure to configure the network buffers that will be available.

    An instance of the STM32 Ethernet driver must be declared in a package. The instance must be aliased because the network stack will need to get an access to it.

    
    with Interfaces;
    with Net.Buffers;
    with Net.Interfaces.STM32;
    with STM32.SDRAM;
    ...
       NET_BUFFER_SIZE : constant Interfaces.Unsigned_32 := Net.Buffers.NET_ALLOC_SIZE * 256;
       Ifnet : aliased Net.Interfaces.STM32.STM32_Ifnet;
    

    The Ethernet driver is initialized by calling the Initialize procedure. By doing so, the Ethernet receive and transmit rings are configured and we are ready to receive and transmit packets. On its side the Ethernet driver will also reserve some memory by using the Reserve and Add_Region operations. The buffers allocated will be used for the Ethernet receive ring.

    
       Net.Buffers.Add_Region (STM32.SDRAM.Reserve (Amount => NET_BUFFER_SIZE), NET_BUFFER_SIZE);
       Ifnet.Initialize;
    

    The Ethernet driver configures the MII transceiver and enables interrupts for the receive and transmit rings.

    Getting the IPv4 address with DHCP

    At this stage, the network stack is almost ready but it does not have any IPv4 address. We are going to use the DHCP protocol to automatically get an IPv4 address, get the default gateway and other network configuration such as the DNS server. The DHCP client uses a UDP socket on port 68 to send and receive DHCP messages. Such DHCP client is provided by the Net.DHCP package and we need to declare an instance of it. The DHCP client is based on the UDP socket support that we are going to use for the echo server. The DHCP client instance must be declared aliased because the UDP socket layer need to get an access to it to propagate the DHCP packets that are received.

    
    with Net.DHCP;
    ...
       Dhcp : aliased Net.DHCP.Client;
    

    The DHCP client instance must be initialized and the Ethernet driver interface must be passed as parameter to correctly configure and bind the UDP socket. After the Initialize procedure is called, the DHCP state machine is ready to enter into action. We don't have an IPv4 address after the procedure returns.

    
       Dhcp.Initialize (Ifnet'Access);
    

    The DHCP client is using an asynchronous implementation to maintain the client state according to RFC 2131. For this it has two important operations that are called by tasks in different contexts. First the Process procedure is responsible for sending requests to the DHCP server and to manage the timeouts used for the retransmissions, renewal and lease expiration. The Process procedure sends the DHCPDISCOVER and DHCPREQUEST messages. On the other hand, the Receive procedure is called by the network stack to handle the DHCP packets sent by the DHCP server. The Receive procedure gets the DHCPOFFER and DHCPACK messages.

    Getting an IPv4 address with the DHCP protocol can take some time and must be repeated continuously due to the DHCP lease expiration. This is why the DHCP client must not be stopped and should continue forever.

    Refer to the DHCP documentation to learn more about this process.

    UDP Echo Server

    Logger protected type

    The echo server will record the message that are received. The message is inserted in the list by the receive task and it is read by the main task. We use the an Ada protected type to protect the list from concurrent accesses.

    Each message is represented by the Message record which has an identifier that is unique and incremented each time a message is received. To avoid dynamic memory allocation the list of message is fixed and is represented by the Message_List array. The list itself is managed by the Logger protected type.

    
    type Message is record
       Id      : Natural := 0;
       Content : String (1 .. 80) := (others => ' ');
    end record;
    type Message_List is array (1 .. 10) of Message;
    
    protected type Logger is
       procedure Echo (Content : in Message);
       function Get return Message_List;
    private
       Id   : Natural := 0;
       List : Message_List;
    end Logger;
    

    The Logger protected type provides the Echo procedure to insert a message to the list and the Get function to retrieve the list of messages.

    Server Declaration

    The UDP Echo Server uses the UDP socket support provided by the Net.Sockets.UDP package. The UDP package defines the Socket abstract type which represents the UDP endpoint. The Socket type is abstract because it defines the Receive procedure that must be implemented. The Receive procedure will be called by the network stack when a UDP packet for the socket is received.

    The declaration of our echo server is the following:

    
    with Net.Buffers;
    with Net.Sockets;
    ...
       type Echo_Server is new Net.Sockets.UDP.Socket with record
          Count    : Natural := 0;
          Messages : Logger;
       end record;
    

    It holds a counter of message as well as the messages in the Logger protected type.

    The echo server must implement the Receive procedure:

    
    overriding
    procedure Receive (Endpoint : in out Echo_Server;
                       From     : in Net.Sockets.Sockaddr_In;
                       Packet   : in out Net.Buffers.Buffer_Type);
    

    The network stack will call the Receive procedure each time a UDP packet for the socket is received. The From parameter will contain the IPv4 address and UDP port of the client that sent the UDP packet. The Packet parameter contains the received UDP packet.

    Server Implementation

    Implementing the server is very easy because we only have to implement the Receive procedure (we will leave the Logger protected type implementation as an exercise to the reader).

    First we use the Get_Data_Size function to get the size of our packet. The function is able to return different sizes to take into account one or several protocol headers. We want to know the size of our UDP packet, excluding the UDP header. We tell Get_Data_Size we want to get the UDP_PACKET size. This size represents the size of the echo message sent by the client.

    
       Msg    : Message;
       Size   : constant Net.Uint16 := Packet.Get_Data_Size (Net.Buffers.UDP_PACKET);
       Len    : constant Natural
            := (if Size > Msg.Content'Length then Msg.Content'Length else Natural (Size));
    

    Having the size we truncate it so that we get a string that fits in our message. We then use the Get_String procedure to retrieve the echo message in a string. This procedure gets from the packet a number of characters that corresponds to the string length passed as parameter.

    
       Packet.Get_String (Msg.Content (1 .. Len));
    

    The Buffer_Type provides other Get operations to extract data from the packet. It maintains a position in the buffer that tells the Get operation the location to read in the packet and each Get updates the position according to what was actually read. There are also several Put operations intended to be used to write and build the packet before sending it. We are not going to use them because the echo server has to return the original packet as is. Instead, we have to tell what is the size of the packet that we are going to send. This is done by the Set_Data_Size procedure:

    
       Packet.Set_Data_Size (Size);
    

    Here we want to give the orignal size so that we return the full packet.

    Now we can use the Send procedure to send the packet back to the client. We use the client IPv4 address and UDP port represented by From as the destination address. The Send procedure returns a status that tells whether the packet was successfully sent or queued.

    
    Status : Net.Error_Code;
    ...
       Endpoint.Send (To => From, Packet => Packet, Status => Status);
    
    Server Initialization

    Now that the Echo_Server type is implemented, we have to make a global instance of it and bind it to the UDP port 7 that corresponds to the UDP echo protocol. The port number must be defined in network byte order (as in Unix Socket API) and this is why it is converted using the To_Network function. We don't know our IPv4 address and by using 0 we tell the UDP stack to use the IPv4 address that is configured on the Ethernet interface.

    
    Server : aliased Echo_Server;
    ...
       Server.Bind (Ifnet'Access, (Port => Net.Headers.To_Network (7),
                                   Addr => (others => 0)));
    

    Main loop and receive task

    As explained in the overview, we need several tasks to handle the display, network housekeeping and reception of Ethernet packets. To make it simple the display, ARP table management and DHCP client management will be handled by the main task. The reception of Ethernet packet will be handled by a second task. It is possible to use a specific task for the ARP management and another one for the DHCP but there is no real benefit in doing so for our simple echo server.

    The main loop repeats calls to the ARP Timeout procedure and the DHCP Process procedure. The Process procedure returns a delay that we are supposed to wait but we are not going to use it for this example. The main loop simply looks as follows:

    
    Dhcp_Timeout : Ada.Real_Time.Time_Span;
    ...
       loop
          Net.Protos.Arp.Timeout (Ifnet);
          Dhcp.Process (Dhcp_Timeout);
          ...
          delay until Ada.Real_Time.Clock + Ada.Real_Time.Milliseconds (500);
       end loop;
    

    The receive task was described in the previous article Using the Ada Embedded Network STM32 Ethernet Driver. The task is declared at package level as follows:

    
       task Controller with
         Storage_Size => (16 * 1024),
         Priority => System.Default_Priority;
    

    And the implementation loops to receive packets from the Ethernet driver and calls either the ARP Receive procedure, the ICMP Receive procedure or the UDP Input procedure. The complete implementation can be found in the receive.adb file.

    Building and testing the server

    To build the UDP echo server and have it run on the STM32 board is a three step process:

    1. First, you will use the arm-eabi-gnatmake command with the echo GNAT project. After successful build, you will get the echo ELF binary image in obj/stm32f746disco/echo.
    2. Then, the ELF image must be converted to binary by extracting the ELF sections that must be put on the flash. This is done by running the arm-eabi-objcopy command.
    3. Finaly, the binary image produced by arm-eabi-objcopy must be put on the flash using the st-util utility. You may have to press the reset button on the board so that the st-util is able to take control of the board; then release the reset button to let st-util the flas
    โŒ
    โŒ