diff --git a/ProtocolV2.md b/ProtocolV2.md new file mode 100644 index 0000000..e15e2d6 --- /dev/null +++ b/ProtocolV2.md @@ -0,0 +1,146 @@ +# BadgeLink Protocol Changes + +## Protocol Version Negotiation (Version 2) + +This update adds protocol version negotiation to BadgeLink. The previous protocol without version negotiation is considered version 1. + +### New Message Types + +#### VersionReq (Request tag 7) + +Sent by the client to negotiate the protocol version. + +| Field | Tag | Type | Description | +|-------|-----|------|-------------| +| client_version | 1 | uint32 | Highest protocol version supported by the client | + +#### VersionResp (Response tag 6) + +Sent by the server in response to a VersionReq. + +| Field | Tag | Type | Description | +|-------|-----|------|-------------| +| server_version | 1 | uint32 | Highest protocol version supported by the server | +| negotiated_version | 2 | uint32 | Protocol version to use for this session | + +### Negotiation Algorithm + +The server calculates the negotiated version as: +``` +negotiated_version = min(client_version, server_version) +``` + +The server stores this negotiated version and uses it for the remainder of the session. + +**Important**: The server resets the negotiated version to 1 when a sync packet is received. This ensures each new connection starts fresh with v1 behavior until version negotiation occurs. + +### Backwards Compatibility + +- **Old server (v1) + New client**: The server responds with `StatusNotSupported`. The client should fall back to version 1 behavior. +- **New server (v2) + Old client**: The client never sends a VersionReq. The server defaults to version 1. +- **New server + New client**: Full version negotiation occurs. + +### Client Implementation + +Recommended flow for new clients: + +``` +1. After sync, send VersionReq with client_version = 2 +2. If response is StatusNotSupported: + - Server is version 1, use legacy behavior +3. If response is VersionResp: + - Use negotiated_version for session behavior + - server_version indicates what features the server supports +``` + +### Server API + +The server exposes `badgelink_get_protocol_version()` which returns the currently negotiated protocol version (defaults to 1 if no VersionReq was received). + +--- + +## Streaming CRC for Downloads (Version 2) + +Version 2 changes how file downloads work to improve performance for large files. + +### Version 1 Behavior (Legacy) + +1. Client sends download request +2. Server reads **entire file** to calculate CRC32 +3. Server responds with `size` and `crc32` +4. Client requests chunks with `XferContinue` +5. Server sends `download_chunk` responses +6. Client sends `XferFinish` +7. Server responds with `StatusOk` + +**Problem**: For large files, the server must read the entire file before sending the first byte. This causes significant delays. + +### Version 2 Behavior (Streaming CRC) + +1. Client sends download request +2. Server uses `stat()` to get file size (no file read) +3. Server responds with `size` and `crc32 = 0` +4. Client requests chunks with `XferContinue` +5. Server sends `download_chunk` responses, computing CRC incrementally +6. Client sends `XferFinish` +7. Server responds with `FsActionResp` or `AppfsActionResp` containing the final `crc32` + +**Benefit**: Download starts immediately without reading the entire file first. + +### Response Differences + +| Event | Version 1 | Version 2 | +|-------|-----------|-----------| +| Download start | `crc32` = actual CRC | `crc32` = 0 | +| XferFinish (FS) | `StatusOk` | `FsActionResp` with `crc32` | +| XferFinish (AppFS) | `StatusOk` | `AppfsActionResp` with `crc32` | + +--- + +## Python Client Updates + +The Python client (`badgelink.py`) has been updated to support protocol version 2. + +### New Features + +- **Automatic version negotiation**: The client automatically negotiates the protocol version with the server on connection. +- **Streaming CRC for downloads**: Downloads use streaming CRC verification for v2 servers. + +### Command Line Options + +``` +--version1 Force protocol version 1 (legacy mode, skip version negotiation) +``` + +Use `--version1` when you need to connect using the legacy protocol, for example when testing v1 compatibility or connecting to a known v1 server. + +### Example Usage + +```bash +# Normal usage (auto-negotiates version) +./badgelink.sh fs download /sd/file.bin local_file.bin + +# Force version 1 protocol +./badgelink.sh --version1 fs download /sd/file.bin local_file.bin +``` + +--- + +## Revision History + +### 2025-12-16: Field Type Change (uint16 to uint32) + +The version fields in `VersionReq` and `VersionResp` were changed from `uint16` to `uint32`. + +**Reason**: Protocol Buffers (proto3) does not have a native `uint16` type. The smallest unsigned integer type available is `uint32`. While nanopb can generate C code with `uint16_t` fields using options, this creates inconsistency between the C implementation and other language bindings (Python, etc.) which use `uint32`. + +**Changes made**: + +| File | Change | +|------|--------| +| `badgelink.proto` | Added `VersionReq` and `VersionResp` messages with `uint32` fields | +| `badgelink.pb.h` | Changed struct fields from `uint16_t` to `uint32_t` | +| `tools/libraries/badgelink_pb2.py` | Regenerated from updated proto file | +| `tools/badgelink.py` | Simplified to use generated protobuf classes instead of manual encoding | + +**Wire compatibility**: This change is wire-compatible. Protobuf varints encode small values (like version numbers 1, 2, etc.) identically regardless of whether the field is declared as `uint16` or `uint32`. Existing implementations will continue to work. diff --git a/badgelink.c b/badgelink.c index ff3a926..9042fbf 100644 --- a/badgelink.c +++ b/badgelink.c @@ -49,6 +49,11 @@ static uint32_t next_serial = 0; // Frame refers here to the networking term, not the computer graphics term. static uint8_t frame_buffer[BADGELINK_BUF_CAP]; +// Protocol version constants. +#define BADGELINK_PROTOCOL_VERSION 2 +// Negotiated protocol version (defaults to 1 for backwards compatibility). +static uint16_t negotiated_version = 1; + // Queue that sends received data over to the BadgeLink thread. static QueueHandle_t rxqueue; // Handle to the BadgeLink thread. @@ -58,7 +63,7 @@ static void badgelink_thread_main(void*); // Prepare the data for the BadgeLink service to start. void badgelink_init() { - rxqueue = xQueueCreate(16, sizeof(fragment_t)); + rxqueue = xQueueCreate(256, sizeof(fragment_t)); } // Start the badgelink service. @@ -128,6 +133,32 @@ void badgelink_send_status(badgelink_StatusCode code) { badgelink_send_packet(); } +// Get the negotiated protocol version. +uint16_t badgelink_get_protocol_version() { + return negotiated_version; +} + +// Handle a version negotiation request. +static void handle_version_req() { + badgelink_VersionReq* req = &badgelink_packet.packet.request.req.version_req; + uint16_t client_version = req->client_version; + + // Negotiate: use the lower of client and server versions. + uint16_t negotiated = client_version < BADGELINK_PROTOCOL_VERSION ? client_version : BADGELINK_PROTOCOL_VERSION; + negotiated_version = negotiated; + + ESP_LOGI(TAG, "Version negotiation: client=%u, server=%u, negotiated=%u", client_version, + BADGELINK_PROTOCOL_VERSION, negotiated); + + // Send response with server version and negotiated version. + badgelink_packet.which_packet = badgelink_Packet_response_tag; + badgelink_packet.packet.response.status_code = badgelink_StatusCode_StatusOk; + badgelink_packet.packet.response.which_resp = badgelink_Response_version_resp_tag; + badgelink_packet.packet.response.resp.version_resp.server_version = BADGELINK_PROTOCOL_VERSION; + badgelink_packet.packet.response.resp.version_resp.negotiated_version = negotiated; + badgelink_send_packet(); +} + // Abort / finish a transfer. static void xfer_stop(bool abnormal) { switch (badgelink_xfer_type) { @@ -237,7 +268,9 @@ static void handle_packet() { return; } // Sync packet received; set next expected serial number and respond with the same sync packet. - next_serial = badgelink_packet.serial + 1; + // Reset negotiated version to 1 for new connections. + next_serial = badgelink_packet.serial + 1; + negotiated_version = 1; badgelink_send_packet(); return; } else if (badgelink_packet.which_packet != badgelink_Packet_request_tag) { @@ -291,6 +324,9 @@ static void handle_packet() { case badgelink_Request_fs_action_tag: badgelink_fs_handle(); break; + case badgelink_Request_version_req_tag: + handle_version_req(); + break; default: badgelink_status_unsupported(); break; diff --git a/badgelink.h b/badgelink.h index 8384ee7..783941f 100644 --- a/badgelink.h +++ b/badgelink.h @@ -17,3 +17,6 @@ void badgelink_start(usb_callback_t usb_callback); // Handle received data. void badgelink_rxdata_cb(uint8_t const* data, size_t len); + +// Get the negotiated protocol version. +uint16_t badgelink_get_protocol_version(); diff --git a/badgelink.pb.c b/badgelink.pb.c index 39cfa57..cb14343 100644 --- a/badgelink.pb.c +++ b/badgelink.pb.c @@ -18,6 +18,12 @@ PB_BIND(badgelink_Response, badgelink_Response, 4) PB_BIND(badgelink_StartAppReq, badgelink_StartAppReq, AUTO) +PB_BIND(badgelink_VersionReq, badgelink_VersionReq, AUTO) + + +PB_BIND(badgelink_VersionResp, badgelink_VersionResp, AUTO) + + PB_BIND(badgelink_Chunk, badgelink_Chunk, 4) diff --git a/badgelink.pb.h b/badgelink.pb.h index 5b767a2..454ec23 100644 --- a/badgelink.pb.h +++ b/badgelink.pb.h @@ -100,6 +100,20 @@ typedef enum _badgelink_NvsValueType { } badgelink_NvsValueType; /* Struct definitions */ +/* Protocol version request. */ +typedef struct _badgelink_VersionReq { + /* Highest protocol version supported by client. */ + uint32_t client_version; +} badgelink_VersionReq; + +/* Protocol version response. */ +typedef struct _badgelink_VersionResp { + /* Highest protocol version supported by server. */ + uint32_t server_version; + /* Negotiated protocol version (min of client and server). */ + uint32_t negotiated_version; +} badgelink_VersionResp; + typedef struct _badgelink_StartAppReq { /* App slug. */ char slug[48]; @@ -287,6 +301,8 @@ typedef struct _badgelink_Request { badgelink_StartAppReq start_app; /* Transfer control request. */ badgelink_XferReq xfer_ctrl; + /* Protocol version request. */ + badgelink_VersionReq version_req; } req; } badgelink_Request; @@ -321,6 +337,8 @@ typedef struct _badgelink_Response { badgelink_FsActionResp fs_resp; /* NVS action response. */ badgelink_NvsActionResp nvs_resp; + /* Protocol version response. */ + badgelink_VersionResp version_resp; } resp; } badgelink_Response; @@ -496,6 +514,10 @@ extern "C" { #define badgelink_Request_nvs_action_tag 4 #define badgelink_Request_start_app_tag 5 #define badgelink_Request_xfer_ctrl_tag 6 +#define badgelink_Request_version_req_tag 7 +#define badgelink_VersionReq_client_version_tag 1 +#define badgelink_VersionResp_server_version_tag 1 +#define badgelink_VersionResp_negotiated_version_tag 2 #define badgelink_NvsEntriesList_entries_tag 1 #define badgelink_NvsEntriesList_total_entries_tag 2 #define badgelink_NvsActionResp_rdata_tag 1 @@ -505,6 +527,7 @@ extern "C" { #define badgelink_Response_appfs_resp_tag 3 #define badgelink_Response_fs_resp_tag 4 #define badgelink_Response_nvs_resp_tag 5 +#define badgelink_Response_version_resp_tag 6 #define badgelink_Packet_serial_tag 1 #define badgelink_Packet_request_tag 2 #define badgelink_Packet_response_tag 3 @@ -527,7 +550,8 @@ X(a, STATIC, ONEOF, MESSAGE, (req,appfs_action,req.appfs_action), 2) \ X(a, STATIC, ONEOF, MESSAGE, (req,fs_action,req.fs_action), 3) \ X(a, STATIC, ONEOF, MESSAGE, (req,nvs_action,req.nvs_action), 4) \ X(a, STATIC, ONEOF, MESSAGE, (req,start_app,req.start_app), 5) \ -X(a, STATIC, ONEOF, UENUM, (req,xfer_ctrl,req.xfer_ctrl), 6) +X(a, STATIC, ONEOF, UENUM, (req,xfer_ctrl,req.xfer_ctrl), 6) \ +X(a, STATIC, ONEOF, MESSAGE, (req,version_req,req.version_req), 7) #define badgelink_Request_CALLBACK NULL #define badgelink_Request_DEFAULT NULL #define badgelink_Request_req_upload_chunk_MSGTYPE badgelink_Chunk @@ -535,19 +559,33 @@ X(a, STATIC, ONEOF, UENUM, (req,xfer_ctrl,req.xfer_ctrl), 6) #define badgelink_Request_req_fs_action_MSGTYPE badgelink_FsActionReq #define badgelink_Request_req_nvs_action_MSGTYPE badgelink_NvsActionReq #define badgelink_Request_req_start_app_MSGTYPE badgelink_StartAppReq +#define badgelink_Request_req_version_req_MSGTYPE badgelink_VersionReq #define badgelink_Response_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UENUM, status_code, 1) \ X(a, STATIC, ONEOF, MESSAGE, (resp,download_chunk,resp.download_chunk), 2) \ X(a, STATIC, ONEOF, MESSAGE, (resp,appfs_resp,resp.appfs_resp), 3) \ X(a, STATIC, ONEOF, MESSAGE, (resp,fs_resp,resp.fs_resp), 4) \ -X(a, STATIC, ONEOF, MESSAGE, (resp,nvs_resp,resp.nvs_resp), 5) +X(a, STATIC, ONEOF, MESSAGE, (resp,nvs_resp,resp.nvs_resp), 5) \ +X(a, STATIC, ONEOF, MESSAGE, (resp,version_resp,resp.version_resp), 6) #define badgelink_Response_CALLBACK NULL #define badgelink_Response_DEFAULT NULL #define badgelink_Response_resp_download_chunk_MSGTYPE badgelink_Chunk #define badgelink_Response_resp_appfs_resp_MSGTYPE badgelink_AppfsActionResp #define badgelink_Response_resp_fs_resp_MSGTYPE badgelink_FsActionResp #define badgelink_Response_resp_nvs_resp_MSGTYPE badgelink_NvsActionResp +#define badgelink_Response_resp_version_resp_MSGTYPE badgelink_VersionResp + +#define badgelink_VersionReq_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, client_version, 1) +#define badgelink_VersionReq_CALLBACK NULL +#define badgelink_VersionReq_DEFAULT NULL + +#define badgelink_VersionResp_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, server_version, 1) \ +X(a, STATIC, SINGULAR, UINT32, negotiated_version, 2) +#define badgelink_VersionResp_CALLBACK NULL +#define badgelink_VersionResp_DEFAULT NULL #define badgelink_StartAppReq_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, STRING, slug, 1) \ @@ -692,6 +730,8 @@ extern const pb_msgdesc_t badgelink_Packet_msg; extern const pb_msgdesc_t badgelink_Request_msg; extern const pb_msgdesc_t badgelink_Response_msg; extern const pb_msgdesc_t badgelink_StartAppReq_msg; +extern const pb_msgdesc_t badgelink_VersionReq_msg; +extern const pb_msgdesc_t badgelink_VersionResp_msg; extern const pb_msgdesc_t badgelink_Chunk_msg; extern const pb_msgdesc_t badgelink_FsUsage_msg; extern const pb_msgdesc_t badgelink_AppfsMetadata_msg; @@ -714,6 +754,8 @@ extern const pb_msgdesc_t badgelink_NvsActionResp_msg; #define badgelink_Request_fields &badgelink_Request_msg #define badgelink_Response_fields &badgelink_Response_msg #define badgelink_StartAppReq_fields &badgelink_StartAppReq_msg +#define badgelink_VersionReq_fields &badgelink_VersionReq_msg +#define badgelink_VersionResp_fields &badgelink_VersionResp_msg #define badgelink_Chunk_fields &badgelink_Chunk_msg #define badgelink_FsUsage_fields &badgelink_FsUsage_msg #define badgelink_AppfsMetadata_fields &badgelink_AppfsMetadata_msg @@ -753,6 +795,8 @@ extern const pb_msgdesc_t badgelink_NvsActionResp_msg; #define badgelink_Request_size 4153 #define badgelink_Response_size 5134 #define badgelink_StartAppReq_size 179 +#define badgelink_VersionReq_size 6 +#define badgelink_VersionResp_size 12 #ifdef __cplusplus } /* extern "C" */ diff --git a/badgelink.proto b/badgelink.proto index 2256260..e8cfa8f 100644 --- a/badgelink.proto +++ b/badgelink.proto @@ -190,6 +190,7 @@ message Request { NvsActionReq nvs_action = 4; StartAppReq start_app = 5; XferReq xfer_ctrl = 6; + VersionReq version_req = 7; } } @@ -199,6 +200,7 @@ message Response { AppfsActionResp appfs_resp = 3; FsActionResp fs_resp = 4; NvsActionResp nvs_resp = 5; + VersionResp version_resp = 6; } StatusCode status_code = 1; @@ -208,3 +210,12 @@ message StartAppReq { string slug = 1; string arg = 2; } + +message VersionReq { + uint32 client_version = 1; +} + +message VersionResp { + uint32 server_version = 1; + uint32 negotiated_version = 2; +} diff --git a/badgelink_appfs.c b/badgelink_appfs.c index 2ef1e29..52c63a7 100644 --- a/badgelink_appfs.c +++ b/badgelink_appfs.c @@ -13,6 +13,8 @@ static char const TAG[] = "badgelink_appfs"; static appfs_handle_t xfer_fd; // CRC32 of file transferred. static uint32_t xfer_crc32; +// Running CRC32 computed during upload. +static uint32_t running_crc; // Calculate the CRC32 of an AppFS file. static uint32_t calc_app_crc32(appfs_handle_t fd) { @@ -69,6 +71,7 @@ void badgelink_appfs_xfer_upload() { ESP_LOGE(TAG, "%s: error %s", __FUNCTION__, esp_err_to_name(ec)); badgelink_status_int_err(); } else { + running_crc = esp_crc32_le(running_crc, chunk->data.bytes, chunk->data.size); badgelink_status_ok(); } } @@ -90,6 +93,10 @@ void badgelink_appfs_xfer_download() { ESP_LOGE(TAG, "%s: error %s", __FUNCTION__, esp_err_to_name(ec)); badgelink_status_int_err(); } else { + // For protocol version 2+, compute streaming CRC during download. + if (badgelink_get_protocol_version() >= 2) { + running_crc = esp_crc32_le(running_crc, chunk->data.bytes, chunk->data.size); + } badgelink_send_packet(); } } @@ -108,10 +115,9 @@ void badgelink_appfs_xfer_stop(bool abnormal) { appfsDeleteFile(name); } else { - uint32_t actual_crc32 = calc_app_crc32(xfer_fd); - if (actual_crc32 != xfer_crc32) { + if (running_crc != xfer_crc32) { ESP_LOGE(TAG, "AppFS upload CRC32 mismatch; expected %08" PRIx32 ", actual %08" PRIx32, xfer_crc32, - actual_crc32); + running_crc); badgelink_status_int_err(); // AppFS can delete by fd so this is the workaround. @@ -130,6 +136,20 @@ void badgelink_appfs_xfer_stop(bool abnormal) { ESP_LOGE(TAG, "AppFS download aborted"); } else { ESP_LOGI(TAG, "AppFS download finished"); + + // For protocol version 2+, send the final CRC. + if (badgelink_get_protocol_version() >= 2) { + badgelink_packet.which_packet = badgelink_Packet_response_tag; + badgelink_packet.packet.response.status_code = badgelink_StatusCode_StatusOk; + badgelink_packet.packet.response.which_resp = badgelink_Response_appfs_resp_tag; + badgelink_AppfsActionResp* resp = &badgelink_packet.packet.response.resp.appfs_resp; + resp->which_val = badgelink_AppfsActionResp_crc32_tag; + resp->val.crc32 = running_crc; + resp->size = badgelink_xfer_size; + badgelink_send_packet(); + } else { + badgelink_status_ok(); + } } } } @@ -251,6 +271,7 @@ void badgelink_appfs_upload() { badgelink_xfer_pos = 0; badgelink_xfer_size = req->id.metadata.size; xfer_crc32 = req->crc32; + running_crc = 0; // This OK response officially starts the transfer. ESP_LOGI(TAG, "AppFS upload started"); @@ -276,11 +297,18 @@ void badgelink_appfs_download() { return; } - // Calculate file CRC32 and get size. - uint32_t crc = calc_app_crc32(fd); + uint32_t crc = 0; int size; appfsEntryInfo(fd, NULL, &size); + if (badgelink_get_protocol_version() >= 2) { + // Protocol version 2+: CRC will be computed during transfer. + running_crc = 0; + } else { + // Protocol version 1: calculate CRC32 upfront by reading entire file. + crc = calc_app_crc32(fd); + } + // Set up transfer. badgelink_xfer_type = BADGELINK_XFER_APPFS; badgelink_xfer_is_upload = false; diff --git a/badgelink_fs.c b/badgelink_fs.c index d8de278..b76a113 100644 --- a/badgelink_fs.c +++ b/badgelink_fs.c @@ -6,18 +6,67 @@ #include "dirent.h" #include "errno.h" #include "esp_crc.h" +#include "esp_heap_caps.h" #include "esp_log.h" #include "fcntl.h" #include "stdio.h" +#include "string.h" #include "sys/stat.h" #include "sys/types.h" #include "unistd.h" static char const TAG[] = "badgelink_fs"; +// Fast SD I/O helpers - use internal DMA RAM for stdio buffers +#ifdef CONFIG_SD_FAST_IO +#define BADGELINK_STDIO_BUF_SIZE 8192 + +static FILE* bl_fast_file = NULL; +static void* bl_fast_buffer = NULL; + +static FILE* bl_sd_fopen(const char* path, const char* mode) { + FILE* f = fopen(path, mode); + if (f == NULL) return NULL; + + // Allocate stdio buffer in internal DMA-capable RAM for fast SD card access + void* buf = heap_caps_malloc(BADGELINK_STDIO_BUF_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + if (buf != NULL) { + setvbuf(f, buf, _IOFBF, BADGELINK_STDIO_BUF_SIZE); + bl_fast_file = f; + bl_fast_buffer = buf; + } + return f; +} + +static void bl_sd_fclose(FILE* f) { + if (f == NULL) return; + + // Check if this file has a tracked fast buffer + if (bl_fast_file == f && bl_fast_buffer != NULL) { + fclose(f); + free(bl_fast_buffer); + bl_fast_file = NULL; + bl_fast_buffer = NULL; + return; + } + fclose(f); +} +#else +// Fallback: just use regular fopen/fclose +static inline FILE* bl_sd_fopen(const char* path, const char* mode) { + return fopen(path, mode); +} + +static inline void bl_sd_fclose(FILE* f) { + if (f != NULL) fclose(f); +} +#endif + static char xfer_path[256]; static FILE* xfer_fd; +static bool xfer_is_sd; static uint32_t xfer_crc32; +static uint32_t running_crc; // Handle a FS request packet. void badgelink_fs_handle() { @@ -68,6 +117,7 @@ void badgelink_fs_xfer_upload() { badgelink_status_int_err(); } } else { + running_crc = esp_crc32_le(running_crc, chunk->data.bytes, chunk->data.size); badgelink_status_ok(); } } @@ -85,6 +135,10 @@ void badgelink_fs_xfer_download() { ESP_LOGE(TAG, "%s: Unknown errno %d", __FUNCTION__, errno); badgelink_status_int_err(); } else { + // For protocol version 2+, compute streaming CRC during download. + if (badgelink_get_protocol_version() >= 2) { + running_crc = esp_crc32_le(running_crc, chunk->data.bytes, chunk->data.size); + } badgelink_send_packet(); } } @@ -92,7 +146,11 @@ void badgelink_fs_xfer_download() { // Finish a FS transfer. void badgelink_fs_xfer_stop(bool abnormal) { if (abnormal) { - fclose(xfer_fd); + if (xfer_is_sd) { + bl_sd_fclose(xfer_fd); + } else { + fclose(xfer_fd); + } if (badgelink_xfer_is_upload) { ESP_LOGE(TAG, "FS upload aborted"); unlink(xfer_path); @@ -101,38 +159,15 @@ void badgelink_fs_xfer_stop(bool abnormal) { } } else if (badgelink_xfer_is_upload) { - // Double-check file CRC32. - uint32_t crc = 0; - uint8_t tmp[128]; - fseek(xfer_fd, 0, SEEK_SET); - for (uint32_t i = 0; i < badgelink_xfer_size; i += sizeof(tmp)) { - uint32_t max = sizeof(tmp) < badgelink_xfer_size - i ? sizeof(tmp) : badgelink_xfer_size - i; - uint32_t actual_read = fread(tmp, 1, max, xfer_fd); - if (actual_read != max) { - long pos = ftell(xfer_fd); - ESP_LOGE(TAG, - "Read too little while checking CRC32; expected %" PRIu32 ", got %" PRIu32 " at offset %ld", - max, actual_read, pos); - fclose(xfer_fd); - unlink(xfer_path); - badgelink_status_int_err(); - return; - } - crc = esp_crc32_le(crc, tmp, max); + if (xfer_is_sd) { + bl_sd_fclose(xfer_fd); + } else { + fclose(xfer_fd); } - fclose(xfer_fd); - - if (crc != xfer_crc32) { - ESP_LOGE(TAG, "FS upload CRC32 mismatch; expected %08" PRIx32 ", actual %08" PRIx32, xfer_crc32, crc); - fseek(xfer_fd, 0, SEEK_SET); - printf("Data:"); - for (size_t i = 0; i < badgelink_xfer_size; i++) { - uint8_t c; - fread(&c, 1, 1, xfer_fd); - printf(" %02x", c); - } - printf("\n"); + if (running_crc != xfer_crc32) { + ESP_LOGE(TAG, "FS upload CRC32 mismatch; expected %08" PRIx32 ", actual %08" PRIx32, xfer_crc32, + running_crc); unlink(xfer_path); badgelink_status_int_err(); } else { @@ -142,8 +177,25 @@ void badgelink_fs_xfer_stop(bool abnormal) { } else { ESP_LOGI(TAG, "FS download finished"); - fclose(xfer_fd); - badgelink_status_ok(); + if (xfer_is_sd) { + bl_sd_fclose(xfer_fd); + } else { + fclose(xfer_fd); + } + + // For protocol version 2+, send the final CRC. + if (badgelink_get_protocol_version() >= 2) { + badgelink_packet.which_packet = badgelink_Packet_response_tag; + badgelink_packet.packet.response.status_code = badgelink_StatusCode_StatusOk; + badgelink_packet.packet.response.which_resp = badgelink_Response_fs_resp_tag; + badgelink_FsActionResp* resp = &badgelink_packet.packet.response.resp.fs_resp; + resp->which_val = badgelink_FsActionResp_crc32_tag; + resp->val.crc32 = running_crc; + resp->size = badgelink_xfer_size; + badgelink_send_packet(); + } else { + badgelink_status_ok(); + } } } @@ -244,7 +296,8 @@ void badgelink_fs_upload() { // Open target file for writing. strlcpy(xfer_path, req->path, sizeof(xfer_path)); - xfer_fd = fopen(req->path, "w+b"); + xfer_is_sd = (strncmp(req->path, "/sd", 3) == 0); + xfer_fd = xfer_is_sd ? bl_sd_fopen(req->path, "w+b") : fopen(req->path, "w+b"); if (!xfer_fd) { if (errno == ENOENT) { badgelink_status_not_found(); @@ -263,6 +316,7 @@ void badgelink_fs_upload() { badgelink_xfer_size = req->size; badgelink_xfer_pos = 0; xfer_crc32 = req->crc32; + running_crc = 0; // This OK response officially starts the transfer. ESP_LOGI(TAG, "FS upload started"); @@ -280,7 +334,8 @@ void badgelink_fs_download() { // Open target file for reading. strlcpy(xfer_path, req->path, sizeof(xfer_path)); - xfer_fd = fopen(req->path, "rb"); + xfer_is_sd = (strncmp(req->path, "/sd", 3) == 0); + xfer_fd = xfer_is_sd ? bl_sd_fopen(req->path, "rb") : fopen(req->path, "rb"); if (!xfer_fd) { if (errno == ENOENT) { badgelink_status_not_found(); @@ -293,17 +348,35 @@ void badgelink_fs_download() { return; } - // Calculate file CRC32. - uint8_t tmp[128]; - uint32_t crc = 0; - uint32_t size = 0; - uint32_t rcount = 1; - while (rcount) { - rcount = fread(tmp, 1, sizeof(tmp), xfer_fd); - crc = esp_crc32_le(crc, tmp, rcount); - size += rcount; + uint32_t crc = 0; + uint32_t size = 0; + + if (badgelink_get_protocol_version() >= 2) { + // Protocol version 2+: use stat() for size, CRC will be computed during transfer. + struct stat statbuf; + if (fstat(fileno(xfer_fd), &statbuf)) { + ESP_LOGE(TAG, "%s: fstat failed, errno %d", __FUNCTION__, errno); + if (xfer_is_sd) { + bl_sd_fclose(xfer_fd); + } else { + fclose(xfer_fd); + } + badgelink_status_int_err(); + return; + } + size = statbuf.st_size; + running_crc = 0; + } else { + // Protocol version 1: calculate CRC32 upfront by reading entire file. + uint8_t tmp[128]; + uint32_t rcount = 1; + while (rcount) { + rcount = fread(tmp, 1, sizeof(tmp), xfer_fd); + crc = esp_crc32_le(crc, tmp, rcount); + size += rcount; + } + fseek(xfer_fd, 0, SEEK_SET); } - fseek(xfer_fd, 0, SEEK_SET); // Set up transfer. badgelink_xfer_type = BADGELINK_XFER_FS; diff --git a/tools/badgelink.py b/tools/badgelink.py index 76c1844..a8c1b17 100755 --- a/tools/badgelink.py +++ b/tools/badgelink.py @@ -332,6 +332,8 @@ def simple_request(self, request: Request|FsActionReq|AppfsActionReq|NvsActionRe request = Request(upload_chunk=request) elif type(request) == StartAppReq: request = Request(start_app=request) + elif type(request) == VersionReq: + request = Request(version_req=request) elif type(request) != Request: raise TypeError("Invalid request type") @@ -377,14 +379,45 @@ def simple_request(self, request: Request|FsActionReq|AppfsActionReq|NvsActionRe class Badgelink: CHUNK_MAX_SIZE = 4096 - - def __init__(self, conn: BadgelinkConnection|BadgeUSB|Serial): + PROTOCOL_VERSION = 2 + + def __init__(self, conn: BadgelinkConnection|BadgeUSB|Serial, force_version1: bool = False): if type(conn) != BadgelinkConnection: conn = BadgelinkConnection(conn) self.conn = conn self.def_timeout = 0.25 self.chunk_timeout = 0.5 self.xfer_timeout = 10 + self.protocol_version = 1 # Default to v1 for backwards compatibility + + if not force_version1: + self._negotiate_version() + + def _negotiate_version(self): + """ + Negotiate protocol version with the badge. + Sends a VersionReq and handles the response. + """ + try: + resp = self.conn.simple_request( + VersionReq(client_version=self.PROTOCOL_VERSION), + timeout=self.def_timeout + ) + + if resp.HasField('version_resp'): + self.protocol_version = resp.version_resp.negotiated_version + print(f"Negotiated protocol version {self.protocol_version} (server supports v{resp.version_resp.server_version})") + else: + # Unexpected response format, fall back to v1 + self.protocol_version = 1 + except NotSupportedError: + # Server doesn't support version negotiation, use v1 + self.protocol_version = 1 + print("Server uses protocol version 1 (legacy)") + except TimeoutError: + # Server didn't respond, use v1 + self.protocol_version = 1 + print("Server uses protocol version 1 (legacy)") def start_app(self, slug: str, app_arg: str): """ @@ -538,11 +571,16 @@ def appfs_download(self, slug: str, path: str): with open(path, "wb") as fd: # Send initial request. meta = self.conn.simple_request(AppfsActionReq(type=FsActionDownload, slug=slug), timeout=self.xfer_timeout).appfs_resp - + + # For v1: crc32 is provided upfront + # For v2: crc32 is 0, will be provided at the end + expected_crc = meta.crc32 if self.protocol_version == 1 else None + # Initial request succeeded; receive remainder of transfer. fd.seek(0, os.SEEK_SET) progress = -1 pos = 0 + running_crc = 0 while pos < meta.size: assert pos == fd.tell() if pos * 100 // meta.size > progress: @@ -554,11 +592,23 @@ def appfs_download(self, slug: str, path: str): print() raise MalformedResponseError("Incorrect chunk position") fd.write(chunk.data) + running_crc = crc32(chunk.data, running_crc) pos += len(chunk.data) print() - + # Finalize the transfer. - self.conn.simple_request(Request(xfer_ctrl=XferFinish), timeout=self.def_timeout) + finish_resp = self.conn.simple_request(Request(xfer_ctrl=XferFinish), timeout=self.def_timeout) + + # For v2, the server sends appfs_resp with crc32 at the end + if self.protocol_version >= 2 and finish_resp.HasField('appfs_resp'): + expected_crc = finish_resp.appfs_resp.crc32 + + # Verify CRC + if expected_crc is not None: + if (running_crc & 0xffffffff) != (expected_crc & 0xffffffff): + print(f"CRC32 mismatch! Expected 0x{expected_crc:08x}, got 0x{running_crc & 0xffffffff:08x}") + raise CommunicationError("CRC32 mismatch") + print("Done!") def appfs_usage(self) -> FsUsage: @@ -647,11 +697,16 @@ def fs_download(self, badge_path: str, host_path: str): with open(host_path, "wb") as fd: # Send initial request. meta = self.conn.simple_request(FsActionReq(type=FsActionDownload, path=badge_path), timeout=self.xfer_timeout).fs_resp - + + # For v1: crc32 is provided upfront + # For v2: crc32 is 0, will be provided at the end + expected_crc = meta.crc32 if self.protocol_version == 1 else None + # Initial request succeeded; receive remainder of transfer. fd.seek(0, os.SEEK_SET) progress = -1 pos = 0 + running_crc = 0 while pos < meta.size: assert pos == fd.tell() if pos * 100 // meta.size > progress: @@ -663,11 +718,23 @@ def fs_download(self, badge_path: str, host_path: str): print() raise MalformedResponseError("Incorrect chunk position") fd.write(chunk.data) + running_crc = crc32(chunk.data, running_crc) pos += len(chunk.data) print() - + # Finalize the transfer. - self.conn.simple_request(Request(xfer_ctrl=XferFinish), timeout=self.xfer_timeout) + finish_resp = self.conn.simple_request(Request(xfer_ctrl=XferFinish), timeout=self.xfer_timeout) + + # For v2, the server sends fs_resp with crc32 at the end + if self.protocol_version >= 2 and finish_resp.HasField('fs_resp'): + expected_crc = finish_resp.fs_resp.crc32 + + # Verify CRC + if expected_crc is not None: + if (running_crc & 0xffffffff) != (expected_crc & 0xffffffff): + print(f"CRC32 mismatch! Expected 0x{expected_crc:08x}, got 0x{running_crc & 0xffffffff:08x}") + raise CommunicationError("CRC32 mismatch") + print("Done!") def fs_mkdir(self, path: str): @@ -830,6 +897,8 @@ def print_row(row: list[str]): parser.add_argument("--timeout", action="store", default=0.25, type=float) parser.add_argument("--chunk-timeout", action="store", default=0.5, type=float) parser.add_argument("--xfer-timeout", action="store", default=10, type=float) + parser.add_argument("--version1", action="store_true", default=False, + help="Force protocol version 1 (legacy mode, skip version negotiation)") subparsers = parser.add_subparsers(required=True, dest="request") # ==== Help texts ==== # @@ -999,7 +1068,7 @@ def print_row(row: list[str]): sys.exit(1) try: - link = Badgelink(port) + link = Badgelink(port, force_version1=args.version1) link.conn.dump_raw = args.dump_raw_bytes link.def_timeout = args.timeout link.chunk_timeout = args.chunk_timeout diff --git a/tools/libraries/badgelink_pb2.py b/tools/libraries/badgelink_pb2.py index 70a5fce..f84a64d 100644 --- a/tools/libraries/badgelink_pb2.py +++ b/tools/libraries/badgelink_pb2.py @@ -1,22 +1,11 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE # source: badgelink.proto -# Protobuf Python Version: 6.33.1 """Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 33, - 1, - '', - 'badgelink.proto' -) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -24,61 +13,65 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0f\x62\x61\x64gelink.proto\x12\tbadgelink\"\x9f\x01\n\x0e\x41ppfsActionReq\x12,\n\x08metadata\x18\x02 \x01(\x0b\x32\x18.badgelink.AppfsMetadataH\x00\x12\x0e\n\x04slug\x18\x03 \x01(\tH\x00\x12%\n\x04type\x18\x01 \x01(\x0e\x32\x17.badgelink.FsActionType\x12\r\n\x05\x63rc32\x18\x04 \x01(\r\x12\x13\n\x0blist_offset\x18\x05 \x01(\rB\x04\n\x02id\"\xb0\x01\n\x0f\x41ppfsActionResp\x12,\n\x08metadata\x18\x01 \x01(\x0b\x32\x18.badgelink.AppfsMetadataH\x00\x12\x0f\n\x05\x63rc32\x18\x02 \x01(\rH\x00\x12$\n\x04list\x18\x03 \x01(\x0b\x32\x14.badgelink.AppfsListH\x00\x12#\n\x05usage\x18\x04 \x01(\x0b\x32\x12.badgelink.FsUsageH\x00\x12\x0c\n\x04size\x18\x05 \x01(\rB\x05\n\x03val\"G\n\tAppfsList\x12&\n\x04list\x18\x01 \x03(\x0b\x32\x18.badgelink.AppfsMetadata\x12\x12\n\ntotal_size\x18\x02 \x01(\r\"K\n\rAppfsMetadata\x12\x0c\n\x04slug\x18\x01 \x01(\t\x12\r\n\x05title\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\r\x12\x0c\n\x04size\x18\x04 \x01(\r\"\'\n\x05\x43hunk\x12\x10\n\x08position\x18\x02 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\"t\n\x0b\x46sActionReq\x12%\n\x04type\x18\x01 \x01(\x0e\x32\x17.badgelink.FsActionType\x12\x0c\n\x04path\x18\x02 \x01(\t\x12\r\n\x05\x63rc32\x18\x03 \x01(\r\x12\x13\n\x0blist_offset\x18\x04 \x01(\r\x12\x0c\n\x04size\x18\x05 \x01(\r\"\xa5\x01\n\x0c\x46sActionResp\x12!\n\x04stat\x18\x01 \x01(\x0b\x32\x11.badgelink.FsStatH\x00\x12\x0f\n\x05\x63rc32\x18\x02 \x01(\rH\x00\x12\'\n\x04list\x18\x03 \x01(\x0b\x32\x17.badgelink.FsDirentListH\x00\x12#\n\x05usage\x18\x04 \x01(\x0b\x32\x12.badgelink.FsUsageH\x00\x12\x0c\n\x04size\x18\x05 \x01(\rB\x05\n\x03val\"(\n\x08\x46sDirent\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06is_dir\x18\x02 \x01(\x08\"E\n\x0c\x46sDirentList\x12!\n\x04list\x18\x01 \x03(\x0b\x32\x13.badgelink.FsDirent\x12\x12\n\ntotal_size\x18\x02 \x01(\r\"S\n\x06\x46sStat\x12\x0c\n\x04size\x18\x01 \x01(\r\x12\r\n\x05mtime\x18\x02 \x01(\x04\x12\r\n\x05\x63time\x18\x03 \x01(\x04\x12\r\n\x05\x61time\x18\x04 \x01(\x04\x12\x0e\n\x06is_dir\x18\x05 \x01(\x08\"%\n\x07\x46sUsage\x12\x0c\n\x04size\x18\x01 \x01(\r\x12\x0c\n\x04used\x18\x02 \x01(\r\"\xb9\x01\n\x0cNvsActionReq\x12&\n\x04type\x18\x01 \x01(\x0e\x32\x18.badgelink.NvsActionType\x12\x0f\n\x07namespc\x18\x02 \x01(\t\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\"\n\x05wdata\x18\x04 \x01(\x0b\x32\x13.badgelink.NvsValue\x12\x13\n\x0blist_offset\x18\x05 \x01(\r\x12*\n\tread_type\x18\x06 \x01(\x0e\x32\x17.badgelink.NvsValueType\"j\n\rNvsActionResp\x12$\n\x05rdata\x18\x01 \x01(\x0b\x32\x13.badgelink.NvsValueH\x00\x12,\n\x07\x65ntries\x18\x02 \x01(\x0b\x32\x19.badgelink.NvsEntriesListH\x00\x42\x05\n\x03val\"M\n\x0eNvsEntriesList\x12$\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x13.badgelink.NvsEntry\x12\x15\n\rtotal_entries\x18\x02 \x01(\r\"O\n\x08NvsEntry\x12%\n\x04type\x18\x01 \x01(\x0e\x32\x17.badgelink.NvsValueType\x12\x0f\n\x07namespc\x18\x02 \x01(\t\x12\x0b\n\x03key\x18\x03 \x01(\t\"v\n\x08NvsValue\x12\x14\n\nnumericval\x18\x02 \x01(\x04H\x00\x12\x13\n\tstringval\x18\x03 \x01(\tH\x00\x12\x11\n\x07\x62lobval\x18\x04 \x01(\x0cH\x00\x12%\n\x04type\x18\x01 \x01(\x0e\x32\x17.badgelink.NvsValueTypeB\x05\n\x03val\"\x82\x01\n\x06Packet\x12%\n\x07request\x18\x02 \x01(\x0b\x32\x12.badgelink.RequestH\x00\x12\'\n\x08response\x18\x03 \x01(\x0b\x32\x13.badgelink.ResponseH\x00\x12\x0e\n\x04sync\x18\x04 \x01(\x08H\x00\x12\x0e\n\x06serial\x18\x01 \x01(\x04\x42\x08\n\x06packet\"\x9f\x02\n\x07Request\x12(\n\x0cupload_chunk\x18\x01 \x01(\x0b\x32\x10.badgelink.ChunkH\x00\x12\x31\n\x0c\x61ppfs_action\x18\x02 \x01(\x0b\x32\x19.badgelink.AppfsActionReqH\x00\x12+\n\tfs_action\x18\x03 \x01(\x0b\x32\x16.badgelink.FsActionReqH\x00\x12-\n\nnvs_action\x18\x04 \x01(\x0b\x32\x17.badgelink.NvsActionReqH\x00\x12+\n\tstart_app\x18\x05 \x01(\x0b\x32\x16.badgelink.StartAppReqH\x00\x12\'\n\txfer_ctrl\x18\x06 \x01(\x0e\x32\x12.badgelink.XferReqH\x00\x42\x05\n\x03req\"\xf6\x01\n\x08Response\x12*\n\x0e\x64ownload_chunk\x18\x02 \x01(\x0b\x32\x10.badgelink.ChunkH\x00\x12\x30\n\nappfs_resp\x18\x03 \x01(\x0b\x32\x1a.badgelink.AppfsActionRespH\x00\x12*\n\x07\x66s_resp\x18\x04 \x01(\x0b\x32\x17.badgelink.FsActionRespH\x00\x12,\n\x08nvs_resp\x18\x05 \x01(\x0b\x32\x18.badgelink.NvsActionRespH\x00\x12*\n\x0bstatus_code\x18\x01 \x01(\x0e\x32\x15.badgelink.StatusCodeB\x06\n\x04resp\"(\n\x0bStartAppReq\x12\x0c\n\x04slug\x18\x01 \x01(\t\x12\x0b\n\x03\x61rg\x18\x02 \x01(\t*\xbf\x01\n\x0c\x46sActionType\x12\x10\n\x0c\x46sActionList\x10\x00\x12\x12\n\x0e\x46sActionDelete\x10\x01\x12\x11\n\rFsActionMkdir\x10\x02\x12\x12\n\x0e\x46sActionUpload\x10\x03\x12\x14\n\x10\x46sActionDownload\x10\x04\x12\x10\n\x0c\x46sActionStat\x10\x05\x12\x11\n\rFsActionCrc23\x10\x06\x12\x14\n\x10\x46sActionGetUsage\x10\x07\x12\x11\n\rFsActionRmdir\x10\x08*^\n\rNvsActionType\x12\x11\n\rNvsActionList\x10\x00\x12\x11\n\rNvsActionRead\x10\x01\x12\x12\n\x0eNvsActionWrite\x10\x02\x12\x13\n\x0fNvsActionDelete\x10\x03*\xce\x01\n\x0cNvsValueType\x12\x11\n\rNvsValueUint8\x10\x00\x12\x10\n\x0cNvsValueInt8\x10\x01\x12\x12\n\x0eNvsValueUint16\x10\x02\x12\x11\n\rNvsValueInt16\x10\x03\x12\x12\n\x0eNvsValueUint32\x10\x04\x12\x11\n\rNvsValueInt32\x10\x05\x12\x12\n\x0eNvsValueUint64\x10\x06\x12\x11\n\rNvsValueInt64\x10\x07\x12\x12\n\x0eNvsValueString\x10\x08\x12\x10\n\x0cNvsValueBlob\x10\t*\xe8\x01\n\nStatusCode\x12\x0c\n\x08StatusOk\x10\x00\x12\x16\n\x12StatusNotSupported\x10\x01\x12\x12\n\x0eStatusNotFound\x10\x02\x12\x13\n\x0fStatusMalformed\x10\x03\x12\x17\n\x13StatusInternalError\x10\x04\x12\x16\n\x12StatusIllegalState\x10\x05\x12\x11\n\rStatusNoSpace\x10\x06\x12\x12\n\x0eStatusNotEmpty\x10\x07\x12\x10\n\x0cStatusIsFile\x10\x08\x12\x0f\n\x0bStatusIsDir\x10\t\x12\x10\n\x0cStatusExists\x10\n*:\n\x07XferReq\x12\x10\n\x0cXferContinue\x10\x00\x12\r\n\tXferAbort\x10\x01\x12\x0e\n\nXferFinish\x10\x02\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0f\x62\x61\x64gelink.proto\x12\tbadgelink\"\x9f\x01\n\x0e\x41ppfsActionReq\x12,\n\x08metadata\x18\x02 \x01(\x0b\x32\x18.badgelink.AppfsMetadataH\x00\x12\x0e\n\x04slug\x18\x03 \x01(\tH\x00\x12%\n\x04type\x18\x01 \x01(\x0e\x32\x17.badgelink.FsActionType\x12\r\n\x05\x63rc32\x18\x04 \x01(\r\x12\x13\n\x0blist_offset\x18\x05 \x01(\rB\x04\n\x02id\"\xb0\x01\n\x0f\x41ppfsActionResp\x12,\n\x08metadata\x18\x01 \x01(\x0b\x32\x18.badgelink.AppfsMetadataH\x00\x12\x0f\n\x05\x63rc32\x18\x02 \x01(\rH\x00\x12$\n\x04list\x18\x03 \x01(\x0b\x32\x14.badgelink.AppfsListH\x00\x12#\n\x05usage\x18\x04 \x01(\x0b\x32\x12.badgelink.FsUsageH\x00\x12\x0c\n\x04size\x18\x05 \x01(\rB\x05\n\x03val\"G\n\tAppfsList\x12&\n\x04list\x18\x01 \x03(\x0b\x32\x18.badgelink.AppfsMetadata\x12\x12\n\ntotal_size\x18\x02 \x01(\r\"K\n\rAppfsMetadata\x12\x0c\n\x04slug\x18\x01 \x01(\t\x12\r\n\x05title\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\r\x12\x0c\n\x04size\x18\x04 \x01(\r\"\'\n\x05\x43hunk\x12\x10\n\x08position\x18\x02 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\"t\n\x0b\x46sActionReq\x12%\n\x04type\x18\x01 \x01(\x0e\x32\x17.badgelink.FsActionType\x12\x0c\n\x04path\x18\x02 \x01(\t\x12\r\n\x05\x63rc32\x18\x03 \x01(\r\x12\x13\n\x0blist_offset\x18\x04 \x01(\r\x12\x0c\n\x04size\x18\x05 \x01(\r\"\xa5\x01\n\x0c\x46sActionResp\x12!\n\x04stat\x18\x01 \x01(\x0b\x32\x11.badgelink.FsStatH\x00\x12\x0f\n\x05\x63rc32\x18\x02 \x01(\rH\x00\x12\'\n\x04list\x18\x03 \x01(\x0b\x32\x17.badgelink.FsDirentListH\x00\x12#\n\x05usage\x18\x04 \x01(\x0b\x32\x12.badgelink.FsUsageH\x00\x12\x0c\n\x04size\x18\x05 \x01(\rB\x05\n\x03val\"(\n\x08\x46sDirent\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06is_dir\x18\x02 \x01(\x08\"E\n\x0c\x46sDirentList\x12!\n\x04list\x18\x01 \x03(\x0b\x32\x13.badgelink.FsDirent\x12\x12\n\ntotal_size\x18\x02 \x01(\r\"S\n\x06\x46sStat\x12\x0c\n\x04size\x18\x01 \x01(\r\x12\r\n\x05mtime\x18\x02 \x01(\x04\x12\r\n\x05\x63time\x18\x03 \x01(\x04\x12\r\n\x05\x61time\x18\x04 \x01(\x04\x12\x0e\n\x06is_dir\x18\x05 \x01(\x08\"%\n\x07\x46sUsage\x12\x0c\n\x04size\x18\x01 \x01(\r\x12\x0c\n\x04used\x18\x02 \x01(\r\"\xb9\x01\n\x0cNvsActionReq\x12&\n\x04type\x18\x01 \x01(\x0e\x32\x18.badgelink.NvsActionType\x12\x0f\n\x07namespc\x18\x02 \x01(\t\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\"\n\x05wdata\x18\x04 \x01(\x0b\x32\x13.badgelink.NvsValue\x12\x13\n\x0blist_offset\x18\x05 \x01(\r\x12*\n\tread_type\x18\x06 \x01(\x0e\x32\x17.badgelink.NvsValueType\"j\n\rNvsActionResp\x12$\n\x05rdata\x18\x01 \x01(\x0b\x32\x13.badgelink.NvsValueH\x00\x12,\n\x07\x65ntries\x18\x02 \x01(\x0b\x32\x19.badgelink.NvsEntriesListH\x00\x42\x05\n\x03val\"M\n\x0eNvsEntriesList\x12$\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x13.badgelink.NvsEntry\x12\x15\n\rtotal_entries\x18\x02 \x01(\r\"O\n\x08NvsEntry\x12%\n\x04type\x18\x01 \x01(\x0e\x32\x17.badgelink.NvsValueType\x12\x0f\n\x07namespc\x18\x02 \x01(\t\x12\x0b\n\x03key\x18\x03 \x01(\t\"v\n\x08NvsValue\x12\x14\n\nnumericval\x18\x02 \x01(\x04H\x00\x12\x13\n\tstringval\x18\x03 \x01(\tH\x00\x12\x11\n\x07\x62lobval\x18\x04 \x01(\x0cH\x00\x12%\n\x04type\x18\x01 \x01(\x0e\x32\x17.badgelink.NvsValueTypeB\x05\n\x03val\"\x82\x01\n\x06Packet\x12%\n\x07request\x18\x02 \x01(\x0b\x32\x12.badgelink.RequestH\x00\x12\'\n\x08response\x18\x03 \x01(\x0b\x32\x13.badgelink.ResponseH\x00\x12\x0e\n\x04sync\x18\x04 \x01(\x08H\x00\x12\x0e\n\x06serial\x18\x01 \x01(\x04\x42\x08\n\x06packet\"\xcd\x02\n\x07Request\x12(\n\x0cupload_chunk\x18\x01 \x01(\x0b\x32\x10.badgelink.ChunkH\x00\x12\x31\n\x0c\x61ppfs_action\x18\x02 \x01(\x0b\x32\x19.badgelink.AppfsActionReqH\x00\x12+\n\tfs_action\x18\x03 \x01(\x0b\x32\x16.badgelink.FsActionReqH\x00\x12-\n\nnvs_action\x18\x04 \x01(\x0b\x32\x17.badgelink.NvsActionReqH\x00\x12+\n\tstart_app\x18\x05 \x01(\x0b\x32\x16.badgelink.StartAppReqH\x00\x12\'\n\txfer_ctrl\x18\x06 \x01(\x0e\x32\x12.badgelink.XferReqH\x00\x12,\n\x0bversion_req\x18\x07 \x01(\x0b\x32\x15.badgelink.VersionReqH\x00\x42\x05\n\x03req\"\xa6\x02\n\x08Response\x12*\n\x0e\x64ownload_chunk\x18\x02 \x01(\x0b\x32\x10.badgelink.ChunkH\x00\x12\x30\n\nappfs_resp\x18\x03 \x01(\x0b\x32\x1a.badgelink.AppfsActionRespH\x00\x12*\n\x07\x66s_resp\x18\x04 \x01(\x0b\x32\x17.badgelink.FsActionRespH\x00\x12,\n\x08nvs_resp\x18\x05 \x01(\x0b\x32\x18.badgelink.NvsActionRespH\x00\x12.\n\x0cversion_resp\x18\x06 \x01(\x0b\x32\x16.badgelink.VersionRespH\x00\x12*\n\x0bstatus_code\x18\x01 \x01(\x0e\x32\x15.badgelink.StatusCodeB\x06\n\x04resp\"(\n\x0bStartAppReq\x12\x0c\n\x04slug\x18\x01 \x01(\t\x12\x0b\n\x03\x61rg\x18\x02 \x01(\t\"$\n\nVersionReq\x12\x16\n\x0e\x63lient_version\x18\x01 \x01(\r\"A\n\x0bVersionResp\x12\x16\n\x0eserver_version\x18\x01 \x01(\r\x12\x1a\n\x12negotiated_version\x18\x02 \x01(\r*\xbf\x01\n\x0c\x46sActionType\x12\x10\n\x0c\x46sActionList\x10\x00\x12\x12\n\x0e\x46sActionDelete\x10\x01\x12\x11\n\rFsActionMkdir\x10\x02\x12\x12\n\x0e\x46sActionUpload\x10\x03\x12\x14\n\x10\x46sActionDownload\x10\x04\x12\x10\n\x0c\x46sActionStat\x10\x05\x12\x11\n\rFsActionCrc23\x10\x06\x12\x14\n\x10\x46sActionGetUsage\x10\x07\x12\x11\n\rFsActionRmdir\x10\x08*^\n\rNvsActionType\x12\x11\n\rNvsActionList\x10\x00\x12\x11\n\rNvsActionRead\x10\x01\x12\x12\n\x0eNvsActionWrite\x10\x02\x12\x13\n\x0fNvsActionDelete\x10\x03*\xce\x01\n\x0cNvsValueType\x12\x11\n\rNvsValueUint8\x10\x00\x12\x10\n\x0cNvsValueInt8\x10\x01\x12\x12\n\x0eNvsValueUint16\x10\x02\x12\x11\n\rNvsValueInt16\x10\x03\x12\x12\n\x0eNvsValueUint32\x10\x04\x12\x11\n\rNvsValueInt32\x10\x05\x12\x12\n\x0eNvsValueUint64\x10\x06\x12\x11\n\rNvsValueInt64\x10\x07\x12\x12\n\x0eNvsValueString\x10\x08\x12\x10\n\x0cNvsValueBlob\x10\t*\xe8\x01\n\nStatusCode\x12\x0c\n\x08StatusOk\x10\x00\x12\x16\n\x12StatusNotSupported\x10\x01\x12\x12\n\x0eStatusNotFound\x10\x02\x12\x13\n\x0fStatusMalformed\x10\x03\x12\x17\n\x13StatusInternalError\x10\x04\x12\x16\n\x12StatusIllegalState\x10\x05\x12\x11\n\rStatusNoSpace\x10\x06\x12\x12\n\x0eStatusNotEmpty\x10\x07\x12\x10\n\x0cStatusIsFile\x10\x08\x12\x0f\n\x0bStatusIsDir\x10\t\x12\x10\n\x0cStatusExists\x10\n*:\n\x07XferReq\x12\x10\n\x0cXferContinue\x10\x00\x12\r\n\tXferAbort\x10\x01\x12\x0e\n\nXferFinish\x10\x02\x62\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'badgelink_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'badgelink_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_FSACTIONTYPE']._serialized_start=2376 - _globals['_FSACTIONTYPE']._serialized_end=2567 - _globals['_NVSACTIONTYPE']._serialized_start=2569 - _globals['_NVSACTIONTYPE']._serialized_end=2663 - _globals['_NVSVALUETYPE']._serialized_start=2666 - _globals['_NVSVALUETYPE']._serialized_end=2872 - _globals['_STATUSCODE']._serialized_start=2875 - _globals['_STATUSCODE']._serialized_end=3107 - _globals['_XFERREQ']._serialized_start=3109 - _globals['_XFERREQ']._serialized_end=3167 - _globals['_APPFSACTIONREQ']._serialized_start=31 - _globals['_APPFSACTIONREQ']._serialized_end=190 - _globals['_APPFSACTIONRESP']._serialized_start=193 - _globals['_APPFSACTIONRESP']._serialized_end=369 - _globals['_APPFSLIST']._serialized_start=371 - _globals['_APPFSLIST']._serialized_end=442 - _globals['_APPFSMETADATA']._serialized_start=444 - _globals['_APPFSMETADATA']._serialized_end=519 - _globals['_CHUNK']._serialized_start=521 - _globals['_CHUNK']._serialized_end=560 - _globals['_FSACTIONREQ']._serialized_start=562 - _globals['_FSACTIONREQ']._serialized_end=678 - _globals['_FSACTIONRESP']._serialized_start=681 - _globals['_FSACTIONRESP']._serialized_end=846 - _globals['_FSDIRENT']._serialized_start=848 - _globals['_FSDIRENT']._serialized_end=888 - _globals['_FSDIRENTLIST']._serialized_start=890 - _globals['_FSDIRENTLIST']._serialized_end=959 - _globals['_FSSTAT']._serialized_start=961 - _globals['_FSSTAT']._serialized_end=1044 - _globals['_FSUSAGE']._serialized_start=1046 - _globals['_FSUSAGE']._serialized_end=1083 - _globals['_NVSACTIONREQ']._serialized_start=1086 - _globals['_NVSACTIONREQ']._serialized_end=1271 - _globals['_NVSACTIONRESP']._serialized_start=1273 - _globals['_NVSACTIONRESP']._serialized_end=1379 - _globals['_NVSENTRIESLIST']._serialized_start=1381 - _globals['_NVSENTRIESLIST']._serialized_end=1458 - _globals['_NVSENTRY']._serialized_start=1460 - _globals['_NVSENTRY']._serialized_end=1539 - _globals['_NVSVALUE']._serialized_start=1541 - _globals['_NVSVALUE']._serialized_end=1659 - _globals['_PACKET']._serialized_start=1662 - _globals['_PACKET']._serialized_end=1792 - _globals['_REQUEST']._serialized_start=1795 - _globals['_REQUEST']._serialized_end=2082 - _globals['_RESPONSE']._serialized_start=2085 - _globals['_RESPONSE']._serialized_end=2331 - _globals['_STARTAPPREQ']._serialized_start=2333 - _globals['_STARTAPPREQ']._serialized_end=2373 + DESCRIPTOR._options = None + _FSACTIONTYPE._serialized_start=2575 + _FSACTIONTYPE._serialized_end=2766 + _NVSACTIONTYPE._serialized_start=2768 + _NVSACTIONTYPE._serialized_end=2862 + _NVSVALUETYPE._serialized_start=2865 + _NVSVALUETYPE._serialized_end=3071 + _STATUSCODE._serialized_start=3074 + _STATUSCODE._serialized_end=3306 + _XFERREQ._serialized_start=3308 + _XFERREQ._serialized_end=3366 + _APPFSACTIONREQ._serialized_start=31 + _APPFSACTIONREQ._serialized_end=190 + _APPFSACTIONRESP._serialized_start=193 + _APPFSACTIONRESP._serialized_end=369 + _APPFSLIST._serialized_start=371 + _APPFSLIST._serialized_end=442 + _APPFSMETADATA._serialized_start=444 + _APPFSMETADATA._serialized_end=519 + _CHUNK._serialized_start=521 + _CHUNK._serialized_end=560 + _FSACTIONREQ._serialized_start=562 + _FSACTIONREQ._serialized_end=678 + _FSACTIONRESP._serialized_start=681 + _FSACTIONRESP._serialized_end=846 + _FSDIRENT._serialized_start=848 + _FSDIRENT._serialized_end=888 + _FSDIRENTLIST._serialized_start=890 + _FSDIRENTLIST._serialized_end=959 + _FSSTAT._serialized_start=961 + _FSSTAT._serialized_end=1044 + _FSUSAGE._serialized_start=1046 + _FSUSAGE._serialized_end=1083 + _NVSACTIONREQ._serialized_start=1086 + _NVSACTIONREQ._serialized_end=1271 + _NVSACTIONRESP._serialized_start=1273 + _NVSACTIONRESP._serialized_end=1379 + _NVSENTRIESLIST._serialized_start=1381 + _NVSENTRIESLIST._serialized_end=1458 + _NVSENTRY._serialized_start=1460 + _NVSENTRY._serialized_end=1539 + _NVSVALUE._serialized_start=1541 + _NVSVALUE._serialized_end=1659 + _PACKET._serialized_start=1662 + _PACKET._serialized_end=1792 + _REQUEST._serialized_start=1795 + _REQUEST._serialized_end=2128 + _RESPONSE._serialized_start=2131 + _RESPONSE._serialized_end=2425 + _STARTAPPREQ._serialized_start=2427 + _STARTAPPREQ._serialized_end=2467 + _VERSIONREQ._serialized_start=2469 + _VERSIONREQ._serialized_end=2505 + _VERSIONRESP._serialized_start=2507 + _VERSIONRESP._serialized_end=2572 # @@protoc_insertion_point(module_scope)