The Xedge C Code for Embedded Devices

Xedge simplifies both the design and production of embedded applications using the Barracuda App Server. Xedge provides a flexible Lua development platform that easily transitions into production, with C code ready for use as-is. Xedge also offers optional customization, while adding value through integrated services and streamlined production capabilities.

This document explains how to build the Xedge C code. Using Xedge for development is explained in a separate document.

The Xedge Embedded Web Server

Figure 1: Xedge components

Xedge's Lua application, which is embedded in the executable, is best visualized as Lua firmware. The Lua firmware is a compressed ZIP file stored inside the executable and made available to the Lua Virtual Machine via the ZipIo.

Compiling Xedge

The following section includes generic build instructions. We provide two ready to use RTOS build projects you may download and use:

Include the following files in your build:

How to Compile the Xedge C Code

Figure 2: Xedge build process

Note: While Xedge is primarily designed to be embedded as a library within programs like firmware, it can also be compiled and tested as a standalone process on Windows and Linux. The Embedded Web Server vs. Embedded App Server video demonstrates how to achieve this.

Files to include:

The file XedgeZip.c must be created by running Xedge.cmd (Windows) or Xedge.sh (Linux). See the BAS-Resources's build directory for details. The file XedgeZip.c is a C array containing the zipped Xedge's resource files (Xedge.zip) and is mounted by the C startup code as a ZipIo and used by the Lua VM's required 'vmio'. We recommend studying the content of the produced ZIP file to get an understanding of the various resource files.

In addition to including the above files in your IDE/Makefile, add the required include path and #define directives as required by your platform. See the readme file builds/[platform]/readme.txt for details.

The file system plugin xrc/DiskIo/[platform]/BaFile.c is optional and may be included if you have a file system in the device and if a suitable porting-layer is found in xrc/DiskIo/ directory. You must add #define NO_BAIO_DISK in your IDE/Makefile if you do not plan on using a file system. See the DiskIo documentation for details.

Required functions:
The Xedge C code (xedge/src/xedge.c) calls the two following functions:

These two functions are typically included in your Lua bindings C code. The two functions are required but can typically be left empty for the initial device porting test. See the header file xedge/src/xedge.h and the example C code led.c for more information.

Enabling Additional Features

BAS Amalgamated (BAS.c) includes features that are by default not compiled. These features can be enabled by the following macros. The macros can be enabled on any platform, including RTOS, unless stated otherwise.

The following macros are required if you plan on using the Let's Encrypt plugin. The macros are pre-set for the following ports: POSIX (Linux/QNX), Windows, VxWorks, and INtime.

All porting layers, except POSIX and Windows, have the following pre-set:

The above construction makes Lua use integer only and excludes floating point numbers. To re-enable floating point, compile the code with:

See inc/arch/XXX/luaconf.h for details.

Using a Network File System During Development

Xedge includes the NetIo, which is handy to use during development and a requirement when developing code on an embedded device without native file system support (when the Xedge C code is compiled with NO_BAIO_DISK). When using the Xedge NetIo client, make sure to start a NetIo File Server on your host computer prior to booting the embedded device. See the Xedge NetIo Tutorial for details on using the Xedge NetIo client and the Mako Server to set up a network file system.

Xedge's Configuration File

Xedge keeps the configured application(s) and their state in an encrypted JSON file. If Xedge is compiled with file system support, this file is persistently stored on the embedded file system. if Xedge is compiled with the macro NO_BAIO_DISK, the JSON file is stored in the browser using Web Storage.

Note:

The Lua application(s) startup state differs between a device with and without file system support:

Interfacing with Device Hardware through Lua Bindings

To interact with the device hardware from Lua through the embedded system's C code, you'll need to create custom Lua bindings. You can find instructions for setting these up in the Getting Started Guide and the Advanced Lua Bindings section. The Xedge code base includes two examples: led.c (basic) and AsynchLua.c (advanced). In addition, you can use SWIG (Simplified Wrapper and Interface Generator), a tool that automates the process of creating bindings between Lua and C/C++ code. The tool generates C or C++ Lua bindings based on header files.

Time

When using a secure TLS client connection, such as HTTPS, or enabling the Automatic Certificate Management feature, the device must either have a clock with the correct time or fetch the time from the Internet using SNTP. The Barracuda App Server relies on the time returned by the platform-dependent function baGetUnixTime() defined in TargConfig.h. This header may also include baSetUnixTime(), which SNTP code can use when updating the time on a device without a clock. When updating the clock using SNTP, send the "sntp" event upon setting the time. See the Events below for details.

Events

Xedge features an event mechanism via the Lua function xedge.event() that allows simple events to be sent asynchronously from C code to Lua code. The Xedge Lua code includes the following global Lua function, which the C code can call to distribute events throughout the system:

function _XedgeEvent(eventname,...)

Lua code can subscribe to events, and the Lua callbacks are triggered when the C code calls _XedgeEvent. The arguments can be any value, with one exception: Xedge requires the "sntp" event to activate Automatic Certificate Management if the device does not include a clock. To activate the certificate management, the C code must call _XedgeEvent("sntp"); see time above for details.

See Send Events Via The Lua Thread Library for more information on how to call _XedgeEvent from C code.

Additional Configuration and Security Settings

ZipBinPwd.h - Global AES ZIP Password

This optional header file enables setting a global password for all ZIP files loaded by the server. See method io:setpasswd() for details on AES encrypted ZIP files. The header file is included if the code is compiled with the USE_ZIPBINPWD macro. The data for this header file is typically created by using binpwd2str.

ZipPublicKey.h - Enforcing Signed ZIP Files

BAS enhances the security of your deployed Lua applications by supporting signed ZIP files, a feature especially valuable for Xedge Multipart Systems. For an overview, refer to the Signed and Encrypted ZIP Files tutorial. To enable this feature, compile Xedge with the USE_ZIPSIGNATURE macro and update ZipPublicKey.h.

When enforcing signed ZIP files, all ZIPs, both embedded and external, must be signed, including Xedge.zip. This means the XedgeZip.c file generated by running Xedge.cmd or Xedge.sh cannot be used as-is. Instead, follow these steps to sign the ZIP file:

signzip.sh Xedge.zip private_key.pem
bin2c -z getLspZipReader Xedge.zip XedgeZip.c

Keep in mind, this feature prevents unsigned ZIP files from being loaded in production environments, but does not restrict a user with IDE access from creating or modifying non-deployed applications. See Creating a Release Build for how to secure or remove the IDE.

SharkTrustX - Automatic Certificate Management

The generated C file tokengen.c includes SharkTrustX settings for the demo portal x.realtimelogic.com. If you're using your own SharkTrustX portal, replace tokengen.c with the version generated by your portal. To exclude this code, compile with the NO_SHARKTRUST macro. Do not include tokengen.c in your build, as xedge.c includes it as a header file.

EncryptionKey.h

The server includes a soft TPM (Trusted Platform Module), as detailed below. To exclude the TPM functionality, compile with the NO_ENCRYPTIONKEY macro.

soft Trusted Platform Module

A Trusted Platform Module (TPM) is a device security chip for storing encryption keys and ensuring system integrity. It enhances security through hardware-based encryption and system checks.

Xedge incorporates a sophisticated soft TPM, which, when properly configured, closely mirrors the security capabilities of a physical TPM chip. This integration ensures robust encryption and system integrity, offering a high level of security akin to hardware-based TPM solutions. The Xedge soft TPM streamlines security processes by removing the necessity for extra hardware, offering a simple yet efficient solution. However, it specializes exclusively in managing Elliptic Curve Cryptography (ECC) keys, focusing its capabilities on this advanced cryptographic system.

The soft TPM in Xedge enhances security through multiple layers, relying on additional measures like signed binaries and restricting firmware modifications to trusted sources. While these steps bolster security, the system maintains a reasonable level of security even without them.

See the TPM API for details.

TPM details and required configuration:

The src/EncryptionKey.h file contains a binary random number of variable lengths the product designer (you) sets. This number, consistent across all firmware copies, is supplemented with extra entropy to generate unique keys for each device. A common method for this is utilizing eFuse registers. If the device has a true random number generator, the eFuse is programmed during the first power-up. In its absence, eFuse registers are configured during manufacturing. Instead of modifying EncryptionKey.h, you may create a new file called NewEncryptionKey.h and compile with the NewEncryptionKey macro.

The Xedge Lua firmware, embedded as a ZIP file within the device, contains exclusive Lua code not accessible externally. This code integrates random numbers generated by the C code at startup. From these, a pre-master secret is calculated. This secret forms the basis for generating multiple unique secrets, instrumental in creating ECC keys. These keys are regenerated with each system restart, significantly reducing the risk of key leakage.

Suggested compile time macros:

The following is recommended to secure the pre-master key in a release build. The Macro USE_DBGMON should not be set, and the code should be compiled with macro NO_LDEBUG set to disable the Lua debug API.

Creating a Release Build

Xedge is designed to offer a range of options for creating a release build, from straightforward to more advanced methods. In this section, we'll explore several of these methods. It's important to note that all of these methods are based on utilizing the C code file xedge.c as-is. Any custom C code you may need would be placed in a separate C file, such as the example file led.c. The focus here is on how to assemble Lua and other resources included in your build.

Monolithic Firmware Release vs. Multipart System

Before exploring the various options, let's distinguish between creating a product with a single component and multiple components. You've worked with multiple components during the development of your Lua application(s) with Xedge. For a release, you can either maintain this multipart system or consolidate everything into a monolithic component that embeds all of your Lua code within the firmware.

Benefits and Drawbacks of a Multipart System

The primary advantage of a multipart system is its efficiency in over-the-air (OTA) upgrades. Since the Barracuda App Server is a mature product, firmware upgrades will likely be infrequent. Most OTA updates will be focused on your applications, and the multipart system ensures these updates are lightweight and fast, making it much faster to update a ZIP file than the larger Xedge firmware.

However, the initial setup of a multipart system involves a multi-step process, including flashing the Xedge firmware and uploading the configuration file and applications. With Xedge32 (the ESP32 port of Xedge), we've simplified this process by enabling the assembly of all components into a single package. This is achieved by pre-populating the embedded file system as part of the firmware installation. For more details, refer to the Xedge32 partition page.

Embedding everything into a monolithic release eliminates the need for a device file system.

Release Build Options

In the following section, we’ll explore several release build options. These options are flexible - you can mix and match them to suit your needs. Option 1, including the Xedge IDE in your build, provides the most straightforward option as you do not need to understand any of the details regarding the files that are included in the resource file Xedge.zip (XedgeZip.c). This option is recommended if you include a web interface. If your device does not include a web interface, options 2 and 3 below may be worthwhile.

  1. Use the Default Xedge Resources (recommended):
  2. Use the Xedge Engine with Customized Resources:

    Remove any unused resource files, including the IDE, to streamline your build.

  3. Fully Customize the Build:

    Replace the default resources entirely and build XedgeZip.c using exclusively your own files.

Below are examples for each build option:

Option 1: Use the Default Xedge Resources

This option is the recommended method as it is the most straightforward. Assuming you have Xedge already running on your device, start by zipping together all your applications, as explained in the Creating a COTS Product section, which describes how to create a multipart system.

Creating a monolithic build with the application ZIP files and the Xedge configuration file embedded in the firmware requires a few more steps.

Start by converting all deployed applications (ZIP files) to C code using the tool bin2c, e.g.

bin2c -z myapp myapp.zip myapp.c

In the function xedgeOpenAUX, install the ZIP file as follows:

balua_installZIO(aux->L, "myapp", myapp());

See the C code example led.c, which includes an embedded ZIP file and an example showing how to install the ZIP file.

Build your system and download it to the target, or build it for HLOS. We must use the Xedge UI to create or modify your Xedge configuration file. Using a browser, navigate to the Xedge UI, right-click the NET file system, and select "New app" to open the application dialog. In the Name field, enter "myapp" or the name you used when calling balua_installZIO. In the URL field, enter $. This unique combination tells Xedge to mount the embedded ZIP file. Configure the rest of the fields as required by your application.

When you have everything configured, download the Xedge configuration file xcfg.bin or use the xcfg.bin produced on your host operating system if you performed this step on a HLOS such as Windows or Linux. Convert xcfg.bin to C code as follows:

bin2c -d xedgeCfg xcfg.bin xedgeCfg.h

The next step is to create a driver for the content of this header file. The example led.c explains how to make a read/write driver, but we only need one to read the content.

#include <xedgeCfg.h>

static int loadXedgeCfgFile(lua_State* L)
{
   if (lua_isstring(L, 1)) /* Write */
      lua_pushnil(L); /* Driver is in read only mode */
   else /* Read */
      lua_pushlstring(L, (char*)xedgeCfg, sizeof(xedgeCfg));
   return 1;
}

int xedgeOpenAUX(XedgeOpenAUX* aux)
{
   balua_installZIO(aux->L, "myapp", myapp());
   /* Install Xedge config file driver */
   aux->xedgeCfgFile = loadXedgeCfgFile;
}

Preparing option 2 and 3

To test the two setups discussed below, compile the code for your host platform, such as Windows or Linux, as demonstrated in the App Server vs. Web Server video. However, when compiling, include the compile-time macro -DBAIO_DISK_PATH=resources. This directive tells the xedge.c C code not to use the ZIP driver in XedgeZip.c, but instead to load resources from the "resources" directory. In other words, xedge.c will mount and use the "resources" directory on your host computer as the main IO (the VM IO).

Option 2: Use the Xedge Engine with Customized Resources

In this example, we retain the Xedge engine along with all its services and APIs but remove the web-based editor and all resources not used by Xedge. Additionally, we eliminate all modules, such as MQTT, from the resources/test/Xedge/.lua directory. You can reintroduce these components as needed.

Below is the minimum set of files required by the Xedge engine. The engine will throw an exception if any of these files are missing:

|   .config -- Xedge standalone startup code
|
+---.certificate
|       cacert.shark -- Enabling trust for client TLS connection
|           -- Key and cert used by the default HTTPS object
|       device_RSA_2048.key
|       device_RSA_2048.pem
|
\---.lua
    |   xedge.lua -- The Xedge engine
    |   404.html
    |   EventEmitter.lua
    |   jwt.lua
    |   rwfile.lua
    |   tpm.lua -- softTPM
    |   wfs.lua
    |
    \---acme -- The Let's encrypt plugin must be included even if not used
    |       bot.lua
    |       dns.lua
    |       engine.lua
    |       log.lua
    |       rtoken.lua
    \---XedgePlugins -- Add your Lua files here

Testing the Setup

  1. Compile the Code: Use the instructions provided above.
  2. Create the "resources" Directory: In the directory where your compiled binary resides, create a subdirectory named "resources".
  3. Prepare the Resources Directory: Unzip Xedge.zip in the resource directory and delete all files in this directory except for the files listed above.
  4. Test the Multipart Option: This option functions identically to the Multipart setup in Option 1. However, because an Xedge configuration file is necessary to start your separate application(s), only delete the files once you have used the Xedge IDE to create your configuration file.
  5. Test the Monolithic Option: Add Lua files that must be executed at startup in the XedgePlugins directory (*). You do not need to create a configuration file if all of your code is embedded in the VM IO. However, you need a configuration file if you intend to embed additional ZIP files, as explained in Option 1.

(*) Any file you add to the XedgePlugins directory will be executed by the Xedge engine at startup. From this point, you can start building your application. Add Lua modules you have developed to the parent directory, i.e., to resources/test/Xedge/.lua.

Option 3: Fully Customize the Build

With this approach, you forgo the benefits provided by the Xedge engine, such as the secure vault user database and the softTPM.

Follow these steps:

  1. Compile the Code: Use the instructions provided above.
  2. Create the "resources" Directory: In the directory where your compiled binary resides, create a subdirectory named "resources".
  3. Create .config: Inside the "resources" directory, create an empty file named .config. This file serves as the main entry point when xedge.c starts the Lua code.
  4. Run the Compiled Code: On the command line, start the compiled code.

At this point, you should encounter the following error: .config error: no start server or onunload. To resolve this, open the .config file in an editor, add the code below, and save the file. Re-run the compiled code, and the error should no longer appear.

local vmio=ba.openio"vm" -- this is currently our "resources" directory

local function start()
   -- Your startup code goes here.
   -- Example code:
   ba.timer(function() trace(ba.datetime"NOW") return true end):set(1000,true,true)
end

local function initialize(x)
   if true == x or nil == x then
      start()
   end
   -- Ignore all other arguments
end

local function onunload()
   -- not used
end

return initialize,onunload -- Send to C code. The C code will then call initialize()

Figure 3: Xedge .config script example

At a minimum, the .config script must have an initialize and on-unload function. Depending on the compile flags, the C code may call the initialize function multiple times. For example, the key material for the pre-master key is inserted into the initialize function. We ignore the parameters in this code and look for two possible start conditions where the provided argument is either true or nil, depending on xedge.c compile-time macros. See the original .config file for details.

The tutorial Rapid Firmware Development provides a good introduction to using option 3 for creating an IoT device.