firmware/installPybricksDialog: add basic metadata check

This will catch some errors. Mainly this is to ensure we show an error
if someone tries to use, e.g. a NXT firmware file that isn't supported
in Pybricks Code yet. Error message isn't great but we don't expect this
to be hit often, if ever, by non-developers.
This commit is contained in:
David Lechner
2023-02-16 14:10:07 -06:00
committed by David Lechner
parent c8bcddbffd
commit 13c69918e1
3 changed files with 96 additions and 1 deletions
@@ -14,6 +14,7 @@ import { useIsMounted } from 'usehooks-ts';
import { alertsShowAlert } from '../../alerts/actions';
import { Hub } from '../../components/hubPicker';
import { ensureError } from '../../utils';
import { validateMetadata } from './';
export type FirmwareData = {
firmwareZip: ArrayBuffer;
@@ -175,6 +176,9 @@ export function useCustomFirmware(zipFile: File | undefined) {
const reader = await FirmwareReader.load(firmwareZip);
const licenseText = await reader.readReadMeOss();
const metadata = await reader.readMetadata();
validateMetadata(metadata);
const data = {
firmwareZip,
licenseText,
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 The Pybricks Authors
import { validateMetadata } from '.';
describe('validateMetadata', () => {
it('should accept 1.x firmware metadata', () => {
expect(() =>
validateMetadata({
'metadata-version': '1.0.0',
'device-id': 0x40,
'firmware-version': '3.0.0',
'checksum-type': 'sum',
'max-firmware-size': 100000,
'mpy-abi-version': 6,
'mpy-cross-options': ['-mno-unicode'],
'user-mpy-offset': 90000,
}),
).not.toThrow();
});
it('should accept 2.x firmware metadata', () => {
expect(() =>
validateMetadata({
'metadata-version': '2.0.0',
'device-id': 0x40,
'firmware-version': '3.0.0',
'checksum-type': 'sum',
'checksum-size': 100000,
'hub-name-offset': 100000 - 16,
'hub-name-size': 16,
}),
).not.toThrow();
});
it('should reject 3.x firmware metadata', () => {
expect(() =>
validateMetadata({
// @ts-expect-error: testing bad data
'metadata-version': '3.0.0',
'device-id': 0x40,
'firmware-version': '3.0.0',
'checksum-type': 'sum',
'checksum-size': 100000,
'hub-name-offset': 100000 - 16,
'hub-name-size': 16,
}),
).toThrow(/"metadata-version"/);
});
it('should reject unsupported device id', () => {
expect(() =>
validateMetadata({
'metadata-version': '2.0.0',
'device-id': 0xe0,
'firmware-version': '3.0.0',
'checksum-type': 'sum',
'checksum-size': 100000,
'hub-name-offset': 100000 - 16,
'hub-name-size': 16,
}),
).toThrow(/"device-id"/);
});
});
+28 -1
View File
@@ -1,5 +1,8 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
// Copyright (c) 2022-2023 The Pybricks Authors
import { FirmwareMetadata, HubType } from '@pybricks/firmware';
import * as semver from 'semver';
const encoder = new TextEncoder();
@@ -18,3 +21,27 @@ export function validateHubName(hubName: string): boolean {
// user earlier for better UX.
return encoded.length < 16;
}
const supportHubs: readonly HubType[] = [
HubType.MoveHub,
HubType.CityHub,
HubType.TechnicHub,
HubType.PrimeHub,
HubType.EssentialHub,
];
export function validateMetadata(metadata: FirmwareMetadata) {
if (semver.satisfies(metadata['metadata-version'], '^1')) {
if (!supportHubs.includes(metadata['device-id'])) {
throw new Error(`"unsupported "device-id": ${metadata['device-id']}`);
}
} else if (semver.satisfies(metadata['metadata-version'], '^2')) {
if (!supportHubs.includes(metadata['device-id'])) {
throw new Error(`"unsupported "device-id": ${metadata['device-id']}`);
}
} else {
throw new Error(
`unsupported "metadata-version": "${metadata['metadata-version']}"`,
);
}
}