Xedge is a powerful framework that simplifies both the design phase and the production process of embedded applications using the Barracuda App Server. It offers a flexible Lua development environment that transitions seamlessly into production, which means:
This document explains how to build the Xedge C code. Using Xedge for development is explained in a separate document.
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.
To help clarify the functionality, device integration, and advantages of Xedge, the following two tutorials may be helpful:
The following section includes generic build instructions. We provide two ready to use RTOS build projects you may download and use:
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.
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.
The Xedge C code (xedge/src/xedge.c) calls the two following functions:
int xedgeInitDiskIo(DiskIo* dio);
- example code in led.cint xedgeOpenAUX(XedgeOpenAUX* aux);
- example code in XedgeInitDiskIo.cThese 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 function xedgeOpenAUX() in led.c for more information. The C file xedge/src/XedgeInitDiskIo.c shows how to initialize and set a base-path/mounting-point, using xedgeOpenAUX()
, if required by the file system.
Starting function Barracuda()
In an RTOS environment, you typically create a dedicated thread to run the barracuda() function (Figure 2). This function never returns, and the thread must have a stack that is at least BA_STACKSZ bytes (24000) in most RTOS environments. In addition, two recommended configuration options that must be set using C code are strongly recommended: call function HttpServer_setErrHnd() and HttpTrace_setFLushCallback(). Examples can be found in HostInit.c.
Operating systems such as VxWorks and INTEGRITY supporting the standard C code entry main() can optionally use the two following C code files instead of creating a dedicated thread as explained above. These generic files are used by all examples included with BAS, not just Xedge. However, note that the stack for main() must be as detailed above.
examples/misc/standalone/Main.c
- the main() function, which calls barracuda()examples/misc/standalone/HostInit.c
- Sets an error handler and trace functionBAS 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.
All porting layers, except POSIX, Windows, and INTEGRITY, 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.
MAXTHREADS
MacroBy default, the Xedge C code initializes an environment similar to the default configuration of the Mako Server. It creates two thread pools:
This setup results in a total of 5 threads by default.
For RTOS environments, you will likely want to customize the total number of threads by defining the MAXTHREADS
macro at compile time. When you set MAXTHREADS
, the two pools are merged into a single shared thread pool. The total number of threads will then be equal to the value specified by MAXTHREADS
.
Important: Defining MAXTHREADS
also disables the Lua function ba.thread.create(), preventing the creation of additional threads via Lua scripts.
For detailed information about thread management, refer to the Lua Thread Library documentation and the Thread C code documentation.
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 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:
You must set up custom Lua bindings if you want your Lua code to interact with your device's hardware through the embedded system's existing/new C code. These bindings act as bridges, allowing Lua scripts to call C functions that control hardware or perform other low-level tasks.
The Xedge code base includes two examples to help you get started:
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.
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.
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.
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.
The server includes a soft TPM (Trusted Platform Module), as detailed below. To exclude the TPM functionality, compile with the NO_ENCRYPTIONKEY macro.
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.
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.
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.
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.
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.
Remove any unused resource files, including the IDE, to compact your build.
Replace the default resources entirely and build XedgeZip.c using exclusively your own files.
Below are examples for each build option:
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; }
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).
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
(*) 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.
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:
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()
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.