Skip to content

Commit

Permalink
Merge pull request #155 from NordicSemiconductor/feature/suit
Browse files Browse the repository at this point in the history
[SUIT] Adding UI to display SUIT manifests
  • Loading branch information
philips77 authored May 28, 2024
2 parents 5222fb0 + df68671 commit 666d690
Show file tree
Hide file tree
Showing 11 changed files with 389 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,19 @@ public void listManifests(@NotNull McuMgrCallback<McuMgrManifestListResponse> ca
public void getManifestState(int role, @NotNull McuMgrCallback<McuMgrManifestStateResponse> callback) {
HashMap<String, Object> payloadMap = new HashMap<>();
payloadMap.put("role", role);
send(OP_READ, ID_MANIFEST_STATE, payloadMap, SHORT_TIMEOUT, McuMgrManifestStateResponse.class, callback);
send(OP_READ, ID_MANIFEST_STATE, payloadMap, SHORT_TIMEOUT, McuMgrManifestStateResponse.class, new McuMgrCallback<>() {
@Override
public void onResponse(@NotNull McuMgrManifestStateResponse response) {
// The role isn't returned in the response, so we need to set it manually.
response.role = role;
callback.onResponse(response);
}

@Override
public void onError(@NotNull McuMgrException error) {
callback.onError(error);
}
});
}

/**
Expand All @@ -120,7 +132,9 @@ public void getManifestState(int role, @NotNull McuMgrCallback<McuMgrManifestSta
public McuMgrManifestStateResponse getManifestState(int role) throws McuMgrException {
HashMap<String, Object> payloadMap = new HashMap<>();
payloadMap.put("role", role);
return send(OP_READ, ID_MANIFEST_STATE, payloadMap, SHORT_TIMEOUT, McuMgrManifestStateResponse.class);
final McuMgrManifestStateResponse response = send(OP_READ, ID_MANIFEST_STATE, payloadMap, SHORT_TIMEOUT, McuMgrManifestStateResponse.class);
response.role = role;
return response;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.runtime.mcumgr.response.suit;

import org.jetbrains.annotations.NotNull;

/**
* Known manifest roles.
* This enum is based on <a href="https://github.com/nrfconnect/sdk-nrf/blob/46f6922cce98c9b9ccb69c2458bf57f9bcfb85b9/subsys/suit/metadata/include/suit_metadata.h#L70-L98">suit_metadata.h</a>.
*/
public enum KnownRole {
UNKNOWN(0x00),
/** Manifest describes the entry-point for all Nordic-controlled manifests. */
SEC_TOP(0x10),
/** Manifest describes SDFW firmware and recovery updates. */
SEC_SDFW(0x11),
/** Manifest describes SYSCTRL firmware update and boot procedures. */
SEC_SYSCTRL(0x12),

/** Manifest describes the entry-point for all OEM-controlled manifests. */
APP_ROOT(0x20),
/** Manifest describes OEM-specific recovery procedure. */
APP_RECOVERY(0x21),
/** Manifest describes OEM-specific binaries, specific for application core. */
APP_LOCAL_1(0x22),
/** Manifest describes OEM-specific binaries, specific for application core. */
APP_LOCAL_2(0x23),
/** Manifest describes OEM-specific binaries, specific for application core. */
APP_LOCAL_3(0x24),

/** Manifest describes radio part of OEM-specific recovery procedure. */
RAD_RECOVERY(0x30),
/** Manifest describes OEM-specific binaries, specific for radio core. */
RAD_LOCAL_1(0x31),
/** Manifest describes OEM-specific binaries, specific for radio core. */
RAD_LOCAL_2(0x32);

/** The role value. */
public final int id;

KnownRole(int id) {
this.id = id;
}

/**
* Returns the role as a {@link KnownRole} enum, or null if the role is unknown.
*/
@NotNull
public static KnownRole getOrNull(final int role) {
for (KnownRole knownRole : KnownRole.values()) {
if (knownRole.id == role) {
return knownRole;
}
}
return UNKNOWN;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

import org.jetbrains.annotations.Nullable;

import java.util.List;

import io.runtime.mcumgr.McuMgrCallback;
Expand All @@ -21,60 +19,13 @@
public class McuMgrManifestListResponse extends McuMgrResponse {

public static class Manifest {

/**
* Known manifest roles.
* This enum is based on <a href="https://github.com/nrfconnect/sdk-nrf/blob/46f6922cce98c9b9ccb69c2458bf57f9bcfb85b9/subsys/suit/metadata/include/suit_metadata.h#L70-L98">suit_metadata.h</a>.
* The manifest role.
*
* @see KnownRole
*/
public enum KnownRole {
/** Manifest describes the entry-point for all Nordic-controlled manifests. */
SEC_TOP(0x10),
/** Manifest describes SDFW firmware and recovery updates. */
SEC_SDFW(0x11),
/** Manifest describes SYSCTRL firmware update and boot procedures. */
SEC_SYSCTRL(0x12),

/** Manifest describes the entry-point for all OEM-controlled manifests. */
APP_ROOT(0x20),
/** Manifest describes OEM-specific recovery procedure. */
APP_RECOVERY(0x21),
/** Manifest describes OEM-specific binaries, specific for application core. */
APP_LOCAL_1(0x22),
/** Manifest describes OEM-specific binaries, specific for application core. */
APP_LOCAL_2(0x23),
/** Manifest describes OEM-specific binaries, specific for application core. */
APP_LOCAL_3(0x24),

/** Manifest describes radio part of OEM-specific recovery procedure. */
RAD_RECOVERY(0x30),
/** Manifest describes OEM-specific binaries, specific for radio core. */
RAD_LOCAL_1(0x31),
/** Manifest describes OEM-specific binaries, specific for radio core. */
RAD_LOCAL_2(0x32);

/** The role value. */
public final int id;

KnownRole(int id) {
this.id = id;
}
}

@JsonProperty("role")
public int role;

/**
* Returns the role as a {@link KnownRole} enum, or null if the role is unknown.
*/
@Nullable
public KnownRole getRoleOrNull() {
for (KnownRole knownRole : KnownRole.values()) {
if (knownRole.id == role) {
return knownRole;
}
}
return null;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;

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

import java.nio.ByteBuffer;
Expand All @@ -21,6 +22,13 @@
/** @noinspection unused*/
public class McuMgrManifestStateResponse extends McuMgrResponse {

/**
* The manifest role.
*
* @see KnownRole
*/
public int role;

/**
* Use {@link #getClassId()} to get the class ID as UUID.
*/
Expand All @@ -42,7 +50,7 @@ public class McuMgrManifestStateResponse extends McuMgrResponse {
@JsonProperty("digest")
public byte[] digest;
@JsonProperty("digest_algorithm")
public DigestAlgorithm digest_algorithm;
public DigestAlgorithm digestAlgorithm;
@JsonProperty("signature_check")
public SignatureVerification signatureCheck;
@JsonProperty("sequence_number")
Expand Down Expand Up @@ -96,6 +104,19 @@ public enum DigestAlgorithm {
DigestAlgorithm(int code) {
this.code = code;
}

@NotNull
@Override
public String toString() {
switch (this) {
case SHA_256:
return "SHA-256";
case SHA_512:
return "SHA-512";
default:
return super.toString();
}
}
}

public enum SignatureVerification {
Expand All @@ -109,6 +130,21 @@ public enum SignatureVerification {
SignatureVerification(int code) {
this.code = code;
}

@NotNull
@Override
public String toString() {
switch (this) {
case NOT_CHECKED:
return "Not checked";
case FAILED:
return "Failed";
case PASSED:
return "Passed";
default:
return super.toString();
}
}
}

public enum DowngradePreventionPolicy {
Expand All @@ -125,6 +161,21 @@ public enum DowngradePreventionPolicy {
DowngradePreventionPolicy(int code) {
this.code = code;
}

@NotNull
@Override
public String toString() {
switch (this) {
case DISABLED:
return "Disabled";
case ENABLED:
return "Enabled";
case UNKNOWN:
return "Unknown";
default:
return super.toString();
}
}
}

public enum IndependentUpdateabilityPolicy {
Expand All @@ -141,6 +192,21 @@ public enum IndependentUpdateabilityPolicy {
IndependentUpdateabilityPolicy(int code) {
this.code = code;
}

@NotNull
@Override
public String toString() {
switch (this) {
case DENIED:
return "Denied";
case ALLOWED:
return "Allowed";
case UNKNOWN:
return "Unknown";
default:
return super.toString();
}
}
}

public enum SignatureVerificationPolicy {
Expand All @@ -159,6 +225,24 @@ public enum SignatureVerificationPolicy {
SignatureVerificationPolicy(int code) {
this.code = code;
}

@NotNull
@Override
public String toString() {
switch (this) {
case DISABLED:
return "Disabled";
case ENABLED_ON_UPDATE:
return "Enabled on update";
case ENABLED_ON_UPDATE_AND_BOOT:
return "Enabled on update and boot";
case UNKNOWN:
return "Unknown";
default:
return super.toString();

}
}
}

private enum ReleaseType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,26 @@
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;

import java.util.List;
import java.util.Locale;

import io.runtime.mcumgr.McuMgrErrorCode;
import io.runtime.mcumgr.exception.McuMgrErrorException;
import io.runtime.mcumgr.exception.McuMgrException;
import io.runtime.mcumgr.response.img.McuMgrImageStateResponse;
import io.runtime.mcumgr.response.suit.KnownRole;
import io.runtime.mcumgr.response.suit.McuMgrManifestStateResponse;
import io.runtime.mcumgr.sample.R;
import io.runtime.mcumgr.sample.databinding.FragmentCardImageControlBinding;
import io.runtime.mcumgr.sample.di.Injectable;
import io.runtime.mcumgr.sample.dialog.HelpDialogFragment;
import io.runtime.mcumgr.sample.dialog.SelectImageDialogFragment;
import io.runtime.mcumgr.sample.utils.StringUtils;
import io.runtime.mcumgr.sample.utils.Utils;
import io.runtime.mcumgr.sample.viewmodel.mcumgr.ImageControlViewModel;
import io.runtime.mcumgr.sample.viewmodel.mcumgr.McuMgrViewModelFactory;
import io.runtime.mcumgr.util.ByteUtil;

public class ImageControlFragment extends Fragment implements Injectable, SelectImageDialogFragment.OnImageSelectedListener {
private static final int REQUEST_TEST = 1;
Expand Down Expand Up @@ -89,6 +97,7 @@ public void onViewCreated(@NonNull final View view, @Nullable final Bundle saved
((ViewGroup) view).getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);

viewModel.getResponse().observe(getViewLifecycleOwner(), this::printImageSlotInfo);
viewModel.getManifests().observe(getViewLifecycleOwner(), this::printSuitManifests);
viewModel.getError().observe(getViewLifecycleOwner(), this::printError);
viewModel.getTestOperationAvailability().observe(getViewLifecycleOwner(),
enabled -> binding.actionTest.setEnabled(enabled));
Expand All @@ -107,7 +116,10 @@ public void onViewCreated(@NonNull final View view, @Nullable final Bundle saved
// Other actions will be optionally enabled by other observers
}
});
binding.actionRead.setOnClickListener(v -> viewModel.read());
binding.actionRead.setOnClickListener(v -> {
binding.imageControlValue.setText(R.string.image_control_loading);
viewModel.read();
});
binding.actionTest.setOnClickListener(v -> onActionClick(REQUEST_TEST));
binding.actionConfirm.setOnClickListener(v -> onActionClick(REQUEST_CONFIRM));
binding.actionErase.setOnClickListener(v -> onActionClick(REQUEST_ERASE));
Expand Down Expand Up @@ -138,6 +150,37 @@ private void onActionClick(final int requestId) {
}
}

private void printSuitManifests(@Nullable final List<McuMgrManifestStateResponse> manifests) {
if (manifests != null) {
final SpannableStringBuilder builder = new SpannableStringBuilder();
int i = 0;
for (McuMgrManifestStateResponse manifest: manifests) {
final KnownRole role = KnownRole.getOrNull(manifest.role);
final int start = builder.length();
builder.append(getString(R.string.image_suit_manifest_role, ++i, role.toString(), manifest.role));
builder.setSpan(new StyleSpan(Typeface.BOLD),
start, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
final String vendor = manifest.isVendorNordic() ? "Nordic Semiconductor ASA" : "Unknown";
final String version = manifest.getVersion();
builder.append(getString(R.string.image_suit_manifest_details,
manifest.getClassId().toString().toUpperCase(Locale.ROOT), "Unknown",
manifest.getVendorId().toString().toUpperCase(Locale.ROOT), vendor,
manifest.downgradePreventionPolicy,
manifest.independentUpdateabilityPolicy,
manifest.signatureVerificationPolicy,
ByteUtil.byteArrayToHex(manifest.digest, "%02X"),
manifest.digestAlgorithm,
manifest.signatureCheck,
manifest.sequenceNumber,
version != null ? version : "Unknown"));
}
binding.imageControlValue.setText(builder);
binding.imageControlError.setVisibility(View.GONE);
} else {
binding.imageControlValue.setText(null);
}
}

@SuppressLint("StringFormatMatches")
private void printImageSlotInfo(@Nullable final McuMgrImageStateResponse response) {
if (response != null) {
Expand Down
Loading

0 comments on commit 666d690

Please sign in to comment.