A file-transfer engine that downloads files described by AWS IoT Job
documents, with bitmap-tracked block retries and progress reporting. Built on
top of crate::jobs.
Two entry points:
Transfer::perform— generic file transfer; needs only aTransferPal.Transfer::perform_ota— OTA firmware update; needs anOtaPal(which extendsTransferPal) and adds self-test, image-state, and reset/activation hooks.
The OTA path is wire-compatible with the Amazon FreeRTOS afr_ota
job-document format produced by AWS's CreateOTAUpdate
control-plane API.
- Receive a job document via
crate::jobsand deserialize it (anOtaJobfor the FreeRTOS format, or your ownJobDocumentimplementation). - Build a
JobContextfrom the parsed execution. - Pick a data interface (MQTT streams or HTTP pre-signed URL) — the engine
negotiates against the
protocolsfield in the job document. - Call
Transfer::perform(generic) orTransfer::perform_ota(firmware). The engine:- drives block requests over the chosen data interface,
- tracks completed blocks via a
Bitmapfor resumption / retry, - reports progress (
IN_PROGRESSwithstatusDetails) back to Jobs, - calls into your
Palto write blocks, finalize, abort, or activate, - publishes a terminal Jobs status on completion or failure.
- (OTA only) After reset, the device re-enters self-test; your
OtaPalaccepts or rejects the new image and reports the outcome.
| Interface | Cargo feature | Notes |
|---|---|---|
| MQTT (IoT Streams) | transfer_mqtt |
Default. Requests blocks over $aws/things/{thing}/streams/{stream}/get/cbor and receives them on .../data/cbor. CBOR-only. |
| HTTP (S3 presigned URL) | transfer_http |
No HTTP client included. Plug your own. |
HTTP via reqwest |
transfer_http_reqwest |
std-only convenience implementation built on reqwest. |
Both interfaces can coexist in one build — the engine picks whichever the job document advertises.
| Format | Type | Notes |
|---|---|---|
Amazon FreeRTOS afr_ota |
OtaJob |
Default for OTA. Compatible with CreateOTAUpdate. |
| Custom | impl JobDocument |
For non-OTA file transfers or custom OTA shapes. |
The wire-format fields (fileid, filesize, streamname, update_data_url,
sig-sha256-ecdsa, …) follow the afr_ota schema 1:1.
The platform abstraction layer (your code) handles flash / filesystem writes and image lifecycle. Two traits, one extending the other:
TransferPal—create_file,write_block,close_file,abort,status_details. All that's needed for generic file transfer.OtaPal— addsset_platform_image_state,get_platform_image_state,activate_new_image,reset_device, plus signature verification hooks. The engine drives theImageStatestate machine (Unknown→Testing→Accepted/Rejected/Aborted).
OTA reasons surface through ImageStateReason and PalError, with
numeric error codes:
| Range | Meaning |
|---|---|
1xxx |
PAL-level errors (signature mismatch, write failure, …) |
2xxx |
Image-state reasons (newer job, version check, user abort, …) |
use rustot::jobs::stream::{JobAgent, parse_job_message};
use rustot::mqtt::{Mqtt, OwnedMessage};
use rustot::transfer::{Transfer, config::Config, encoding::afr_ota::OtaJob, error::TransferError};
async fn run_ota<C: rustot::mqtt::MqttClient, P: rustot::transfer::pal::OtaPal>(
mqtt: Mqtt<&C>,
pal: &mut P,
) -> Result<(), TransferError> {
let agent = JobAgent::new(&mqtt);
let mut sub = agent.subscribe().await.map_err(|_| TransferError::Mqtt)?;
let message = sub.next_message().await.ok_or(TransferError::Mqtt)?;
let mut owned = OwnedMessage::<256, 4096>::from_ref(&message).unwrap();
drop(message);
sub.unsubscribe().await.ok();
let execution = parse_job_message::<OtaJob>(&mut owned)
.ok_or(TransferError::InvalidJobDocument)?;
let ctx = build_job_context(execution)?; // user code, see tests/common
let cfg = Config { block_size: 4096, ..Default::default() };
Transfer::perform_ota(&mqtt, &mqtt, &ctx, pal, &cfg).await
}A complete working PAL is in
tests/common/file_handler.rs.
| Feature | Default | Effect |
|---|---|---|
transfer_mqtt |
yes | Build the MQTT data interface (uses minicbor). |
transfer_http |
no | Build the HTTP data interface (no client baked in). |
transfer_http_reqwest |
no | std-only convenience: HTTP via reqwest + rustls. |
- CBOR only on the MQTT data interface. AWS IoT Streams require CBOR.
- One file per job. The
afr_otaschema allows multiple files but the engine tracks one active transfer at a time. - Self-test policy is delegated to the PAL. The engine drives the state transitions; your code decides whether the new image passes self-test.
Unit tests cover bitmap accounting, block validation, and topic / encoding round-trips:
cargo test --lib transfer::End-to-end OTA against real AWS IoT (tests/ota_mqtt.rs) requires an
identity.pfx under tests/secrets/ and AWS credentials. The test issues a
real CreateOTAUpdate, drives the device through the full job lifecycle,
and asserts cloud-side SUCCEEDED:
RUST_LOG=trace \
THING_NAME=MyTestThing \
AWS_HOSTNAME=xxxxxxxx-ats.iot.eu-west-1.amazonaws.com \
cargo test --test ota_mqtt --features "transfer_mqtt,log"