Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Adding support for pushing cache images for SUIT updates #190

Merged
merged 15 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,49 @@
import android.util.Pair;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;

import io.runtime.mcumgr.dfu.suit.model.CacheImage;
import io.runtime.mcumgr.exception.McuMgrException;

/** @noinspection unused*/
/**
* Represents a set of images to be sent to the device using teh Image group (manager).
* <p>
* The Image manager can be used to update devices with MCUboot and SUIT bootloaders.
* For SUIT bootloaders a dedicated SUIT manager should be used, but some devices support both
* or only Image manager (e.g. for recovery).
* @noinspection unused
*/
public class ImageSet {
/**
* List of target images to be sent to the device.
* <p>
* A device with MCUboot bootloader can have multiple images. Each image is identified by
* an image index. Images must be sent with the "image" parameter set to the image index.
* When all images are sent the client should send "test" or "confirm" command to confirm them
* and "reset" command to begin the update.
*/
@NotNull
private final List<TargetImage> images;

/**
* Cache images are used to update devices supporting SUIT manifest. The cache images are
* sent after the SUIT manifest and contain parts of firmware that are not included in the
* manifest.
* <p>
* In case the cache images are not null, {@link #images} must contain a single SUIT file.
* <p>
* Flow:
* 1. Send .suit file ("image" is set to 0 (default))
* 2. Send cache images, each with "image" parameter set to the partition ID.
* 3. Send "confirm" command (without hash) to begin the update.
*/
@Nullable
private List<CacheImage> cacheImages;

/**
* Creates an empty image set. Use {@link #add(TargetImage)} to add targets.
*/
Expand All @@ -37,27 +69,52 @@ public List<TargetImage> getImages() {
return images;
}

@Nullable
public List<CacheImage> getCacheImages() {
return cacheImages;
}

@NotNull
public ImageSet add(TargetImage binary) {
images.add(binary);
return this;
}

@NotNull
public ImageSet add(CacheImage cacheImage) {
if (cacheImages == null) {
cacheImages = new ArrayList<>();
}
cacheImages.add(cacheImage);
return this;
}

@NotNull
public ImageSet set(List<CacheImage> cacheImages) {
this.cacheImages = cacheImages;
return this;
}

@NotNull
public ImageSet add(byte[] image) throws McuMgrException {
images.add(new TargetImage(image));
return this;
}

@NotNull
public ImageSet add(Pair<Integer, byte[]> image) throws McuMgrException {
images.add(new TargetImage(image.first, image.second));
return this;
}

@NotNull
public ImageSet add(List<android.util.Pair<Integer, byte[]>> images) throws McuMgrException {
for (Pair<Integer, byte[]> image : images)
this.images.add(new TargetImage(image.first, image.second));
return this;
}

@NotNull
public ImageSet removeImagesWithImageIndex(int imageIndex) {
for (int i = 0; i < images.size(); i++) {
if (images.get(i).imageIndex == imageIndex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.runtime.mcumgr.dfu.mcuboot.task;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -18,12 +19,16 @@
class Confirm extends FirmwareUpgradeTask {
private final static Logger LOG = LoggerFactory.getLogger(Confirm.class);

private final byte @NotNull [] hash;
private final byte @Nullable [] hash;

Confirm(final byte @NotNull [] hash) {
this.hash = hash;
}

Confirm() {
this.hash = null;
}

@Override
@NotNull
public State getState() {
Expand All @@ -47,21 +52,24 @@ public void onResponse(@NotNull final McuMgrImageStateResponse response) {
performer.onTaskFailed(Confirm.this, new McuMgrErrorException(response.getReturnCode()));
return;
}
// Search for slot for which the confirm command was sent and check its status.
for (final McuMgrImageStateResponse.ImageSlot slot : response.images) {
if (Arrays.equals(slot.hash, hash)) {
if (slot.permanent || slot.confirmed) {
performer.onTaskCompleted(Confirm.this);
} else {
performer.onTaskFailed(Confirm.this, new McuMgrException("Image not confirmed."));

// MCUboot returns the list of images in the response.
final McuMgrImageStateResponse.ImageSlot[] images = response.images;
if (images != null) {
// Search for slot for which the confirm command was sent and check its status.
for (final McuMgrImageStateResponse.ImageSlot slot : images) {
if (Arrays.equals(slot.hash, hash)) {
if (slot.permanent || slot.confirmed) {
performer.onTaskCompleted(Confirm.this);
} else {
performer.onTaskFailed(Confirm.this, new McuMgrException("Image not confirmed."));
}
return;
}
return;
}
}

// Some implementations do not report all primary slots.
// performer.onTaskFailed(Confirm.this, new McuMgrException("Confirmed image not found."));

// SUIT implementation of Image manager does not return images from Confirm command.
// Instead, the device will reset and the new image will be booted.
performer.onTaskCompleted(Confirm.this);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.List;

import io.runtime.mcumgr.McuMgrCallback;
import io.runtime.mcumgr.dfu.mcuboot.FirmwareUpgradeManager.Mode;
import io.runtime.mcumgr.dfu.mcuboot.FirmwareUpgradeManager.Settings;
import io.runtime.mcumgr.dfu.mcuboot.FirmwareUpgradeManager.State;
import io.runtime.mcumgr.dfu.mcuboot.model.ImageSet;
import io.runtime.mcumgr.dfu.mcuboot.model.TargetImage;
import io.runtime.mcumgr.dfu.suit.model.CacheImage;
import io.runtime.mcumgr.exception.McuMgrErrorException;
import io.runtime.mcumgr.exception.McuMgrException;
import io.runtime.mcumgr.image.ImageWithHash;
import io.runtime.mcumgr.image.SUITImage;
import io.runtime.mcumgr.managers.DefaultManager;
import io.runtime.mcumgr.managers.ImageManager;
import io.runtime.mcumgr.response.dflt.McuMgrBootloaderInfoResponse;
Expand Down Expand Up @@ -238,6 +241,10 @@ public void onResponse(@NotNull final McuMgrImageStateResponse response) {
}
}
if (!mcuMgrImage.needsConfirmation()) {
// Since nRF Connect SDK v.2.8 the SUIT image requires no confirmation.
if (mcuMgrImage instanceof SUITImage) {
performer.enqueue(new Confirm());
}
continue;
}
if (allowRevert && mode != Mode.NONE) {
Expand Down Expand Up @@ -287,6 +294,15 @@ public void onResponse(@NotNull final McuMgrImageStateResponse response) {
}
}
}

// Enqueue uploading all cache images.
final List<CacheImage> cacheImages = images.getCacheImages();
if (cacheImages != null) {
for (final CacheImage cacheImage : cacheImages) {
performer.enqueue(new Upload(cacheImage.image, cacheImage.partitionId));
}
}

// To make sure the reset command are added just once, they're added based on flags.
if (initialResetRequired) {
performer.enqueue(new ResetBeforeUpload(noSwap));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.runtime.mcumgr.dfu.FirmwareUpgradeCallback;
import io.runtime.mcumgr.dfu.FirmwareUpgradeController;
import io.runtime.mcumgr.dfu.FirmwareUpgradeSettings;
import io.runtime.mcumgr.dfu.suit.model.CacheImageSet;

/** @noinspection unused*/
public class SUITUpgradeManager implements FirmwareUpgradeController {
Expand Down Expand Up @@ -142,9 +143,28 @@ public void setResourceCallback(@Nullable final OnResourceRequiredCallback resou
* Start the upgrade.
* <p>
* This method should be used for SUIT candidate envelopes files.
*
* @param settings the firmware upgrade settings.
* @param envelope the SUIT candidate envelope.
*/
public synchronized void start(@NotNull final FirmwareUpgradeSettings settings,
final byte @NotNull [] envelope) {
start(settings, envelope, null);
}

/**
* Start the upgrade.
* <p>
* This method should be used for SUIT candidate envelopes files.
*
* @param settings the firmware upgrade settings.
* @param envelope the SUIT candidate envelope.
* @param cacheImages cache images to be uploaded together with the SUIT envelope before
* starting the update.
*/
public synchronized void start(@NotNull final FirmwareUpgradeSettings settings,
final byte @NotNull [] envelope,
@Nullable final CacheImageSet cacheImages) {
if (mPerformer.isBusy()) {
LOG.info("Firmware upgrade is already in progress");
return;
Expand All @@ -154,7 +174,8 @@ public synchronized void start(@NotNull final FirmwareUpgradeSettings settings,
mInternalCallback.onUpgradeStarted(this);
final SUITUpgradePerformer.Settings performerSettings =
new SUITUpgradePerformer.Settings(settings, mResourceCallback);
mPerformer.start(mTransport, performerSettings, envelope);

mPerformer.start(mTransport, performerSettings, envelope, cacheImages);
}

//******************************************************************
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.runtime.mcumgr.McuMgrTransport;
import io.runtime.mcumgr.dfu.FirmwareUpgradeCallback;
import io.runtime.mcumgr.dfu.FirmwareUpgradeSettings;
import io.runtime.mcumgr.dfu.suit.model.CacheImageSet;
import io.runtime.mcumgr.dfu.suit.task.PerformDfu;
import io.runtime.mcumgr.dfu.suit.task.SUITUpgradeTask;
import io.runtime.mcumgr.exception.McuMgrException;
Expand Down Expand Up @@ -51,9 +52,10 @@ SUITUpgradeManager.State getState() {

void start(@NotNull final McuMgrTransport transport,
@NotNull final Settings settings,
final byte @NotNull [] envelope) {
final byte @NotNull [] envelope,
@Nullable final CacheImageSet cacheImageSet) {
LOG.trace("Starting SUIT upgrade");
super.start(transport, settings, new PerformDfu(envelope));
super.start(transport, settings, new PerformDfu(envelope, cacheImageSet));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.runtime.mcumgr.dfu.suit.model;

import org.jetbrains.annotations.NotNull;

/** @noinspection unused*/
public class CacheImage {

/** Target partition ID. */
public final int partitionId;

/**
* The image.
*/
public final byte @NotNull [] image;

/**
* A wrapper for a partition cache raw image and the ID of the partition.
*
* @param partition the partition ID.
* @param data the signed binary to be sent.
*/
public CacheImage(int partition, byte @NotNull [] data) {
this.partitionId = partition;
this.image = data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.runtime.mcumgr.dfu.suit.model;

import android.util.Pair;

import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;

/** @noinspection unused*/
public class CacheImageSet {
@NotNull
private final List<CacheImage> images;

/**
* Creates an empty image set. Use {@link #add(CacheImage)} to add targets.
*/
public CacheImageSet() {
this.images = new ArrayList<>(4);
}

/**
* Creates an image set with given targets.
* @param targets image targets.
*/
public CacheImageSet(@NotNull final List<CacheImage> targets) {
this.images = targets;
}

/**
* Returns list of targets.
*/
@NotNull
public List<CacheImage> getImages() {
return images;
}

public CacheImageSet add(CacheImage image) {
images.add(image);
return this;
}

public CacheImageSet add(int partition, byte[] image) {
images.add(new CacheImage(partition, image));
return this;
}

public CacheImageSet add(Pair<Integer, byte[]> image) {
images.add(new CacheImage(image.first, image.second));
return this;
}

public CacheImageSet add(List<Pair<Integer, byte[]>> images) {
for (Pair<Integer, byte[]> image : images)
this.images.add(new CacheImage(image.first, image.second));
return this;
}
}
Loading