|
| 1 | +#include <nrf_log.h> |
| 2 | +#include "FSService.h" |
| 3 | +#include "components/ble/BleController.h" |
| 4 | +#include "systemtask/SystemTask.h" |
| 5 | + |
| 6 | +using namespace Pinetime::Controllers; |
| 7 | + |
| 8 | +constexpr ble_uuid16_t FSService::fsServiceUuid; |
| 9 | +constexpr ble_uuid128_t FSService::fsVersionUuid; |
| 10 | +constexpr ble_uuid128_t FSService::fsTransferUuid; |
| 11 | + |
| 12 | +int FSServiceCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) { |
| 13 | + auto* fsService = static_cast<FSService*>(arg); |
| 14 | + return fsService->OnFSServiceRequested(conn_handle, attr_handle, ctxt); |
| 15 | +} |
| 16 | + |
| 17 | +FSService::FSService(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::FS& fs) |
| 18 | + : systemTask {systemTask}, |
| 19 | + fs {fs}, |
| 20 | + characteristicDefinition {{.uuid = &fsVersionUuid.u, |
| 21 | + .access_cb = FSServiceCallback, |
| 22 | + .arg = this, |
| 23 | + .flags = BLE_GATT_CHR_F_READ, |
| 24 | + .val_handle = &versionCharacteristicHandle}, |
| 25 | + { |
| 26 | + .uuid = &fsTransferUuid.u, |
| 27 | + .access_cb = FSServiceCallback, |
| 28 | + .arg = this, |
| 29 | + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY, |
| 30 | + .val_handle = &transferCharacteristicHandle, |
| 31 | + }, |
| 32 | + {0}}, |
| 33 | + serviceDefinition { |
| 34 | + {/* Device Information Service */ |
| 35 | + .type = BLE_GATT_SVC_TYPE_PRIMARY, |
| 36 | + .uuid = &fsServiceUuid.u, |
| 37 | + .characteristics = characteristicDefinition}, |
| 38 | + {0}, |
| 39 | + } { |
| 40 | +} |
| 41 | + |
| 42 | +void FSService::Init() { |
| 43 | + int res = 0; |
| 44 | + res = ble_gatts_count_cfg(serviceDefinition); |
| 45 | + ASSERT(res == 0); |
| 46 | + |
| 47 | + res = ble_gatts_add_svcs(serviceDefinition); |
| 48 | + ASSERT(res == 0); |
| 49 | +} |
| 50 | + |
| 51 | +int FSService::OnFSServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context) { |
| 52 | + if (attributeHandle == versionCharacteristicHandle) { |
| 53 | + NRF_LOG_INFO("FS_S : handle = %d", versionCharacteristicHandle); |
| 54 | + int res = os_mbuf_append(context->om, &fsVersion, sizeof(fsVersion)); |
| 55 | + return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; |
| 56 | + } |
| 57 | + if (attributeHandle == transferCharacteristicHandle) { |
| 58 | + return FSCommandHandler(connectionHandle, context->om); |
| 59 | + } |
| 60 | + return 0; |
| 61 | +} |
| 62 | + |
| 63 | +int FSService::FSCommandHandler(uint16_t connectionHandle, os_mbuf* om) { |
| 64 | + auto command = static_cast<commands>(om->om_data[0]); |
| 65 | + NRF_LOG_INFO("[FS_S] -> FSCommandHandler Command %d", command); |
| 66 | + // Just always make sure we are awake... |
| 67 | + systemTask.PushMessage(Pinetime::System::Messages::StartFileTransfer); |
| 68 | + vTaskDelay(10); |
| 69 | + while (systemTask.IsSleeping()) { |
| 70 | + vTaskDelay(100); // 50ms |
| 71 | + } |
| 72 | + lfs_dir_t dir = {0}; |
| 73 | + lfs_info info = {0}; |
| 74 | + lfs_file f = {0}; |
| 75 | + switch (command) { |
| 76 | + case commands::READ: { |
| 77 | + NRF_LOG_INFO("[FS_S] -> Read"); |
| 78 | + auto* header = (ReadHeader*) om->om_data; |
| 79 | + uint16_t plen = header->pathlen; |
| 80 | + if (plen > maxpathlen) { //> counts for null term |
| 81 | + return -1; |
| 82 | + } |
| 83 | + memcpy(filepath, header->pathstr, plen); |
| 84 | + filepath[plen] = 0; // Copy and null teminate string |
| 85 | + ReadResponse resp; |
| 86 | + os_mbuf* om; |
| 87 | + resp.command = commands::READ_DATA; |
| 88 | + resp.status = 0x01; |
| 89 | + resp.chunkoff = header->chunkoff; |
| 90 | + int res = fs.Stat(filepath, &info); |
| 91 | + if (res == LFS_ERR_NOENT && info.type != LFS_TYPE_DIR) { |
| 92 | + resp.status = (int8_t) res; |
| 93 | + resp.chunklen = 0; |
| 94 | + resp.totallen = 0; |
| 95 | + om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse)); |
| 96 | + } else { |
| 97 | + resp.chunklen = std::min(header->chunksize, info.size); // TODO add mtu somehow |
| 98 | + resp.totallen = info.size; |
| 99 | + fs.FileOpen(&f, filepath, LFS_O_RDONLY); |
| 100 | + fs.FileSeek(&f, header->chunkoff); |
| 101 | + uint8_t fileData[resp.chunklen] = {0}; |
| 102 | + resp.chunklen = fs.FileRead(&f, fileData, resp.chunklen); |
| 103 | + om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse)); |
| 104 | + os_mbuf_append(om, fileData, resp.chunklen); |
| 105 | + fs.FileClose(&f); |
| 106 | + } |
| 107 | + |
| 108 | + ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om); |
| 109 | + break; |
| 110 | + } |
| 111 | + case commands::READ_PACING: { |
| 112 | + NRF_LOG_INFO("[FS_S] -> Readpacing"); |
| 113 | + auto* header = (ReadHeader*) om->om_data; |
| 114 | + ReadResponse resp; |
| 115 | + resp.command = commands::READ_DATA; |
| 116 | + resp.status = 0x01; |
| 117 | + resp.chunkoff = header->chunkoff; |
| 118 | + int res = fs.Stat(filepath, &info); |
| 119 | + if (res == LFS_ERR_NOENT && info.type != LFS_TYPE_DIR) { |
| 120 | + resp.status = (int8_t) res; |
| 121 | + resp.chunklen = 0; |
| 122 | + resp.totallen = 0; |
| 123 | + } else { |
| 124 | + resp.chunklen = std::min(header->chunksize, info.size); // TODO add mtu somehow |
| 125 | + resp.totallen = info.size; |
| 126 | + fs.FileOpen(&f, filepath, LFS_O_RDONLY); |
| 127 | + fs.FileSeek(&f, header->chunkoff); |
| 128 | + } |
| 129 | + os_mbuf* om; |
| 130 | + if (resp.chunklen > 0) { |
| 131 | + uint8_t fileData[resp.chunklen] = {0}; |
| 132 | + resp.chunklen = fs.FileRead(&f, fileData, resp.chunklen); |
| 133 | + om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse)); |
| 134 | + os_mbuf_append(om, fileData, resp.chunklen); |
| 135 | + } else { |
| 136 | + resp.chunklen = 0; |
| 137 | + om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse)); |
| 138 | + } |
| 139 | + fs.FileClose(&f); |
| 140 | + ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om); |
| 141 | + break; |
| 142 | + } |
| 143 | + case commands::WRITE: { |
| 144 | + NRF_LOG_INFO("[FS_S] -> Write"); |
| 145 | + auto* header = (WriteHeader*) om->om_data; |
| 146 | + uint16_t plen = header->pathlen; |
| 147 | + if (plen > maxpathlen) { //> counts for null term |
| 148 | + return -1; // TODO make this actually return a BLE notif |
| 149 | + } |
| 150 | + memcpy(filepath, header->pathstr, plen); |
| 151 | + filepath[plen] = 0; // Copy and null teminate string |
| 152 | + fileSize = header->totalSize; |
| 153 | + WriteResponse resp; |
| 154 | + resp.command = commands::WRITE_PACING; |
| 155 | + resp.offset = header->offset; |
| 156 | + resp.modTime = 0; |
| 157 | + |
| 158 | + int res = fs.FileOpen(&f, filepath, LFS_O_RDWR | LFS_O_CREAT); |
| 159 | + if (res == 0) { |
| 160 | + fs.FileClose(&f); |
| 161 | + resp.status = (res == 0) ? 0x01 : (int8_t) res; |
| 162 | + } |
| 163 | + resp.freespace = std::min(fs.getSize() - (fs.GetFSSize() * fs.getBlockSize()), fileSize - header->offset); |
| 164 | + auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(WriteResponse)); |
| 165 | + ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om); |
| 166 | + break; |
| 167 | + } |
| 168 | + case commands::WRITE_DATA: { |
| 169 | + NRF_LOG_INFO("[FS_S] -> WriteData"); |
| 170 | + auto* header = (WritePacing*) om->om_data; |
| 171 | + WriteResponse resp; |
| 172 | + resp.command = commands::WRITE_PACING; |
| 173 | + resp.offset = header->offset; |
| 174 | + int res = 0; |
| 175 | + |
| 176 | + if (!(res = fs.FileOpen(&f, filepath, LFS_O_RDWR | LFS_O_CREAT))) { |
| 177 | + if ((res = fs.FileSeek(&f, header->offset)) >= 0) { |
| 178 | + res = fs.FileWrite(&f, header->data, header->dataSize); |
| 179 | + } |
| 180 | + fs.FileClose(&f); |
| 181 | + } |
| 182 | + if (res < 0) { |
| 183 | + resp.status = (int8_t) res; |
| 184 | + } |
| 185 | + resp.freespace = std::min(fs.getSize() - (fs.GetFSSize() * fs.getBlockSize()), fileSize - header->offset); |
| 186 | + auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(WriteResponse)); |
| 187 | + ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om); |
| 188 | + break; |
| 189 | + } |
| 190 | + case commands::DELETE: { |
| 191 | + NRF_LOG_INFO("[FS_S] -> Delete"); |
| 192 | + auto* header = (DelHeader*) om->om_data; |
| 193 | + uint16_t plen = header->pathlen; |
| 194 | + char path[plen + 1] = {0}; |
| 195 | + memcpy(path, header->pathstr, plen); |
| 196 | + path[plen] = 0; // Copy and null teminate string |
| 197 | + DelResponse resp {}; |
| 198 | + resp.command = commands::DELETE_STATUS; |
| 199 | + int res = fs.FileDelete(path); |
| 200 | + resp.status = (res == 0) ? 0x01 : (int8_t) res; |
| 201 | + auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(DelResponse)); |
| 202 | + ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om); |
| 203 | + break; |
| 204 | + } |
| 205 | + case commands::MKDIR: { |
| 206 | + NRF_LOG_INFO("[FS_S] -> MKDir"); |
| 207 | + auto* header = (MKDirHeader*) om->om_data; |
| 208 | + uint16_t plen = header->pathlen; |
| 209 | + char path[plen + 1] = {0}; |
| 210 | + memcpy(path, header->pathstr, plen); |
| 211 | + path[plen] = 0; // Copy and null teminate string |
| 212 | + MKDirResponse resp {}; |
| 213 | + resp.command = commands::MKDIR_STATUS; |
| 214 | + resp.modification_time = 0; |
| 215 | + int res = fs.DirCreate(path); |
| 216 | + resp.status = (res == 0) ? 0x01 : (int8_t) res; |
| 217 | + auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(MKDirResponse)); |
| 218 | + ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om); |
| 219 | + break; |
| 220 | + } |
| 221 | + case commands::LISTDIR: { |
| 222 | + NRF_LOG_INFO("[FS_S] -> ListDir"); |
| 223 | + ListDirHeader* header = (ListDirHeader*) om->om_data; |
| 224 | + uint16_t plen = header->pathlen; |
| 225 | + char path[plen + 1] = {0}; |
| 226 | + path[plen] = 0; // Copy and null teminate string |
| 227 | + memcpy(path, header->pathstr, plen); |
| 228 | + |
| 229 | + ListDirResponse resp {}; |
| 230 | + |
| 231 | + resp.command = commands::LISTDIR_ENTRY; |
| 232 | + resp.status = 0x01; |
| 233 | + resp.totalentries = 0; |
| 234 | + resp.entry = 0; |
| 235 | + resp.modification_time = 0; |
| 236 | + int res = fs.DirOpen(path, &dir); |
| 237 | + if (res != 0) { |
| 238 | + resp.status = (int8_t) res; |
| 239 | + auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(ListDirResponse)); |
| 240 | + ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om); |
| 241 | + break; |
| 242 | + }; |
| 243 | + while (fs.DirRead(&dir, &info)) { |
| 244 | + resp.totalentries++; |
| 245 | + } |
| 246 | + fs.DirRewind(&dir); |
| 247 | + while (true) { |
| 248 | + res = fs.DirRead(&dir, &info); |
| 249 | + if (res <= 0) { |
| 250 | + break; |
| 251 | + } |
| 252 | + switch (info.type) { |
| 253 | + case LFS_TYPE_REG: { |
| 254 | + resp.flags = 0; |
| 255 | + resp.file_size = info.size; |
| 256 | + break; |
| 257 | + } |
| 258 | + case LFS_TYPE_DIR: { |
| 259 | + resp.flags = 1; |
| 260 | + resp.file_size = 0; |
| 261 | + break; |
| 262 | + } |
| 263 | + } |
| 264 | + |
| 265 | + // strcpy(resp.path, info.name); |
| 266 | + resp.path_length = strlen(info.name); |
| 267 | + auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(ListDirResponse)); |
| 268 | + os_mbuf_append(om, info.name, resp.path_length); |
| 269 | + ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om); |
| 270 | + /* |
| 271 | + * Todo Figure out how to know when the previous Notify was TX'd |
| 272 | + * For now just delay 100ms to make sure that the data went out... |
| 273 | + */ |
| 274 | + vTaskDelay(100); // Allow stuff to actually go out over the BLE conn |
| 275 | + resp.entry++; |
| 276 | + } |
| 277 | + assert(fs.DirClose(&dir) == 0); |
| 278 | + resp.file_size = 0; |
| 279 | + resp.path_length = 0; |
| 280 | + resp.flags = 0; |
| 281 | + auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(ListDirResponse)); |
| 282 | + ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om); |
| 283 | + break; |
| 284 | + } |
| 285 | + case commands::MOVE: { |
| 286 | + NRF_LOG_INFO("[FS_S] -> Move"); |
| 287 | + MoveHeader* header = (MoveHeader*) om->om_data; |
| 288 | + uint16_t plen = header->OldPathLength; |
| 289 | + // Null Terminate string |
| 290 | + header->pathstr[plen] = 0; |
| 291 | + char path[header->NewPathLength + 1] = {0}; |
| 292 | + memcpy(path, &header->pathstr[plen + 1], header->NewPathLength); |
| 293 | + path[header->NewPathLength] = 0; // Copy and null teminate string |
| 294 | + MoveResponse resp {}; |
| 295 | + resp.command = commands::MOVE_STATUS; |
| 296 | + int8_t res = (int8_t) fs.Rename(header->pathstr, path); |
| 297 | + resp.status = (res == 0) ? 1 : res; |
| 298 | + auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(MoveResponse)); |
| 299 | + ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om); |
| 300 | + } |
| 301 | + default: |
| 302 | + break; |
| 303 | + } |
| 304 | + NRF_LOG_INFO("[FS_S] -> done "); |
| 305 | + systemTask.PushMessage(Pinetime::System::Messages::StopFileTransfer); |
| 306 | + return 0; |
| 307 | +} |
| 308 | + |
| 309 | +// Loads resp with file data given a valid filepath header and resp |
| 310 | +void FSService::prepareReadDataResp(ReadHeader* header, ReadResponse* resp) { |
| 311 | + // uint16_t plen = header->pathlen; |
| 312 | + resp->command = commands::READ_DATA; |
| 313 | + resp->chunkoff = header->chunkoff; |
| 314 | + resp->status = 0x01; |
| 315 | + struct lfs_info info = {}; |
| 316 | + int res = fs.Stat(filepath, &info); |
| 317 | + if (res == LFS_ERR_NOENT && info.type != LFS_TYPE_DIR) { |
| 318 | + resp->status = 0x03; |
| 319 | + resp->chunklen = 0; |
| 320 | + resp->totallen = 0; |
| 321 | + } else { |
| 322 | + lfs_file f; |
| 323 | + resp->chunklen = std::min(header->chunksize, info.size); |
| 324 | + resp->totallen = info.size; |
| 325 | + fs.FileOpen(&f, filepath, LFS_O_RDONLY); |
| 326 | + fs.FileSeek(&f, header->chunkoff); |
| 327 | + resp->chunklen = fs.FileRead(&f, resp->chunk, resp->chunklen); |
| 328 | + fs.FileClose(&f); |
| 329 | + } |
| 330 | +} |
0 commit comments