Welcome to libpull’s documentation!¶
Libpull is a library to perform software updates for Internet of Things devices. The library is build around three main principles:
- Security: having support for the most used cryptographic libraries and hardware security modules;
- Portability: aims to be OS agnostic, support a wide number of boards and work with several network protocols;
- Platform constraints: aims to targets Class 1 devices, thus having a small memory footprint and reducing network bandwidth;
The library includes a bootloader and an update agent, however, it is possible to use the functions provided by the library to build your own agent that better fits all your platform and application needs.
Get Started¶
In the following pages you will learn how libpull is build, the main logic and how to contribute.
Library Logic¶
Libpull has been build around of three main properties:
- security;
- portability;
- platform constraints;
It targets Class 1 constrained devices, characterized by 100 kB of ROM and 10 kB or RAM, as defined in RF 7228, but thanks to its small memory footprint and modular approach can be useful even on more powerful devices.
Documentation Terminology¶
To understand the following sections is necessary to agree on some terminology and concepts used to analyze and describe the update process.

We first define the terminology to describe an update:
- image: the software that will be executed on the device. It can be a firmware, in case it must be loaded by the device MCU, or could be a software module that can be loaded at runtime by the OS;
- manifest: the set of data related to the image, describing its size, its version and including all the data used for cryptographic verification;
- update image: the union of image and manifest representing the update. This is the data that must be generated and transmitted by the server to the client.
We also define the terminology to describe how an update is stored in memory as:
- memory object: the section of memory used to store the update. This can be a file, a segment of the internal or external flash, a device or any other memory abstraction defined by the user of the library;
- running object: the currently running image;
- device memory: a generic memory of the device that contains one running image and one or more memory objects.
Software Updates Components¶
Analyzing the structure of the update process we identified three distinct components:
- The vendor server is the server owned by the vendor. This server is the first point where the update is built and thus, it is used to assert its integrity and authenticity. This server perform the following actions:
- Builds the image;
- Generates a manifest file;
- Generates the update image;
- Sends the update image to the provisioning server.
- The provisioning server is the server in charge of communicating with the device. It may be managed by the device vendor or not. It performs the following actions:
- Notifies updates availability;
- Updates the manifest;
- Sends the update image to the device;
- Logs the device status.
- The update agent is the code running on the device with the goal of getting the newest firmware available. It performs the following actions:
- Checks the presence of updates;
- Receives the updates;
- Validates the update.
Portability Requirements¶
Portability can be considered as the possibility to reuse the same software in many environments. This required to designing the solution with the appropriate abstraction layers that allow to configure it according to the device capabilities and required logic. We identified four portability requirements:
- Operating System agnostic: IoT devices are based on a multitude of operating systems (mainly RTOS). The libpull core has been designed as a freestanding core that does not uses any any OS-specific API but instead relies on abstraction layers that must be specialized for each OS.
- Network protocol agnostic: Libpull has been designed to support many network protocols. For example, we already tested it with CoAP/CoAPS and BLE, but with more powerful devices, it can easily support the HTTP/HTTPS.
- Cryptographic library agnostic: The library has been implemented to make sure it is compatible with a multitude of cryptographic libraries. We currently tested them with TinyDTLS, tinycrypt and the Atmel CryptoAuthLib.
- Manifest encoding agnostic. The manifest describing the update can be encoded using different formats, such as simple binary format, JSON, CBOR, to support outcoming solutions and needs.
Constrained Devices Requirements¶
The library has a small memory footprint since it must be suitable for Class 1 devices. We are continuously monitoring this aspect using a script executed in the CI build, to make sure we know how each modification impacts the library size.
Moreover, the library supports many memory types. We tested it with standard Linux files but also with direct flash access. Moreover, the memory slots are configurable in a way that they can be placed in different memories, supporting also IoT devices with an external flash connected to them.
We also aims to cover several update methods, namely: static, dynamic and seamless software updates.
- Dynamic Software Updates. In this configuration, the update image is represented by a module that can be loaded at runtime by the running image. The advantage of this configuration is that no-reboot is needed, making it suitable also for real-time application with high availability needs. To allow the use of this configuration, the OS must be capable of loading, and if necessary linking, the modules at runtime. The library does not explicitly manage the activation and relinking of the code, since it is a process highly bounded with the platform choises.
- Static Software Updates. In this configuration, the update image is represented by the whole OS. In this method, the presence of a bootloader is required. The advantage of this configuration is the possibility to perform atomical updates, loading a new image, and avoiding the problems of dynamic linking. Moreover, this requires the reboot of the device that in many applications is not always possible.
- Seamless Software Updates. Also known as A/B updates, seamless updates use one memory object to store the running object and another to store the update. All the logic to perform the update is placed in the image and the bootloader just needs to load the newer version, thus each boot will be performed at the same time. This configuration requires that the two memory objects are bootable and thus stored in the internal memory.
Libpull Agents¶
Libpull is a library that exposes all the features to create a complete update system. It already includes an update agent and a bootloader, but it can be used to build your own if necessary. In the next sections we will describe the agents included in the library.
Update Agents¶
The update agent is the application using the libpull library to effectively perform the update. It is in charge of communicating with the network and coordinate the operations to successfully download, verify and apply the update image. It should be normally executed in parallel to the standard application. In this way, when an update is available, the device will require the minimum time to obtain it.
The update agent defines all the configurations of the library, such as the endpoint used, the resources that must be accessed by the subscriber and the receiver, the type of connection that must be used, the polling timeout. Moreover, it must be able to recover from errors, and safely fail if the errors are not recoverable. For this reason, the update agent has been implemented as a while loop that exits only if an unrecoverable error has been encountered or the update process is successful.

Bootloader¶
The first execution of the bootloader is called bootstrap. During the bootstrap the memory objects are erased, and if the recovery image is enabled, the running object is stored into a specific memory object, allowing a fast recovery in case of failures.
To recognize the first run from the other runs, the bootloader needs to store its state in a persistent memory. This is done defining a new memory object, called bootloader_ctx, that is stored in the last page of the internal memory and contains a bit, used to indicate if the bootloader is running for the first time. The bootloader_ctx is generated during the building phase and flashed to the board at the correct offset. The first_run bit is initially set to one and can be only set to 0 once by the bootloader since that memory is write protected.
If the bootloader finds a newest version in a memory object compared to the version of the running one, it verifies the signature of that object and, if valid, copies it to the running object. The verification is performed before the copy, since if the signature is invalid it avoids a write cycle.
An important operation performed by the bootloader is to write protect all the sectors of the Flash before loading the image, preventing in this way the update agent and all the software running after the bootloader from modifying the content of the internal memory and prevents an attacker to store persistent data in the internal Flash.

Build your own agent¶
If the agents included in the library are not sufficient for your application, you can still use the functions provided by the library to create your own update agent or bootloader that follows your specific logic.
To do that you may want to follow the API Documentation
Cryptographic Libraries¶
The implementation of the security modules consists of high-level interfaces implemented using different cryptographic libraries. This allows to perform the signature verification without changing the code of the library, but still using different cryptographic libraries.
Supported Libraries¶
Libpull currently supports three cryptographic libraries:
- TinyDTLS is a library that provides all the functions to instantiate a DTLS connection. It supports many cryptographic algorithms, such as Rijndael (AES), SHA256, HMAC-SHA256, ECC (with secp256r1 key). It can perform the DTLS handshake using PSK or the ECDH algorithm. It is distributed under the MIT license and maintained by the Eclipse for IoT project.
- TinyCrypt. It is a small-footprint cryptography library that explicitly targets constrained devices. It supports many cryptographic algorithms, such as SHA-256 hash functions, HMAC-SHA256, AES-128 (with AES-CBC, AES-CTR, and AES-CMAC encryption modes), ECC-DH key changes, and ECDSA. It is built in a modular way, allowing to include only the required modules.
- Atmel CryptoAuthLib. This library is provided by Atmel and allows to interact with their CryptoAuthentication modules. It is a very modular library and bases its function on a HAL layer in charge of communicating with the device using I2C or SPI.
Cryptographic Libraries Memory Footprint¶
The choice of the cryptographic library to include was sustained by an analysis of the memory footprint of several cryptographic libraries, to identify the smallest in terms of Data and Text size.
The comparison has been performed building a simple application able to perform the verification with each library and comparing the size of the hashing and ECC functions. The output of the comparison is shown in the table above.
Library | SHA2 | ECC | ECDSA |
---|---|---|---|
TinyDTLS | 3800 | 7531 | 9888 |
tinycrypt | 3656 | 8968 | 11241 |
PolarSSL | 6056 | 23046 | 27735 |
MatrixSSL | 3864 | 29103 | 34022 |
WolfSSL | 4592 | 31443 | 34777 |
LibTomCrypt | 4354 | 35959 | 38256 |
You can find more informations on the methodology used in the specific repository.
Documentation Guidelines¶
The libpull documentation is build using the following tools:
Documentation Overview¶
The documentation is partially contained in the build/doc
folder
and partially included as Doxygen comments in the code.
The documentation is written partially using the reStructuredText markup language and part using MarkDown, to exploit the adavantages of both. In fact, writing MarkDown is much simpler and easier to maintain compared to reStructuredText. However, the latter allows to build complex structures and indexes.
The documentation generation performs logically the following steps:
- Extract XML form the code using Doxygen;
- Parse XML using Breathe and generate reStructuredText files;
- Parse reStructuredText and MarkDown to build the finally documentation using sphinx.
Building the documentation¶
We build the documentation automatically using Read The Docs.
The tools recursively searches for a conf.py
script in the whole repository
and executes sphinx inside of that folder. In the conf.py
script you can
find instructions to invoke Doxygen and to configure Breathe.
If you want to build the documentation locally you need to have the following tools installed:
- Doxygen >= 1.8.13
- Sphinx >= 1.7.6
- Breathe >= 4.9.1
You will find a list of the required python packages in the
requirements.txt
file.
Once you have all the dependencies installed you can build the documentation
using the Makefile contained in the build/doc
folder.
You can see all the available targets invoking make
. If you want to build
the HTML documentation you can use html
target, such as:
make html
Documentation CI¶
You can see the state of the current documentation by analyzing the Read The Docs builds for libpull. Moreover, we are building the documentation on Travis to be sure all functions are documented.
Contributing to Libpull¶
🚀 First of all, thank you for taking the time to contribute! 🚀
What should I know before I get started?¶
If you want to contribute to Libpull you should have understand the basics of the library. To do it a good approach is to read the documentation and follow the tutorial to test it on a real device.
Contributing¶
If you readed the documentation and checked the open issues, then you are ready to contribute it. The contribution may be diveded by the contribution goal.
Increase portability¶
The libpull library aims to be a very portable library with a freestanding core and some platform specific modules.
Support a new RTOS¶
If you want to integrate a new RTOS we ask you to check the following points:
- the OS is well known, open source and still maintained;
- you are able to maintain and provide support for a certain period;
- the OS has a minimum number of platforms on which is supported;
If the following checks passes, you can open an issue and discuss together the integration of the new RTOS.
Support a new MCU¶
If you want to provide support for a new MCU you should check the following points:
- you are able to maintain and provide support for a certain period;
- the drivers to interact with the MCU (such as writing memory, etc) are open source;
- the MCU is still in production.
If the following checks passes, you can open an issue to discuss the integation of the new MCU. If you already have an implementation that follows the logic of the library you can may want to open directly a pull request.
Reporting bugs¶
Please open an issue indicating all the steps to the reproduce the bug/vulnerability. If you already have a solution please feel free to open a pull request, where it will be also easier to discuss the improvements.
Improve documentation¶
If you found an error or want to improve some documentation
pages plase open a pull request with the fix. To check that
the documentation still builds please follow the steps described
in build/documentation/README.md
.
Styleguides¶
Git commit messages¶
For the commit messages we use the conventional commits standardized approach. This allows us to have a structured message for each commit that can be used then to easily generate the changelog for each new release.
A conventional commit message is composed in the following way:
<type>[optional scope]: <description>
where type is one of the following prefix:
- feat: introducing a new feature;
- fix: fixing a bug/vulnerability;
- docs: adding/improving documentation;
- style: fixing indentation/code style;
- refactor: refactoring code;
- perf: a code change to improve performance;
- test: adding/fixing unit tests;
Since this process can be tediuos while writing commits we suggest the cz-cli tool that helps in writing commits conformant to the conventional commits standards.
The optional scope can be used to specify where the type is applied. For example, in case we are adding a new test for the memory module we could write a commit message such as:
test(memory): add a test to detect a failure;
When writing the description considering the following rules:
- Use the present tense (“Fix bug” and not “Fixed bug”)
- Use the imperative mood (“Teach library to…” and not “Teaches library to…”)
C code¶
All the C code must be formatted with clang-format. When submitting PR please avoid reformatting the code until the changes has been approved to increase readibility of the diff.
We are using cppcheck to validate and find possible errors in the code. Check if there are new issues on Codacy.
Tutorial¶
To guide you in using libpull with your platform we provide a tutorial based on our reference platform. Integrating libpull with your solution should not be too far from the steps described in this tutorial.
Introduction¶
In this tutorial you will undestand the steps needed to configure libpull and integrate it with your platform.
This tutorial is based the Zephyr OS and the Nordic nRF52840 board.
What you will learn¶
In this tutorial you will learn the following concepts:
- Build and test libpull;
- Build and execute the libpull server;
- Build and execute libpull on a device;
- Send an update to a device using an OpenThread network.
What you will need¶
To complete this tutorial you will need the following components:
- 2x Nordic nRF52840;
- 2x Micro USB Male to USB A Male cable;
- A computer with Linux or Mac OSX; (in the future we will provide another tutorial for Windows)
- The Arm toolchain already installed;

To follow this guide we do not assume any specific IDE and we assume we are familiar with the command line interface.
Getting Started¶
Prepare the Toolchain¶
We assume you already downloaded and installed the ARM toolchain. You can test if it works typing:
$ arm-none-eabi-gcc --version
Clone the Zephyr repository¶
Create a folder in your home directory and move to it:
$ mkdir ~/libpull_tutorial
$ cd ~/libpull_tutorial
Clone the Zephyr repository:
$ git clone https://github.com/zephyrproject-rtos/zephyr
Build a Zephyr example¶
To test if your setup is ready to work with Zephyr and the nRF52840 board, build the hello world sample provided by the Zephyr project and load it to the board.
You can follow the official documentation for this task or the next steps:
$ cd zephyr
$ source zephyr-env.sh
$ cd samples/hello_world
$ mkdir build && cd build
$ cmake -GNinja -DBOARD=nrf52840_pca10056 ..
$ ninja
If the build was successfull you are now ready to flash the firmware on the device:
$ ninja flash
To read the serial output we use Minicom, but you can use every serial communication program you like (i.e., screen).
If everthing was correct you should see the following output:
***** Booting Zephyr OS v1.12.0-290-g7a7e4f583 *****
Hello World! arm
Install the flashing tool¶
To flash the libpull generated firmware we will use nrfjprog
. You can have it
instaling the
nRF5x Command Line Tools.
This program is needed to interact with the nRF52840 board. To test if it works use the command:
$ nrfjprog --ids
that shows the serial numbers of all the boards connected to the computer.
Libpull Setup¶
In this part of the tutorial we will see how to work with libpull, setup the server, compile and flash the device.
Clone libpull¶
Moving back to the tutorial folder previously created we are now ready to clone the libpull repository.
$ cd ~/libpull_tutorial
$ git clone https://github.com/libpull/libpull
$ cd libpull
Build the library¶
The libpull build system is based on the GNU Build system and is currently compatible onlye with Linux and Mac OSX.
ℹ️ We are planning to move the build system to CMake.
To build the library we first need to download the dependencies:
$ ./autogen.sh
The script will download and compile the dependencies,
and call the autoreconf
program in charge of generating the configure script.
We can now configure the library using:
$ ./configure
Too see the configuration options type ./configure --help
.
The previous script will generate the makefiles for the whole project. To build the library just type:
$ make
If the build is successfull we are ready to move forward.
Build and execute the server¶
Since the update must be downloaded OTA (Over The Air) we need a running server. Libpull currently provides a testing server.
⚠️ The server is not ready for production.
To execute the server you can use the makefile target run_server
. It will automatically create the assets, build the server and execute it. Otherwise, to have a better undestanding on the process you can read the Makefile
and execute each target individually:
$ make assets
$ make server
$ make run_server
The first target will invoke the script utils/assets_generator.sh
that will create a new folder called assets
and place inside it several files used by the unit tests.
Execute the library tests¶
If you want to be sure that the library has been built correctly and the can commmunicate correctly with the server, you can execute the Unit Tests with the following command:
make check
If all the tests passes you configuration is correct. If not, you should check the output printed on the server since you may have some network configuration (i.e. firewall) that is interfering with the connection.
If everything works we are now ready to start testing libpull on the device.
Network Setup¶
To send the firmware Over The Air using the OpenThread network we need to setup an OpenThread border router.
ℹ️ The setup suggested by the official OpenThread documentation requires to use a Raspberry Pi 3 or a BeagleBone Black. Since we want to keep the setup simple we will describe a border router configuration when the thread device is directly connected to a computer. However, if you already have such a setup or you prefer to follow the official guide skip the following sections.
Install a Linux Virtual Machine¶
If you are using Mac OSX you need to install a virtual machine since the OTBR tool provided by OpenThread works only on Linux.
Download and flash the border router¶
To install a Thread border router you can follow two paths:
- clone the repository and build it;
- download an already built version;
Since OpenThread provides an already built version for our board we will follow the second approach.
⚠️ We assume you have some knowledge on the Thread networks. If not, you might want to run the OpenThread Simulation Codelab, to get familiar with the basics Thread concepts.
You can download and extract a prebuild version of the firmware using the following commands:
$ wget https://openthread.io/guides/ncp/ot-ncp-ftd-gccb354fb-nrf52840.tar.gz
$ tar -xzvf ot-ncp-ftd-gccb354fb-nrf52840.tar.gz
You should now have a hex file called ot-ncp-ftd-gccb354fb-nrf52840.hex
containing the firmware. You can flash it following the steps at this link or following the next steps:
$ nrfjprog -f nrf52 --chiperase --program ot-ncp-ftd-gccb354fb-nrf52840.hex --reset
If the flashing was successfull you should see the following output:
Parsing hex file.
Erasing user available code and UICR flash areas.
Applying system reset.
Checking that the area to write is not protected.
Programming device.
Applying system reset.
Run.
Connect and test the border router¶
Since the flashed firmware enables the use of native USB CDC ACM as a serial transport, we need to connect the board using the other micro USB.
ℹ️ You can have a visual description at this link.
- Power off the board;
- Disconnect the micro USB from the board;
- Change the
nRF power source
switch from VDD to USB; - Attach the micro USB cable to the nRF USB port on the long side of the board;
- Power on the board;
If the previous passages have been performed correctly you should not see all leds off on the board.
Install the OpenThread OTBR¶
If you prefer, you can follow the official Border Router guide at this link.
⚠️ If you are using a virtual machine you need to allow the access to the USB port.
In virtualbox you can do it by adding a filter for the SEGGER J-Link
USB device
in the ports settings.
Clone the border router on your Linux machine:
$ git clone https://github.com/openthread/borderrouter
Install the dependencies (you admin password may be required):
$ cd borderrouter
$ ./script/bootstrap
Compile and install OTBR:
$ ./script/setup
Check if the device has been recognized by your Linux machine checking the available devices
under /dev/tty.*
.
Configure the device port in the wpantund
configuration file /etc/wpantund.conf
.
The effective port depends on your configuration but, for example, can be /dev/ttyACM0
or /dev/ttyUSB0
.
Rebooting the Linux machine all the installed services shluld be executed at startup.
To check if the services are running you can use the sudo systemctl status
command. All the following services should be listed and enabled:
- avahi-daemon.service
- otbr-agent.service
- otbr-web.service
- wpantund.service
If not all of them are running you can use the script located at borderrouter/script/server
to start them.
You can now verify if the borderrouter is successfully configured and the board has been recognized by using the wpanctl
command.
$ sudo wpanctl status
If the configuration is correct you should see an output similar to:
wpan0 => [
"NCP:State" => "offline"
"Daemon:Enabled" => true
"NCP:Version" => "OPENTHREAD/20170716-00506-gccb354fb-dirty; NRF52840; Mar 15 2018 14:43:28"
"Daemon:Version" => "0.08.00d (/50eedbb; Aug 7 2018 08:22:56)"
"Config:NCP:DriverName" => "spinel"
"NCP:HardwareAddress" => [5DC574B951D3EADB]
]
If the value of NCP:State is different from “offline”, you can find some solutions at this link.
Connnect to the border router web interface¶
The OpenThread border router has a web interface to configure the network. You can connect to it by accessing the address localhost:80
with a browser.
ℹ️ If you are using a virtual machine without a web interface you can route the port 80 to your host machine.
Once you can access the web interface you should move to the Form
page to create a new network, as shown in the image.

Once you clicked the Form
button you should see a popup with the following message FORM operation is successful
.
Test the OpenThread network¶
We will now test the created OpenThread network using the other nRF52840 board and a Zephyr sample.
Move back to the Zephyr cloned repository:
$ cd ~/libpull_tutorial/zephyr
To test the network we will use the Echo client
example. We need to create a configuration file that targets our specific board.
$ cd samples/net/echo_client
$ wget https://gist.githubusercontent.com/AntonioLangiu/5d4184085cf81a816c0b904b27b41c7e/raw/fe0bc763b991b64686534a5e0c2cd12c760a7771/prj_nrf52840_ot.conf
The configuration file contains the directive to enable OpenThread and the OpenThread shell that we will use for testing. To understand the configuration you can read the Zephyr documentation.
We can now build and flash the firmware on the board:
$ mkdir build && build
$ cmake -GNinja -DBOARD=nrf52840_pca10056 -DCONF_FILE=prj_nrf52840_ot.conf ..
$ ninja
If the build was successfull we can now flash the device and access the shell using Minicom:
$ ninja flash
$ minicom -D /dev/tty.usbmodem1411
⚠️ Since you have two devices connected, plase check the ID of the device you want to flash using the nrfjprog.
You should now have access to the OpenThread shell typing the following commands inside of minicom:
select ot
cmd help
Typing cmd scan
you should see the following output:
| J | Network Name | Extended PAN | PAN | MAC Address | Ch | dBm | LQI |
+---+----------------------+------------------+------+------------------+----+-----+-----+
| 0 | Libpull Tutorial"""" | 1111111122222222 | 1234 | 8a246d3fd47592be | 15 | -44 | 50 |
OpenThread provides an official documentation for the CLI describing each command and how to use it.
To test if the device is able to communicate with the server using the OpenThread network already created we can use netcat to listed for udp packets and send an udp packet from the device.
First we need to understand the network configuration of our computer. With the command ifconfig
we can see the various interfaces of our PC and the IP address assigned to them. You can find the global IPv6 address reachable from the OpenThread network checking the addresses assigned to the wpan0
interface.
In our case the configuration was as follow, but in your computer it will be different:
wpan0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST> mtu 1280
inet6 fdde:ad00:beef:0:666c:4b75:8e5a:ae76 prefixlen 64 scopeid 0x0<global>
inet6 fe80::f0b9:99f1:64e8:d66b prefixlen 64 scopeid 0x20<link>
inet6 fd11:22::f0b9:99f1:64e8:d66b prefixlen 64 scopeid 0x0<global>
inet6 fe80::9cf5:8d3a:2c6c:171f prefixlen 64 scopeid 0x20<link>
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 500 (UNSPEC)
RX packets 13 bytes 696 (696.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 41 bytes 5844 (5.8 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
We can listen for incoming udp packets using the:
$ netcat -ul <global_ipv6_address_of_wpan0_interface> 2115
command. Moving to the minicom connection we can now send an udp packet through the OpenThread network using the:
ot> cmd udp send <global_ipv6_address_of_wpan0_interface> 2115 Test
We should now see the Test
message printed from the netcat listening server.
To test if the communication is correctly working you can also use the ping command integrated into the OpenThread CLI:
ot> cmd ping <global_ipv6_address_of_wpan0_interface>
Moving forward¶
Setting an OpenThread network really depends from your available configuration. You should have a basic understanding of the OpenThread principles and how to configure a Linux network.
If you arrived here, it means you are able to send and receive packets from your device to the listening server using the OpenThread network. This is required to move formward since to receive the firmware the device will need to communicate with our testing server.
Libpull Build¶
In this part of the tutorial you will learn how to build the libpull bootloader, the update agent, and how to compose them togeather to create a flashable firmware.
Zephyr build¶
To build the bootloader we need to go to the specific platform build folder. Libpull defines a folder for each platform containing the all the scripts and tools necessary for the platform, in our case Zephyr.
Move to the Zephyr platform folder:
$ cd ~/libpull_tutorial/libpull/build/zephyr
On this folder you will find the following content:
- autogen.sh: script to clone the dependencies;
- application: folder containing the code and the build system for the application;
- bootloader: folder containing the code and build system for the bootloader;
- board: folder containing the boards specific files;
- bootloader_ctx: folder containing the bootloader context files;
- config.toml: configuration file of the firmware_tool program;
- make_firmware.sh: a script to build together the bootloader and the application in a single flashable image;
- test: a folder containing a set of tests used to test the board with libpull;
We will start executing the tests to see if they pass on the device, and also to start undestanding how the build process works.
First of all, let’s clone the dependencies using the:
$ ./autogen.sh
script contained in the build/zephyr
folder.
Once Zephyr has been cloned, you have to import the zephyr-env.sh
variables to your environment. You can do it with the commands:
$ cd ext/zephyr
$ source zephyr-env.sh
Execute libpull platform specific tests¶
Moving to the test
folder we will find three tests with different goals:
- memory: tests the ability to read and write data to the flash;
- network: tests the ability to send and receive packets using our connection;
- security: tests the ability to perform the firmware verification using a specific cryptographic library;
This tests ensure that libpull correctly works on the choosen platform.
For each test you can build it entering in the test folder and typing the following commands:
$ mkdir build && cd build
$ cmake -GNinja -DBOARD=nrf52840_pca10056 -DCONF_FILE=prj.conf ..
$ ninja
$ ninja flash
$ minicom -D /dev/tty.your_device
Analyzing the output of the test you can understand if that particular component of libpull correctly works on your board and with your configuration.
Build the bootloader¶
To build the bootloader you need to perform the same Zephyr build steps in the bootloder directory:
$ cd ~/libpull_tutorial/libpull/build/zephyr/bootloader
$ mkdir build && cd build
$ cmake -GNinja -DBOARD=nrf52840_pca10056 -DCONF_FILE=prj.conf ..
$ ninja
The bootloader, once loaded on the device, needs to have a storage to save persistent data. This is what is in libpull is called the bootloader_ctx.
To create a bootloader context we provide a program contained in the bootloader_ctx
folder of each platform.
You can build it using the following commands:
$ cd bootloader_ctx
$ make
If the build was successfull you should now have a bootloader_ctx.bin
file and you are ready to move to the next steps.
Build the application¶
The application contains the update agent in charge of contacting the server to receive the update. This means that the update agent must be able to commicate with the server and must know its IP address.
The IP address of the server, that in this case is the one of your wpan0 network, must be hardcoded in the application/src/runner.c
file, editing the SERVER ADDR
preprocessor variable.
#define SERVER_ADDR <your_public_ipv6_address>
The public IPv6 address can be taken from the ifconfig command as previously shown in this tutorial.
To build the application you need to perform the same Zephyr build steps in the application directory:
$ cd ~/libpull_tutorial/libpull/build/zephyr/application
$ mkdir build && cd build
$ cmake -GNinja -DBOARD=nrf52840_pca10056 -DCONF_FILE=prj.conf ..
$ ninja
If the build was successfull you can now move to the next step.
Create the flashable firmware¶
Now that we have a bootloader and an application we can build a flashable firmware. Each platform folder contains a ./make_firmware.sh
script that will invoke all the programs to create a flashable binary according to the specific variables of the board.
In the case of the nRF52840 you can find the board variables in the board/nrf52840_pca10056/Makefile.conf
file.
To create a flashable firmware you can invoke the previosly described script:
$ ./make_firmware.sh
If the firmware has been successfully created you will find it on the main folder in two formats:
- fimrware.hex (Intel Hex version)
- firmare.bin (Binary version)
Flash the firmware¶
We can use now the nrfjprog
to flash the firmware, using the command:
$ nrfjprog --eraseall
$ nrfjprog --program firmware.hex
$ nrfjprog --reset
Using minicom you can analyze the output. If everything is working correctly you should see the bootloader output:
***** Booting Zephyr OS v0.0.1-179-g67dce7f *****
Bootloader started
id: bootable 2 - non bootable 3
id: bootable 2 - non bootable 3
version: bootable 1 - non bootable 0
Start phase GET_OBJECT_MANIFEST
Start phase CALCULATING_DIGEST
Digest: initial offset 256u final offset 226396 size 226140
Start phase VERIFY_DIGEST
Start phase VERIFY_VENDOR_SIGNATURE
Vendor Signature Valid
Start phase VERIFY_SERVER_SIGNATURE
Server Signature Valid
loading object
loading address 9100
At the address 9100 the ./make_firmware.sh
script placed the application that, once boooted should print the following messages:
***** Booting Zephyr OS v0.0.1-179-g67dce7f *****
Zephyr Shell, Zephyr version: 1.12.0
Type 'help' for a list of available commands
shell> [net] [INF] ot_state_changed_handler: State changed! Flags: 0x000010e4 Current role: 2
[net] [INF] ot_state_changed_handler: State changed! Flags: 0x00000001 Current role: 2
[net] [INF] ot_state_changed_handler: State changed! Flags: 0x00000200 Current role: 2
[net] [INF] ot_state_changed_handler: State changed! Flags: 0x00000001 Current role: 2
Getting the newest firmware from memory
the id of the newest firmware is 2
Checking for updates
....
At this point we are ready for the last phase, starting the update server and sending a new image to the device.
Sending the update¶
In this last step of the tutorial you will learn how to put all the pieces togeather and send the update to the device.
Build the update¶
We first need to create an update with a version higher than the one currently running on the device.
The version of the update must be setted in the config.toml
configuration file. It is used by the firmware_tool
application provided with libpull.
The default firmware version is 0x0001
. To make the update possible we should send to the device a firmware with a version stricly higher than the one already installed. We can, for example, set the version to 0x0002
.
Once we have an higher version we can generate the firmware using the ./make_firmware.sh
script.
This will generate, as before, a firmware containing both the application with the manifest and the bootloader. However, to send an update we want to send only the application part, since the bootloader cannot be changed on the device. Thus we need to take it from the firmware
folder where the application firmware is generated.
Inside of the firmware
folder (located at ~/libpull_tutorial/libpull/build/zephyr/firmware/
) we can find the following files:
- firmware.bin.tmp // The firmware generated by the application build system;
- manifest.bin // The manifest created from it;
- firmware.bin // A binary composed by the manifest + the firmware;
The server must send only the last one, thus we should pass it as a server argument.
Execute the server¶
The make run_server
target used at the start of the tutorial prepares the server for the Unit Tests. However, we need to send the firware previsouly created, thus we can execute the server with the following commands:
$ cd ~/libpull_tutorial/libpull/
$ ./utils/server/server -f build/zephyr/firmware/firmware.bin
However, the previous command is not sufficient since, until we configured the internal network to route packets from wpan0 to the localhost, we also need to instruct the server to listend on the correct interface.
The server offers an option to do it:
./utils/server/server -A <your_public_ipv6_addr> -f firmware/firmware.bin
Where :raw-html-m2r:`<your_public_ipv6_addr>`* is a global IPv6 address assigned to your *wpan0 interface.
Update the device¶
Once the server is started, you can restart the device and wait for the update process to start. It should start immediately, downloading the manifest and then the whole update.
If the process was correct, you should see an output like this:
***** Booting Zephyr OS v0.0.1-179-g67dce7f *****
Bootloader started
id: bootable 2 - non bootable 3
version: bootable 1 - non bootable 0
Start phase GET_OBJECT_MANIFEST
Start phase CALCULATING_DIGEST
Digest: initial offset 256u final offset 226396 size 226140
Start phase VERIFY_DIGEST
Start phase VERIFY_VENDOR_SIGNATURE
Vendor Signature Valid
Start phase VERIFY_SERVER_SIGNATURE
Server Signature Valid
loading object
loading address 9100
[net] [INF] openthread_init: OpenThread version: OPENTHREAD/20170716-00461-gdb4759cc-dirty; NONE; Aug 7 2018 16:43:32
[net] [INF] openthread_init: Network name: Libpull Tutorial
[net] [INF] ot_state_changed_handler: State changed! Flags: 0x003f333f Current role: 1
***** Booting Zephyr OS v0.0.1-179-g67dce7f *****
Zephyr Shell, Zephyr version: 1.12.0
Type 'help' for a list of available commands
shell> [net] [INF] ot_state_changed_handler: State changed! Flags: 0x000010e4 Current role: 2
[net] [INF] ot_state_changed_handler: State changed! Flags: 0x00000001 Current role: 2
[net] [INF] ot_state_changed_handler: State changed! Flags: 0x00000200 Current role: 2
[net] [INF] ot_state_changed_handler: State changed! Flags: 0x00000001 Current role: 2
Getting the newest firmware from memory
the id of the newest firmware is 2
Checking for updates
subsriber_cb: message received
Latest_version: 1 Provisioning version 2
An update is available
An update is available
Pages erased correctly
Manifest still not received
Received 64 bytes. Expected 0 bytes
Manifest still not received
Received 128 bytes. Expected 0 bytes
Manifest still not received
Received 192 bytes. Expected 0 bytes
Manifest still not received
Received 256 bytes. Expected 0 bytes
Manifest still not received
Manifest received
Platform: beef
Version: 0002
Size: 226140
valid: 0101 0202 received 0101 0202
Received 320 bytes. Expected 226396 bytes
...
Received 226396 bytes. Expected 226396 bytes
Start phase GET_OBJECT_MANIFEST
Start phase CALCULATING_DIGEST
Digest: initial offset 256u final offset 226396 size 226140
Start phase VERIFY_DIGEST
Start phase VERIFY_VENDOR_SIGNATURE
Vendor Signature Valid
Start phase VERIFY_SERVER_SIGNATURE
Server Signature Valid
Supported Platforms¶
Libpull currently supports the following platforms:
Zephyr Platform¶
Libpull can be used to perform Software Updates for IoT devices using the Zephyr platform.
We tested and provide the following components:
- an update agent to update a Zephyr device using OpenThread;
- an update agent to update a Zephyr device using Bluetooth Low Energy (BLE);
- a bootloader able to manage the images and upgrade once a new image is present;
For the Zephyr platform we currently support the following boards:
However, you can easily integrate another board by following this tutorial.
Contiki-NG Platform¶
Libpull can be used to perform Software Updates for IoT devices using the Contiki-NG platform.
We tested and provide the following components:
- an update agent to update a Contiki-NG device using 6LoWPAN and CoAP;
- a bootloader able to manage the images and upgrade once a new image is present;
For the Contiki-NG platform we currently support the following boards:
However, you can easily integrate another board by following this tutorial.
Riot Platform¶
Libpull can be used to perform Software Updates for IoT devices using the RIOT platform.
We tested and provide the following components:
- an update agent to update a RIOT device using 6LoWPAN and CoAP;
- a bootloader able to manage the images and upgrade once a new image is present;
For the RIOT platform we currently support the following boards:
However, you can easily integrate another board by following this tutorial.
New Platform¶
If you want to integrate a new platform you will find here a small tutorial suggesting the steps to do it in the right way.
Build libpull with the same build system¶
You have two approaches to build libpull:
- build a static library and link it to your platform;
- build it with your platform build system;
Both approaches should lead you to the same result, however, the second one is more easier and less error prone, thus is the one that we used for all our supported platforms and is what we suggest also in your case.
If you want to build libpull togeather with your build system you may want to follow the approach already used for the GNU Makefile, watching the Contiki-NG or RIOT platforms, or the CMake used when building Zephyr.
Build the tests¶
We provide three tests that can be easily executed on the board. Each of them targets a specific feature of libpull that is platform dependent.
- memory test: allows to verify the correct implementation of the memory interface;
- security test: allows to test the security functions (such as digest ECC key validation) directly on hardware;
- network test: allows to test the correct implementation of the connection interface.
Only when all these tests passes you can be sure that libpull will work correctly with your platform and that you implemented all the interfaces correctly.
To build the tests you can take as example the already supported platforms.
Build the application¶
The application you will execute on the IoT device must periodically execute the update agent to check if a new update is available and start in that case the process.
Our update agent is build on top of coroutines. This means that you can easily use it also with platforms where abstractions such a thead or processes are not available and you only rely on a main while loop.
You can follow the examples on the other supported platforms to see how to invoke and configure the update agent. You will find informations on the API documentation.
Build the bootloader¶
Our bootloader can be easily integrated with the OS to benefit from all the facilities it offers. This allows, for example, to support all the boards supported by the OS just by providing support for it.
If you are building a bootloader for a specific solution and you do not want to rely on an OS as a basis for it, you can build it bare metal, still using the bootloader agent that we provide.
To see how to configure and execute the bootloader please check the API documentation.
Contributing?¶
Do you think the new OS you are supporting may be useful also for others? Please open an issue or a pull request to discuss a possible integration.
API Documentation¶
The API documentation is generated using Doxygen. to create a XML rappresentation from the sources. The XML is used by Breathe to generate the corresponding rst, used by Sphinx to generate the final documentation hosted on Read The Docs.
You can also generate the API documentation locally, by using the Doxyfile located in build/doc/source/Doxyfile
Libpull is logically organized in the following modules:
Common API¶
The common modules contains functions and interfaces that are shared in the whole library. This allows to grant consistency and to reduce code duplication. You can include all the common modules by including
#include <libpull/common.h>
Callback¶
-
typedef void (*
callback
)(pull_error err, const char *data, int len, void *more)¶ Callback used to handle network events.
- Parameters
err
: Error received or PULL_SUCCESS if no error.data
: Received data.len
: Lenght of received data.more
: Raw pointer passed during connection initialization.
Crc16¶
Warning
doxygengroup: Cannot find namespace “com_crc16” in doxygen xml output for project “libpull” from directory: ./doxyxml/
Error¶
-
enum
com_error
::
pull_error
¶ Each module should returns just errors of that particular module. In this way it is possible for the calling function to have a finite set of errors returned by the function.
Values:
-
PULL_SUCCESS
= 0¶
-
GENERIC_ERROR
¶ Generic error
-
NOT_IMPLEMENTED_ERROR
¶ Method not implemented
-
INVALID_ARGUMENTS_ERROR
¶
-
CONNECTION_INIT_ERROR
¶ The connection initialization failed
-
CALLBACK_ERROR
¶ The callback could not be setted
-
RESOLVER_ERROR
¶ Error resolving the backend
-
INVALID_URL_ERROR
¶ The URL of the provided resource is invalid
-
BLOCK_WISE_ERROR
¶ Error during the Block-Wise transfer
-
INVALID_METHOD_ERROR
¶ The request method is invalid or not supported
-
INVALID_RESOURCE_ERROR
¶ The requested resource is invalid
-
INVALID_DATA_ERROR
¶ The received data is invalid
-
INVALID_CONN_DATA_ERROR
¶ The data used to inizialize the connection is invalid
-
REQUEST_ERROR
¶ Error performing the request
-
REQUEST_RST_ERROR
¶
-
SEND_ERROR
¶
-
MEMORY_ERROR
¶
-
INVALID_OBJECT_ERROR
¶
-
INVALID_ACCESS_ERROR
¶
-
MEMORY_MAPPING_ERROR
¶
-
MEMORY_OPEN_ERROR
¶
-
MEMORY_CLOSE_ERROR
¶
-
MEMORY_ERASE_ERROR
¶
-
MEMORY_READ_ERROR
¶
-
MEMORY_WRITE_ERROR
¶
-
MEMORY_FLUSH_ERROR
¶
-
READ_MANIFEST_ERROR
¶
-
GET_NEWEST_ERROR
¶
-
GET_OLDEST_ERROR
¶
-
COPY_FIRMWARE_ERROR
¶
-
INVALIDATE_OBJECT_ERROR
¶
-
WRITE_MANIFEST_ERROR
¶
-
INVALID_MANIFEST_ERROR
¶
-
RECEIVER_OPEN_ERROR
¶
-
RECEIVER_CHUNK_ERROR
¶
-
RECEIVER_CLOSE_ERROR
¶
-
INVALID_SIZE_ERROR
¶
-
INVALID_IDENTITY_ERROR
¶
-
NETWORK_ERROR
¶
-
SUBSCRIBE_ERROR
¶
-
SUBSCRIBER_CHECK_ERROR
¶
-
DIGEST_INIT_ERROR
¶
-
DIGEST_UPDATE_ERROR
¶
-
DIGEST_FINAL_ERROR
¶
-
SHA256_INIT_ERROR
¶
-
SHA256_UPDATE_ERROR
¶
-
SHA256_FINAL_ERROR
¶
-
NOT_SUPPORTED_CURVE_ERROR
¶
-
VERIFICATION_FAILED_ERROR
¶
-
SIGN_FAILED_ERROR
¶
-
-
const char *
err_as_str
(pull_error err)¶ This function returns a string representing the literal representation of the error.
- Return
- Pointer to a string describing the error.
- Parameters
err
: The error to be printed.
-
GENERATE_ENUM
(ENUM)¶
-
GENERATE_STRING
(STRING)¶
-
FOREACH_ERROR
(ERROR)¶
External¶
-
const mem_slot_t
memory_slots
[]¶ This structure contains a list of the memory slots needed by your application. We already have a standard definition but you can define it by yourself and personalize according to your needs.
An example of a correctly implemented memory slot is structure is:
const mem_slot_t memory_slots[] = { { .id = OBJ_1, .bootable = true, .loaded = true }, { .id = OBJ_2, .bootable = false, .loaded = false }, {OBJ_END} };
-
OBJ_END
¶ OBJ_END must be used to end a memory slot list. Internally the library uses it to know when to stop cycling over the structure.
Identity¶
-
pull_error
update_random
(identity_t *identity)¶ Function to update the random using a PRNG. The implementation is platform dependent and must often rely on hardware solutions included in the platform since there is no entropy source in many RTOS.
- Return
- PULL_SUCCESS on success, an error otherwise.
- Parameters
identity
: Pointer to the identity struct containing the value to be updated.
-
pull_error
validate_identity
(identity_t valid_identity, identity_t received_identity)¶ Function to validate an identity comparing it to the one stored on the device.
- Return
- PULL_SUCCESS if the identity is valid, an error otherwise.
- Parameters
valid_identity
: Pointe to the identity stored on the device.received_identity
: Pointer to the identity received that must be validated.
-
struct
identity_t
¶ - #include <identity.h>
Structure used to univocally identify a device.
- Note
- This struct is often passed by value in the library, so it must not contain any pointer.
Logger¶
-
uint8_t
verbosity_level
¶ The verbosity_level variable indicates the log_impling level. If you want to change it at runtime you can define the external symbol in your application. In case you don’t need to change it at runtime you can define it as a constant, defining the LOGGER_VERBOSITY define. In this way the compiler will be able to optimize the code and remove the non needed debugging directives.
4 logging level are supported:
- Error: Just the errors are printed.
- Warn: Error and warnings are printed.
- Info: Error, warnign and also information strings.
- Debug: All the output is printed. This affect heavily the memory footprint of the library.
-
VERBOSITY_ERROR
¶
-
VERBOSITY_WARN
¶
-
VERBOSITY_INFO
¶
-
VERBOSITY_DEBUG
¶
-
log_output
(...)¶
-
log_impl
(...)¶
-
log_err
(...)¶
-
log_debug
(...)¶
-
log_info
(...)¶
-
log_error
(err, ...)¶ This function takes as first parameter an error and then an arbitrary number of arguments that will be printed. The first arguemnt will be used to print the literal value of the error.
-
log_warn
(...)¶
Pull Assert¶
-
PULL_ASSERT
(cond)¶
Types¶
-
typedef uint16_t
version_t
¶ Version type to be used across the library.
-
typedef uint16_t
platform_t
¶ Platform type to be used across the library
-
typedef uint32_t
address_t
¶ Address type to be used across the library
-
typedef int8_t
mem_id_t
¶ Identifier for the memory objects. It supports at most 255 objects This must be a signed integer since negative values are used to define an invalid object
Memory API¶
Manifest¶
-
platform_t
get_platform
(const manifest_t *mt)¶
-
uint8_t *
get_server_key_x
(const manifest_t *mt)¶
-
uint8_t *
get_server_key_y
(const manifest_t *mt)¶
-
uint8_t *
get_digest
(const manifest_t *mt)¶
-
uint8_t *
get_vendor_signature_r
(const manifest_t *mt, uint8_t *size)¶
-
uint8_t *
get_vendor_signature_s
(const manifest_t *mt, uint8_t *size)¶
-
uint8_t *
get_server_signature_r
(const manifest_t *mt, uint8_t *size)¶
-
uint8_t *
get_server_signature_s
(const manifest_t *mt, uint8_t *size)¶
-
void
set_platform
(manifest_t *mt, platform_t platform)¶
-
void
set_server_key_x
(manifest_t *mt, uint8_t *server_key_x)¶
-
void
set_server_key_y
(manifest_t *mt, uint8_t *server_key_y)¶
-
void
set_digest
(manifest_t *mt, uint8_t *digest)¶
-
int
set_vendor_signature_r
(manifest_t *mt, uint8_t *vendor_signature_r, uint8_t size)¶
-
int
set_vendor_signature_s
(manifest_t *mt, uint8_t *vendor_signature_s, uint8_t size)¶
-
int
set_server_signature_r
(manifest_t *mt, uint8_t *server_signature_r, uint8_t size)¶
-
int
set_server_signature_s
(manifest_t *mt, uint8_t *server_signature_s, uint8_t size)¶
-
void
print_manifest
(const manifest_t *mt)¶ Print manifest known values.
- Parameters
mt
: Pointer to a manifest structure.
-
identity_t
get_identity
(const manifest_t *mt)¶
-
void
set_identity
(manifest_t *mt, identity_t identity)¶
-
pull_error
verify_signature
(manifest_t *mt, digest_func df, const uint8_t *pub_x, const uint8_t *pub_y, ecc_func_t ef)¶
-
FOREACH_ITEM
(ITEM)¶
-
DEFINE_GETTER
(type, name)¶ The scope of this file is to define the interface of a manifest. It can be implemented using different encodings, but each approach should implement this interface to be usable by the library
-
DEFINE_SETTER
(type, name)¶
-
DEFINE_GETTER_MEMORY
(type, name)¶
-
DEFINE_SETTER_MEMORY
(type, name)¶
Memory Interface¶
-
enum
mem_memint
::
mem_mode_t
¶ Values:
-
READ_ONLY
= 0¶
-
WRITE_CHUNK
= 1¶
-
WRITE_ALL
= 2¶
-
SEQUENTIAL_REWRITE
= 3¶
-
-
typedef struct mem_object_t
mem_object_t
¶
-
typedef struct mem_slot_t
mem_slot_t
¶
-
pull_error
memory_open
(mem_object_t *ctx, mem_id_t id, mem_mode_t mode)¶ Open a memory object.
The implementation of this function should open a memory object given the id. This can be mapped to any phisical object, such as a file, a ROM or even an allocated memory object. It depends on the needs of the platform and the application.
- Return
- PULL_SUCCESS if the memory was correctly open or the specific erro
- Parameters
ctx
: An unitialized memory objectid
: The id of the memory object. The obj_id enum must be defined when implementing this interface.mode
: The mode used to open the memory_object (i.e. READ, WRITE, etc)
-
int
memory_read
(mem_object_t *ctx, void *memory_buffer, size_t size, address_t offset)¶ Read bytes from a memory object.
This function reads size bytes from a memory object at the specified offset into the given memory buffer.
- Return
- The number of readed bytes or a negative number in case of error.
- Parameters
ctx
: The already opened memory object.memory_buffer
: The memory buffer acting as destination.size
: The number of bytes to read.offset
: The offset in the memory object from where to start reading.
-
int
memory_write
(mem_object_t *ctx, const void *memory_buffer, size_t size, address_t offset)¶ Write bytes into a memory object.
This function writes size bytes into an opened memory object at the offset specified.
- Return
- The number of written bytes or a negative number in case of error.
- Parameters
ctx
: An opened memory object.memory_buffer
: The memory buffer to be written.size
: The size of the memory buffer.offset
: The offset into the memory object.
-
pull_error
memory_close
(mem_object_t *ctx)¶ Close the memory object.
This should close and deallocate all the initialized resources.
- Return
- PULL_SUCCESS on success or a specific error otherwise.
- Parameters
ctx
: The memory object.
Memory Objects¶
-
pull_error
get_newest_firmware
(mem_id_t *id, version_t *version, mem_object_t *obj_t, bool prefer_bootable)¶ Get the id of the memory object containing the newest firmware.
- Return
- PULL_SUCCESS on success or a specific error otherwise.
- Parameters
id
: The id of the newest object.version
: The version of the newest object.obj_t
: A temporary mem_object_t used by the function.
-
pull_error
get_oldest_firmware
(mem_id_t *obj, version_t *version, mem_object_t *obj_t, bool prefer_bootable)¶ Get the id of the memory object containing the oldest firmware.
- Return
- PULL_SUCCESS on success or a specific error otherwise.
- Parameters
obj
: The id of the oldest objectversion
: The version of the oldest object.obj_t
: A temporary mem_object_t used by the function.
-
pull_error
copy_firmware
(mem_object_t *src, mem_object_t *dst, uint8_t *buffer, size_t buffer_size)¶ Copy the firmware s into the firmware d.
This function will use the size specified in the s firmware manifest to correcly copy the firmware.
- Note
- The buffer will be used to read from object s and to write to obejct d. If you are working with flash and your memory implementation is not buffered you can pass a buffer with size equal to the size of a flash page.
- Return
- PULL_SUCCESS on success or a specific error otherwise.
- Parameters
src
: The source memory object.dst
: The destination memory object.buffer
: Buffer used to copy the objectbuffer_size
: The size of the buffer
-
pull_error
swap_slots
(mem_object_t *obj1, mem_object_t *obj2, mem_object_t *obj_swap, size_t swap_size, uint8_t *buffer, size_t buffer_size)¶ Swap two slots using a swap memory_object.
This function swaps two slots using a memory objects with id SWAP
This functions assumes that the size of the SWAP memory object is is at least as big as the buffer_size and that the swap size is a multiple of the buffer size, such that swap_size % buffer_size == 0
- Note
- The buffer will be used to read from object s and to write to obejct d. If you are working with flash and your memory implementation is not buffered you can pass a buffer with size equal to the size of a flash page.
- Parameters
obj1
: The fist memory objectobj2
: The second memory objectobj_swap
: The memory object used for swappingswap_size
: The size of the swapping memory objectbuffer
: Buffer used to copy the objectbuffer_size
: The size of the buffer
- Return
- PULL_SUCCESS on success or a specific error otherwise.
-
pull_error
read_firmware_manifest
(mem_object_t *obj, manifest_t *mt)¶ Read the manifest of the memory object.
This function will use the size specified in the s firmware manifest to correcly copy the firmware.
- Return
- PULL_SUCCESS on success or a specific error otherwise.
- Parameters
obj_t
: The memory object where the manifeset is stored.mt
: manifest of the memory object.
-
pull_error
write_firmware_manifest
(mem_object_t *obj_t, const manifest_t *mt)¶ Write the manifest into the memory object.
- Return
- PULL_SUCCESS on success or a specific error otherwise.
- Parameters
obj_t
: The memory object where the manifeset must be stored.mt
: The manifest to be written.
-
pull_error
invalidate_object
(mem_id_t id, mem_object_t *obj_t)¶ Invalidate a memory object.
- Return
- Parameters
id
: Id of the object to invalidate.obj_t
: temporary variable used to open the object.
Network API¶
async_interface connection_config connection_interface gatt receiver subscriber writer
Async Interface¶
-
void
loop_once
(conn_ctx *ctx, uint32_t timeout)¶ Loop one time and return.
- Parameters
ctx
: An already initialized connection context.timeout
: Time to wait before returning if no event is scheduled.
Connection Interface¶
-
enum
net_connint
::
rest_method
¶ Verbs supported by the REST connection
Values:
-
GET
¶
-
PUT
¶ Standard REST verb
-
POST
¶ Standard REST verb
-
DELETE
¶ Standard REST verb
-
OPTIONS
¶ Standard REST verb
-
GET_BLOCKWISE2
¶ Standard REST verb CoAP specific method
-
-
typedef enum rest_method
rest_method
¶ Verbs supported by the REST connection
-
pull_error
conn_init
(conn_ctx *ctx, const char *addr, uint16_t port, conn_type type, void *data)¶ Init the connection context. This functions initialize the connection context and start the connection with the backend specified in the parameters.
- Return
- PULL_SUCCESS if success or the specific error otherwise.
- Parameters
ctx
:addr
: Backend address.port
: Backend port.type
: Connection type. This enumberation can be defined in the interface implementation. (i.e., TCP, UDP, DTLS, etc..);data
: This raw pointer stores data for the specific connection type (e.g., keys for a DTLS connection).
-
pull_error
conn_on_data
(conn_ctx *ctx, callback handler, void *more)¶ Set the callback to be called in case of a new event. The callback is related to the connection and not the single request. This means that all the requests must be handled by the same callback. This is not a problem in the way the connection module is used by the library because each connection is used just for a specific operation and, in case you want to reuse a connection, you must be sure that all the previous requests has been satisfied.
- Return
- PULL_SUCCESS if the callback has been correcly setted.
- Parameters
ctx
: An alredy initialized connection context.handler
: The callback handler.more
: A pointer that will be passed to the callback every time it is called.
-
pull_error
conn_request
(conn_ctx *ctx, rest_method method, const char *resource, const char *data, uint16_t length)¶ Perform a request to the backend. The request is performed using the method, the resource and the data specified.
- Return
- PULL_SUCCESS if the request was correcly sent or the specific error otherwise.
- Parameters
ctx
: An already initialized connection context.method
: The rest method to perform the request (e.g., GET, PUT, etc).resource
: The REST resource.data
: (Optional) The data to be sent or NULL.length
: The lenght of the data or 0;
Gatt¶
-
pull_error
libpull_gatt_init
()¶
-
LIBPULL_SERVICE_UUID
¶
-
LIBPULL_VERSION_UUID
¶
-
LIBPULL_PLATFORM_UUID
¶
-
LIBPULL_IDENTITY_UUID
¶
-
LIBPULL_UPDATE_UUID
¶
-
LIBPULL_STATE_UUID
¶
-
LIBPULL_RESULT_UUID
¶
-
LIBPULL_RECEIVED_UUID
¶
-
LIBPULL_IMAGE_UUID
¶
Receiver¶
-
typedef struct receiver_ctx
receiver_ctx
¶ Receiver context used to hold data for the receiver function
-
pull_error
receiver_open
(receiver_ctx *ctx, conn_ctx *conn, identity_t *identity, const char *resource, mem_object_t *obj)¶ Open the receiver context. This function start the connection with the backend. It uses a connection object to communicate with it and needs a string rappresenting the resource we want to receive from the backend. It stores the received content into a memory object.
- Return
- PULL_SUCCESS in case the receiver was correcly initialized or the specific error otherwise.
- Parameters
ctx
: The receiver context that should be passed to every receiver function.conn
: The connection object. It must be already initialized.identity
: The device identity used for this particular requestresource
: The resource we want to download from the backend.obj
: Memory object used to store the received data. It must be opened.
-
pull_error
receiver_chunk
(receiver_ctx *ctx)¶ Receive and store a chunk of the update into the memory object.
- Return
- PULL_SUCCESS in case the chunk was correcly downloaded and stored or the specific error otherwise.
- Parameters
ctx
: The previously initialized receiver context.
-
pull_error
receiver_close
(receiver_ctx *ctx)¶ Close the receiver context and close the connection with the server.
- Return
- PULL_SUCCESS in case the context was correcly closed or the specific error otherwise.
- Parameters
ctx
: The receiver context to close.
-
RECEIVER_H_
¶
-
MESSAGE_VERSION
¶
-
struct
receiver_ctx
- #include <receiver.h>
Receiver context used to hold data for the receiver function
Subscriber¶
-
void
subscriber_cb
(pull_error err, const char *data, int len, void *more)¶ Callback handling the data received by the server. There is a default implementation of this callback but it can be overridden by the user by passing its own callback to the check update function.
- Note
- The more parameter can be useful to pass receive some structure from the function creating the trasnport when the callback is called.
- Parameters
err
: Error received by the calling function. PULL_SUCCESS if no error.data
: Data received by the network. NULL if error.len
: Lenght of the received data. 0 if error.more
: Pointer passed during initialization of the connection object.
-
pull_error
subscribe
(subscriber_ctx *ctx, conn_ctx *conn, const char *resource, mem_object_t *obj_t)¶ Subscribe to a backend for updates. This functions initialize the subsciber context and subscribe to a specific resource. It requires an already initialized connection context.
- Return
- PULL_SUCCESS on success or the specific error otherwise.
- Parameters
ctx
: A pointer to the subscriber.conn
: An already opended connection object.resource
: A string rapresenting the resource.obj_t
: A temporary memory object.
-
pull_error
check_updates
(subscriber_ctx *ctx, callback cb)¶ Check the presence of an update. This blocking function perform a request to the specified backend and resource and handles the response using the provided callback. An already defined callback is provided with the library, however you can define your own logic matching the protocol used in your server.
- Return
- PULL_SUCCESS on success or the specific error otherwise.
- Parameters
ctx
: An already initialized ubscriber context.cb
: The callback.
-
pull_error
unsubscribe
(subscriber_ctx *ctx)¶ Unsubscribe from the backend. This function closes context, however does not closes the connection tham must be closed by the caller.
- Return
- PULL_SUCCESS on success or the specific error otherwise.
- Parameters
ctx
: The subscriber context to close.
Writer¶
-
typedef pull_error (*
validate_mt
)(manifest_t *mt, void *user_data)¶
-
typedef struct writer_ctx_t
writer_ctx_t
¶
-
pull_error
writer_open
(writer_ctx_t *ctx, mem_object_t *obj, validate_mt cb, void *user_data)¶
-
pull_error
writer_chunk
(writer_ctx_t *ctx, const char *data, uint32_t len)¶
-
pull_error
writer_close
(writer_ctx_t *ctx)¶
-
WRITER_BUFFER_LEN
¶
Security API¶
Digest¶
-
typedef union digest_ctx
digest_ctx
¶
-
struct
digest_func
¶ - #include <digest.h>
Abstraction to use different cryptographic libraries to calculate the hash
SHA 256¶
-
SHA256_INIT
(lib)¶
-
SHA256_UPDATE
(lib)¶
-
SHA256_FINAL
(lib)¶
-
DIGEST_FUNC
(lib)¶ This struct defines a set of default digest function. You can define your own structure adding the function you need.
Verifier¶
-
safestore_t
-
pull_error
verify_object
(mem_object_t *obj, digest_func f, const uint8_t *x, const uint8_t *y, ecc_func_t ef, uint8_t *buffer, size_t buffer_len)¶ This function verifies the signature on the object id.
The digest function and the curve must match the one used to generate the signature stored into the memory object metadata. The ECC signature does not accept any format to reduce space used to store keys.
- Note
- The size of the buffer must be greather or equal to the chunk of manifest to be hashed
- Return
- PULL_SUCCESS if verification succeded or the specific error otherwise.
- Parameters
obj_t
: The memory object to validatef
: The digest function.x
: The X parameter of the signer’s public key.y
: The Y parameter of the signer’s public key.curve
: The curve parameters.buffer
: The buffer used to read data from the objectbuffer_len
: The size of the buffer
- union
Public Members
-
uint8_t struct::x[32]
-
uint8_t struct::y[32]
-
Agents API¶
Bootloader Agent¶
-
enum
ag_bl
::
agent_event_t
¶ Events returned by the bootloader agent.
Values:
-
EVENT_INIT
= 0¶
-
EVENT_CONTINUE_START_
¶
-
EVENT_BOOT
¶
-
EVENT_VALIDATE_BOOTABLE_START
¶
-
EVENT_VALIDATE_BOOTABLE_STOP
¶
-
EVENT_BOOTSTRAP
¶
-
EVENT_FIRST_BOOT
¶
-
EVENT_GET_NEWEST_FIRMWARE
¶
-
EVENT_GET_NEWEST_NON_BOOTABLE
¶
-
EVENT_STORE_BOOTLAODER_CTX
¶
-
EVENT_UPGRADE
¶
-
EVENT_UPGRADE_COPY_START
¶
-
EVENT_UPGRADE_COPY_STOP
¶
-
EVENT_UPGRADE_SUCCESS
¶
-
EVENT_VALIDATE_NON_BOOTABLE
¶
-
EVENT_VALIDATE_NON_BOOTABLE_START
¶
-
EVENT_VALIDATE_NON_BOOTABLE_STOP
¶
-
EVENT_CONTINUE_STOP_
¶
-
EVENT_FAILURE_START_
¶
-
EVENT_VALIDATE_BOOTABLE_FAILURE
¶
-
EVENT_BOOTSTRAP_FAILURE
¶
-
EVENT_BOOTSTRAP_FAILURE_2
¶
-
EVENT_FATAL_FAILURE
¶
-
EVENT_FIRST_BOOT_FAILURE
¶
-
EVENT_GET_NEWEST_FIRMWARE_FAILURE
¶
-
EVENT_GET_NEWEST_FIRMWARE_FAILURE_2
¶
-
EVENT_GET_NEWEST_NON_BOOTABLE_FAILURE
¶
-
EVENT_STORE_BOOTLAODER_CTX_FAILURE
¶
-
EVENT_UPGRADE_COPY_FAILURE
¶
-
EVENT_UPGRADE_FAILURE
¶
-
EVENT_UPGRADE_FAILURE_2
¶
-
EVENT_UPGRADE_FAILURE_3
¶
-
EVENT_UPGRADE_FAILURE_4
¶
-
EVENT_VALIDATE_NON_BOOTABLE_INVALID
¶
-
EVENT_VALIDATE_NON_BOOTABLE_FAILURE
¶
-
EVENT_FAILURE_STOP_
¶
-
EVENT_FINISH
¶
-
EVENT_INIT
= 0
-
EVENT_CONTINUE_START_
-
EVENT_SUBSCRIBE
¶
-
EVENT_CHECKING_UPDATES
¶
-
EVENT_CHECKING_UPDATES_TIMEOUT
¶
-
EVENT_SEARCHING_SLOT
¶
-
EVENT_CONN_RECEIVER
¶
-
EVENT_RECEIVE
¶
-
EVENT_VERIFY
¶
-
EVENT_FINAL
¶
-
EVENT_APPLY
¶
-
EVENT_VERIFY_BEFORE
¶
-
EVENT_VERIFY_AFTER
¶
-
EVENT_CONTINUE_END_
¶
-
EVENT_SEND_START_
¶
-
EVENT_CHECKING_UPDATES_SEND
¶
-
EVENT_RECEIVE_SEND
¶
-
EVENT_SEND_END_
¶
-
EVENT_RECOVER_START_
¶
-
EVENT_CHECKING_UPDATES_RECOVER
¶
-
EVENT_RECEIVE_RECOVER
¶
-
EVENT_RECOVER_END_
¶
-
EVENT_FAILURE_START_
-
EVENT_INIT_FAILURE
¶
-
EVENT_SUBSCRIBE_FAILURE
¶
-
EVENT_SEARCHING_SLOT_FAILURE
¶
-
EVENT_SEARCHING_SLOT_FAILURE_2
¶
-
EVENT_CONN_RECEIVER_FAILURE
¶
-
EVENT_CONN_RECEIVER_FAILURE_2
¶
-
EVENT_VERIFY_FAILURE
¶
-
EVENT_INVALIDATE_OBJECT_FAILURE
¶
-
EVENT_FAILURE_END_
¶
-
-
typedef enum agent_event_t
agent_event_t
¶ Events returned by the bootloader agent.
-
typedef struct bootloader_agent_config
bootloader_agent_config
¶ Configuration structure of the bootloader agent.
-
static void
bootloader_agent_vendor_keys
(bootloader_agent_config *cfg, uint8_t *x, uint8_t *y)¶ Function to configure the vendor keys.
- Parameters
cfg
: Pointer to configuration structure.x
: X component of the vendor key.y
: Y component of the vendor key.
-
static void
bootloader_agent_digest_func
(bootloader_agent_config *cfg, digest_func df)¶ Function to configure the bootloader digest function.
- Parameters
cfg
: Pointer to the configuration structure.df
: Digest function to be used. (For a list of them check the security/digest documentation).
-
static void
bootloader_agent_ecc_func
(bootloader_agent_config *cfg, ecc_func_t ef)¶ Function to configure the bootloader ECC function.
- Parameters
cfg
: Pointer to the configuration structure.ef
: ECC function to be used. (For a list of them check the security/ecc documentation).
-
static void
bootloader_agent_set_buffer
(bootloader_agent_config *cfg, uint8_t *buffer, size_t buffer_size)¶ Function to set the buffer used by the bootloader.
- Note
- When using flash memory, to optimize the IO performance the buffer size should be equal to the page size of your flash memory.
- Parameters
cfg
: Pointer to the configuration structure.buffer
: Pointer to the buffer.buffer_size
: Size of the buffer.
-
agent_event_t
bootloader_agent
(bootloader_agent_config *cfg, void **event_data)¶ Function executing the bootloader agent. This function is internally build with a set of coroutines that stops the function at different execution points. This allows you to perform several actions during according to the returned phase, to check for errors and try to recover, etc.
- Return
- Structure indicating the current state and the data related to it.
- Parameters
cfg
: Pointer to the configuration structure.
-
IS_CONTINUE
(event)¶
-
IS_FAILURE
(event)¶
-
GET_BOOT_ID
(event_data)¶
-
GET_ERROR
(event_data)¶
-
FOREACH_IGNORED_EVENT
(ACTION)¶
-
struct
bootloader_agent_config
- #include <bootloader_agent.h>
Configuration structure of the bootloader agent.
Update Agent¶
-
enum
ag_update
::
agent_event_t
¶ This states will be used by the update agent coroutines.
Values:
-
EVENT_INIT
= 0¶
-
EVENT_CONTINUE_START_
¶
-
EVENT_BOOT
¶
-
EVENT_VALIDATE_BOOTABLE_START
¶
-
EVENT_VALIDATE_BOOTABLE_STOP
¶
-
EVENT_BOOTSTRAP
¶
-
EVENT_FIRST_BOOT
¶
-
EVENT_GET_NEWEST_FIRMWARE
¶
-
EVENT_GET_NEWEST_NON_BOOTABLE
¶
-
EVENT_STORE_BOOTLAODER_CTX
¶
-
EVENT_UPGRADE
¶
-
EVENT_UPGRADE_COPY_START
¶
-
EVENT_UPGRADE_COPY_STOP
¶
-
EVENT_UPGRADE_SUCCESS
¶
-
EVENT_VALIDATE_NON_BOOTABLE
¶
-
EVENT_VALIDATE_NON_BOOTABLE_START
¶
-
EVENT_VALIDATE_NON_BOOTABLE_STOP
¶
-
EVENT_CONTINUE_STOP_
¶
-
EVENT_FAILURE_START_
¶
-
EVENT_VALIDATE_BOOTABLE_FAILURE
¶
-
EVENT_BOOTSTRAP_FAILURE
¶
-
EVENT_BOOTSTRAP_FAILURE_2
¶
-
EVENT_FATAL_FAILURE
¶
-
EVENT_FIRST_BOOT_FAILURE
¶
-
EVENT_GET_NEWEST_FIRMWARE_FAILURE
¶
-
EVENT_GET_NEWEST_FIRMWARE_FAILURE_2
¶
-
EVENT_GET_NEWEST_NON_BOOTABLE_FAILURE
¶
-
EVENT_STORE_BOOTLAODER_CTX_FAILURE
¶
-
EVENT_UPGRADE_COPY_FAILURE
¶
-
EVENT_UPGRADE_FAILURE
¶
-
EVENT_UPGRADE_FAILURE_2
¶
-
EVENT_UPGRADE_FAILURE_3
¶
-
EVENT_UPGRADE_FAILURE_4
¶
-
EVENT_VALIDATE_NON_BOOTABLE_INVALID
¶
-
EVENT_VALIDATE_NON_BOOTABLE_FAILURE
¶
-
EVENT_FAILURE_STOP_
¶
-
EVENT_FINISH
¶
-
EVENT_INIT
= 0
-
EVENT_CONTINUE_START_
-
EVENT_SUBSCRIBE
¶
-
EVENT_CHECKING_UPDATES
¶
-
EVENT_CHECKING_UPDATES_TIMEOUT
¶
-
EVENT_SEARCHING_SLOT
¶
-
EVENT_CONN_RECEIVER
¶
-
EVENT_RECEIVE
¶
-
EVENT_VERIFY
¶
-
EVENT_FINAL
¶
-
EVENT_APPLY
¶
-
EVENT_VERIFY_BEFORE
¶
-
EVENT_VERIFY_AFTER
¶
-
EVENT_CONTINUE_END_
¶
-
EVENT_SEND_START_
¶
-
EVENT_CHECKING_UPDATES_SEND
¶
-
EVENT_RECEIVE_SEND
¶
-
EVENT_SEND_END_
¶
-
EVENT_RECOVER_START_
¶
-
EVENT_CHECKING_UPDATES_RECOVER
¶
-
EVENT_RECEIVE_RECOVER
¶
-
EVENT_RECOVER_END_
¶
-
EVENT_FAILURE_START_
-
EVENT_INIT_FAILURE
¶
-
EVENT_SUBSCRIBE_FAILURE
¶
-
EVENT_SEARCHING_SLOT_FAILURE
¶
-
EVENT_SEARCHING_SLOT_FAILURE_2
¶
-
EVENT_CONN_RECEIVER_FAILURE
¶
-
EVENT_CONN_RECEIVER_FAILURE_2
¶
-
EVENT_VERIFY_FAILURE
¶
-
EVENT_INVALIDATE_OBJECT_FAILURE
¶
-
EVENT_FAILURE_END_
¶
-
-
typedef enum agent_event_t
agent_event_t
This states will be used by the update agent coroutines.
-
typedef struct update_agent_ctx_t
update_agent_ctx_t
¶ Context of the update agent.
-
conn_config_t
update_agent_config
::
subscriber
¶
-
conn_config_t
update_agent_config
::
receiver
¶
-
uint8_t
update_agent_config
::
reuse_connection
¶
-
identity_t
update_agent_config
::
identity
¶
-
uint8_t *
update_agent_config
::
vendor_x
¶
-
uint8_t *
update_agent_config
::
vendor_y
¶
-
ecc_func_t
update_agent_config
::
ef
¶
-
uint8_t *
update_agent_config
::
buffer
¶
-
size_t
update_agent_config
::
buffer_size
¶
-
conn_ctx
update_agent_ctx_t
::
sconn
¶
-
subscriber_ctx
update_agent_ctx_t
::
sctx
¶
-
conn_ctx
update_agent_ctx_t
::
rconn
¶
-
mem_object_t
update_agent_ctx_t
::
new_obj
¶
-
mem_object_t
update_agent_ctx_t
::
obj_t
¶
-
pull_error
update_agent_ctx_t
::
err
¶
-
static void
update_agent_reuse_connection
(update_agent_config *cfg, uint8_t reuse)¶ The update agents connects to the subscription server and the provisioning server. If the connection to both server should be done with the same connection than the connection must be reused.
- Parameters
cfg
: Pointer to the configuration structure.reuse
: Boolean indicating if the connection should be reused (1 to reuse).
-
static void
update_agent_set_identity
(update_agent_config *cfg, identity_t identity)¶ Function to set the device identity used to identify the device with the server.
- Parameters
cfg
: Pointer to the configuration structure.identity
: Identity structure.
-
static void
update_agent_vendor_keys
(update_agent_config *cfg, uint8_t *x, uint8_t *y)¶ Function to set the vendor keys.
- Parameters
cfg
: Pointer to the configuration structure.x
: The X component of the vendor key.y
: The Y component of the vendor key.
-
static void
update_agent_digest_func
(update_agent_config *cfg, digest_func df)¶ Function to set the digest function.
- Parameters
cfg
: Pointer to the configuration structure.df
: Digest function to be used. (To see all the available digest functions check the documentation at security/digest).
-
static void
update_agent_ecc_func
(update_agent_config *cfg, ecc_func_t ef)¶ Function to set the ECC function.
- Parameters
cfg
: Pointer to the configuration structure.ef
: ECC function to be used. (To see all the available ECC functions check the documentation at security/ECC).
-
static void
update_agent_set_buffer
(update_agent_config *cfg, uint8_t *buffer, size_t buffer_size)¶ Function to set the buffer for the update agent.
- Parameters
cfg
: Pointer to the configuration structure.buffer
: Pointer to the buffer.buffer_size
: Size of the buffer.
-
agent_event_t
update_agent
(update_agent_config *cfg, update_agent_ctx_t *ctx, void **agent_data)¶ Function to execute the update agent. This function will return several times, each time with a different message indicating the state of the update agent. In this way you can interact with the update agent modifying the states.
- Return
- Messages containing the current event.
- Parameters
cfg
: Pointer to the configuration structure.ctx
: Pointer to the update agent context.
-
AGENTS_UPDATE_H_
¶
-
IS_CONTINUE
(agent_event)
-
IS_SEND
(agent_event)¶
-
IS_RECOVER
(agent_event)¶
-
IS_FAILURE
(agent_event)
-
GET_CONNECTION
(event_data)¶
-
GET_ERROR
(event_data)
-
FOREACH_IGNORED_EVENT
(ACTION)
-
struct
update_agent_config
¶ - #include <update.h>
Configuration structure for the update agent.
-
struct
update_agent_ctx_t
- #include <update.h>
Context of the update agent.
Utils¶
Bootloader Context¶
-
uint8_t
bootloader_ctx
::
vendor_key
[64]¶
-
uint8_t
bootloader_ctx
::
buffer
[63]¶
-
uint8_t
bootloader_ctx
::
startup_flags
¶
-
LIBPULL_AGENTS_BOOTLOADER_CTX_H_
¶
-
FIRST_BOOT
¶
-
struct
bootloader_ctx
¶ - #include <bootloader_ctx.h>
Structure of the bootloader context. It is used to store data used by the bootloader.