diff --git a/common/protob/Makefile b/common/protob/Makefile
index f8df2d2d5d..c2feff5a84 100644
--- a/common/protob/Makefile
+++ b/common/protob/Makefile
@@ -1,4 +1,4 @@
-check: messages.pb messages-binance.pb messages-bitcoin.pb messages-bootloader.pb messages-cardano.pb messages-common.pb messages-crypto.pb messages-debug.pb messages-ethereum.pb messages-management.pb messages-monero.pb messages-nem.pb messages-ripple.pb messages-stellar.pb messages-tezos.pb messages-eos.pb
+check: messages.pb messages-binance.pb messages-bitcoin.pb messages-ble.pb messages-bootloader.pb messages-cardano.pb messages-common.pb messages-crypto.pb messages-debug.pb messages-ethereum.pb messages-management.pb messages-monero.pb messages-nem.pb messages-ripple.pb messages-stellar.pb messages-tezos.pb messages-eos.pb
%.pb: %.proto
protoc -I/usr/include -I. $< -o $@
diff --git a/common/protob/messages-ble.proto b/common/protob/messages-ble.proto
new file mode 100644
index 0000000000..724482efe0
--- /dev/null
+++ b/common/protob/messages-ble.proto
@@ -0,0 +1,41 @@
+syntax = "proto2";
+package hw.trezor.messages.ble;
+
+// Sugar for easier handling in Java
+option java_package = "com.satoshilabs.trezor.lib.protobuf";
+option java_outer_classname = "TrezorMessageBLE";
+
+option (include_in_bitcoin_only) = true;
+
+import "messages.proto";
+
+
+/**
+ * Request: initializes upload of a new ble firmware im
+ * @start
+ * @next UploadBLEFirmwareNextChunk
+ * @next Failure
+ */
+message UploadBLEFirmwareInit {
+ required bytes init_data = 1;
+ required uint32 binsize = 2;
+}
+
+
+/**
+ * Response: Requests next chunk of a new ble firmware im
+ * @next UploadBLEFirmwareChunk
+ */
+message UploadBLEFirmwareNextChunk {
+ required uint32 offset = 1;
+}
+
+/**
+ * Request: sends next chunk of a new ble firmware im
+ * @next UploadBLEFirmwareNextChunk
+ * @next Success
+ * @next Failure
+ */
+message UploadBLEFirmwareChunk {
+ required bytes data = 1;
+}
diff --git a/common/protob/messages.proto b/common/protob/messages.proto
index c0ea0ff753..344d64bf48 100644
--- a/common/protob/messages.proto
+++ b/common/protob/messages.proto
@@ -136,6 +136,11 @@ enum MessageType {
MessageType_FirmwareRequest = 8 [(bitcoin_only) = true, (wire_out) = true, (wire_bootloader) = true];
MessageType_SelfTest = 32 [(bitcoin_only) = true, (wire_in) = true, (wire_bootloader) = true];
+ // BLE
+ MessageType_UploadBLEFirmwareInit = 8000 [(bitcoin_only) = true, (wire_in) = true];
+ MessageType_UploadBLEFirmwareNextChunk = 8001 [(bitcoin_only) = true, (wire_out) = true];
+ MessageType_UploadBLEFirmwareChunk = 8002 [(bitcoin_only) = true, (wire_in) = true];
+
// Bitcoin
MessageType_GetPublicKey = 11 [(bitcoin_only) = true, (wire_in) = true];
MessageType_PublicKey = 12 [(bitcoin_only) = true, (wire_out) = true];
diff --git a/core/SConscript.firmware b/core/SConscript.firmware
index dbb15c17e9..5a7e40357a 100644
--- a/core/SConscript.firmware
+++ b/core/SConscript.firmware
@@ -344,6 +344,7 @@ SOURCE_STMHAL = [
'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_sdram.c',
'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c',
'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim_ex.c',
+ 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_uart.c',
'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_ll_fmc.c',
'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_ll_sdmmc.c',
'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_ll_usb.c',
@@ -378,6 +379,8 @@ SOURCE_TREZORHAL = [
'embed/trezorhal/usbd_ioreq.c',
'embed/trezorhal/util.s',
'embed/trezorhal/vectortable.s',
+ 'embed/trezorhal/dfu/dfu.c',
+ 'embed/trezorhal/dfu/fwu.c',
]
@@ -470,6 +473,7 @@ env.Replace(
'embed/rust',
'embed/firmware',
'embed/lib',
+ 'embed/firmware/dfu',
'embed/trezorhal',
'embed/extmod/modtrezorui',
'vendor/micropython',
diff --git a/core/SConscript.unix b/core/SConscript.unix
index ad2bc03a9f..8b1a469311 100644
--- a/core/SConscript.unix
+++ b/core/SConscript.unix
@@ -348,6 +348,7 @@ SOURCE_MICROPYTHON = [
SOURCE_UNIX = [
'embed/unix/common.c',
+ 'embed/unix/dfu/dfu.c',
'embed/unix/display-unix.c',
'embed/unix/flash.c',
'embed/unix/main.c',
diff --git a/core/embed/extmod/modtrezorio/modtrezorio-ble.h b/core/embed/extmod/modtrezorio/modtrezorio-ble.h
new file mode 100644
index 0000000000..08bffb9e72
--- /dev/null
+++ b/core/embed/extmod/modtrezorio/modtrezorio-ble.h
@@ -0,0 +1,80 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "dfu/dfu.h"
+
+/// package: trezorio.ble
+
+/// def update_init(data: bytes, binsize: int) -> int:
+/// """
+/// Initializes the BLE firmware update
+/// """
+STATIC mp_obj_t mod_trezorio_BLE_update_init(mp_obj_t data, mp_obj_t binsize) {
+ mp_buffer_info_t buffer = {0};
+ mp_int_t binsize_int = mp_obj_get_int(binsize);
+
+ mp_get_buffer_raise(data, &buffer, MP_BUFFER_READ);
+
+ dfu_result_t result = dfu_update_init(buffer.buf, buffer.len, binsize_int);
+ if (result == DFU_NEXT_CHUNK) {
+ return mp_obj_new_int(0);
+ } else if (result == DFU_SUCCESS) {
+ return mp_obj_new_int(1);
+ } else {
+ mp_raise_msg(&mp_type_RuntimeError, "Upload failed.");
+ }
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_BLE_update_init_obj,
+ mod_trezorio_BLE_update_init);
+
+/// def update_chunk(chunk: bytes) -> int:
+/// """
+/// Writes next chunk of BLE firmware update
+/// """
+STATIC mp_obj_t mod_trezorio_BLE_update_chunk(mp_obj_t data) {
+ mp_buffer_info_t buffer = {0};
+
+ mp_get_buffer_raise(data, &buffer, MP_BUFFER_READ);
+
+ dfu_result_t result = dfu_update_chunk(buffer.buf, buffer.len);
+
+ if (result == DFU_NEXT_CHUNK) {
+ return mp_obj_new_int(0);
+ } else if (result == DFU_SUCCESS) {
+ return mp_obj_new_int(1);
+ } else {
+ mp_raise_msg(&mp_type_RuntimeError, "Upload failed.");
+ }
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_BLE_update_chunk_obj,
+ mod_trezorio_BLE_update_chunk);
+
+STATIC const mp_rom_map_elem_t mod_trezorio_BLE_globals_table[] = {
+ {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ble)},
+ {MP_ROM_QSTR(MP_QSTR_update_init),
+ MP_ROM_PTR(&mod_trezorio_BLE_update_init_obj)},
+ {MP_ROM_QSTR(MP_QSTR_update_chunk),
+ MP_ROM_PTR(&mod_trezorio_BLE_update_chunk_obj)},
+};
+STATIC MP_DEFINE_CONST_DICT(mod_trezorio_BLE_globals,
+ mod_trezorio_BLE_globals_table);
+
+STATIC const mp_obj_module_t mod_trezorio_BLE_module = {
+ .base = {&mp_type_module},
+ .globals = (mp_obj_dict_t *)&mod_trezorio_BLE_globals};
diff --git a/core/embed/extmod/modtrezorio/modtrezorio.c b/core/embed/extmod/modtrezorio/modtrezorio.c
index 0874f166bc..9d8588707e 100644
--- a/core/embed/extmod/modtrezorio/modtrezorio.c
+++ b/core/embed/extmod/modtrezorio/modtrezorio.c
@@ -48,6 +48,7 @@ bool usb_connected_previously = true;
#include "modtrezorio-webusb.h"
#include "modtrezorio-usb.h"
// clang-format on
+#include "modtrezorio-ble.h"
#ifdef USE_SBU
#include "modtrezorio-sbu.h"
#endif
@@ -57,7 +58,7 @@ bool usb_connected_previously = true;
#endif
/// package: trezorio.__init__
-/// from . import fatfs, sdcard
+/// from . import fatfs, sdcard, ble
/// POLL_READ: int # wait until interface is readable and return read data
/// POLL_WRITE: int # wait until interface is writable
@@ -88,6 +89,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorio_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR_fatfs), MP_ROM_PTR(&mod_trezorio_fatfs_module)},
{MP_ROM_QSTR(MP_QSTR_sdcard), MP_ROM_PTR(&mod_trezorio_sdcard_module)},
#endif
+ {MP_ROM_QSTR(MP_QSTR_ble), MP_ROM_PTR(&mod_trezorio_BLE_module)},
#ifdef USE_TOUCH
{MP_ROM_QSTR(MP_QSTR_TOUCH), MP_ROM_INT(TOUCH_IFACE)},
diff --git a/core/embed/firmware/main.c b/core/embed/firmware/main.c
index b788f7b99b..db21539f16 100644
--- a/core/embed/firmware/main.c
+++ b/core/embed/firmware/main.c
@@ -72,6 +72,9 @@
#include "supervise.h"
#ifdef USE_SECP256K1_ZKP
#include "zkp_context.h"
+
+#include "dfu/dfu.h"
+
#endif
// from util.s
@@ -143,6 +146,8 @@ int main(void) {
sdcard_init();
#endif
+ dfu_init();
+
#if !defined TREZOR_MODEL_1
// jump to unprivileged mode
// http://infocenter.arm.com/help/topic/com.arm.doc.dui0552a/CHDBIBGJ.html
diff --git a/core/embed/trezorhal/dfu/dfu.c b/core/embed/trezorhal/dfu/dfu.c
new file mode 100644
index 0000000000..5cf26ec497
--- /dev/null
+++ b/core/embed/trezorhal/dfu/dfu.c
@@ -0,0 +1,176 @@
+//
+// main.c
+// nrf52-dfu
+//
+// Sample host application to demonstrate the usage of our C library for the
+// Nordic firmware update protocol.
+//
+// Created by Andreas Schweizer on 30.11.2018.
+// Copyright © 2018-2019 Classy Code GmbH
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+#include STM32_HAL_H
+#include "dfu.h"
+#include "fwu.h"
+
+static TFwu sFwu;
+static UART_HandleTypeDef urt;
+
+static uint32_t tick_start = 0;
+
+void txFunction(struct SFwu *fwu, uint8_t *buf, uint8_t len);
+static uint8_t readData(uint8_t *data, int maxLen);
+
+void dfu_init(void) {
+ GPIO_InitTypeDef GPIO_InitStructure;
+
+ __HAL_RCC_USART1_CLK_ENABLE();
+ __HAL_RCC_GPIOA_CLK_ENABLE();
+
+ GPIO_InitStructure.Pin = GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12;
+ GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
+ GPIO_InitStructure.Pull = GPIO_NOPULL;
+ GPIO_InitStructure.Alternate = GPIO_AF7_USART1;
+ GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW;
+ HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
+
+ urt.Init.Mode = UART_MODE_TX_RX;
+ urt.Init.BaudRate = 115200;
+ urt.Init.HwFlowCtl = UART_HWCONTROL_RTS_CTS;
+ urt.Init.OverSampling = UART_OVERSAMPLING_16;
+ urt.Init.Parity = UART_PARITY_NONE, urt.Init.StopBits = UART_STOPBITS_1;
+ urt.Init.WordLength = UART_WORDLENGTH_8B;
+ urt.Instance = USART1;
+
+ HAL_UART_Init(&urt);
+
+ // sFwu.commandObject = datfile;
+ // sFwu.commandObjectLen = sizeof(datfile);
+ // sFwu.dataObject = NULL;
+ // sFwu.dataObjectLen = sizeof(binfile);
+ // sFwu.txFunction = txFunction;
+ // sFwu.responseTimeoutMillisec = 5000;
+}
+
+dfu_result_t dfu_update_process(void) {
+ while (1) {
+ // Can send 4 chars...
+ // (On a microcontroller, you'd use the TX Empty interrupt or test a
+ // register.)
+
+ fwuCanSendData(&sFwu, 4);
+
+ // Data available? Get up to 4 bytes...
+ // (On a microcontroller, you'd use the RX Available interrupt or test a
+ // register.)
+ uint8_t rxBuf[4];
+ uint8_t rxLen = readData(rxBuf, 4);
+ if (rxLen > 0) {
+ fwuDidReceiveData(&sFwu, rxBuf, rxLen);
+ }
+
+ // Give the firmware update module a timeslot to continue the process.
+ EFwuProcessStatus status = fwuYield(&sFwu, 0);
+
+ if (status == FWU_STATUS_COMPLETION) {
+ return DFU_SUCCESS;
+ }
+
+ if (status == FWU_STATUS_FAILURE) {
+ return DFU_FAIL;
+ }
+
+ if (HAL_GetTick() - tick_start > 2000) {
+ return DFU_FAIL;
+ }
+
+ if (fwuIsReadyForChunk(&sFwu)) {
+ return DFU_NEXT_CHUNK;
+ }
+ }
+}
+
+dfu_result_t dfu_update_init(uint8_t *data, uint32_t len, uint32_t binary_len) {
+ sFwu.commandObject = data;
+ sFwu.commandObjectLen = len;
+ sFwu.dataObject = NULL;
+ sFwu.dataObjectLen = binary_len;
+ sFwu.txFunction = txFunction;
+ sFwu.responseTimeoutMillisec = 2000;
+
+ tick_start = HAL_GetTick();
+
+ // Prepare the firmware update process.
+ fwuInit(&sFwu);
+
+ // Start the firmware update process.
+ fwuExec(&sFwu);
+
+ return dfu_update_process();
+}
+
+dfu_result_t dfu_update_chunk(uint8_t *data, uint32_t len) {
+ tick_start = HAL_GetTick();
+
+ fwuSendChunk(&sFwu, data, len);
+
+ return dfu_update_process();
+}
+
+dfu_result_t dfu_update_do(uint8_t *datfile, uint32_t datfile_len,
+ uint8_t *binfile, uint32_t binfile_len) {
+ uint32_t chunk_offset = 0;
+ uint32_t rem_data = binfile_len;
+
+ dfu_result_t res = dfu_update_init(datfile, datfile_len, binfile_len);
+
+ while (res == DFU_NEXT_CHUNK) {
+ // Send the next chunk of the data object.
+ uint32_t chunk_size = 4096;
+ if (rem_data < 4096) {
+ chunk_size = rem_data;
+ rem_data = 0;
+ } else {
+ rem_data -= 4096;
+ }
+ res = dfu_update_chunk(&binfile[chunk_offset], chunk_size);
+ chunk_offset += chunk_size;
+ }
+
+ return res;
+}
+
+void txFunction(struct SFwu *fwu, uint8_t *buf, uint8_t len) {
+ HAL_UART_Transmit(&urt, buf, len, 10);
+}
+
+static uint8_t readData(uint8_t *data, int maxLen) {
+ HAL_StatusTypeDef result = HAL_UART_Receive(&urt, data, maxLen, 0);
+
+ if (result == HAL_OK) {
+ return maxLen;
+ } else {
+ if (urt.RxXferCount == maxLen) {
+ return 0;
+ }
+ return maxLen - urt.RxXferCount - 1;
+ }
+}
diff --git a/core/embed/trezorhal/dfu/dfu.h b/core/embed/trezorhal/dfu/dfu.h
new file mode 100644
index 0000000000..fcd6eeb36b
--- /dev/null
+++ b/core/embed/trezorhal/dfu/dfu.h
@@ -0,0 +1,17 @@
+
+#ifndef __DFU_H__
+#define __DFU_H__
+
+typedef enum {
+ DFU_NEXT_CHUNK,
+ DFU_SUCCESS,
+ DFU_FAIL,
+} dfu_result_t;
+
+void dfu_init(void);
+dfu_result_t dfu_update_init(uint8_t *data, uint32_t len, uint32_t binary_len);
+dfu_result_t dfu_update_chunk(uint8_t *data, uint32_t len);
+dfu_result_t dfu_update_do(uint8_t *datfile, uint32_t datfile_len,
+ uint8_t *binfile, uint32_t binfile_len);
+
+#endif
diff --git a/core/embed/trezorhal/dfu/fwu.c b/core/embed/trezorhal/dfu/fwu.c
new file mode 100644
index 0000000000..4937aed4e9
--- /dev/null
+++ b/core/embed/trezorhal/dfu/fwu.c
@@ -0,0 +1,664 @@
+//
+// fwu.c
+// nrf52-dfu
+//
+// C library for the Nordic firmware update protocol.
+//
+// Created by Andreas Schweizer on 30.11.2018.
+// Copyright © 2018-2019 Classy Code GmbH
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+#include "fwu.h"
+#include
+
+// TODO too big, split in separate files!
+
+typedef enum {
+ FWU_PS_IDLE = 0,
+ FWU_PS_PING = 10,
+ FWU_PS_RCPT_NOTIF = 20,
+ FWU_PS_MTU = 30,
+ FWU_PS_OBJ1_SELECT = 40,
+ FWU_PS_OBJ1_CREATE = 50,
+ FWU_PS_OBJ1_WRITE = 60,
+ FWU_PS_OBJ1_CRC_GET = 70,
+ FWU_PS_OBJ1_EXECUTE = 80,
+ FWU_PS_OBJ2_SELECT = 90,
+ FWU_PS_OBJ2_WAIT_FOR_CHUNK = 91,
+ FWU_PS_OBJ2_CREATE = 100,
+ FWU_PS_OBJ2_WRITE = 110,
+ FWU_PS_OBJ2_CRC_GET = 120,
+ FWU_PS_OBJ2_EXECUTE = 130,
+ FWU_PS_FAIL = 254,
+ FWU_PS_DONE = 255,
+} EFwuProcessState;
+
+// Process requests, triggering process state transitions.
+typedef enum {
+ FWU_PR_NONE = 0,
+ FWU_PR_START = 1,
+ FWU_PR_RECEIVED_RESPONSE,
+ FWU_PR_REQUEST_FAILED,
+ FWU_PR_REQUEST_SENT,
+} EFwuProcessRequest;
+
+typedef enum {
+ FWU_CS_IDLE = 0,
+ FWU_CS_SEND = 1, // sending data from the private request buffer
+ FWU_CS_RECEIVE = 2, // receiving data into the private response buffer
+ FWU_CS_FAIL = 3,
+ FWU_CS_DONE = 4,
+} EFwuCommandState;
+
+// Command requests, triggering command state transitions.
+typedef enum {
+ FWU_CR_NONE = 0,
+ FWU_CR_SEND = 1,
+ FWU_CR_SENDONLY = 2,
+ FWU_CR_EOM_RECEIVED = 3,
+ FWU_CR_RX_OVERFLOW = 4,
+ FWU_CR_INVALID_ESCAPE_SEQ,
+} EFwuCommandRequest;
+
+#define FWU_EOM 0xC0
+#define FWU_RESPONSE_START 0x60
+#define FWU_RESPONSE_SUCCESS 0x01
+
+// PING 09 01 C0 -> 60 09 01 01 C0
+static uint8_t sPingRequest[] = {0x09, 0x01};
+static uint8_t sPingRequestLen = 2;
+
+// SET RECEIPT 02 00 00 C0 -> 60 02 01 C0
+static uint8_t sSetReceiptRequest[] = {0x02, 0x00, 0x00};
+static uint8_t sSetReceiptRequestLen = 3;
+
+// Get the preferred MTU size on the request.
+// GET MTU 07 -> 60 07 01 83 00 C0
+static uint8_t sGetMtuRequest[] = {0x07};
+static uint8_t sGetMtuRequestLen = 1;
+
+// Triggers the last transferred object of the specified type to be selected
+// and queries information (max size, cur offset, cur CRC) about the object.
+// If there's no object of the specified type, the object type is still
+// selected, CRC and offset are 0 in this case.
+// SELECT OBJECT 06 01 C0 -> 60 06 01 00 01 00 00 00 00 00 00 00 00 00 00 C0
+static uint8_t sSelectObjectRequest[] = {0x06, 0x01};
+static uint8_t sSelectObjectRequestLen = 2;
+
+// Creating a command or data object; the target reserves the space, resets the
+// progress since the last Execute command and selects the new object.)
+// CREATE OBJECT 01 01 87 00 00 00 C0 -> 60 01 01 C0
+static uint8_t sCreateObjectRequest[] = {0x01, 0x01, 0x87, 0x00, 0x00, 0x00};
+static uint8_t sCreateObjectRequestLen = 6;
+
+// CRC GET 03 C0 -> 60 03 01 87 00 00 00 38 f4 97 72 C0
+static uint8_t sGetCrcRequest[] = {0x03};
+static uint8_t sGetCrcRequestLen = 1;
+
+// Execute an object after it has been fully transmitted.
+// EXECUTE OBJECT 04 C0 -> 60 04 01 C0
+static uint8_t sExecuteObjectRequest[] = {0x04};
+static uint8_t sExecuteObjectRequestLen = 1;
+
+static void fwuYieldProcessFsm(TFwu *fwu, uint32_t elapsedMillisec);
+static void fwuYieldCommandFsm(TFwu *fwu, uint32_t elapsedMillisec);
+
+static EFwuResponseStatus fwuTestReceivedPacketValid(TFwu *fwu);
+
+// Don't send more than FWU_REQUEST_BUF_SIZE bytes.
+// Don't include the EOM.
+static void fwuPrepareSendBuffer(TFwu *fwu, uint8_t *data, uint8_t len);
+
+static void fwuPrepareLargeObjectSendBuffer(TFwu *fwu, uint8_t requestCode);
+
+// static void fwuDebugPrintStatus(TFwu *fwu, char *msg);
+
+static void updateCrc(TFwu *fwu, uint8_t b);
+static void fwuSignalFailure(TFwu *fwu, EFwuResponseStatus reason);
+static inline uint16_t fwuLittleEndianToHost16(uint8_t *bytes);
+static inline uint32_t fwuLittleEndianToHost32(uint8_t *bytes);
+static inline void fwuHostToLittleEndian32(uint32_t v, uint8_t *bytes);
+
+// First function to call to set up the internal state in the FWU structure.
+void fwuInit(TFwu *fwu) {
+ fwu->privateProcessState = FWU_PS_IDLE;
+ fwu->privateProcessRequest = FWU_PR_NONE;
+ fwu->privateCommandState = FWU_CS_IDLE;
+
+ fwu->processStatus = FWU_STATUS_UNDEFINED;
+ fwu->responseStatus = FWU_RSP_OK;
+}
+
+// Execute the firmware update.
+void fwuExec(TFwu *fwu) {
+ // Start with sending a PING command to the target to see if it's there...
+ fwu->privateProcessRequest = FWU_PR_START;
+}
+
+// Call regularly to allow asynchronous processing to continue.
+EFwuProcessStatus fwuYield(TFwu *fwu, uint32_t elapsedMillisec) {
+ // Nothing to do if processing has failed or successfully completed...
+ if (fwu->processStatus == FWU_STATUS_FAILURE ||
+ fwu->privateProcessState == FWU_PS_FAIL) {
+ return FWU_STATUS_FAILURE;
+ } else if (fwu->processStatus == FWU_STATUS_COMPLETION ||
+ fwu->privateProcessState == FWU_PS_DONE) {
+ return FWU_STATUS_COMPLETION;
+ }
+
+ // Processing is ongoing, yield to FSMs.
+ fwuYieldCommandFsm(fwu, elapsedMillisec);
+ fwuYieldProcessFsm(fwu, elapsedMillisec);
+
+ return fwu->processStatus;
+}
+
+// Call after data from the target has been received.
+void fwuDidReceiveData(TFwu *fwu, uint8_t *bytes, uint8_t len) {
+ while (len > 0) {
+ if (fwu->privateResponseLen == FWU_RESPONSE_BUF_SIZE) {
+ fwu->privateCommandRequest = FWU_CR_RX_OVERFLOW;
+ return;
+ }
+ uint8_t c = *bytes++;
+ if (c == FWU_EOM) {
+ fwu->privateCommandRequest = FWU_CR_EOM_RECEIVED;
+ }
+
+ if (c == 0xDB) {
+ fwu->privateResponseEscapeCharacter = 1;
+ } else {
+ if (fwu->privateResponseEscapeCharacter) {
+ fwu->privateResponseEscapeCharacter = 0;
+ if (c == 0xDC) {
+ c = 0xC0;
+ } else if (c == 0xDD) {
+ c = 0xDB;
+ } else {
+ fwu->privateCommandRequest = FWU_CR_INVALID_ESCAPE_SEQ;
+ return;
+ }
+ }
+ fwu->privateResponseBuf[fwu->privateResponseLen++] = c;
+ }
+ len--;
+ }
+}
+
+// Inform the FWU module that it may send maxLen bytes of data to the target.
+void fwuCanSendData(TFwu *fwu, uint8_t maxLen) {
+ fwu->privateSendBufSpace = maxLen;
+}
+
+void fwuSendChunk(TFwu *fwu, uint8_t *buf, uint32_t len) {
+ if (fwu->privateProcessState == FWU_PS_OBJ2_WAIT_FOR_CHUNK &&
+ fwu->privateDataObjectSize == len) {
+ fwu->dataObject = buf;
+ fwu->privateProcessState = FWU_PS_OBJ2_CREATE;
+ }
+}
+
+bool fwuIsReadyForChunk(TFwu *fwu) {
+ return fwu->privateProcessState == FWU_PS_OBJ2_WAIT_FOR_CHUNK;
+}
+
+static void fwuYieldProcessFsm(TFwu *fwu, uint32_t elapsedMillisec) {
+ uint8_t tmpPrivateProcessRequest = fwu->privateProcessRequest;
+ fwu->privateProcessRequest = FWU_PR_NONE;
+
+ // No processing in final states
+ if (fwu->privateProcessState == FWU_PS_DONE ||
+ fwu->privateProcessState == FWU_PS_FAIL) {
+ return;
+ }
+
+ // Failure handling
+ if (tmpPrivateProcessRequest == FWU_PR_REQUEST_FAILED) {
+ fwu->privateProcessState = FWU_PS_FAIL;
+ fwu->processStatus = FWU_STATUS_FAILURE;
+ return;
+ }
+
+ // Executing the firmware update process.
+ switch (fwu->privateProcessState) {
+ case FWU_PS_IDLE:
+ if (tmpPrivateProcessRequest == FWU_PR_START) {
+ // Send a PING and switch to the PING state to wait for the response.
+ fwuPrepareSendBuffer(fwu, sPingRequest, sPingRequestLen);
+ fwu->privateProcessState = FWU_PS_PING;
+ }
+ break;
+
+ // PING: Check if the nRF52 DFU code is listening
+ case FWU_PS_PING:
+ // Wait for the PING response, then verify it.
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ // ID match?
+ if (fwu->privateRequestBuf[1] == fwu->privateResponseBuf[3]) {
+ // Send a SET_RECEIPT and switch to the corresponding state to wait
+ // for the response.
+ fwuPrepareSendBuffer(fwu, sSetReceiptRequest, sSetReceiptRequestLen);
+ fwu->privateProcessState = FWU_PS_RCPT_NOTIF;
+ } else {
+ fwuSignalFailure(fwu, FWU_RSP_PING_ID_MISMATCH);
+ }
+ }
+ break;
+
+ // RCPT_NOTIF: Define Receipt settings
+ case FWU_PS_RCPT_NOTIF:
+ // Wait for the SET_RECEIPT response.
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ // Send a SET_RECEIPT and switch to the corresponding state to wait for
+ // the response.
+ fwuPrepareSendBuffer(fwu, sGetMtuRequest, sGetMtuRequestLen);
+ fwu->privateProcessState = FWU_PS_MTU;
+ }
+ break;
+
+ // FWU_PS_MTU: Get maximum transmission unit size
+ case FWU_PS_MTU:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ fwu->privateMtuSize =
+ fwuLittleEndianToHost16(&fwu->privateResponseBuf[3]);
+ // Send a SET_RECEIPT and switch to the corresponding state to wait for
+ // the response.
+ sSelectObjectRequest[1] = 0x01; // select object 1 (command object)
+ fwuPrepareSendBuffer(fwu, sSelectObjectRequest,
+ sSelectObjectRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ1_SELECT;
+ }
+ break;
+
+ // FWU_PS_OBJ1_SELECT: Select the INIT command object
+ case FWU_PS_OBJ1_SELECT:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ uint32_t maxSize = fwuLittleEndianToHost32(&fwu->privateResponseBuf[3]);
+ if (maxSize < fwu->commandObjectLen) {
+ fwuSignalFailure(fwu, FWU_RSP_INIT_COMMAND_TOO_LARGE);
+ } else {
+ sCreateObjectRequest[1] = 0x01; // create type 1 object (COMMAND)
+ fwuHostToLittleEndian32(fwu->commandObjectLen,
+ &sCreateObjectRequest[2]);
+ fwuPrepareSendBuffer(fwu, sCreateObjectRequest,
+ sCreateObjectRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ1_CREATE;
+ }
+ }
+ break;
+
+ // FWU_PS_OBJ1_CREATE: Create the INIT command object
+ case FWU_PS_OBJ1_CREATE:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ fwu->privateProcessState = FWU_PS_OBJ1_WRITE;
+ fwu->privateObjectBuf = fwu->commandObject;
+ fwu->privateObjectLen = fwu->commandObjectLen;
+ fwu->privateObjectIx = 0;
+ fwu->privateObjectCrc = 0xffffffff;
+ fwuPrepareLargeObjectSendBuffer(fwu, 0x08);
+ }
+ break;
+
+ // FWU_PS_OBJ1_WRITE: Write the INIT command object
+ case FWU_PS_OBJ1_WRITE:
+ if (tmpPrivateProcessRequest == FWU_PR_REQUEST_SENT) {
+ // more to send?
+ if (fwu->privateObjectIx == fwu->privateObjectLen) {
+ // no - request the CRC of the written data...
+ fwuPrepareSendBuffer(fwu, sGetCrcRequest, sGetCrcRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ1_CRC_GET;
+ } else {
+ fwuPrepareLargeObjectSendBuffer(fwu, 0x08);
+ }
+ }
+ break;
+
+ // FWU_PS_OBJ1_CRC_GET: Checksum verification
+ case FWU_PS_OBJ1_CRC_GET:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ // uint32_t actualLen =
+ // fwuLittleEndianToHost32(&fwu->privateResponseBuf[3]);
+ uint32_t actualCks =
+ fwuLittleEndianToHost32(&fwu->privateResponseBuf[7]);
+ if (actualCks == ~fwu->privateObjectCrc) {
+ // Checksum is OK; execute the command!
+ fwuPrepareSendBuffer(fwu, sExecuteObjectRequest,
+ sExecuteObjectRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ1_EXECUTE;
+ } else {
+ fwuSignalFailure(fwu, FWU_RSP_CHECKSUM_ERROR);
+ }
+ }
+ break;
+
+ case FWU_PS_OBJ1_EXECUTE:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ sSelectObjectRequest[1] = 0x02; // select object 2 (DATA object)
+ fwu->privateDataObjectOffset = 0; // from the beginning
+ fwuPrepareSendBuffer(fwu, sSelectObjectRequest,
+ sSelectObjectRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ2_SELECT;
+ }
+ break;
+
+ // FWU_PS_OBJ2_SELECT: Select the DATA object
+ case FWU_PS_OBJ2_SELECT:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ fwu->privateDataObjectMaxSize =
+ fwuLittleEndianToHost32(&fwu->privateResponseBuf[3]);
+ fwu->privateObjectCrc =
+ 0xffffffff; // do it here because it's global for the entire blob
+ // We'll create and execute multiple data objects, so it's ok if the
+ // actual size is greater than max size.
+ fwu->privateDataObjectSize =
+ (fwu->dataObjectLen -
+ fwu->privateDataObjectOffset); // nof bytes remaining
+ if (fwu->privateDataObjectSize > fwu->privateDataObjectMaxSize) {
+ fwu->privateDataObjectSize = fwu->privateDataObjectMaxSize;
+ }
+ sCreateObjectRequest[1] = 0x02; // create type 2 object (COMMAND)
+ fwuHostToLittleEndian32(fwu->privateDataObjectSize,
+ &sCreateObjectRequest[2]);
+ fwuPrepareSendBuffer(fwu, sCreateObjectRequest,
+ sCreateObjectRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ2_WAIT_FOR_CHUNK;
+ }
+ break;
+ case FWU_PS_OBJ2_WAIT_FOR_CHUNK:
+ break;
+ // FWU_PS_OBJ2_CREATE: Create the DATA object
+ case FWU_PS_OBJ2_CREATE:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ fwu->privateProcessState = FWU_PS_OBJ2_WRITE;
+ fwu->privateObjectBuf = fwu->dataObject;
+ fwu->privateObjectLen = fwu->privateDataObjectSize;
+ fwu->privateObjectIx = 0;
+ fwuPrepareLargeObjectSendBuffer(fwu, 0x08);
+ }
+ break;
+
+ // FWU_PS_OBJ2_WRITE: Write the DATA object
+ case FWU_PS_OBJ2_WRITE:
+ if (tmpPrivateProcessRequest == FWU_PR_REQUEST_SENT) {
+ // more to send?
+ if (fwu->privateObjectIx == fwu->privateObjectLen) {
+ // no - request the CRC of the written data...
+ fwuPrepareSendBuffer(fwu, sGetCrcRequest, sGetCrcRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ2_CRC_GET;
+ } else {
+ fwuPrepareLargeObjectSendBuffer(fwu, 0x08);
+ }
+ }
+ break;
+
+ // FWU_PS_OBJ2_CRC_GET: Checksum verification
+ case FWU_PS_OBJ2_CRC_GET:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ // uint32_t actualLen =
+ // fwuLittleEndianToHost32(&fwu->privateResponseBuf[3]);
+ uint32_t actualCks =
+ fwuLittleEndianToHost32(&fwu->privateResponseBuf[7]);
+ if (actualCks == ~fwu->privateObjectCrc) {
+ // Checksum is OK; execute the command!
+ fwuPrepareSendBuffer(fwu, sExecuteObjectRequest,
+ sExecuteObjectRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ2_EXECUTE;
+
+ } else {
+ fwuSignalFailure(fwu, FWU_RSP_CHECKSUM_ERROR);
+ }
+ }
+ break;
+
+ case FWU_PS_OBJ2_EXECUTE:
+ if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
+ fwu->privateDataObjectOffset += fwu->privateDataObjectSize;
+ if (fwu->privateDataObjectOffset == fwu->dataObjectLen) {
+ fwu->privateProcessState = FWU_PS_DONE;
+ fwu->processStatus = FWU_STATUS_COMPLETION;
+
+ } else {
+ // We'll create and execute multiple data objects, so it's ok if the
+ // actual size is greater than max size.
+ fwu->privateDataObjectSize =
+ (fwu->dataObjectLen -
+ fwu->privateDataObjectOffset); // nof bytes remaining
+ if (fwu->privateDataObjectSize > fwu->privateDataObjectMaxSize) {
+ fwu->privateDataObjectSize = fwu->privateDataObjectMaxSize;
+ }
+ sCreateObjectRequest[1] = 0x02; // create type 2 object (COMMAND)
+ fwuHostToLittleEndian32(fwu->privateDataObjectSize,
+ &sCreateObjectRequest[2]);
+ fwuPrepareSendBuffer(fwu, sCreateObjectRequest,
+ sCreateObjectRequestLen);
+ fwu->privateProcessState = FWU_PS_OBJ2_WAIT_FOR_CHUNK;
+ }
+ }
+ break;
+
+ default:
+ fwu->privateProcessState = FWU_PS_FAIL;
+ break;
+ }
+}
+
+static void fwuYieldCommandFsm(TFwu *fwu, uint32_t elapsedMillisec) {
+ uint8_t toSend;
+
+ // Automatically return from final states to IDLE.
+ if (fwu->privateCommandState == FWU_CS_DONE ||
+ fwu->privateCommandState == FWU_CS_FAIL) {
+ fwu->privateCommandState = FWU_CS_IDLE;
+ }
+
+ // Timeout?
+ if (fwu->privateCommandState != FWU_CS_IDLE) {
+ if (fwu->privateCommandTimeoutRemainingMillisec < elapsedMillisec) {
+ fwu->privateCommandTimeoutRemainingMillisec = 0;
+ } else {
+ fwu->privateCommandTimeoutRemainingMillisec -= elapsedMillisec;
+ }
+ if (fwu->privateCommandTimeoutRemainingMillisec == 0) {
+ fwuSignalFailure(fwu, FWU_RSP_TIMEOUT);
+ return;
+ }
+ }
+
+ // Catch errors
+ if (fwu->privateCommandRequest == FWU_CR_RX_OVERFLOW) {
+ fwuSignalFailure(fwu, FWU_RSP_RX_OVERFLOW);
+ return;
+ }
+ if (fwu->privateCommandRequest == FWU_CR_INVALID_ESCAPE_SEQ) {
+ fwuSignalFailure(fwu, FWU_RSP_RX_INVALID_ESCAPE_SEQ);
+ return;
+ }
+
+ switch (fwu->privateCommandState) {
+ case FWU_CS_IDLE:
+ // Ready and waiting for a transmission request.
+ if (fwu->privateCommandRequest == FWU_CR_SEND ||
+ fwu->privateCommandRequest == FWU_CR_SENDONLY) {
+ fwu->privateCommandSendOnly =
+ fwu->privateCommandRequest == FWU_CR_SENDONLY ? 1 : 0;
+ fwu->privateCommandRequest = FWU_CR_NONE;
+ fwu->privateCommandState = FWU_CS_SEND;
+ fwu->privateCommandTimeoutRemainingMillisec =
+ fwu->responseTimeoutMillisec;
+ }
+ break;
+ case FWU_CS_SEND:
+ // Continue sending data until the entire request has been sent.
+ toSend = fwu->privateRequestLen - fwu->privateRequestIx;
+ if (toSend == 0) {
+ if (fwu->privateCommandSendOnly) {
+ // This was a fire-and-forget request; we don't expect a response.
+ fwu->privateProcessRequest = FWU_PR_REQUEST_SENT;
+ fwu->privateCommandState = FWU_CS_DONE;
+ } else {
+ // The request has been sent; wait for response.
+ fwu->privateCommandState = FWU_CS_RECEIVE;
+ }
+ } else if (fwu->privateSendBufSpace > 0) {
+ uint8_t n = fwu->privateSendBufSpace;
+ if (n > toSend) {
+ n = toSend;
+ }
+ fwu->txFunction(fwu, &fwu->privateRequestBuf[fwu->privateRequestIx], n);
+ fwu->privateRequestIx += n;
+ }
+ break;
+ case FWU_CS_RECEIVE:
+ // Continue receiving data until the end-of-message marker has been
+ // received.
+ if (fwu->privateCommandRequest == FWU_CR_EOM_RECEIVED) {
+ fwu->privateCommandRequest = FWU_CR_NONE;
+ EFwuResponseStatus responseStatus = fwuTestReceivedPacketValid(fwu);
+ if (responseStatus == FWU_RSP_OK) {
+ // Inform the process state machine that command reception has
+ // completed.
+ fwu->privateProcessRequest = FWU_PR_RECEIVED_RESPONSE;
+ fwu->privateCommandState = FWU_CS_DONE;
+ } else {
+ fwu->responseStatus = responseStatus;
+ fwu->privateCommandState = FWU_CS_FAIL;
+ }
+ }
+ break;
+ default:
+ fwu->privateCommandState = FWU_CS_FAIL;
+ break;
+ }
+}
+
+static EFwuResponseStatus fwuTestReceivedPacketValid(TFwu *fwu) {
+ // 60 C0
+ if (fwu->privateResponseLen < 4) {
+ return FWU_RSP_TOO_SHORT;
+ }
+ if (fwu->privateResponseBuf[0] != FWU_RESPONSE_START) {
+ return FWU_RSP_START_MARKER_MISSING;
+ }
+ if (fwu->privateResponseBuf[1] != fwu->privateRequestBuf[0]) {
+ return FWU_RSP_REQUEST_REFERENCE_INVALID;
+ }
+ if (fwu->privateResponseBuf[2] != FWU_RESPONSE_SUCCESS) {
+ return FWU_RSP_ERROR_RESPONSE;
+ }
+ if (fwu->privateResponseBuf[fwu->privateResponseLen - 1] != FWU_EOM) {
+ return FWU_RSP_END_MARKER_MISSING;
+ }
+ return FWU_RSP_OK;
+}
+
+static void fwuPrepareLargeObjectSendBuffer(TFwu *fwu, uint8_t requestCode) {
+ uint16_t bytesTodo = fwu->privateObjectLen - fwu->privateObjectIx;
+ uint16_t bufSpace = FWU_REQUEST_BUF_SIZE - 2;
+
+ uint16_t i;
+ uint8_t *p = &fwu->privateRequestBuf[0];
+
+ *p++ = requestCode;
+ fwu->privateRequestLen = 2; // including requestCode and FWU_EOM
+ fwu->privateRequestIx = 0;
+
+ if (bytesTodo > 32) {
+ bytesTodo = 32;
+ }
+
+ for (i = 0; i < bytesTodo && bufSpace >= 2; i++) {
+ uint8_t b = fwu->privateObjectBuf[fwu->privateObjectIx];
+ // SLIP escape characters: C0->DBDC, DB->DBDD
+ if (b == 0xC0 || b == 0xDB) {
+ *p++ = 0xDB;
+ *p++ = (b == 0xC0) ? 0xDC : 0xDD;
+ fwu->privateRequestLen += 2;
+ bufSpace -= 2;
+ } else {
+ *p++ = b;
+ fwu->privateRequestLen++;
+ bufSpace--;
+ }
+ updateCrc(fwu, b);
+ fwu->privateObjectIx++;
+ }
+
+ *p = FWU_EOM;
+ fwu->privateCommandRequest = FWU_CR_SENDONLY;
+}
+
+static void fwuPrepareSendBuffer(TFwu *fwu, uint8_t *data, uint8_t len) {
+ // TODO assert privateCommandState == FWU_CS_IDLE | _DONE | _FAIL
+ // TODO assert len <= FWU_REQUEST_BUF_SIZE
+
+ uint8_t i;
+ uint8_t *p = &fwu->privateRequestBuf[0];
+
+ fwu->privateRequestIx = 0;
+ fwu->privateRequestLen = len + 1;
+ fwu->privateResponseLen = 0;
+
+ // Copy the data into our internal buffer.
+ for (i = 0; i < len; i++) {
+ *p++ = *data++;
+ }
+
+ // Add the end-of-message marker.
+ *p = FWU_EOM;
+
+ // Ready to send!
+ fwu->privateCommandRequest = FWU_CR_SEND;
+}
+
+static void updateCrc(TFwu *fwu, uint8_t b) {
+ uint8_t i;
+ uint32_t crc = fwu->privateObjectCrc;
+ crc ^= b;
+ for (i = 0; i < 8; i++) {
+ uint32_t m = (crc & 1) ? 0xffffffff : 0;
+ crc = (crc >> 1) ^ (0xedb88320u & m);
+ }
+ fwu->privateObjectCrc = crc;
+}
+
+static void fwuSignalFailure(TFwu *fwu, EFwuResponseStatus reason) {
+ fwu->responseStatus = reason;
+ fwu->privateCommandState = FWU_CS_FAIL;
+ // Signal failure to process state machine
+ fwu->privateProcessRequest = FWU_PR_REQUEST_FAILED;
+}
+
+static inline uint16_t fwuLittleEndianToHost16(uint8_t *bytes) {
+ return bytes[0] | ((uint16_t)bytes[1] << 8);
+}
+
+static inline uint32_t fwuLittleEndianToHost32(uint8_t *bytes) {
+ return bytes[0] | ((uint16_t)bytes[1] << 8) | ((uint32_t)bytes[2] << 16) |
+ ((uint32_t)bytes[3] << 24);
+}
+
+static inline void fwuHostToLittleEndian32(uint32_t v, uint8_t *bytes) {
+ uint8_t i;
+ for (i = 0; i < 4; i++) {
+ *bytes++ = v & 0xff;
+ v = v >> 8;
+ }
+}
diff --git a/core/embed/trezorhal/dfu/fwu.h b/core/embed/trezorhal/dfu/fwu.h
new file mode 100644
index 0000000000..a380b40c1e
--- /dev/null
+++ b/core/embed/trezorhal/dfu/fwu.h
@@ -0,0 +1,128 @@
+//
+// fwu.h
+// nrf52-dfu
+//
+// C library for the Nordic firmware update protocol.
+//
+// Created by Andreas Schweizer on 30.11.2018.
+// Copyright © 2018-2019 Classy Code GmbH
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+#ifndef __FWU_H__
+#define __FWU_H__ 1
+
+#include
+#include
+
+struct SFwu;
+
+#define FWU_REQUEST_BUF_SIZE 67
+#define FWU_RESPONSE_BUF_SIZE 16
+
+typedef enum {
+ FWU_STATUS_UNDEFINED = 0,
+ FWU_STATUS_FAILURE = 1,
+ FWU_STATUS_COMPLETION = 2,
+} EFwuProcessStatus;
+
+typedef enum {
+ FWU_RSP_OK = 0,
+ FWU_RSP_TOO_SHORT = 1,
+ FWU_RSP_START_MARKER_MISSING = 2,
+ FWU_RSP_END_MARKER_MISSING = 3,
+ FWU_RSP_REQUEST_REFERENCE_INVALID = 4,
+ FWU_RSP_ERROR_RESPONSE = 5,
+ FWU_RSP_TIMEOUT = 6,
+ FWU_RSP_PING_ID_MISMATCH = 7,
+ FWU_RSP_RX_OVERFLOW = 8,
+ FWU_RSP_INIT_COMMAND_TOO_LARGE = 9,
+ FWU_RSP_CHECKSUM_ERROR = 10,
+ FWU_RSP_DATA_OBJECT_TOO_LARGE = 11,
+ FWU_RSP_RX_INVALID_ESCAPE_SEQ = 12,
+} EFwuResponseStatus;
+
+typedef void (*FTxFunction)(struct SFwu *fwu, uint8_t *buf, uint8_t len);
+
+typedef struct SFwu {
+ // --- public - define these before calling fwuInit ---
+ // .dat
+ uint8_t *commandObject;
+ uint32_t commandObjectLen;
+ // .bin
+ uint8_t *dataObject;
+ uint32_t dataObjectLen;
+ // Sending bytes to the target
+ FTxFunction txFunction;
+ // Timeout when waiting for a response from the target
+ uint32_t responseTimeoutMillisec;
+ // --- public - result codes
+ // Overall process status code
+ EFwuProcessStatus processStatus;
+ // Response status code
+ EFwuResponseStatus responseStatus;
+ // --- private, don't modify ---
+ uint32_t privateDataObjectOffset;
+ uint32_t privateDataObjectSize;
+ uint32_t privateDataObjectMaxSize;
+ uint8_t privateProcessState;
+ uint8_t privateCommandState;
+ uint8_t privateCommandSendOnly;
+ uint32_t privateCommandTimeoutRemainingMillisec;
+ uint8_t privateRequestBuf[FWU_REQUEST_BUF_SIZE + 1];
+ uint8_t privateRequestLen;
+ uint8_t privateRequestIx;
+ uint8_t privateResponseBuf[FWU_RESPONSE_BUF_SIZE];
+ uint8_t privateResponseEscapeCharacter;
+ uint8_t privateResponseLen;
+ uint32_t privateResponseTimeElapsedMillisec;
+ uint8_t privateSendBufSpace;
+ uint8_t privateProcessRequest;
+ uint8_t privateCommandRequest;
+ uint16_t privateMtuSize;
+ // sending a large object buffer
+ uint8_t *privateObjectBuf;
+ uint32_t privateObjectLen;
+ uint32_t privateObjectIx;
+ uint32_t privateObjectCrc;
+} TFwu;
+
+// First function to call to set up the internal state in the FWU structure.
+void fwuInit(TFwu *fwu);
+
+// Execute the firmware update.
+void fwuExec(TFwu *fwu);
+
+// Call regularly to allow asynchronous processing to continue.
+EFwuProcessStatus fwuYield(TFwu *fwu, uint32_t elapsedMillisec);
+
+// Call after data from the target has been received.
+void fwuDidReceiveData(TFwu *fwu, uint8_t *bytes, uint8_t len);
+
+// Inform the FWU module that it may send maxLen bytes of data to the target.
+void fwuCanSendData(TFwu *fwu, uint8_t maxLen);
+
+// Call to send a chunk of the data object to the target.
+void fwuSendChunk(TFwu *fwu, uint8_t *buf, uint32_t len);
+
+// Call to check if a chunk of the data object can be sent to the target.
+bool fwuIsReadyForChunk(TFwu *fwu);
+
+#endif // __FWU_H__
diff --git a/core/embed/unix/dfu/dfu.c b/core/embed/unix/dfu/dfu.c
new file mode 100644
index 0000000000..2c67420ed2
--- /dev/null
+++ b/core/embed/unix/dfu/dfu.c
@@ -0,0 +1,56 @@
+//
+// main.c
+// nrf52-dfu
+//
+// Sample host application to demonstrate the usage of our C library for the
+// Nordic firmware update protocol.
+//
+// Created by Andreas Schweizer on 30.11.2018.
+// Copyright © 2018-2019 Classy Code GmbH
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+#include "dfu.h"
+#include
+
+static uint32_t binsize = 0;
+static uint32_t uploaded_total = 0;
+
+void dfu_init(void) {}
+
+dfu_result_t dfu_update_init(uint8_t *data, uint32_t len, uint32_t binary_len) {
+ binsize = binary_len;
+ uploaded_total = 0;
+ return DFU_NEXT_CHUNK;
+}
+
+dfu_result_t dfu_update_chunk(uint8_t *data, uint32_t len) {
+ uploaded_total += len;
+ if (uploaded_total >= binsize) {
+ return DFU_SUCCESS;
+ } else {
+ return DFU_NEXT_CHUNK;
+ }
+}
+
+dfu_result_t dfu_update_do(uint8_t *datfile, uint32_t datfile_len,
+ uint8_t *binfile, uint32_t binfile_len) {
+ return DFU_SUCCESS;
+}
diff --git a/core/embed/unix/dfu/dfu.h b/core/embed/unix/dfu/dfu.h
new file mode 100644
index 0000000000..8a7ed268de
--- /dev/null
+++ b/core/embed/unix/dfu/dfu.h
@@ -0,0 +1,19 @@
+
+#ifndef __DFU_H__
+#define __DFU_H__
+
+#include
+
+typedef enum {
+ DFU_NEXT_CHUNK,
+ DFU_SUCCESS,
+ DFU_FAIL,
+} dfu_result_t;
+
+void dfu_init(void);
+dfu_result_t dfu_update_init(uint8_t *data, uint32_t len, uint32_t binary_len);
+dfu_result_t dfu_update_chunk(uint8_t *data, uint32_t len);
+dfu_result_t dfu_update_do(uint8_t *datfile, uint32_t datfile_len,
+ uint8_t *binfile, uint32_t binfile_len);
+
+#endif
diff --git a/core/mocks/generated/trezorio/__init__.pyi b/core/mocks/generated/trezorio/__init__.pyi
index e84de507c0..c2bd1793ea 100644
--- a/core/mocks/generated/trezorio/__init__.pyi
+++ b/core/mocks/generated/trezorio/__init__.pyi
@@ -190,7 +190,7 @@ class WebUSB:
"""
Sends message using USB WebUSB (device) or UDP (emulator).
"""
-from . import fatfs, sdcard
+from . import fatfs, sdcard, ble
POLL_READ: int # wait until interface is readable and return read data
POLL_WRITE: int # wait until interface is writable
TOUCH: int # interface id of the touch events
diff --git a/core/src/all_modules.py b/core/src/all_modules.py
index c9f28bd33c..b08ae0c12a 100644
--- a/core/src/all_modules.py
+++ b/core/src/all_modules.py
@@ -299,6 +299,8 @@ apps.management.backup_device
import apps.management.backup_device
apps.management.backup_types
import apps.management.backup_types
+apps.management.ble.upload_ble_firmware_init
+import apps.management.ble.upload_ble_firmware_init
apps.management.change_pin
import apps.management.change_pin
apps.management.change_wipe_code
diff --git a/core/src/apps/management/ble/upload_ble_firmware_init.py b/core/src/apps/management/ble/upload_ble_firmware_init.py
new file mode 100644
index 0000000000..4c81aac3ab
--- /dev/null
+++ b/core/src/apps/management/ble/upload_ble_firmware_init.py
@@ -0,0 +1,78 @@
+from trezorio import ble
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from trezor.wire import GenericContext
+ from trezor.messages import (
+ UploadBLEFirmwareInit,
+ UploadBLEFirmwareChunk,
+ Success,
+ )
+
+
+async def upload_ble_firmware_chunk(
+ ctx: GenericContext, msg: UploadBLEFirmwareChunk
+) -> int:
+ result = ble.update_chunk(msg.data)
+
+ return result
+
+
+async def upload_ble_firmware_init(
+ ctx: GenericContext, msg: UploadBLEFirmwareInit
+) -> Success:
+ from trezor.enums import ButtonRequestType
+ from trezor.messages import (
+ UploadBLEFirmwareNextChunk,
+ UploadBLEFirmwareChunk,
+ Success,
+ )
+ from trezor.ui.layouts import confirm_action
+
+ await confirm_action(
+ ctx,
+ "confirm_upload_ble_firmware",
+ "Upload BLE firmware",
+ "",
+ "Update BLE FW?\n",
+ reverse=True,
+ verb="Confirm",
+ br_code=ButtonRequestType.Other,
+ )
+
+ from trezor.ui.layouts import progress
+
+ progress_layout = progress("Uploading...")
+
+ upload_progress = 0
+
+ p = int(1000 * upload_progress / msg.binsize)
+ progress_layout.report(p)
+
+ res = ble.update_init(msg.init_data, msg.binsize)
+
+ await ctx.write(UploadBLEFirmwareNextChunk(offset=0))
+
+ if res == 0:
+
+ while True:
+
+ received_msg = await ctx.read(UploadBLEFirmwareChunk)
+
+ result = await upload_ble_firmware_chunk(ctx, received_msg)
+
+ upload_progress += len(received_msg.data)
+ p = int(1000 * upload_progress / msg.binsize)
+ progress_layout.report(p)
+
+ if result == 0:
+ result_msg = UploadBLEFirmwareNextChunk(offset=0)
+ await ctx.write(result_msg)
+ del (result_msg, received_msg)
+ else:
+ del received_msg
+ break
+
+ progress_layout.report(1000)
+
+ return Success(message="BLE firmware update successful")
diff --git a/core/src/apps/workflow_handlers.py b/core/src/apps/workflow_handlers.py
index 057450b293..2841c1a6fd 100644
--- a/core/src/apps/workflow_handlers.py
+++ b/core/src/apps/workflow_handlers.py
@@ -55,6 +55,10 @@ def _find_message_handler_module(msg_type: int) -> str:
if utils.USE_SD_CARD and msg_type == MessageType.SdProtect:
return "apps.management.sd_protect"
+ # BLE
+ if msg_type == MessageType.UploadBLEFirmwareInit:
+ return "apps.management.ble.upload_ble_firmware_init"
+
# bitcoin
if msg_type == MessageType.AuthorizeCoinJoin:
return "apps.bitcoin.authorize_coinjoin"
diff --git a/core/src/trezor/enums/MessageType.py b/core/src/trezor/enums/MessageType.py
index c245a903b2..a068363574 100644
--- a/core/src/trezor/enums/MessageType.py
+++ b/core/src/trezor/enums/MessageType.py
@@ -50,6 +50,9 @@ FirmwareErase = 6
FirmwareUpload = 7
FirmwareRequest = 8
SelfTest = 32
+UploadBLEFirmwareInit = 8000
+UploadBLEFirmwareNextChunk = 8001
+UploadBLEFirmwareChunk = 8002
GetPublicKey = 11
PublicKey = 12
SignTx = 15
diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py
index eead516c86..f48f4e7085 100644
--- a/core/src/trezor/enums/__init__.py
+++ b/core/src/trezor/enums/__init__.py
@@ -72,6 +72,9 @@ if TYPE_CHECKING:
FirmwareUpload = 7
FirmwareRequest = 8
SelfTest = 32
+ UploadBLEFirmwareInit = 8000
+ UploadBLEFirmwareNextChunk = 8001
+ UploadBLEFirmwareChunk = 8002
GetPublicKey = 11
PublicKey = 12
SignTx = 15
diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py
index c37a6e0233..18ce1f100f 100644
--- a/core/src/trezor/messages.py
+++ b/core/src/trezor/messages.py
@@ -1177,6 +1177,50 @@ if TYPE_CHECKING:
def is_type_of(cls, msg: Any) -> TypeGuard["TxAckPrevExtraDataWrapper"]:
return isinstance(msg, cls)
+ class UploadBLEFirmwareInit(protobuf.MessageType):
+ init_data: "bytes"
+ binsize: "int"
+
+ def __init__(
+ self,
+ *,
+ init_data: "bytes",
+ binsize: "int",
+ ) -> None:
+ pass
+
+ @classmethod
+ def is_type_of(cls, msg: Any) -> TypeGuard["UploadBLEFirmwareInit"]:
+ return isinstance(msg, cls)
+
+ class UploadBLEFirmwareNextChunk(protobuf.MessageType):
+ offset: "int"
+
+ def __init__(
+ self,
+ *,
+ offset: "int",
+ ) -> None:
+ pass
+
+ @classmethod
+ def is_type_of(cls, msg: Any) -> TypeGuard["UploadBLEFirmwareNextChunk"]:
+ return isinstance(msg, cls)
+
+ class UploadBLEFirmwareChunk(protobuf.MessageType):
+ data: "bytes"
+
+ def __init__(
+ self,
+ *,
+ data: "bytes",
+ ) -> None:
+ pass
+
+ @classmethod
+ def is_type_of(cls, msg: Any) -> TypeGuard["UploadBLEFirmwareChunk"]:
+ return isinstance(msg, cls)
+
class CardanoBlockchainPointerType(protobuf.MessageType):
block_index: "int"
tx_index: "int"
diff --git a/core/tools/convert_ble_firmware.py b/core/tools/convert_ble_firmware.py
new file mode 100644
index 0000000000..c7509ce3a9
--- /dev/null
+++ b/core/tools/convert_ble_firmware.py
@@ -0,0 +1,35 @@
+import click
+import zipfile
+
+
+def convert_file(archive, infile, outfile, name):
+ data = archive.read(infile)
+ with open(outfile, "w") as outfile:
+ outfile.write("// Firmware BLOB - automatically generated\n")
+ outfile.write("\n")
+ outfile.write(f"#ifndef __FW_BLOB_{name}_H__\n")
+ outfile.write(f"#define __FW_BLOB_{name}_H__ 1\n")
+ outfile.write("\n")
+
+ outfile.write(f"uint8_t {name}[] = " + "{")
+
+ for i, byte in enumerate(data):
+ if i % 16 == 0:
+ outfile.write("\n ")
+ outfile.write("0x{:02x}, ".format(byte))
+
+ outfile.write("\n};\n")
+ outfile.write("\n")
+ outfile.write("#endif\n")
+
+
+@click.command()
+@click.argument("infile", type=click.File("rb"))
+def convert(infile):
+ with zipfile.ZipFile(infile) as archive:
+ convert_file(archive, "ble_firmware.bin", "./embed/firmware/dfu/ble_firmware_bin.h", "binfile")
+ convert_file(archive, "ble_firmware.dat", "./embed/firmware/dfu/ble_firmware_dat.h", "datfile")
+
+
+if __name__ == "__main__":
+ convert()
diff --git a/python/src/trezorlib/ble/__init__.py b/python/src/trezorlib/ble/__init__.py
new file mode 100644
index 0000000000..d57fd66d11
--- /dev/null
+++ b/python/src/trezorlib/ble/__init__.py
@@ -0,0 +1,34 @@
+import typing as t
+
+from .. import messages
+from ..tools import session
+
+if t.TYPE_CHECKING:
+ from ..client import TrezorClient
+
+
+@session
+def update(
+ client: "TrezorClient",
+ datfile: bytes,
+ binfile: bytes,
+ progress_update: t.Callable[[int], t.Any] = lambda _: None,
+):
+ chunk_len = 4096
+ offset = 0
+
+ resp = client.call(
+ messages.UploadBLEFirmwareInit(init_data=datfile, binsize=len(binfile))
+ )
+
+ while isinstance(resp, messages.UploadBLEFirmwareNextChunk):
+
+ payload = binfile[offset : offset + chunk_len]
+ resp = client.call(messages.UploadBLEFirmwareChunk(data=payload))
+ progress_update(chunk_len)
+ offset += chunk_len
+
+ if isinstance(resp, messages.Success):
+ return
+ else:
+ raise RuntimeError(f"Unexpected message {resp}")
diff --git a/python/src/trezorlib/cli/ble.py b/python/src/trezorlib/cli/ble.py
new file mode 100644
index 0000000000..a6ac7dadc1
--- /dev/null
+++ b/python/src/trezorlib/cli/ble.py
@@ -0,0 +1,62 @@
+# This file is part of the Trezor project.
+#
+# Copyright (C) 2012-2022 SatoshiLabs and contributors
+#
+# This library is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3
+# as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the License along with this library.
+# If not, see .
+
+import sys
+import zipfile
+from typing import TYPE_CHECKING, BinaryIO
+
+import click
+
+from .. import ble, exceptions
+from . import with_client
+
+if TYPE_CHECKING:
+ from ..client import TrezorClient
+
+
+@click.group(name="ble")
+def cli() -> None:
+ """BLE commands."""
+
+
+@cli.command()
+# fmt: off
+@click.argument("package", type=click.File("rb"))
+# fmt: on
+@with_client
+def update(
+ client: "TrezorClient",
+ package: BinaryIO,
+) -> None:
+ """Upload new BLE firmware to device."""
+
+ with zipfile.ZipFile(package) as archive:
+ binfile = archive.read("ble_firmware.bin")
+ datfile = archive.read("ble_firmware.dat")
+
+ """Perform the final act of loading the firmware into Trezor."""
+ try:
+ click.echo("Uploading...\r", nl=False)
+ with click.progressbar(
+ label="Uploading", length=len(binfile), show_eta=False
+ ) as bar:
+ ble.update(client, datfile, binfile, bar.update)
+ click.echo("Update successful.")
+ except exceptions.Cancelled:
+ click.echo("Update aborted on device.")
+ except exceptions.TrezorException as e:
+ click.echo(f"Update failed: {e}")
+ sys.exit(3)
diff --git a/python/src/trezorlib/cli/trezorctl.py b/python/src/trezorlib/cli/trezorctl.py
index b820f28d96..ff0fe7c6c6 100755
--- a/python/src/trezorlib/cli/trezorctl.py
+++ b/python/src/trezorlib/cli/trezorctl.py
@@ -32,6 +32,7 @@ from . import (
AliasedGroup,
TrezorConnection,
binance,
+ ble,
btc,
cardano,
cosi,
@@ -86,6 +87,7 @@ COMMAND_ALIASES = {
"upgrade-firmware": firmware.update,
"firmware-upgrade": firmware.update,
"firmware-update": firmware.update,
+ "ble-update": ble.update,
}
@@ -415,6 +417,7 @@ cli.add_command(tezos.cli)
cli.add_command(firmware.cli)
cli.add_command(debug.cli)
+cli.add_command(ble.cli)
#
# Main
diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py
index c0e47c271e..7e48907ba5 100644
--- a/python/src/trezorlib/messages.py
+++ b/python/src/trezorlib/messages.py
@@ -80,6 +80,9 @@ class MessageType(IntEnum):
FirmwareUpload = 7
FirmwareRequest = 8
SelfTest = 32
+ UploadBLEFirmwareInit = 8000
+ UploadBLEFirmwareNextChunk = 8001
+ UploadBLEFirmwareChunk = 8002
GetPublicKey = 11
PublicKey = 12
SignTx = 15
@@ -2040,6 +2043,51 @@ class TxAckPrevExtraDataWrapper(protobuf.MessageType):
self.extra_data_chunk = extra_data_chunk
+class UploadBLEFirmwareInit(protobuf.MessageType):
+ MESSAGE_WIRE_TYPE = 8000
+ FIELDS = {
+ 1: protobuf.Field("init_data", "bytes", repeated=False, required=True),
+ 2: protobuf.Field("binsize", "uint32", repeated=False, required=True),
+ }
+
+ def __init__(
+ self,
+ *,
+ init_data: "bytes",
+ binsize: "int",
+ ) -> None:
+ self.init_data = init_data
+ self.binsize = binsize
+
+
+class UploadBLEFirmwareNextChunk(protobuf.MessageType):
+ MESSAGE_WIRE_TYPE = 8001
+ FIELDS = {
+ 1: protobuf.Field("offset", "uint32", repeated=False, required=True),
+ }
+
+ def __init__(
+ self,
+ *,
+ offset: "int",
+ ) -> None:
+ self.offset = offset
+
+
+class UploadBLEFirmwareChunk(protobuf.MessageType):
+ MESSAGE_WIRE_TYPE = 8002
+ FIELDS = {
+ 1: protobuf.Field("data", "bytes", repeated=False, required=True),
+ }
+
+ def __init__(
+ self,
+ *,
+ data: "bytes",
+ ) -> None:
+ self.data = data
+
+
class FirmwareErase(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 6
FIELDS = {