|
| 1 | +/* |
| 2 | + * Mbed-OS Microcontroller Library |
| 3 | + * Copyright (c) 2020 Embedded Planet |
| 4 | + * Copyright (c) 2020 ARM Limited |
| 5 | + * SPDX-License-Identifier: Apache-2.0 |
| 6 | + * |
| 7 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 8 | + * you may not use this file except in compliance with the License. |
| 9 | + * You may obtain a copy of the License at |
| 10 | + * |
| 11 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | + * |
| 13 | + * Unless required by applicable law or agreed to in writing, software |
| 14 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 15 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 16 | + * See the License for the specific language governing permissions and |
| 17 | + * limitations under the License |
| 18 | + */ |
| 19 | + |
| 20 | +#ifndef MBED_OS_EXPERIMENTAL_BLE_SERVICES_SERVICES_INC_DFUSERVICE_H_ |
| 21 | +#define MBED_OS_EXPERIMENTAL_BLE_SERVICES_SERVICES_INC_DFUSERVICE_H_ |
| 22 | + |
| 23 | +#include "ble/common/UUID.h" |
| 24 | +#include "ble/BLE.h" |
| 25 | +#include "ble/GattServer.h" |
| 26 | +#include "ble/Gap.h" |
| 27 | + |
| 28 | +#include "platform/Callback.h" |
| 29 | +#include "platform/Span.h" |
| 30 | +#include "platform/CircularBuffer.h" |
| 31 | +#include "platform/PlatformMutex.h" |
| 32 | + |
| 33 | +#include "BlockDevice.h" |
| 34 | + |
| 35 | +/** |
| 36 | + * Maximum length of data (in bytes) that the DFU service |
| 37 | + * can receive at one time. |
| 38 | + * |
| 39 | + * Typically MTU - 3 bytes for overhead |
| 40 | + */ |
| 41 | +#ifndef MBED_CONF_CORDIO_DESIRED_ATT_MTU |
| 42 | +#define BLE_DFU_SERVICE_MAX_DATA_LEN MBED_CONF_BLE_DFU_SERVICE_BUFFER_SIZE |
| 43 | +#else |
| 44 | +#define BLE_DFU_SERVICE_MAX_DATA_LEN (MBED_CONF_CORDIO_DESIRED_ATT_MTU - 3) |
| 45 | +#endif |
| 46 | + |
| 47 | +/** |
| 48 | + * RX Buffer Sizes |
| 49 | + */ |
| 50 | +#ifndef MBED_CONF_BLE_DFU_SERVICE_RX_BUFFER_SIZE |
| 51 | +#define MBED_CONF_BLE_DFU_SERVICE_RX_BUFFER_SIZE 256 |
| 52 | +#endif |
| 53 | + |
| 54 | +/** |
| 55 | + * Maximum number of slots available |
| 56 | + */ |
| 57 | +#ifndef MBED_CONF_BLE_DFU_SERVICE_MAX_SLOTS |
| 58 | +#define MBED_CONF_BLE_DFU_SERVICE_MAX_SLOTS 3 |
| 59 | +#endif |
| 60 | + |
| 61 | +/** |
| 62 | + * UUIDs |
| 63 | + */ |
| 64 | +namespace uuids { |
| 65 | +namespace DFUService { |
| 66 | + |
| 67 | + const UUID BaseUUID("53880000-65fd-4651-ba8e-91527f06c887"); |
| 68 | + const UUID SlotUUID("53880001-65fd-4651-ba8e-91527f06c887"); |
| 69 | + const UUID OffsetUUID("53880002-65fd-4651-ba8e-91527f06c887"); |
| 70 | + const UUID BinaryStreamUUID("53880003-65fd-4651-ba8e-91527f06c887"); |
| 71 | + const UUID ControlUUID("53880004-65fd-4651-ba8e-91527f06c887"); |
| 72 | + const UUID StatusUUID("53880005-65fd-4651-ba8e-91527f06c887"); |
| 73 | + |
| 74 | +}} |
| 75 | + |
| 76 | + |
| 77 | +/** |
| 78 | + * API Brainstorm: |
| 79 | + * DFU service will have several characteristics: |
| 80 | + * - Current Offset (Read/Write), gives the offset address, in bytes, of the write pointer |
| 81 | + * --- Writes to this characteristic while there is data in binary data stream buffer will be rejected. |
| 82 | + * A rejected write will initiate flushing the buffer to the selected slot block device. |
| 83 | + * Subsequent writes will be rejected until flushing is complete. |
| 84 | + * Note: any writes to the binary data stream characteristic while the buffer is being flushed will be ignored |
| 85 | + * --- If the delta bit is enabled, any memory sections skipped will be written with bytes copied from the primary application |
| 86 | + * - Binary Data stream, variable-length array characteristic for streaming the update in binary. |
| 87 | + * The underlying block device will be written at the offset given by current offset for each byte written to this characteristic. |
| 88 | + * The offset is incremented for each byte written |
| 89 | + * - DFU Control Characteristic |
| 90 | + * - Notify/Indicate/Read (for flow control bit mainly) |
| 91 | + * - Write (w/ response), ability to add security requirements |
| 92 | + * - Bit flags: |
| 93 | + * --- DFU Enable, DFU abort = write 0 during update |
| 94 | + * --- DFU Commit |
| 95 | + * --- Delta mode (any skipped sections will be written with existing app data) |
| 96 | + * --- Flow Control Bit (if set, peer should pause writing to binary stream characteristic) |
| 97 | + * - Write is only allowed if DFU is currently allowed |
| 98 | + * - Allows application/device to prepare for an update (cache/save data, shutdown certain things, erase/prepare flash area) |
| 99 | + * - Status characteristic |
| 100 | + * - Notify/Indicate/Read |
| 101 | + * - Error code (eg: update too large, invalid update (after reboot), etc) |
| 102 | + * - Selected Slot |
| 103 | + * - Write (w/ response) |
| 104 | + * - Write is only allowed if slot has valid block device |
| 105 | + * - Deselected slot BD is deinited, selected slot is inited |
| 106 | + * - Similar to offset, writes to this characteristic while there is data in the binary data stream buffer |
| 107 | + * will flush the buffer to the selected block device before the selected slot change is applied. |
| 108 | + * Note: In delta mode, selecting a new slot WILL NOT result in the remaining data in the slot being written with copied application data |
| 109 | + * To accomplish this, the peer should write the offset characteristic to the point where data should be copied before changing slots. |
| 110 | + * |
| 111 | + * Notes: |
| 112 | + * - Valid slots are intended to be empirically determined by the peer (as necessary) |
| 113 | + * by attempting to set the |
| 114 | + * |
| 115 | + * - Should writes to the binary data stream be synchronized with flash write waits? Potentially much slower |
| 116 | + * - Control bitflags class? Use std::bitset? |
| 117 | + * |
| 118 | + */ |
| 119 | +#if BLE_FEATURE_GATT_SERVER |
| 120 | + |
| 121 | +class DFUService : public ble::GattServer::EventHandler, |
| 122 | + public ble::Gap::EventHandler, |
| 123 | + private mbed::NonCopyable<DFUService> { |
| 124 | + |
| 125 | +public: |
| 126 | + |
| 127 | + |
| 128 | + /** |
| 129 | + * As per Bluetooth Core specification V5.2, Vol 3, Part F, Table 3.4 (Error Codes) |
| 130 | + * ATT Error Codes between 0x80 and 0x9F are reserved for use by the application |
| 131 | + * |
| 132 | + * These error codes are valid for the DFUService application layer in addition to those |
| 133 | + * defined in the GattAuthCallbackReply_t enum. |
| 134 | + */ |
| 135 | + enum ApplicationError_t { |
| 136 | + |
| 137 | + AUTH_CALLBACK_REPLY_ATTERR_APP_BUSY = 0x019E, /** DFUService is busy (eg: flush in progress) */ |
| 138 | + AUTH_CALLBACK_REPLY_ATTERR_APP_INVALID_SLOT_NUM = 0x019F, /** Client requested invalid slot index */ |
| 139 | + |
| 140 | + }; |
| 141 | + |
| 142 | + /** |
| 143 | + * Class encapsulating a change to the DFU control characteristic |
| 144 | + */ |
| 145 | + class ControlChange { |
| 146 | + |
| 147 | + /* Allow DFUService to instantiate ControlChange instances */ |
| 148 | + friend DFUService; |
| 149 | + |
| 150 | + public: |
| 151 | + |
| 152 | + const DFUService& service() const { |
| 153 | + return _dfu_svc; |
| 154 | + } |
| 155 | + |
| 156 | + uint8_t value() const { |
| 157 | + return _new_value; |
| 158 | + } |
| 159 | + |
| 160 | + uint8_t get_changed_bits() const { |
| 161 | + return (_old_value ^ _new_value); |
| 162 | + } |
| 163 | + |
| 164 | + protected: |
| 165 | + |
| 166 | + ControlChange(DFUService& service, uint8_t value) : |
| 167 | + _dfu_svc(service), _old_value(service.get_dfu_control_bits()), |
| 168 | + _new_value(value) { |
| 169 | + } |
| 170 | + |
| 171 | + protected: |
| 172 | + |
| 173 | + DFUService& _dfu_svc; |
| 174 | + |
| 175 | + uint8_t _old_value; |
| 176 | + uint8_t _new_value; |
| 177 | + |
| 178 | + }; |
| 179 | + |
| 180 | +public: |
| 181 | + |
| 182 | + /** |
| 183 | + * Instantiate a DFUService instance |
| 184 | + * @param[in] bd BlockDevice to use for storing update candidates in slot 0 |
| 185 | + */ |
| 186 | + DFUService(mbed::BlockDevice *bd); |
| 187 | + |
| 188 | + virtual ~DFUService(); |
| 189 | + |
| 190 | + uint8_t get_dfu_control_bits() const { |
| 191 | + return _dfu_control; |
| 192 | + } |
| 193 | + |
| 194 | + bool is_dfu_enabled() const { |
| 195 | + // TODO return _dfu_contrl & DFU_ENABLED_BIT |
| 196 | + } |
| 197 | + |
| 198 | + void start(BLE &ble_interface); |
| 199 | + |
| 200 | + void assign_slot_block_device(uint8_t slot, mbed::BlockDevice *bd); |
| 201 | + |
| 202 | + /** |
| 203 | + * Register a callback to be executed when a write request occurs for the |
| 204 | + * DFU Control characteristic. The application may then accept or reject the |
| 205 | + * requested changes as appropriate. |
| 206 | + * |
| 207 | + * @param[in] cb Application callback or nullptr to deregister |
| 208 | + * |
| 209 | + * @note If the application does not explicitly reject the control request, |
| 210 | + * the request will be accepted by default. |
| 211 | + */ |
| 212 | + void on_dfu_control_request(mbed::Callback<GattAuthCallbackReply_t(ControlChange&)> cb) { |
| 213 | + _ctrl_req_cb = cb; |
| 214 | + } |
| 215 | + |
| 216 | + /** |
| 217 | + * Register a callback to be executed when a write is committed to the |
| 218 | + * DFU Control characteristic |
| 219 | + * |
| 220 | + * @param[in] cb Application callback or nullptr to deregister |
| 221 | + * |
| 222 | + */ |
| 223 | + void on_dfu_control_change(mbed::Callback<void(ControlChange&)> cb) { |
| 224 | + _ctrl_update_cb = cb; |
| 225 | + } |
| 226 | + |
| 227 | +protected: |
| 228 | + |
| 229 | + void set_status(); |
| 230 | + |
| 231 | + /** GattServer::EventHandler overrides */ |
| 232 | + void onDataWritten(const GattWriteCallbackParams ¶ms) override; |
| 233 | + |
| 234 | + /** Gap::EventHandler overrides */ |
| 235 | + void onDisconnectionComplete(const ble::DisconnectionCompleteEvent &event) override; |
| 236 | + |
| 237 | + /** Internal handlers */ |
| 238 | + void on_slot_write_request(GattWriteAuthCallbackParams *params); |
| 239 | + void on_slot_written(uint8_t new_slot); |
| 240 | + |
| 241 | + void on_offset_write_request(GattWriteAuthCallbackParams *params); |
| 242 | + void on_offset_written(uint32_t new_offset); |
| 243 | + |
| 244 | + void on_bds_written(mbed::Span<const uint8_t> data); |
| 245 | + |
| 246 | + void on_dfu_ctrl_write_request(GattWriteAuthCallbackParams *params); |
| 247 | + void on_dfu_ctrl_written(uint8_t new_ctrl); |
| 248 | + |
| 249 | +protected: |
| 250 | + |
| 251 | + /** Selected slot */ |
| 252 | + uint8_t _selected_slot = 0; |
| 253 | + |
| 254 | + /** Current offset address */ |
| 255 | + uint32_t _current_offset = 0; |
| 256 | + |
| 257 | + /** RX Buffer for binary serial */ |
| 258 | + uint8_t _rxbuf[BLE_DFU_SERVICE_MAX_DATA_LEN] = { 0 }; |
| 259 | + |
| 260 | + /** DFU control */ |
| 261 | + uint8_t _dfu_control = 0; |
| 262 | + |
| 263 | + /** Update status */ |
| 264 | + uint8_t _status = 0; |
| 265 | + |
| 266 | + /** Gatt Characteristics */ |
| 267 | + GattCharacteristic _slot_char; |
| 268 | + GattCharacteristic _offset_char; |
| 269 | + GattCharacteristic _rx_char; |
| 270 | + GattCharacteristic _dfu_ctrl_char; |
| 271 | + GattCharacteristic _status_char; |
| 272 | + |
| 273 | + GattCharacteristic* _characteristics[5]; |
| 274 | + |
| 275 | + GattService _dfu_service; |
| 276 | + |
| 277 | + GattServer* _server; |
| 278 | + |
| 279 | + /** Slot BlockDevices */ |
| 280 | + mbed::BlockDevice *_slot_bds[MBED_CONF_BLE_DFU_SERVICE_MAX_SLOTS] = { 0 }; |
| 281 | + |
| 282 | + /** Application callbacks */ |
| 283 | + mbed::Callback<GattAuthCallbackReply_t(ControlChange&)> _ctrl_req_cb = nullptr; |
| 284 | + mbed::Callback<void(ControlChange&)> _ctrl_update_cb = nullptr; |
| 285 | + |
| 286 | + /** Internal circular buffer */ |
| 287 | + mbed::CircularBuffer<uint8_t, MBED_CONF_BLE_DFU_SERVICE_RX_BUFFER_SIZE> _bin_stream_buf; |
| 288 | + |
| 289 | + /** Flush binary stream buffer flag */ |
| 290 | + bool _flush_bin_buf = false; |
| 291 | + |
| 292 | + /** Mutex */ |
| 293 | + PlatformMutex _mutex; |
| 294 | + |
| 295 | +}; |
| 296 | + |
| 297 | +#endif //BLE_FEATURE_GATT_SERVER |
| 298 | + |
| 299 | + |
| 300 | +#endif /* MBED_OS_EXPERIMENTAL_BLE_SERVICES_SERVICES_INC_DFUSERVICE_H_ */ |
0 commit comments