Skip to content

Commit aca6764

Browse files
authored
Merge pull request #190 from NordicSemiconductor/feature/suit-push
Feature: Adding support for pushing cache images for SUIT updates
2 parents dee5992 + 1785347 commit aca6764

File tree

23 files changed

+856
-109
lines changed

23 files changed

+856
-109
lines changed

mcumgr-core/src/main/java/io/runtime/mcumgr/dfu/mcuboot/model/ImageSet.java

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,49 @@
33
import android.util.Pair;
44

55
import org.jetbrains.annotations.NotNull;
6+
import org.jetbrains.annotations.Nullable;
67

78
import java.util.ArrayList;
89
import java.util.List;
910

11+
import io.runtime.mcumgr.dfu.suit.model.CacheImage;
1012
import io.runtime.mcumgr.exception.McuMgrException;
1113

12-
/** @noinspection unused*/
14+
/**
15+
* Represents a set of images to be sent to the device using teh Image group (manager).
16+
* <p>
17+
* The Image manager can be used to update devices with MCUboot and SUIT bootloaders.
18+
* For SUIT bootloaders a dedicated SUIT manager should be used, but some devices support both
19+
* or only Image manager (e.g. for recovery).
20+
* @noinspection unused
21+
*/
1322
public class ImageSet {
23+
/**
24+
* List of target images to be sent to the device.
25+
* <p>
26+
* A device with MCUboot bootloader can have multiple images. Each image is identified by
27+
* an image index. Images must be sent with the "image" parameter set to the image index.
28+
* When all images are sent the client should send "test" or "confirm" command to confirm them
29+
* and "reset" command to begin the update.
30+
*/
1431
@NotNull
1532
private final List<TargetImage> images;
1633

34+
/**
35+
* Cache images are used to update devices supporting SUIT manifest. The cache images are
36+
* sent after the SUIT manifest and contain parts of firmware that are not included in the
37+
* manifest.
38+
* <p>
39+
* In case the cache images are not null, {@link #images} must contain a single SUIT file.
40+
* <p>
41+
* Flow:
42+
* 1. Send .suit file ("image" is set to 0 (default))
43+
* 2. Send cache images, each with "image" parameter set to the partition ID.
44+
* 3. Send "confirm" command (without hash) to begin the update.
45+
*/
46+
@Nullable
47+
private List<CacheImage> cacheImages;
48+
1749
/**
1850
* Creates an empty image set. Use {@link #add(TargetImage)} to add targets.
1951
*/
@@ -37,27 +69,52 @@ public List<TargetImage> getImages() {
3769
return images;
3870
}
3971

72+
@Nullable
73+
public List<CacheImage> getCacheImages() {
74+
return cacheImages;
75+
}
76+
77+
@NotNull
4078
public ImageSet add(TargetImage binary) {
4179
images.add(binary);
4280
return this;
4381
}
4482

83+
@NotNull
84+
public ImageSet add(CacheImage cacheImage) {
85+
if (cacheImages == null) {
86+
cacheImages = new ArrayList<>();
87+
}
88+
cacheImages.add(cacheImage);
89+
return this;
90+
}
91+
92+
@NotNull
93+
public ImageSet set(List<CacheImage> cacheImages) {
94+
this.cacheImages = cacheImages;
95+
return this;
96+
}
97+
98+
@NotNull
4599
public ImageSet add(byte[] image) throws McuMgrException {
46100
images.add(new TargetImage(image));
47101
return this;
48102
}
49103

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

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

117+
@NotNull
61118
public ImageSet removeImagesWithImageIndex(int imageIndex) {
62119
for (int i = 0; i < images.size(); i++) {
63120
if (images.get(i).imageIndex == imageIndex) {

mcumgr-core/src/main/java/io/runtime/mcumgr/dfu/mcuboot/task/Confirm.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.runtime.mcumgr.dfu.mcuboot.task;
22

33
import org.jetbrains.annotations.NotNull;
4+
import org.jetbrains.annotations.Nullable;
45
import org.slf4j.Logger;
56
import org.slf4j.LoggerFactory;
67

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

21-
private final byte @NotNull [] hash;
22+
private final byte @Nullable [] hash;
2223

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

28+
Confirm() {
29+
this.hash = null;
30+
}
31+
2732
@Override
2833
@NotNull
2934
public State getState() {
@@ -47,21 +52,24 @@ public void onResponse(@NotNull final McuMgrImageStateResponse response) {
4752
performer.onTaskFailed(Confirm.this, new McuMgrErrorException(response.getReturnCode()));
4853
return;
4954
}
50-
// Search for slot for which the confirm command was sent and check its status.
51-
for (final McuMgrImageStateResponse.ImageSlot slot : response.images) {
52-
if (Arrays.equals(slot.hash, hash)) {
53-
if (slot.permanent || slot.confirmed) {
54-
performer.onTaskCompleted(Confirm.this);
55-
} else {
56-
performer.onTaskFailed(Confirm.this, new McuMgrException("Image not confirmed."));
55+
56+
// MCUboot returns the list of images in the response.
57+
final McuMgrImageStateResponse.ImageSlot[] images = response.images;
58+
if (images != null) {
59+
// Search for slot for which the confirm command was sent and check its status.
60+
for (final McuMgrImageStateResponse.ImageSlot slot : images) {
61+
if (Arrays.equals(slot.hash, hash)) {
62+
if (slot.permanent || slot.confirmed) {
63+
performer.onTaskCompleted(Confirm.this);
64+
} else {
65+
performer.onTaskFailed(Confirm.this, new McuMgrException("Image not confirmed."));
66+
}
67+
return;
5768
}
58-
return;
5969
}
6070
}
61-
62-
// Some implementations do not report all primary slots.
63-
// performer.onTaskFailed(Confirm.this, new McuMgrException("Confirmed image not found."));
64-
71+
// SUIT implementation of Image manager does not return images from Confirm command.
72+
// Instead, the device will reset and the new image will be booted.
6573
performer.onTaskCompleted(Confirm.this);
6674
}
6775

mcumgr-core/src/main/java/io/runtime/mcumgr/dfu/mcuboot/task/Validate.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@
55
import org.slf4j.LoggerFactory;
66

77
import java.util.Arrays;
8+
import java.util.List;
89

910
import io.runtime.mcumgr.McuMgrCallback;
1011
import io.runtime.mcumgr.dfu.mcuboot.FirmwareUpgradeManager.Mode;
1112
import io.runtime.mcumgr.dfu.mcuboot.FirmwareUpgradeManager.Settings;
1213
import io.runtime.mcumgr.dfu.mcuboot.FirmwareUpgradeManager.State;
1314
import io.runtime.mcumgr.dfu.mcuboot.model.ImageSet;
1415
import io.runtime.mcumgr.dfu.mcuboot.model.TargetImage;
16+
import io.runtime.mcumgr.dfu.suit.model.CacheImage;
1517
import io.runtime.mcumgr.exception.McuMgrErrorException;
1618
import io.runtime.mcumgr.exception.McuMgrException;
1719
import io.runtime.mcumgr.image.ImageWithHash;
20+
import io.runtime.mcumgr.image.SUITImage;
1821
import io.runtime.mcumgr.managers.DefaultManager;
1922
import io.runtime.mcumgr.managers.ImageManager;
2023
import io.runtime.mcumgr.response.dflt.McuMgrBootloaderInfoResponse;
@@ -238,6 +241,10 @@ public void onResponse(@NotNull final McuMgrImageStateResponse response) {
238241
}
239242
}
240243
if (!mcuMgrImage.needsConfirmation()) {
244+
// Since nRF Connect SDK v.2.8 the SUIT image requires no confirmation.
245+
if (mcuMgrImage instanceof SUITImage) {
246+
performer.enqueue(new Confirm());
247+
}
241248
continue;
242249
}
243250
if (allowRevert && mode != Mode.NONE) {
@@ -287,6 +294,15 @@ public void onResponse(@NotNull final McuMgrImageStateResponse response) {
287294
}
288295
}
289296
}
297+
298+
// Enqueue uploading all cache images.
299+
final List<CacheImage> cacheImages = images.getCacheImages();
300+
if (cacheImages != null) {
301+
for (final CacheImage cacheImage : cacheImages) {
302+
performer.enqueue(new Upload(cacheImage.image, cacheImage.partitionId));
303+
}
304+
}
305+
290306
// To make sure the reset command are added just once, they're added based on flags.
291307
if (initialResetRequired) {
292308
performer.enqueue(new ResetBeforeUpload(noSwap));

mcumgr-core/src/main/java/io/runtime/mcumgr/dfu/suit/SUITUpgradeManager.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.runtime.mcumgr.dfu.FirmwareUpgradeCallback;
1212
import io.runtime.mcumgr.dfu.FirmwareUpgradeController;
1313
import io.runtime.mcumgr.dfu.FirmwareUpgradeSettings;
14+
import io.runtime.mcumgr.dfu.suit.model.CacheImageSet;
1415

1516
/** @noinspection unused*/
1617
public class SUITUpgradeManager implements FirmwareUpgradeController {
@@ -142,9 +143,28 @@ public void setResourceCallback(@Nullable final OnResourceRequiredCallback resou
142143
* Start the upgrade.
143144
* <p>
144145
* This method should be used for SUIT candidate envelopes files.
146+
*
147+
* @param settings the firmware upgrade settings.
148+
* @param envelope the SUIT candidate envelope.
145149
*/
146150
public synchronized void start(@NotNull final FirmwareUpgradeSettings settings,
147151
final byte @NotNull [] envelope) {
152+
start(settings, envelope, null);
153+
}
154+
155+
/**
156+
* Start the upgrade.
157+
* <p>
158+
* This method should be used for SUIT candidate envelopes files.
159+
*
160+
* @param settings the firmware upgrade settings.
161+
* @param envelope the SUIT candidate envelope.
162+
* @param cacheImages cache images to be uploaded together with the SUIT envelope before
163+
* starting the update.
164+
*/
165+
public synchronized void start(@NotNull final FirmwareUpgradeSettings settings,
166+
final byte @NotNull [] envelope,
167+
@Nullable final CacheImageSet cacheImages) {
148168
if (mPerformer.isBusy()) {
149169
LOG.info("Firmware upgrade is already in progress");
150170
return;
@@ -154,7 +174,8 @@ public synchronized void start(@NotNull final FirmwareUpgradeSettings settings,
154174
mInternalCallback.onUpgradeStarted(this);
155175
final SUITUpgradePerformer.Settings performerSettings =
156176
new SUITUpgradePerformer.Settings(settings, mResourceCallback);
157-
mPerformer.start(mTransport, performerSettings, envelope);
177+
178+
mPerformer.start(mTransport, performerSettings, envelope, cacheImages);
158179
}
159180

160181
//******************************************************************

mcumgr-core/src/main/java/io/runtime/mcumgr/dfu/suit/SUITUpgradePerformer.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.runtime.mcumgr.McuMgrTransport;
99
import io.runtime.mcumgr.dfu.FirmwareUpgradeCallback;
1010
import io.runtime.mcumgr.dfu.FirmwareUpgradeSettings;
11+
import io.runtime.mcumgr.dfu.suit.model.CacheImageSet;
1112
import io.runtime.mcumgr.dfu.suit.task.PerformDfu;
1213
import io.runtime.mcumgr.dfu.suit.task.SUITUpgradeTask;
1314
import io.runtime.mcumgr.exception.McuMgrException;
@@ -51,9 +52,10 @@ SUITUpgradeManager.State getState() {
5152

5253
void start(@NotNull final McuMgrTransport transport,
5354
@NotNull final Settings settings,
54-
final byte @NotNull [] envelope) {
55+
final byte @NotNull [] envelope,
56+
@Nullable final CacheImageSet cacheImageSet) {
5557
LOG.trace("Starting SUIT upgrade");
56-
super.start(transport, settings, new PerformDfu(envelope));
58+
super.start(transport, settings, new PerformDfu(envelope, cacheImageSet));
5759
}
5860

5961
@Override
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.runtime.mcumgr.dfu.suit.model;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
5+
/** @noinspection unused*/
6+
public class CacheImage {
7+
8+
/** Target partition ID. */
9+
public final int partitionId;
10+
11+
/**
12+
* The image.
13+
*/
14+
public final byte @NotNull [] image;
15+
16+
/**
17+
* A wrapper for a partition cache raw image and the ID of the partition.
18+
*
19+
* @param partition the partition ID.
20+
* @param data the signed binary to be sent.
21+
*/
22+
public CacheImage(int partition, byte @NotNull [] data) {
23+
this.partitionId = partition;
24+
this.image = data;
25+
}
26+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package io.runtime.mcumgr.dfu.suit.model;
2+
3+
import android.util.Pair;
4+
5+
import org.jetbrains.annotations.NotNull;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
10+
/** @noinspection unused*/
11+
public class CacheImageSet {
12+
@NotNull
13+
private final List<CacheImage> images;
14+
15+
/**
16+
* Creates an empty image set. Use {@link #add(CacheImage)} to add targets.
17+
*/
18+
public CacheImageSet() {
19+
this.images = new ArrayList<>(4);
20+
}
21+
22+
/**
23+
* Creates an image set with given targets.
24+
* @param targets image targets.
25+
*/
26+
public CacheImageSet(@NotNull final List<CacheImage> targets) {
27+
this.images = targets;
28+
}
29+
30+
/**
31+
* Returns list of targets.
32+
*/
33+
@NotNull
34+
public List<CacheImage> getImages() {
35+
return images;
36+
}
37+
38+
public CacheImageSet add(CacheImage image) {
39+
images.add(image);
40+
return this;
41+
}
42+
43+
public CacheImageSet add(int partition, byte[] image) {
44+
images.add(new CacheImage(partition, image));
45+
return this;
46+
}
47+
48+
public CacheImageSet add(Pair<Integer, byte[]> image) {
49+
images.add(new CacheImage(image.first, image.second));
50+
return this;
51+
}
52+
53+
public CacheImageSet add(List<Pair<Integer, byte[]>> images) {
54+
for (Pair<Integer, byte[]> image : images)
55+
this.images.add(new CacheImage(image.first, image.second));
56+
return this;
57+
}
58+
}

0 commit comments

Comments
 (0)