Merge pull request #520 from pybricks/dlech

firmware checksum validation
This commit is contained in:
David Lechner
2021-08-13 11:01:11 -05:00
committed by GitHub
5 changed files with 129 additions and 10 deletions
+9 -1
View File
@@ -4,6 +4,12 @@
## [Unreleased]
### Fixed
- Fix flashing firmware on Android [support#403].
### Changed
- Checksum is now validated as firmware flash progresses instead of just at the
end [support#433].
## [1.1.0-beta.3] - 2021-07-20
@@ -46,13 +52,15 @@
Prerelease changes are documented at [support#48].
<!-- let's try to keep this list sorted -->
<!-- let's try to keep this list sorted alphabetically -->
[issue#470]: https://github.com/pybricks/pybricks-code/issues/470
[issue#471]: https://github.com/pybricks/pybricks-code/issues/471
[issue#472]: https://github.com/pybricks/pybricks-code/issues/472
[support#48]: https://github.com/pybricks/support/issues/48
[support#375]: https://github.com/pybricks/support/issues/375
[support#378]: https://github.com/pybricks/support/issues/378
[support#403]: https://github.com/pybricks/support/issues/403
[support#433]: https://github.com/pybricks/support/issues/433
[v3.0.0]: https://github.com/pybricks/pybricks-micropython/blob/master/CHANGELOG.md#300---2021-06-08
[v3.1.0a1]: https://github.com/pybricks/pybricks-micropython/blob/master/CHANGELOG.md#310a1---2021-06-23
[v3.1.0a2]: https://github.com/pybricks/pybricks-micropython/blob/master/CHANGELOG.md#310a2---2021-07-06
+2 -1
View File
@@ -1386,6 +1386,7 @@ describe('flashFirmware', () => {
const dummyPayload = new ArrayBuffer(0);
let id = 2;
for (let count = 1, offset = 0; ; count++, offset += 14) {
action = await saga.take();
expect(action).toEqual(
@@ -1413,7 +1414,7 @@ describe('flashFirmware', () => {
expect(action).toEqual(checksumRequest(++id));
saga.put(didRequest(id));
saga.put(checksumResponse(0));
saga.put(checksumResponse(0x9b));
}
}
+36 -8
View File
@@ -53,7 +53,7 @@ import {
compile,
} from '../mpy/actions';
import { RootState } from '../reducers';
import { defined, maybe } from '../utils';
import { defined, hex, maybe } from '../utils';
import { fmod, sumComplement32 } from '../utils/math';
import { isAndroid } from '../utils/os';
import {
@@ -381,8 +381,16 @@ function* flashFirmware(action: FlashFirmwareFlashAction): Generator {
const maxDataSize =
(!isAndroid() && MaxProgramFlashSize.get(info.hubType)) || 14;
let runningChecksum = 0xff;
for (let count = 1, offset = 0; ; count++) {
const payload = firmware.slice(offset, offset + maxDataSize);
runningChecksum = payload.reduce(
(prev, curr) => prev ^ curr,
runningChecksum,
);
const programAction = yield* put(
programRequest(
nextMessageId(),
@@ -407,13 +415,33 @@ function* flashFirmware(action: FlashFirmwareFlashAction): Generator {
// the hub is not known and could vary by device.
if (count % 10 === 0) {
const checksumAction = yield* put(checksumRequest(nextMessageId()));
yield* all({
const { response } = yield* all({
sent: waitForDidRequest(checksumAction.id),
checksum: waitForResponse<BootloaderChecksumResponseAction>(
response: waitForResponse<BootloaderChecksumResponseAction>(
BootloaderResponseActionType.Checksum,
5000,
),
});
if (response.checksum !== runningChecksum) {
// istanbul ignore next
if (process.env.NODE_ENV !== 'test') {
console.error(
`checksum: got ${hex(response.checksum, 2)} expected ${hex(
runningChecksum,
2,
)}`,
);
}
yield* put(
didFailToFinish(
FailToFinishReasonType.HubError,
HubError.ChecksumMismatch,
),
);
yield* disconnectAndCancel();
}
}
}
@@ -432,14 +460,14 @@ function* flashFirmware(action: FlashFirmwareFlashAction): Generator {
yield* disconnectAndCancel();
}
const checksum = firmware.reduce((prev, curr) => prev ^ curr, 0xff);
if (flash.checksum !== checksum) {
if (flash.checksum !== runningChecksum) {
// istanbul ignore next
if (process.env.NODE_ENV !== 'test') {
console.error(
'checksum:',
flash.checksum.toString(16).padStart(2, '0').padStart(4, '0x'),
checksum.toString(16).padStart(2, '0').padStart(4, '0x'),
`final checksum: got ${hex(flash.checksum, 2)} expected ${hex(
runningChecksum,
2,
)}`,
);
}
yield* put(
Executable
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
"""
Serve local build with https for testing.
This is useful, e.g. for Android (WebBluetooth doesn't work without https).
Usage:
yarn build
./tools/serve.py
Browse to https://localhost:8443 on the local machine or
https://<name-or-address>:8443 on a remote device.
The browser will complain about the self-signed certificate, but this can be
bypassed (click "Advanced" in browser).
"""
from http.server import HTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
import os
import ssl
THIS_DIR = Path(__file__).parent
TEST_CERT = (THIS_DIR / 'test.pem').absolute()
BUILD_DIR = (THIS_DIR / '..' / 'build').absolute()
# HTTPServer serves the current directory.
os.chdir(BUILD_DIR)
httpd = HTTPServer(('', 8443), SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket(httpd.socket, certfile=TEST_CERT, server_side=True)
httpd.serve_forever()
+48
View File
@@ -0,0 +1,48 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDLpTko58nNd2cE
q8MaB1Wv4xren/xiNomDwf44QK9jZGzvHqnXCKTQ0KsLxtZt7Ne6PU1WdIA3bf6/
nVzVUhauP0+DF8t6bTH4GSlvLXKGFW6dkCkTMw8ginh73PkZL+OaQvaMVJd42CW9
bPhFMF6tXsLq661ObXwTE+vBopSim4EEzy+VKXB8BHvMYvfMcWiJihKJhCPLZE9p
iHBO3yByMs0fLe6UQ/MAHXFVprxnXJ+P2mMe5s/c0BEcTK1deNy9ngKcUvKGCcfj
T19xtkS4IHS0csz0fkZ+93tGjTm9eCLAOmyvKRljhOMFZKV4J1w4We3Y2b+xHN9w
psukl2+JAgMBAAECggEBAKacuEZaVYYQQa3VpZs/EwEDavFnrYLhIeM1rXtymMxV
1rZQvR4ciVP61WaeOMPY+ar7CrJIQLqY+MrmwZQaREotrC0SI703Z6GXXIoEgeVn
1Yd98c0ughraj7avVPIhdzK3xKWNdBw/o6j2Za2d5Ggt1oWDath2xQOK0eEr1jWu
VNfZNMvjyvevbyzuBLlF2970ZbOKAi/nrqtmw8mlhqDK5hy+j0K37hAbxizVDmU6
vZfGhgmgBqXCla59T4rN3StK6zYVfmZKuGHWQbBxbWuLpFA+m09WKF599rUgZG7e
l5RC1dOXCa4bOv8yVRULb5LSIyBHxIfPiqShw/gUm3ECgYEA5/QkAOw4M2RSpaGb
K0yaoAKkZ5whX3rzudOVocQkeMKETpiPcLRZpO/z3A/bRjDg5YG7FNhbBI2nqGHR
ShkZIZlx7OeZPsT4IpciKDGOjoWhfk2a3gaUj+24hUO13hK1DrudI0t+USb6wjfP
C+hWoonEOjGK9jzPj1fwKMwHbYUCgYEA4MHN8RkXP6AgdLleKB+/AE613Ozmc54F
I0uL5xXUny+udhYcflsF357ztj3vWZzWOhVvp84O7puJu9J84wQQtG/v6kD+pIA7
TKJAcJ8eL/gZgbEAEbw1fBZLFkpis6sD06tDiCRx3qWez3pr/UiPLw4ldIsNnyZu
RfqL8LjspzUCgYEAqDfl+IlYBOVoDtkR+Kf4p7zi0IVpqp43rffl18ixDqc7XujD
dmBFdxnSiDEzvEbsg2W/cNGryLSW98XhC9UB5d0rwzz/ZYdh0Ww8CFxQX8pIIZpU
i1rIWU10+ajud5ynlIS55IP2mxIi38GrtZ4NXpl9JxsRBZ16hiGneV7msCUCgYEA
iZAVLGve/uJS+x45svU7QkTqqr2Uj084ddHXXyjNamEUttHxvqSXg7cWE4T06jRE
KmjUXjCQkxo/wuWh6pCiaM3aWdZOBQE3SkBEkKnTICkJ84cSM8iuM7SxYINGxhD1
1qgMPVeRGmTkPrj/P5cZjANsiVhSjgZHmC0xWbTVo8ECgYBh6R7N4B/xYH+UgV1U
qyzFjLjsBQoIuhx5JpHCXmzZV3B/TV9mi3D1hcl4oS6nnww/j5WKggdUq6542975
iVsnw8OFCSpU/R9P6crPwDEkdZt9miv1BOdh95AqxpmLEJrfXuVNYur0yur7Yv+E
AVMgdBof4DtucX+v+sLxuHAppA==
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDSzCCAjOgAwIBAgIUM9tef51ph3HVoJqF1h7ch5XmOQowDQYJKoZIhvcNAQEL
BQAwNTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAoM
CFB5YnJpY2tzMB4XDTIxMDgxMzE0NDQyOFoXDTQ4MTIyOTE0NDQyOFowNTELMAkG
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAoMCFB5YnJpY2tz
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy6U5KOfJzXdnBKvDGgdV
r+Ma3p/8YjaJg8H+OECvY2Rs7x6p1wik0NCrC8bWbezXuj1NVnSAN23+v51c1VIW
rj9PgxfLem0x+Bkpby1yhhVunZApEzMPIIp4e9z5GS/jmkL2jFSXeNglvWz4RTBe
rV7C6uutTm18ExPrwaKUopuBBM8vlSlwfAR7zGL3zHFoiYoSiYQjy2RPaYhwTt8g
cjLNHy3ulEPzAB1xVaa8Z1yfj9pjHubP3NARHEytXXjcvZ4CnFLyhgnH409fcbZE
uCB0tHLM9H5Gfvd7Ro05vXgiwDpsrykZY4TjBWSleCdcOFnt2Nm/sRzfcKbLpJdv
iQIDAQABo1MwUTAdBgNVHQ4EFgQUm+DZlQWXqhjtS6ATH3Ns0fzchs0wHwYDVR0j
BBgwFoAUm+DZlQWXqhjtS6ATH3Ns0fzchs0wDwYDVR0TAQH/BAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAQEAlpu1OrX9wb8m2eaZOVumjNna4Xx4pYJG13AdfWx5D4qR
PqbxdUfxnlk3F6pD15ELK1NK94k6/XarScBxDwMexSxKVPPeU5aOG2JRUkWaN4wg
NIYo7xQeoorgcNqnV8pIZMh3IUKnL3i9BFrMlhBIZCb/y0XdmJbvb5P9yU3x+z9C
w1hPKjyhEIYIGnjTh1Rj5feciQSxcouDVOZSgi2iscGePK5734rAzO3zZzbi4lbs
R2rK2RVrX/s51njLgCHpwPonfEqy8TrMSJvCtH0OG7wQCNbnFOakfzvChpWI/1bI
Qlai98tuzS49ekXd/dL6lXZi8z7c9Necj21mMgePKQ==
-----END CERTIFICATE-----