ESP RainMaker Neo OTA Job Document Format
This document describes the expected format for ESP RainMaker Neo OTA job documents used in AWS IoT Jobs.
Overview
ESP RainMaker Neo OTA job documents extend the standard AWS IoT FreeRTOS OTA (AFR-OTA) job document format with additional ESP RainMaker Neo-specific fields. The job document maintains backward compatibility with AFR-OTA while adding ESP RainMaker Neo-specific configuration options.
Job Document Structure
A complete ESP RainMaker Neo OTA job document contains two main sections:
afr_ota- Required for AFR-OTA compatibilityrmng_ota- ESP RainMaker Neo-specific configuration and metadata
AFR-OTA Section (afr_ota)
This section maintains compatibility with the AWS IoT FreeRTOS OTA agent and follows the standard AFR-OTA job document format:
{
"afr_ota": {
"protocols": ["MQTT"],
"streamname": "string",
"files": [
{
"filepath": "string",
"filesize": number,
"fileid": "string",
"certfile": "string",
"sig-sha256-ecdsa": "string"
}
]
}
}
AFR-OTA Fields
Field |
Type |
Required |
Description |
|---|---|---|---|
|
Array of strings |
Yes |
Supported protocols ( |
|
String |
Yes (if MQTT) |
Name of the AWS IoT stream containing the firmware files (used for MQTT). Must be under the Streamname length limit). |
|
Array of objects |
Yes |
Array of file objects to be downloaded |
File Object Fields
Field |
Type |
Required |
Description |
|---|---|---|---|
|
String |
Yes |
Path where the file should be stored on the device |
|
Number |
Yes |
Size of the file in bytes |
|
String |
Yes |
Unique identifier for the file |
|
String |
No |
Path to certificate file for signature verification |
|
String |
Only when signature verification is enabled |
Base64 signature for the file (algorithm-specific field name). Required, and
must be valid base64, when |
ESP RainMaker Neo OTA Section (rmng_ota)
This section contains ESP RainMaker Neo-specific configuration and metadata:
{
"rmng_ota": {
"filetype": "string",
"fw_version": "string",
"min_fw_version": "string",
"file_md5": "string",
"metadata": {
"key": "value"
},
"download_window": {
"validity": {
"start": number,
"end": number
},
"daily": {
"start": number,
"end": number
}
}
}
}
ESP RainMaker Neo OTA Fields
Field |
Type |
Required |
Description |
|---|---|---|---|
|
String |
No |
|
|
String |
Yes for the default filetype handler; otherwise no, unless |
Target firmware version to be installed. Must match the firmware version embedded in the downloaded image exactly — the device verifies this after download (before reboot) to prevent downgrade-by-lying attacks. Custom filetype handlers that set |
|
String |
No |
Minimum firmware version required for update |
|
String |
No |
Hex MD5 (case-insensitive) of the entire OTA image. When present, enables auto-resume of an interrupted download and an end-to-end MD5 integrity check after download (before reboot). Omit to disable both. |
|
Object |
No |
Additional metadata for the OTA update |
|
Object |
No |
Time window restrictions for the update. Parsed only when
|
Download Window Object
Every field is optional and independently defaulted — a partial download_window is valid, and any field left out simply imposes no restriction.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
Object |
No |
Overall validity period for the update |
|
Number |
No |
Start timestamp (Unix epoch time). Defaults to |
|
Number |
No |
End timestamp (Unix epoch time). Defaults to |
|
Object |
No |
Daily time restrictions within the validity period |
|
Number |
No |
Daily start time in minutes since midnight. Defaults to |
|
Number |
No |
Daily end time in minutes since midnight. Defaults to |
Complete Example
{
"afr_ota": {
"protocols": ["MQTT"],
"streamname": "AFR_OTA-stream-1234567890",
"files": [
{
"filepath": "/firmware.bin",
"filesize": 1048576,
"fileid": "firmware_v1.2.3",
"certfile": "/ota-test-cert",
"sig-sha256-ecdsa": "MEQCIG...signature..."
}
]
},
"rmng_ota": {
"fw_version": "1.2.3",
"min_fw_version": "1.0.0",
"file_md5": "d41d8cd98f00b204e9800998ecf8427e",
"metadata": {
"release_notes": "Bug fixes and performance improvements",
"priority": "high"
},
"download_window": {
"validity": {
"start": 1704067200,
"end": 1706745600
},
"daily": {
"start": 1200,
"end": 1440
}
}
}
}
Implementation Notes
The
afr_otasection is required for compatibility with existing AFR-OTA implementationsThe
rmng_otasection is required and are fields specific to ESP RainMaker Neo. A job document without it is rejected with reason"Missing RMNG-specific fields in job document"(or handed to the custom job-document callback, see below)min_fw_versionrequiresfw_version: a job that declares a minimum without a target version is rejectedDownload windows restrict when the device can perform the OTA update:
validitydefines the overall time period when the update is alloweddailydefines the allowed hours within each day (in minutes since midnight)
All timestamps are in Unix epoch time (seconds since 1970-01-01 00:00:00 UTC)
The device requires
"MQTT"as the firstprotocolsentry and downloads fromstreamname
Protocol ordering (protocols[0])
The device parses the job document with the AWS Jobs OTA parser, which inspects only afr_ota.protocols[0] — it does not search the array for a protocol the device supports:
protocols[0] == "MQTT"→streamnameis read and the firmware is downloaded over MQTT file-streams.Anything else → the parser takes the HTTP branch, which requires
fileType,auth_schemeandupdate_data_urlfields that ESP RainMaker Neo job documents never carry. Parsing fails and the job is REJECTED with reason"Missing AFR-OTA-specific fields in job document".
So ["MQTT"] and ["MQTT", "HTTP"] both work, while ["HTTP", "MQTT"] is rejected even though MQTT appears in the array. Job documents generated by the tooling in tools/ always emit ["MQTT"].
Streamname length limit (MQTT)
When using the MQTT transport, the streamname field is constrained by the vendored AWS MQTT file-streams library. Streamnames longer than this limit are rejected at job-document parse with reason "Image reference is invalid", before any download is attempted.
AWS Jobs wraps the user-supplied jobId into the streamname as AFR_OTA-<jobId> (8-char prefix). Practically, this means jobId length must stay ≤ the limit, minus 8 characters when generating OTA jobs.
A device that receives a job with an oversize streamname reports the job as REJECTED and resumes normal operation; the job is not re-fetched after reboot.
Image Header Verification
After download, before reboot, the device verifies that the binary’s embedded application descriptor matches both the running app’s project name and the job-declared fw_version:
Project name: the downloaded image’s embedded project name must match the running app’s project name exactly. Prevents cross-project flashing (e.g. a
switchbuild landing on alightdevice).Firmware version: the downloaded image’s embedded version must match the job’s
fw_versionfield byte-for-byte. Prevents downgrade-by-lying (job claims2.0.0, binary is actually1.0.0).
Both checks run before the MD5 integrity check and before signature verification, so a mismatched image is rejected even if its signature happens to be valid for some other (downgraded or cross-project) build. A mismatch fails the job with no reboot, reported as "Image header invalid".
Filetype handlers that do not track versions (get_version = NULL on the filetype handler context) may omit fw_version; in that case the handler must implement its own verify_image_header callback (or accept the resulting downgrade exposure by setting it to NULL, which skips verification entirely — strongly discouraged).
Auto-resume
When the job document declares file_md5 (and CONFIG_RMNG_OTA_RESUME is enabled, default y), a download interrupted by reboot/power-loss/connectivity issues resumes from where it left off instead of restarting from byte 0. The partially-written bytes already sit in the inactive app partition; the device only needs to know they belong to this image and how much it already has.
Image identity.
file_md5is the identity token. The device persists a small descriptor (md5, file size, and MQTT block size) to NVS alongside the progress tracker. On a re-fetched in-progress job it resumes only when the descriptor matches the current job exactly. A different image (different md5/size) or a changed MQTT block size forces a fresh full download.Progress tracker. MQTT persists a received-block bitmap (updated on each batch boundary to bound NVS wear).
Integrity. After the download completes, the full-image MD5 is recomputed over the written partition and compared against
file_md5. A mismatch fails the job (see status_details). This is the safety net for resumed bytes, which bypass any single continuous hash, and runs independently of signature verification.Best-effort. Resume never makes a job fail that a normal OTA would have completed: any failure in the resume path (older ESP-IDF without
esp_ota_resume, NVS read error, tracker mismatch, etc.) transparently falls back to a fresh full download.
Custom (non-firmware) filetype handlers manage their own resume — see custom_filetypes.
Custom non-OTA Job Documents
Format
A custom non-OTA job document is defined as a job document missing any required keys to be a valid ESP RainMaker Neo OTA document:
If there are minimal required keys, but some keys have invalid values, this is treated as a malformed ESP RainMaker Neo OTA document and will be rejected instead.
To truly ensure a non-OTA job document, avoid using the
afr_otaandrmng_otakeys at all.
Handlers
If custom job document handling is enabled (CONFIG_RMNG_OTA_CUSTOM_JOB_SUPPORT=y), then:
The user provides a custom job document callback in the OTA configuration.
The user uses the custom job status reporting API to report job statuses.