From 64ff8d9e1911edad8b5778b0f25b5e645448b6fc Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 7 Sep 2021 18:03:42 -0700 Subject: [PATCH 1/3] v3 BLE file service: Add file modification times --- ports/nrf/fatfs_port.c | 8 ++ supervisor/fatfs_port.h | 34 +++++++ supervisor/shared/bluetooth/file_transfer.c | 91 +++++++++++++------ .../shared/bluetooth/file_transfer_protocol.h | 37 +++++--- 4 files changed, 130 insertions(+), 40 deletions(-) create mode 100644 supervisor/fatfs_port.h diff --git a/ports/nrf/fatfs_port.c b/ports/nrf/fatfs_port.c index 5347af4177..f2644adbd3 100644 --- a/ports/nrf/fatfs_port.c +++ b/ports/nrf/fatfs_port.c @@ -30,7 +30,11 @@ #include "shared-bindings/rtc/RTC.h" #include "shared-bindings/time/__init__.h" +DWORD _time_override = 0; DWORD get_fattime(void) { + if (_time_override > 0) { + return _time_override; + } #if CIRCUITPY_RTC timeutils_struct_time_t tm; common_hal_rtc_get_time(&tm); @@ -40,3 +44,7 @@ DWORD get_fattime(void) { return ((2016 - 1980) << 25) | ((9) << 21) | ((1) << 16) | ((16) << 11) | ((43) << 5) | (35 / 2); #endif } + +void override_fattime(DWORD time) { + _time_override = time; +} diff --git a/supervisor/fatfs_port.h b/supervisor/fatfs_port.h new file mode 100644 index 0000000000..e76ced524d --- /dev/null +++ b/supervisor/fatfs_port.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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 MICROPY_INCLUDED_SUPERVISOR_FATFS_PORT_H +#define MICROPY_INCLUDED_SUPERVISOR_FATFS_PORT_H + +#include "lib/oofatfs/ff.h" + +void override_fattime(DWORD time); + +#endif // MICROPY_INCLUDED_SUPERVISOR_FATFS_PORT_H diff --git a/supervisor/shared/bluetooth/file_transfer.c b/supervisor/shared/bluetooth/file_transfer.c index 908cc958d6..eaebb3fbcb 100644 --- a/supervisor/shared/bluetooth/file_transfer.c +++ b/supervisor/shared/bluetooth/file_transfer.c @@ -28,6 +28,7 @@ #include "extmod/vfs.h" #include "extmod/vfs_fat.h" +#include "lib/timeutils/timeutils.h" #include "shared-bindings/_bleio/__init__.h" #include "shared-bindings/_bleio/Adapter.h" @@ -41,6 +42,7 @@ #include "common-hal/_bleio/__init__.h" +#include "supervisor/fatfs_port.h" #include "supervisor/shared/autoreload.h" #include "supervisor/shared/bluetooth/file_transfer_protocol.h" #include "supervisor/shared/tick.h" @@ -98,7 +100,7 @@ void supervisor_start_bluetooth_file_transfer(void) { NULL, // no initial value NULL); // no description - uint32_t version = 2; + uint32_t version = 3; mp_buffer_info_t bufinfo; bufinfo.buf = &version; bufinfo.len = sizeof(version); @@ -131,12 +133,23 @@ void supervisor_start_bluetooth_file_transfer(void) { #define ANY_COMMAND 0x00 #define THIS_COMMAND 0x01 +uint64_t truncate_time(uint64_t input_time, DWORD *fattime) { + timeutils_struct_time_t tm; + uint64_t seconds_since_epoch = timeutils_seconds_since_epoch_from_nanoseconds_since_1970(input_time); + timeutils_seconds_since_epoch_to_struct_time(seconds_since_epoch, &tm); + uint64_t truncated_time = timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970((seconds_since_epoch / 2) * 2 * 1000000000); + + *fattime = ((tm.tm_year - 1980) << 25) | (tm.tm_mon << 21) | (tm.tm_mday << 16) | + (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); + return truncated_time; +} + // Used by read and write. STATIC FIL active_file; STATIC uint8_t _process_read(const uint8_t *raw_buf, size_t command_len) { struct read_command *command = (struct read_command *)raw_buf; - size_t header_size = 12; - size_t response_size = 16; + size_t header_size = sizeof(struct read_command); + size_t response_size = sizeof(struct read_data); uint8_t data_buffer[response_size]; struct read_data response; response.command = READ_DATA; @@ -190,38 +203,38 @@ STATIC uint8_t _process_read(const uint8_t *raw_buf, size_t command_len) { return READ_PACING; } -STATIC uint8_t _process_read_pacing(const uint8_t *command, size_t command_len) { - size_t response_size = 4 * sizeof(uint32_t); - uint32_t response[response_size / sizeof(uint32_t)]; - uint8_t *response_bytes = (uint8_t *)response; - response_bytes[0] = READ_DATA; - response_bytes[1] = STATUS_OK; - uint32_t offset = ((uint32_t *)command)[1]; - uint32_t chunk_size = ((uint32_t *)command)[2]; +STATIC uint8_t _process_read_pacing(const uint8_t *raw_buf, size_t command_len) { + struct read_pacing *command = (struct read_pacing *)raw_buf; + struct read_data response; + response.command = READ_DATA; + response.status = STATUS_OK; + size_t response_size = sizeof(struct read_data); + uint32_t total_length = f_size(&active_file); // Write out the response header. - chunk_size = MIN(chunk_size, total_length - offset); - response[1] = offset; - response[2] = total_length; - response[3] = chunk_size; + uint32_t chunk_size = MIN(command->chunk_size, total_length - command->chunk_offset); + response.chunk_offset = command->chunk_offset; + response.total_length = total_length; + response.data_size = chunk_size; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, response_size, NULL, 0); - f_lseek(&active_file, offset); + f_lseek(&active_file, command->chunk_offset); // Write out the chunk contents. We can do this in small pieces because PacketBuffer // will assemble them into larger packets of its own. size_t chunk_offset = 0; + uint8_t data[20]; while (chunk_offset < chunk_size) { size_t quantity_read; - size_t read_size = MIN(chunk_size - chunk_offset, response_size); - FRESULT result = f_read(&active_file, response, read_size, &quantity_read); + size_t read_size = MIN(chunk_size - chunk_offset, sizeof(data)); + FRESULT result = f_read(&active_file, &data, read_size, &quantity_read); if (quantity_read == 0 || result != FR_OK) { // TODO: If we can't read everything, then the file must have been shortened. Maybe we // should return 0s to pad it out. break; } - common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, quantity_read, NULL, 0); + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&data, quantity_read, NULL, 0); chunk_offset += quantity_read; } - if ((offset + chunk_size) >= total_length) { + if ((chunk_offset + chunk_size) >= total_length) { f_close(&active_file); return ANY_COMMAND; } @@ -230,10 +243,11 @@ STATIC uint8_t _process_read_pacing(const uint8_t *command, size_t command_len) // Used by write and write data to know when the write is complete. STATIC size_t total_write_length; +STATIC uint64_t _truncated_time; STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { struct write_command *command = (struct write_command *)raw_buf; - size_t header_size = 12; + size_t header_size = sizeof(struct write_command); struct write_pacing response; response.command = WRITE_PACING; response.status = STATUS_OK; @@ -262,6 +276,9 @@ STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { #endif FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; + DWORD fattime; + _truncated_time = truncate_time(command->modification_time, &fattime); + override_fattime(fattime); FRESULT result = f_open(fs, &active_file, path, FA_WRITE | FA_OPEN_ALWAYS); if (result != FR_OK) { response.status = STATUS_ERROR; @@ -269,6 +286,7 @@ STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { #if CIRCUITPY_USB_MSC usb_msc_unlock(); #endif + override_fattime(0); return ANY_COMMAND; } // Write out the pacing response. @@ -281,12 +299,14 @@ STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { f_lseek(&active_file, offset); f_truncate(&active_file); f_close(&active_file); + override_fattime(0); #if CIRCUITPY_USB_MSC usb_msc_unlock(); #endif } response.offset = offset; response.free_space = chunk_size; + response.truncated_time = _truncated_time; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); if (chunk_size == 0) { // Don't reload until everything is written out of the packet buffer. @@ -301,7 +321,7 @@ STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { STATIC uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) { struct write_data *command = (struct write_data *)raw_buf; - size_t header_size = 12; + size_t header_size = sizeof(struct write_data); struct write_pacing response; response.command = WRITE_PACING; response.status = STATUS_OK; @@ -312,6 +332,7 @@ STATIC uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) { #if CIRCUITPY_USB_MSC usb_msc_unlock(); #endif + override_fattime(0); return ANY_COMMAND; } // We need to receive another packet to have the full path. @@ -329,6 +350,7 @@ STATIC uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) { #if CIRCUITPY_USB_MSC usb_msc_unlock(); #endif + override_fattime(0); return ANY_COMMAND; } offset += command->data_size; @@ -336,10 +358,12 @@ STATIC uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) { size_t chunk_size = MIN(total_write_length - offset, 512); response.offset = offset; response.free_space = chunk_size; + response.truncated_time = _truncated_time; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); if (total_write_length == offset) { f_truncate(&active_file); f_close(&active_file); + override_fattime(0); #if CIRCUITPY_USB_MSC usb_msc_unlock(); #endif @@ -387,7 +411,7 @@ STATIC FRESULT _delete_directory_contents(FATFS *fs, const TCHAR *path) { STATIC uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) { const struct delete_command *command = (struct delete_command *)raw_buf; - size_t header_size = 4; + size_t header_size = sizeof(struct delete_command); struct delete_status response; response.command = DELETE_STATUS; response.status = STATUS_OK; @@ -423,7 +447,7 @@ STATIC uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) { STATIC uint8_t _process_mkdir(const uint8_t *raw_buf, size_t command_len) { const struct mkdir_command *command = (struct mkdir_command *)raw_buf; - size_t header_size = 4; + size_t header_size = sizeof(struct mkdir_command); struct mkdir_status response; response.command = MKDIR_STATUS; response.status = STATUS_OK; @@ -438,10 +462,14 @@ STATIC uint8_t _process_mkdir(const uint8_t *raw_buf, size_t command_len) { return THIS_COMMAND; } FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; - char *path = (char *)((uint8_t *)command) + header_size; + char *path = (char *)command->path; // TODO: Check that the final character is a `/` path[command->path_length - 1] = '\0'; + DWORD fattime; + response.truncated_time = truncate_time(command->modification_time, &fattime); + override_fattime(fattime); FRESULT result = f_mkdir(fs, path); + override_fattime(0); if (result != FR_OK) { response.status = STATUS_ERROR; } @@ -452,8 +480,8 @@ STATIC uint8_t _process_mkdir(const uint8_t *raw_buf, size_t command_len) { STATIC uint8_t _process_listdir(uint8_t *raw_buf, size_t command_len) { const struct listdir_command *command = (struct listdir_command *)raw_buf; struct listdir_entry *entry = (struct listdir_entry *)raw_buf; - size_t header_size = 4; - size_t response_size = 5 * sizeof(uint32_t); + size_t header_size = sizeof(struct listdir_command); + size_t response_size = sizeof(struct listdir_entry); // We reuse the command buffer so that we can produce long packets without // making the stack large. if (command->path_length > (COMMAND_SIZE - header_size - 1)) { // -1 for the null we'll write @@ -469,7 +497,7 @@ STATIC uint8_t _process_listdir(uint8_t *raw_buf, size_t command_len) { } FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; - char *path = (char *)command->path; + char *path = (char *)&command->path; // -1 because fatfs doesn't want a trailing / path[command->path_length - 1] = '\0'; // mp_printf(&mp_plat_print, "list %s\n", path); @@ -502,6 +530,13 @@ STATIC uint8_t _process_listdir(uint8_t *raw_buf, size_t command_len) { for (size_t i = 0; i < total_entries; i++) { res = f_readdir(&dir, &file_info); entry->entry_number = i; + uint64_t truncated_time = timeutils_mktime(1980 + (file_info.fdate >> 9), + (file_info.fdate >> 5) & 0xf, + file_info.fdate & 0x1f, + file_info.ftime >> 11, + (file_info.ftime >> 5) & 0x1f, + (file_info.ftime & 0x1f) * 2) * 1000000000ULL; + entry->truncated_time = truncated_time; if ((file_info.fattrib & AM_DIR) != 0) { entry->flags = 1; // Directory entry->file_size = 0; diff --git a/supervisor/shared/bluetooth/file_transfer_protocol.h b/supervisor/shared/bluetooth/file_transfer_protocol.h index 2c8fe1e5e7..3de8716c3b 100644 --- a/supervisor/shared/bluetooth/file_transfer_protocol.h +++ b/supervisor/shared/bluetooth/file_transfer_protocol.h @@ -32,6 +32,11 @@ // See https://github.com/adafruit/Adafruit_CircuitPython_BLE_File_Transfer // for full protocol documentation and a Python client API. +// Each struct is packed so that no padding is added by the compiler. (structs +// may having padding at the end in order to align a particular element when in +// an array of the struct.) So, be careful that types added are aligned. Otherwise, +// the compiler may generate more code than necessary. + // 0x00 - 0x0f are never used by the protocol as a command #define READ 0x10 struct read_command { @@ -41,7 +46,7 @@ struct read_command { uint32_t chunk_offset; uint32_t chunk_size; uint8_t path[]; -}; +} __attribute__((packed)); #define READ_DATA 0x11 struct read_data { @@ -52,7 +57,7 @@ struct read_data { uint32_t total_length; uint32_t data_size; uint8_t data[]; -}; +} __attribute__((packed)); #define READ_PACING 0x12 struct read_pacing { @@ -61,7 +66,7 @@ struct read_pacing { uint16_t reserved; uint32_t chunk_offset; uint32_t chunk_size; -}; +} __attribute__((packed)); #define WRITE 0x20 struct write_command { @@ -69,9 +74,10 @@ struct write_command { uint8_t reserved; uint16_t path_length; uint32_t offset; + uint64_t modification_time; uint32_t total_length; uint8_t path[]; -}; +} __attribute__((packed)); #define WRITE_PACING 0x21 struct write_pacing { @@ -79,8 +85,9 @@ struct write_pacing { uint8_t status; uint16_t reserved; uint32_t offset; + uint64_t truncated_time; uint32_t free_space; -}; +} __attribute__((packed)); #define WRITE_DATA 0x22 struct write_data { @@ -90,7 +97,7 @@ struct write_data { uint32_t offset; uint32_t data_size; uint8_t data[]; -}; +} __attribute__((packed)); #define DELETE 0x30 struct delete_command { @@ -98,27 +105,32 @@ struct delete_command { uint8_t reserved; uint16_t path_length; uint8_t path[]; -}; +} __attribute__((packed)); #define DELETE_STATUS 0x31 struct delete_status { uint8_t command; uint8_t status; -}; +} __attribute__((packed)); #define MKDIR 0x40 struct mkdir_command { uint8_t command; uint8_t reserved; uint16_t path_length; + uint32_t reserved2; + uint64_t modification_time; uint8_t path[]; -}; +} __attribute__((packed)); #define MKDIR_STATUS 0x41 struct mkdir_status { uint8_t command; uint8_t status; -}; + uint16_t reserved; + uint32_t reserved2; + uint64_t truncated_time; +} __attribute__((packed)); #define LISTDIR 0x50 struct listdir_command { @@ -126,7 +138,7 @@ struct listdir_command { uint8_t reserved; uint16_t path_length; uint8_t path[]; -}; +} __attribute__((packed)); #define LISTDIR_ENTRY 0x51 struct listdir_entry { @@ -136,9 +148,10 @@ struct listdir_entry { uint32_t entry_number; uint32_t entry_count; uint32_t flags; + uint64_t truncated_time; uint32_t file_size; uint8_t path[]; -}; +} __attribute__((packed)); #define STATUS_OK 0x01 #define STATUS_ERROR 0x02 From b0adf65d94a260334d4cd906cef1e774731f748b Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Mon, 13 Sep 2021 14:44:05 -0700 Subject: [PATCH 2/3] Bump to v4 with move and dir path tweaks --- supervisor/shared/bluetooth/file_transfer.c | 111 ++++++++++++++++-- .../shared/bluetooth/file_transfer_protocol.h | 18 +++ 2 files changed, 122 insertions(+), 7 deletions(-) diff --git a/supervisor/shared/bluetooth/file_transfer.c b/supervisor/shared/bluetooth/file_transfer.c index eaebb3fbcb..342e69c66e 100644 --- a/supervisor/shared/bluetooth/file_transfer.c +++ b/supervisor/shared/bluetooth/file_transfer.c @@ -100,7 +100,7 @@ void supervisor_start_bluetooth_file_transfer(void) { NULL, // no initial value NULL); // no description - uint32_t version = 3; + uint32_t version = 4; mp_buffer_info_t bufinfo; bufinfo.buf = &version; bufinfo.len = sizeof(version); @@ -269,7 +269,7 @@ STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { // Check to see if USB has already been mounted. If not, then we "eject" from USB until we're done. #if CIRCUITPY_USB && CIRCUITPY_USB_MSC if (storage_usb_enabled() && !usb_msc_lock()) { - response.status = STATUS_ERROR; + response.status = STATUS_ERROR_READONLY; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); return ANY_COMMAND; } @@ -421,6 +421,14 @@ STATIC uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) { common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct delete_status), NULL, 0); return ANY_COMMAND; } + // Check to see if USB has already been mounted. If not, then we "eject" from USB until we're done. + #if CIRCUITPY_USB && CIRCUITPY_USB_MSC + if (storage_usb_enabled() && !usb_msc_lock()) { + response.status = STATUS_ERROR_READONLY; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct delete_status), NULL, 0); + return ANY_COMMAND; + } + #endif // We need to receive another packet to have the full path. if (command_len < header_size + command->path_length) { return THIS_COMMAND; @@ -428,9 +436,9 @@ STATIC uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) { FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; char *path = (char *)((uint8_t *)command) + header_size; path[command->path_length] = '\0'; - FRESULT result = FR_OK; FILINFO file; - if (f_stat(fs, path, &file) == FR_OK) { + FRESULT result = f_stat(fs, path, &file); + if (result == FR_OK) { if ((file.fattrib & AM_DIR) != 0) { result = _delete_directory_contents(fs, path); } @@ -438,10 +446,19 @@ STATIC uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) { result = f_unlink(fs, path); } } + #if CIRCUITPY_USB_MSC + usb_msc_unlock(); + #endif if (result != FR_OK) { response.status = STATUS_ERROR; } common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct delete_status), NULL, 0); + if (result == FR_OK) { + // Don't reload until everything is written out of the packet buffer. + common_hal_bleio_packet_buffer_flush(&_transfer_packet_buffer); + // Trigger an autoreload + autoreload_start(); + } return ANY_COMMAND; } @@ -457,23 +474,44 @@ STATIC uint8_t _process_mkdir(const uint8_t *raw_buf, size_t command_len) { common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct mkdir_status), NULL, 0); return ANY_COMMAND; } + // Check to see if USB has already been mounted. If not, then we "eject" from USB until we're done. + #if CIRCUITPY_USB && CIRCUITPY_USB_MSC + if (storage_usb_enabled() && !usb_msc_lock()) { + response.status = STATUS_ERROR_READONLY; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct mkdir_status), NULL, 0); + return ANY_COMMAND; + } + #endif // We need to receive another packet to have the full path. if (command_len < header_size + command->path_length) { return THIS_COMMAND; } FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; char *path = (char *)command->path; - // TODO: Check that the final character is a `/` - path[command->path_length - 1] = '\0'; + // -1 because fatfs doesn't want a trailing / + if (path[command->path_length - 1] == '/') { + path[command->path_length - 1] = '\0'; + } else { + path[command->path_length] = '\0'; + } DWORD fattime; response.truncated_time = truncate_time(command->modification_time, &fattime); override_fattime(fattime); FRESULT result = f_mkdir(fs, path); override_fattime(0); + #if CIRCUITPY_USB_MSC + usb_msc_unlock(); + #endif if (result != FR_OK) { response.status = STATUS_ERROR; } common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct mkdir_status), NULL, 0); + if (result == FR_OK) { + // Don't reload until everything is written out of the packet buffer. + common_hal_bleio_packet_buffer_flush(&_transfer_packet_buffer); + // Trigger an autoreload + autoreload_start(); + } return ANY_COMMAND; } @@ -499,7 +537,11 @@ STATIC uint8_t _process_listdir(uint8_t *raw_buf, size_t command_len) { FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; char *path = (char *)&command->path; // -1 because fatfs doesn't want a trailing / - path[command->path_length - 1] = '\0'; + if (path[command->path_length - 1] == '/') { + path[command->path_length - 1] = '\0'; + } else { + path[command->path_length] = '\0'; + } // mp_printf(&mp_plat_print, "list %s\n", path); FF_DIR dir; FRESULT res = f_opendir(fs, &dir, path); @@ -564,6 +606,58 @@ STATIC uint8_t _process_listdir(uint8_t *raw_buf, size_t command_len) { return ANY_COMMAND; } +STATIC uint8_t _process_move(const uint8_t *raw_buf, size_t command_len) { + const struct move_command *command = (struct move_command *)raw_buf; + size_t header_size = sizeof(struct move_command); + struct move_status response; + response.command = MOVE_STATUS; + response.status = STATUS_OK; + // +2 for null terminators. + uint32_t total_path_length = command->old_path_length + command->new_path_length + 1; + if (total_path_length > (COMMAND_SIZE - header_size - 1)) { + // TODO: throw away any more packets of path. + response.status = STATUS_ERROR; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct move_status), NULL, 0); + return ANY_COMMAND; + } + // Check to see if USB has already been mounted. If not, then we "eject" from USB until we're done. + #if CIRCUITPY_USB && CIRCUITPY_USB_MSC + if (storage_usb_enabled() && !usb_msc_lock()) { + response.status = STATUS_ERROR_READONLY; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct move_status), NULL, 0); + return ANY_COMMAND; + } + #endif + // We need to receive another packet to have the full path. + if (command_len < header_size + total_path_length) { + return THIS_COMMAND; + } + FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; + char *old_path = (char *)command->paths; + old_path[command->old_path_length] = '\0'; + + char *new_path = old_path + command->old_path_length + 1; + new_path[command->new_path_length] = '\0'; + + // mp_printf(&mp_plat_print, "move %s to %s\n", old_path, new_path); + + FRESULT result = f_rename(fs, old_path, new_path); + #if CIRCUITPY_USB_MSC + usb_msc_unlock(); + #endif + if (result != FR_OK) { + response.status = STATUS_ERROR; + } + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct move_status), NULL, 0); + if (result == FR_OK) { + // Don't reload until everything is written out of the packet buffer. + common_hal_bleio_packet_buffer_flush(&_transfer_packet_buffer); + // Trigger an autoreload + autoreload_start(); + } + return ANY_COMMAND; +} + // Background state that must live across background calls. After the _process // helpers to force them to not use them. STATIC uint8_t current_command[COMMAND_SIZE] __attribute__ ((aligned(4))); @@ -625,6 +719,9 @@ void supervisor_bluetooth_file_transfer_background(void) { case LISTDIR: next_command = _process_listdir(current_command, current_offset); break; + case MOVE: + next_command = _process_move(current_command, current_offset); + break; } // Preserve the offset if we are waiting for more from this command. if (next_command != THIS_COMMAND) { diff --git a/supervisor/shared/bluetooth/file_transfer_protocol.h b/supervisor/shared/bluetooth/file_transfer_protocol.h index 3de8716c3b..3bc1f611c5 100644 --- a/supervisor/shared/bluetooth/file_transfer_protocol.h +++ b/supervisor/shared/bluetooth/file_transfer_protocol.h @@ -153,10 +153,28 @@ struct listdir_entry { uint8_t path[]; } __attribute__((packed)); +#define MOVE 0x60 +struct move_command { + uint8_t command; + uint8_t reserved; + uint16_t old_path_length; + uint16_t new_path_length; + // paths is two strings. The first is old_path and then a reserved byte. + // The last path is new_path. + uint8_t paths[]; +} __attribute__((packed)); + +#define MOVE_STATUS 0x61 +struct move_status { + uint8_t command; + uint8_t status; +} __attribute__((packed)); + #define STATUS_OK 0x01 #define STATUS_ERROR 0x02 #define STATUS_ERROR_NO_FILE 0x03 #define STATUS_ERROR_PROTOCOL 0x04 +#define STATUS_ERROR_READONLY 0x05 #endif // MICROPY_INCLUDED_SUPERVISOR_SHARED_BLUETOOTH_FILE_TRANSFER_PROTOCOL_H From 4659102cfe447bfce6bb77ff880ac69b950dba83 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Mon, 13 Sep 2021 16:12:40 -0700 Subject: [PATCH 3/3] Factor out common code and comment it --- supervisor/shared/bluetooth/file_transfer.c | 74 ++++++++++----------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/supervisor/shared/bluetooth/file_transfer.c b/supervisor/shared/bluetooth/file_transfer.c index 342e69c66e..03806ca5e1 100644 --- a/supervisor/shared/bluetooth/file_transfer.c +++ b/supervisor/shared/bluetooth/file_transfer.c @@ -133,6 +133,9 @@ void supervisor_start_bluetooth_file_transfer(void) { #define ANY_COMMAND 0x00 #define THIS_COMMAND 0x01 +// FATFS has a two second timestamp resolution but the BLE API allows for nanosecond resolution. +// This function truncates the time the time to a resolution storable by FATFS and fills in the +// FATFS encoded version into fattime. uint64_t truncate_time(uint64_t input_time, DWORD *fattime) { timeutils_struct_time_t tm; uint64_t seconds_since_epoch = timeutils_seconds_since_epoch_from_nanoseconds_since_1970(input_time); @@ -245,6 +248,22 @@ STATIC uint8_t _process_read_pacing(const uint8_t *raw_buf, size_t command_len) STATIC size_t total_write_length; STATIC uint64_t _truncated_time; +// Returns true if usb is active and replies with an error if so. If not, it grabs +// the USB mass storage lock and returns false. Make sure to release the lock with +// usb_msc_unlock() when the transaction is complete. +STATIC bool _usb_active(void *response, size_t response_size) { + // Check to see if USB has already been mounted. If not, then we "eject" from USB until we're done. + #if CIRCUITPY_USB && CIRCUITPY_USB_MSC + if (storage_usb_enabled() && !usb_msc_lock()) { + // Status is always the second byte of the response. + ((uint8_t *)response)[1] = STATUS_ERROR_READONLY; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)response, response_size, NULL, 0); + return true; + } + #endif + return false; +} + STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { struct write_command *command = (struct write_command *)raw_buf; size_t header_size = sizeof(struct write_command); @@ -265,15 +284,9 @@ STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { char *path = (char *)command->path; path[command->path_length] = '\0'; - - // Check to see if USB has already been mounted. If not, then we "eject" from USB until we're done. - #if CIRCUITPY_USB && CIRCUITPY_USB_MSC - if (storage_usb_enabled() && !usb_msc_lock()) { - response.status = STATUS_ERROR_READONLY; - common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); + if (_usb_active(&response, sizeof(struct write_pacing))) { return ANY_COMMAND; } - #endif FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; DWORD fattime; @@ -421,14 +434,9 @@ STATIC uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) { common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct delete_status), NULL, 0); return ANY_COMMAND; } - // Check to see if USB has already been mounted. If not, then we "eject" from USB until we're done. - #if CIRCUITPY_USB && CIRCUITPY_USB_MSC - if (storage_usb_enabled() && !usb_msc_lock()) { - response.status = STATUS_ERROR_READONLY; - common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct delete_status), NULL, 0); + if (_usb_active(&response, sizeof(struct delete_status))) { return ANY_COMMAND; } - #endif // We need to receive another packet to have the full path. if (command_len < header_size + command->path_length) { return THIS_COMMAND; @@ -462,6 +470,17 @@ STATIC uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) { return ANY_COMMAND; } +// NULL-terminate the path and remove any trailing /. Older versions of the +// protocol require it but newer ones do not. +STATIC void _terminate_path(char *path, size_t path_length) { + // -1 because fatfs doesn't want a trailing / + if (path[path_length - 1] == '/') { + path[path_length - 1] = '\0'; + } else { + path[path_length] = '\0'; + } +} + STATIC uint8_t _process_mkdir(const uint8_t *raw_buf, size_t command_len) { const struct mkdir_command *command = (struct mkdir_command *)raw_buf; size_t header_size = sizeof(struct mkdir_command); @@ -474,26 +493,17 @@ STATIC uint8_t _process_mkdir(const uint8_t *raw_buf, size_t command_len) { common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct mkdir_status), NULL, 0); return ANY_COMMAND; } - // Check to see if USB has already been mounted. If not, then we "eject" from USB until we're done. - #if CIRCUITPY_USB && CIRCUITPY_USB_MSC - if (storage_usb_enabled() && !usb_msc_lock()) { - response.status = STATUS_ERROR_READONLY; - common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct mkdir_status), NULL, 0); + if (_usb_active(&response, sizeof(struct mkdir_status))) { return ANY_COMMAND; } - #endif // We need to receive another packet to have the full path. if (command_len < header_size + command->path_length) { return THIS_COMMAND; } FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; char *path = (char *)command->path; - // -1 because fatfs doesn't want a trailing / - if (path[command->path_length - 1] == '/') { - path[command->path_length - 1] = '\0'; - } else { - path[command->path_length] = '\0'; - } + _terminate_path(path, command->path_length); + DWORD fattime; response.truncated_time = truncate_time(command->modification_time, &fattime); override_fattime(fattime); @@ -536,12 +546,7 @@ STATIC uint8_t _process_listdir(uint8_t *raw_buf, size_t command_len) { FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; char *path = (char *)&command->path; - // -1 because fatfs doesn't want a trailing / - if (path[command->path_length - 1] == '/') { - path[command->path_length - 1] = '\0'; - } else { - path[command->path_length] = '\0'; - } + _terminate_path(path, command->path_length); // mp_printf(&mp_plat_print, "list %s\n", path); FF_DIR dir; FRESULT res = f_opendir(fs, &dir, path); @@ -620,14 +625,9 @@ STATIC uint8_t _process_move(const uint8_t *raw_buf, size_t command_len) { common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct move_status), NULL, 0); return ANY_COMMAND; } - // Check to see if USB has already been mounted. If not, then we "eject" from USB until we're done. - #if CIRCUITPY_USB && CIRCUITPY_USB_MSC - if (storage_usb_enabled() && !usb_msc_lock()) { - response.status = STATUS_ERROR_READONLY; - common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct move_status), NULL, 0); + if (_usb_active(&response, sizeof(struct move_status))) { return ANY_COMMAND; } - #endif // We need to receive another packet to have the full path. if (command_len < header_size + total_path_length) { return THIS_COMMAND;