Skip to content

Gateway improvements v2#281

Draft
mniestroj wants to merge 10 commits into
mainfrom
gateway-improvements-v2
Draft

Gateway improvements v2#281
mniestroj wants to merge 10 commits into
mainfrom
gateway-improvements-v2

Conversation

@mniestroj

Copy link
Copy Markdown
Collaborator

No description provided.

mniestroj added 10 commits July 16, 2026 15:36
Read CONFIG_EXAMPLE_FW_UPDATE_COMPONENT from the DUT's generated
.config instead of hardcoding "ci_ota_fw" in the ota_firmware fixture,
so the pytest side always agrees with whatever component name the C
side was actually built with.

The lookup uses the runners.core.BuildConfiguration helper from
zephyr/scripts/west_commands and twister_harness.helpers.domains_helper
to pick the default sysbuild domain's build directory.

Signed-off-by: Marcin Niestroj <marcin.niestroj@canonical.com>
The refactor to the pouch CoAP stack replaced the previously unbounded
k_fifo_put() used to hand blocks from the CoAP producer to the pouch
consumer with a fixed-size pouch_msgq_put() called with POUCH_NO_WAIT.
Whenever the BLE bearer could not drain fast enough to keep up with the
CoAP block delivery rate, the very next block_cb returned -ENOMEM, which
in turn made pouch_gateway_cloud_forward_pouch() propagate the error and
abort the entire downlink transfer.

This is easy to hit with real workloads such as an OTA firmware download
proxied to a BLE peripheral, where the incoming CoAP rate is fast
compared to the outgoing GATT throughput.

Restore the pre-rewrite semantics by waiting up to
CONFIG_POUCH_GATEWAY_DOWNLINK_BLOCK_TIMEOUT seconds for a free slot in
the queue, the same timeout that is already used to acquire a buffer
from the pouch_buf pool a few lines above. The CoAP producer thread now
applies backpressure to the transport while the consumer (BLE bearer)
catches up, instead of failing the whole transfer on the first burst.

Fixes: 3fc341c ("gateway: rewrite over pouch coap stack")
Signed-off-by: Marcin Niestroj <marcin.niestroj@canonical.com>
Mirror what coap_client and http_client already do: add a second
firmware-update mode that hashes the incoming OTA payload with SHA256 and
logs the digest instead of writing the data to the MCUboot slot and
rebooting.

Introduce an EXAMPLE_FW_UPDATE_MODE choice between:

  * EXAMPLE_FW_UPDATE_FLASH
  * EXAMPLE_FW_UPDATE_SHA256_VERIFY

Extend the CMakeLists.txt to pick the matching source file. The verifier
itself is a straight copy of coap_client/src/fw_update_test.c with the same
log lines ("OTA computed SHA256: <hex>") that the shared pytest OTA harness
already relies on.

Signed-off-by: Marcin Niestroj <marcin.niestroj@canonical.com>
Add a new pouch.gateway.ota twister scenario that exercises the
Golioth OTA download path end-to-end through the gateway's Pouch BLE
tunnel, using the ble_gatt peripheral (already spawned as a sysbuild
sub-image) as the OTA target device.

The scenario builds the peripheral with the SHA256-verify OTA mode
introduced in the previous commit, so it hashes the downloaded payload
and logs "OTA computed SHA256: <hex>" instead of touching MCUboot slots,
allowing the test to run on nrf52_bsim without a real bootloader flow.

The new test_ota.py mirrors the shared examples/zephyr/pytest/test_ota.py
that coap_client and http_client use: wait for "Credentials loaded",
wait for the SHA256 line, and compare against the digest returned by the
ota_firmware fixture. Because the gateway/pytest/ tree is separate from
the shared examples/zephyr/pytest/ tree (it overrides the dut and
creds_dir fixtures to accommodate the sysbuild BSIM layout), the
test_id, artifacts_to_cleanup, cohort, and ota_firmware fixtures are
copied verbatim, with two small adjustments:

  - fw_update_component reads CONFIG_EXAMPLE_FW_UPDATE_COMPONENT from
    the peripheral image's build (via runners.core.BuildConfiguration
    on <build_dir>/peripheral_ble_gatt_example_0) rather than from the
    default sysbuild domain the shared fixture uses, because the OTA
    target is not the top-level gateway image.
  - ota_firmware generates an 8 KiB payload instead of 400 KiB. The
    BLE-tunneled OTA path is much slower than the direct coap/http_client
    download, and 8 KiB is enough to force multi-block delivery through
    the pouch tunnel while staying well within the twister timeout.

The peripheral is configured with a CI-only overlay
(overlay-ble_gatt-ci-ota.conf) applied via
peripheral_ble_gatt_example_0_EXTRA_CONF_FILE=... in sample.yaml. This
sets CONFIG_EXAMPLE_FW_UPDATE_COMPONENT="ci_ota_fw", matching the
package name coap_client and http_client already use for their CI OTA
artifacts. Overriding via an EXTRA_CONF_FILE rather than a plain
sample.yaml Kconfig assignment is required because twister strips the
outer quotes from sysbuild-image-scoped extra_args entries that don't
start with CONFIG_ / SB_CONFIG_ (see
zephyr/scripts/pylib/twister/twisterlib/runner.py:1711), which would
turn the string into a malformed Kconfig literal.

The two existing settings tests grow a pytest_root pointing at
test_sample.py so twister does not additionally try to collect and run
test_ota.py under them.

Timeouts are declared per test rather than shared through common:
lesc and auth keep their existing 180 s budget (they finish in ~35 s);
the OTA scenario gets 300 s to accommodate the multi-block BLE-tunneled
download.

Signed-off-by: Marcin Niestroj <marcin.niestroj@canonical.com>
…test

Move all fixtures and both tests into examples/zephyr/pytest, and drive
the gateway-vs-direct differences from focused pytest options passed
from each sample.yaml instead of duplicating fixtures per example.

The consolidated conftest exposes three options:

  --gateway-cloud     create the gateway cloud identity fixture
  --target-domain     sysbuild sub-image that is the Pouch DUT
  --ota-image-size    OTA payload size in bytes (default 400 KiB)

sample.yaml usage:

  - gateway / gateway_custom_connect pass --gateway-cloud and
    --target-domain=peripheral_ble_gatt_example_0.
  - pouch.gateway.ota additionally passes --ota-image-size=8192
    because BLE-tunneled OTA is much slower than direct downloads.
  - coap_client and http_client pass no pytest_args and rely on
    defaults.

Notable fixture changes:

  - target_build_dir picks the sysbuild sub-image build tree; both
    creds_dir and build_conf derive from it.
  - dut always uses 'west flash -d <build>', which works for the plain
    native_sim binary just like it does for the gateway's BabbleSim
    sysbuild layout (which spawns every sub-image at once).
  - gateway_creds signs the gateway cert against the shared CA already
    present in creds_dir; skipped when --gateway-cloud is off.
  - setup deletes LED settings on [device, gateway] with None filtered
    out, so direct-client tests reuse the same cleanup path.
  - settings_device becomes 'gateway or device'.
  - BuildConfiguration for the OTA target sub-image goes through
    target_build_dir so it stays correct on both native_sim and
    BabbleSim sysbuild layouts.
  - pytest_dut_scope: module is set inside each scenario's
    harness_config because twister replaces (not deep-merges) that
    map.

Drop the previous --gateway-name / GOLIOTH_GATEWAY_NAME override: no CI
workflow or doc exercises it, and the gateway fixture now always
generates a fresh cloud identity per test run.

Verified with:
  west twister -c -v -p nrf52_bsim/native -T pouch/examples/zephyr/gateway -W
  west twister -c -v -p native_sim/native -T pouch/examples/zephyr/http_client -W
  west twister -c -v -p native_sim/native -T pouch/examples/zephyr/coap_client -W
  west twister --dry-run -p nrf52_bsim/native -T pouch/examples/zephyr/gateway_custom_connect
…als helper

Split the CA generation and the device-cert-signing logic out of the
`creds` fixture so downstream conftests can reuse them.

  - New `ca` module-scope fixture generates ca.key.pem, ca.crt.pem and
    ca.der in creds_dir and returns their paths.
  - New `generate_device_credentials(dest_dir, device_name, project_id,
    ca_key, ca_cert, *, crt_der_name="crt.der", key_der_name="key.der")`
    helper signs a device key + cert against the shared CA and writes
    the device DER files into dest_dir under configurable names.
  - `creds` now depends on `ca` and delegates the device-cert generation
    to generate_device_credentials; it still uploads the CA cert to
    Golioth as before.

The only observable change for existing consumers is an extra ca.der
file in creds_dir, which is unused today but ready for firmware images
that want to load the CA from the filesystem.

Signed-off-by: Marcin Niestroj <marcin.niestroj@canonical.com>
…tials

Rewire gateway_creds to reuse the shared `ca` fixture and
generate_device_credentials helper introduced in the previous commit
instead of inlining the openssl invocations.

The dependency direction becomes:

  ca                   (CA private key + cert; from plugin)
  |-- creds            (peripheral cert; from plugin)
  \`-- gateway_creds    (gateway cert; local)

gateway_creds no longer depends on `creds`, so the two device-cert
fixtures are peers instead of one shadowing the other.

The gateway build directory is still separate from `creds_dir` today,
so gateway_creds copies the CA DER file from `creds_dir` into
`gateway_dir` for the firmware to load.

Signed-off-by: Marcin Niestroj <marcin.niestroj@canonical.com>
Add EXAMPLE_POUCH_DEVICE_CRT_FILENAME and EXAMPLE_POUCH_DEVICE_KEY_FILENAME
Kconfig strings (defaulting to "crt.der" and "key.der") and consume them
in credentials.c instead of hardcoding the filenames. No behavior change
with the defaults; the knobs let a caller point a single shared
credentials directory at per-device filenames later.

Signed-off-by: Marcin Niestroj <marcin.niestroj@canonical.com>
…DER filenames

Add EXAMPLE_POUCH_DEVICE_CRT_FILENAME / EXAMPLE_POUCH_DEVICE_KEY_FILENAME
Kconfig strings (defaulting to "crt.der" / "key.der") in both
gateway/Kconfig and gateway_custom_connect/Kconfig, and consume them in
gateway/src/credentials.c (shared with gateway_custom_connect via
CMakeLists) instead of hardcoding the filenames on the load_certificate
/ load_private_key (pouch identity) path.

The parallel Kconfig knobs for the gateway mTLS identity
(EXAMPLE_COAP_CLIENT_GW_DEVICE_CRT/KEY_FILENAME) already existed and are
untouched here.

No behavior change with the defaults; the knobs let a shared
credentials directory address pouch identities by per-device filenames
later.

Signed-off-by: Marcin Niestroj <marcin.niestroj@canonical.com>
…-device DER filenames

Route both peripheral and gateway sub-images at a single shared
credentials directory so all device certs live next to a single
CA, keyed by per-device DER filenames.

Sysbuild side:

  - SB_CONFIG_PERIPHERAL_MOUNT_CREDS and SB_CONFIG_GATEWAY_MOUNT_CREDS
    change from bool to string: they now carry the host-side directory
    mounted at /creds inside each sub-image. Empty disables the mount.
  - gateway/sysbuild.cmake and gateway_custom_connect/sysbuild.cmake
    consume the string in the -volume=... cmdline arg instead of the
    hardcoded "creds" path, and derive the pouch identity + gateway
    mTLS DER filenames from the sub-image name (e.g.
    "peripheral_ble_gatt_example_0_crt.der", "gateway_crt.der"). This
    keeps the sample.yaml files small: each scenario only sets the
    mount source once.
  - The gateway and gateway_custom_connect sample.yaml files set both
    strings to "../creds", so each sub-image (whose sim runtime cwd is
    its own build tree) resolves the mount at <build>/creds/.

Pytest side:

  - Drop the local creds_dir override; creds_dir now resolves to
    <build>/creds via the plugin default, shared by both sub-images.
  - Override the plugin creds fixture locally so it reads the pouch
    identity DER filenames from the target sub-image build_conf and
    passes them to generate_device_credentials; falls back to the
    plugin defaults (crt.der / key.der) when the Kconfigs are absent
    (direct-client samples).
  - Add gateway_build_dir / gateway_build_conf fixtures and update
    gateway_creds to write into the shared creds_dir under gateway
    filenames read from the gateway sub-image build_conf. ca.der is
    already there from the ca fixture, so gateway_creds no longer
    copies it.

Signed-off-by: Marcin Niestroj <marcin.niestroj@canonical.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant