Initial commit: OTA chunk transfer protocol implementation
- Protocol definition with 6-byte header (chunk_idx: 2B, payload: N B, crc32: 4B) - Node.js server with chunk-based HTTP delivery - C client library with resumable download support - CRC32 implementation (IEEE 802.3) - Test suites for both server and CRC32 - 1024B default chunk size (0.59% overhead)
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
/**
|
||||
* @file chunk_client.c
|
||||
* @brief Chunk Transfer Client Implementation
|
||||
*
|
||||
* Note: This implementation requires an HTTP client library.
|
||||
* For ESP32, use ESP-IDF's HTTP Client or Arduino's HTTPClient.
|
||||
* For POSIX systems, use libcurl.
|
||||
*/
|
||||
|
||||
#include "protocol.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* HTTP client interface (to be implemented per platform) */
|
||||
typedef struct {
|
||||
/**
|
||||
* Send HTTP GET request
|
||||
* @param url Full URL
|
||||
* @param response Buffer for response data
|
||||
* @param response_len Buffer size
|
||||
* @param actual_len Actual response length (output)
|
||||
* @param status_code HTTP status code (output)
|
||||
* @return 0 on success, error code otherwise
|
||||
*/
|
||||
int (*http_get)(const char *url, uint8_t *response, size_t response_len,
|
||||
size_t *actual_len, int *status_code);
|
||||
|
||||
/**
|
||||
* Get header value from last response
|
||||
* @param name Header name
|
||||
* @return Header value or NULL
|
||||
*/
|
||||
const char *(*http_get_header)(const char *name);
|
||||
|
||||
void *ctx; /* Platform-specific context */
|
||||
} http_client_t;
|
||||
|
||||
/* Client state */
|
||||
typedef struct {
|
||||
chunk_client_config_t config;
|
||||
http_client_t http;
|
||||
|
||||
char current_file[256];
|
||||
uint32_t file_size;
|
||||
uint16_t total_chunks;
|
||||
uint16_t current_chunk;
|
||||
chunk_state_t state;
|
||||
chunk_err_t error_code;
|
||||
bool cancel_requested;
|
||||
} chunk_client_state_t;
|
||||
|
||||
static chunk_client_state_t client_state;
|
||||
|
||||
/**
|
||||
* Initialize chunk client
|
||||
*/
|
||||
chunk_err_t chunk_client_init(const chunk_client_config_t *config) {
|
||||
if (!config || !config->server_url) {
|
||||
return CHUNK_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
memset(&client_state, 0, sizeof(client_state));
|
||||
memcpy(&client_state.config, config, sizeof(chunk_client_config_t));
|
||||
|
||||
/* Set default chunk size if not specified */
|
||||
if (client_state.config.chunk_size == 0) {
|
||||
client_state.config.chunk_size = CHUNK_DEFAULT_SIZE;
|
||||
}
|
||||
|
||||
client_state.state = CHUNK_STATE_IDLE;
|
||||
client_state.error_code = CHUNK_ERR_OK;
|
||||
|
||||
return CHUNK_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file size from server
|
||||
*/
|
||||
static chunk_err_t get_file_size(const char *filename, uint32_t *size) {
|
||||
char url[512];
|
||||
snprintf(url, sizeof(url), "%s/ota?file=%s",
|
||||
client_state.config.server_url, filename);
|
||||
|
||||
/* Send HEAD or GET request to get Content-Length */
|
||||
uint8_t dummy[1];
|
||||
size_t actual_len;
|
||||
int status_code;
|
||||
|
||||
int ret = client_state.http.http_get(url, dummy, sizeof(dummy),
|
||||
&actual_len, &status_code);
|
||||
if (ret != 0) {
|
||||
return CHUNK_ERR_NETWORK;
|
||||
}
|
||||
|
||||
if (status_code != 200) {
|
||||
return (status_code == 404) ? CHUNK_ERR_FILE_NOT_FOUND : CHUNK_ERR_SERVER_ERROR;
|
||||
}
|
||||
|
||||
/* Parse Content-Length from headers */
|
||||
const char *cl = client_state.http.http_get_header("X-File-Size");
|
||||
if (!cl) {
|
||||
cl = client_state.http.http_get_header("Content-Length");
|
||||
}
|
||||
|
||||
if (cl) {
|
||||
*size = (uint32_t)atoi(cl);
|
||||
} else {
|
||||
/* Fallback: estimate from first chunk */
|
||||
*size = client_state.config.chunk_size;
|
||||
}
|
||||
|
||||
return CHUNK_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a single chunk
|
||||
*/
|
||||
static chunk_err_t download_chunk(uint16_t chunk_idx, uint8_t *buffer,
|
||||
uint16_t *payload_len, uint32_t *crc32) {
|
||||
char url[512];
|
||||
snprintf(url, sizeof(url), "%s/ota?file=%s&chunk=%u",
|
||||
client_state.config.server_url,
|
||||
client_state.current_file, chunk_idx);
|
||||
|
||||
/* Allocate buffer for chunk response */
|
||||
size_t response_size = 2 + client_state.config.chunk_size + 4;
|
||||
uint8_t *response = (uint8_t *)malloc(response_size);
|
||||
if (!response) {
|
||||
return CHUNK_ERR_MEMORY;
|
||||
}
|
||||
|
||||
size_t actual_len;
|
||||
int status_code;
|
||||
|
||||
int ret = client_state.http.http_get(url, response, response_size,
|
||||
&actual_len, &status_code);
|
||||
|
||||
if (ret != 0) {
|
||||
free(response);
|
||||
return CHUNK_ERR_NETWORK;
|
||||
}
|
||||
|
||||
if (status_code == 416) {
|
||||
free(response);
|
||||
return CHUNK_ERR_RANGE_INVALID;
|
||||
}
|
||||
|
||||
if (status_code != 200 && status_code != 206) {
|
||||
free(response);
|
||||
return CHUNK_ERR_SERVER_ERROR;
|
||||
}
|
||||
|
||||
/* Parse chunk: [chunk_idx: 2B][payload: N B][crc32: 4B] */
|
||||
if (actual_len < 6) { /* Minimum: 2 + 0 + 4 */
|
||||
free(response);
|
||||
return CHUNK_ERR_NETWORK;
|
||||
}
|
||||
|
||||
uint16_t received_idx = (response[0] << 8) | response[1];
|
||||
if (received_idx != chunk_idx) {
|
||||
free(response);
|
||||
return CHUNK_ERR_NETWORK; /* Chunk index mismatch */
|
||||
}
|
||||
|
||||
/* Calculate payload length */
|
||||
uint16_t total_chunk_size = client_state.config.chunk_size;
|
||||
uint32_t remaining = client_state.file_size - (uint32_t)chunk_idx * total_chunk_size;
|
||||
uint16_t expected_payload = (uint16_t)(remaining < total_chunk_size ? remaining : total_chunk_size);
|
||||
|
||||
if (actual_len != 2 + expected_payload + 4) {
|
||||
/* Last chunk might have different size */
|
||||
if (chunk_idx == client_state.total_chunks - 1) {
|
||||
*payload_len = (uint16_t)(actual_len - 6);
|
||||
} else {
|
||||
free(response);
|
||||
return CHUNK_ERR_NETWORK;
|
||||
}
|
||||
} else {
|
||||
*payload_len = expected_payload;
|
||||
}
|
||||
|
||||
/* Copy payload */
|
||||
memcpy(buffer, response + 2, *payload_len);
|
||||
|
||||
/* Extract CRC32 */
|
||||
*crc32 = (response[2 + *payload_len] << 24) |
|
||||
(response[3 + *payload_len] << 16) |
|
||||
(response[4 + *payload_len] << 8) |
|
||||
response[5 + *payload_len];
|
||||
|
||||
free(response);
|
||||
return CHUNK_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start file download
|
||||
*/
|
||||
chunk_err_t chunk_client_download(const char *filename) {
|
||||
if (!filename) {
|
||||
return CHUNK_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
/* Store filename */
|
||||
strncpy(client_state.current_file, filename, sizeof(client_state.current_file) - 1);
|
||||
client_state.current_file[sizeof(client_state.current_file) - 1] = '\0';
|
||||
|
||||
/* Get file size */
|
||||
chunk_err_t err = get_file_size(filename, &client_state.file_size);
|
||||
if (err != CHUNK_ERR_OK) {
|
||||
client_state.state = CHUNK_STATE_ERROR;
|
||||
client_state.error_code = err;
|
||||
if (client_state.config.on_error) {
|
||||
client_state.config.on_error(err, client_state.config.user_ctx);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Calculate total chunks */
|
||||
client_state.total_chunks = chunk_calc_total_chunks(
|
||||
client_state.file_size, client_state.config.chunk_size);
|
||||
|
||||
client_state.current_chunk = 0;
|
||||
client_state.state = CHUNK_STATE_TRANSFERRING;
|
||||
client_state.cancel_requested = false;
|
||||
|
||||
/* Buffer for chunk payload */
|
||||
uint8_t *payload = (uint8_t *)malloc(client_state.config.chunk_size);
|
||||
if (!payload) {
|
||||
client_state.state = CHUNK_STATE_ERROR;
|
||||
client_state.error_code = CHUNK_ERR_MEMORY;
|
||||
return CHUNK_ERR_MEMORY;
|
||||
}
|
||||
|
||||
/* Download all chunks */
|
||||
for (uint16_t i = 0; i < client_state.total_chunks; i++) {
|
||||
if (client_state.cancel_requested) {
|
||||
free(payload);
|
||||
client_state.state = CHUNK_STATE_IDLE;
|
||||
return CHUNK_ERR_OK; /* Cancel is not an error */
|
||||
}
|
||||
|
||||
uint16_t payload_len;
|
||||
uint32_t crc32;
|
||||
|
||||
err = download_chunk(i, payload, &payload_len, &crc32);
|
||||
if (err != CHUNK_ERR_OK) {
|
||||
free(payload);
|
||||
client_state.state = CHUNK_STATE_ERROR;
|
||||
client_state.error_code = err;
|
||||
if (client_state.config.on_error) {
|
||||
client_state.config.on_error(err, client_state.config.user_ctx);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Verify CRC32 */
|
||||
uint32_t calculated_crc = chunk_crc32(payload, payload_len);
|
||||
if (calculated_crc != crc32) {
|
||||
free(payload);
|
||||
client_state.state = CHUNK_STATE_ERROR;
|
||||
client_state.error_code = CHUNK_ERR_CRC;
|
||||
if (client_state.config.on_error) {
|
||||
client_state.config.on_error(CHUNK_ERR_CRC, client_state.config.user_ctx);
|
||||
}
|
||||
return CHUNK_ERR_CRC;
|
||||
}
|
||||
|
||||
/* Call chunk received callback */
|
||||
if (client_state.config.on_chunk_received) {
|
||||
client_state.config.on_chunk_received(i, payload, payload_len,
|
||||
client_state.config.user_ctx);
|
||||
}
|
||||
|
||||
/* Progress callback */
|
||||
if (client_state.config.on_progress) {
|
||||
client_state.config.on_progress(i + 1, client_state.total_chunks,
|
||||
client_state.config.user_ctx);
|
||||
}
|
||||
|
||||
client_state.current_chunk = i + 1;
|
||||
}
|
||||
|
||||
free(payload);
|
||||
|
||||
/* Transfer complete */
|
||||
client_state.state = CHUNK_STATE_COMPLETE;
|
||||
if (client_state.config.on_complete) {
|
||||
client_state.config.on_complete(client_state.file_size,
|
||||
client_state.config.user_ctx);
|
||||
}
|
||||
|
||||
return CHUNK_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume download from specific chunk
|
||||
*/
|
||||
chunk_err_t chunk_client_resume(const char *filename, uint16_t start_chunk) {
|
||||
if (!filename) {
|
||||
return CHUNK_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
/* Store filename */
|
||||
strncpy(client_state.current_file, filename, sizeof(client_state.current_file) - 1);
|
||||
client_state.current_file[sizeof(client_state.current_file) - 1] = '\0';
|
||||
|
||||
/* Get file size */
|
||||
chunk_err_t err = get_file_size(filename, &client_state.file_size);
|
||||
if (err != CHUNK_ERR_OK) {
|
||||
client_state.state = CHUNK_STATE_ERROR;
|
||||
client_state.error_code = err;
|
||||
if (client_state.config.on_error) {
|
||||
client_state.config.on_error(err, client_state.config.user_ctx);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Calculate total chunks */
|
||||
client_state.total_chunks = chunk_calc_total_chunks(
|
||||
client_state.file_size, client_state.config.chunk_size);
|
||||
|
||||
/* Validate start chunk */
|
||||
if (start_chunk >= client_state.total_chunks) {
|
||||
return CHUNK_ERR_RANGE_INVALID;
|
||||
}
|
||||
|
||||
client_state.current_chunk = start_chunk;
|
||||
client_state.state = CHUNK_STATE_RESUMING;
|
||||
client_state.cancel_requested = false;
|
||||
|
||||
/* Buffer for chunk payload */
|
||||
uint8_t *payload = (uint8_t *)malloc(client_state.config.chunk_size);
|
||||
if (!payload) {
|
||||
client_state.state = CHUNK_STATE_ERROR;
|
||||
client_state.error_code = CHUNK_ERR_MEMORY;
|
||||
return CHUNK_ERR_MEMORY;
|
||||
}
|
||||
|
||||
/* Download remaining chunks */
|
||||
for (uint16_t i = start_chunk; i < client_state.total_chunks; i++) {
|
||||
if (client_state.cancel_requested) {
|
||||
free(payload);
|
||||
client_state.state = CHUNK_STATE_IDLE;
|
||||
return CHUNK_ERR_OK;
|
||||
}
|
||||
|
||||
uint16_t payload_len;
|
||||
uint32_t crc32;
|
||||
|
||||
err = download_chunk(i, payload, &payload_len, &crc32);
|
||||
if (err != CHUNK_ERR_OK) {
|
||||
free(payload);
|
||||
client_state.state = CHUNK_STATE_ERROR;
|
||||
client_state.error_code = err;
|
||||
if (client_state.config.on_error) {
|
||||
client_state.config.on_error(err, client_state.config.user_ctx);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Verify CRC32 */
|
||||
uint32_t calculated_crc = chunk_crc32(payload, payload_len);
|
||||
if (calculated_crc != crc32) {
|
||||
free(payload);
|
||||
client_state.state = CHUNK_STATE_ERROR;
|
||||
client_state.error_code = CHUNK_ERR_CRC;
|
||||
if (client_state.config.on_error) {
|
||||
client_state.config.on_error(CHUNK_ERR_CRC, client_state.config.user_ctx);
|
||||
}
|
||||
return CHUNK_ERR_CRC;
|
||||
}
|
||||
|
||||
/* Call chunk received callback */
|
||||
if (client_state.config.on_chunk_received) {
|
||||
client_state.config.on_chunk_received(i, payload, payload_len,
|
||||
client_state.config.user_ctx);
|
||||
}
|
||||
|
||||
/* Progress callback */
|
||||
if (client_state.config.on_progress) {
|
||||
client_state.config.on_progress(i + 1, client_state.total_chunks,
|
||||
client_state.config.user_ctx);
|
||||
}
|
||||
|
||||
client_state.current_chunk = i + 1;
|
||||
}
|
||||
|
||||
free(payload);
|
||||
|
||||
/* Transfer complete */
|
||||
client_state.state = CHUNK_STATE_COMPLETE;
|
||||
if (client_state.config.on_complete) {
|
||||
client_state.config.on_complete(client_state.file_size,
|
||||
client_state.config.user_ctx);
|
||||
}
|
||||
|
||||
return CHUNK_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel current transfer
|
||||
*/
|
||||
void chunk_client_cancel(void) {
|
||||
client_state.cancel_requested = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current transfer state
|
||||
*/
|
||||
chunk_state_t chunk_client_get_state(void) {
|
||||
return client_state.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current progress
|
||||
*/
|
||||
chunk_err_t chunk_client_get_progress(uint16_t *current_chunk, uint16_t *total_chunks) {
|
||||
if (!current_chunk || !total_chunks) {
|
||||
return CHUNK_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
*current_chunk = client_state.current_chunk;
|
||||
*total_chunks = client_state.total_chunks;
|
||||
|
||||
return CHUNK_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup client resources
|
||||
*/
|
||||
void chunk_client_cleanup(void) {
|
||||
memset(&client_state, 0, sizeof(client_state));
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* @file crc32.c
|
||||
* @brief CRC32 Implementation (IEEE 802.3)
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* IEEE 802.3 CRC32 polynomial (reversed) */
|
||||
#define CRC32_POLY 0xEDB88320UL
|
||||
|
||||
/* CRC32 lookup table (256 entries) */
|
||||
static uint32_t crc32_table[256];
|
||||
static bool crc32_table_init = false;
|
||||
|
||||
/**
|
||||
* Initialize CRC32 lookup table
|
||||
*/
|
||||
static void crc32_init_table(void) {
|
||||
if (crc32_table_init) return;
|
||||
|
||||
for (uint32_t i = 0; i < 256; i++) {
|
||||
uint32_t crc = i;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (crc & 1) {
|
||||
crc = (crc >> 1) ^ CRC32_POLY;
|
||||
} else {
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
crc32_table[i] = crc;
|
||||
}
|
||||
crc32_table_init = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate CRC32 (IEEE 802.3 polynomial)
|
||||
*
|
||||
* @param data Input data
|
||||
* @param len Input length
|
||||
* @return CRC32 value
|
||||
*/
|
||||
uint32_t chunk_crc32(const uint8_t *data, size_t len) {
|
||||
crc32_init_table();
|
||||
|
||||
uint32_t crc = 0xFFFFFFFFUL; /* Initial value */
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uint8_t index = (crc ^ data[i]) & 0xFF;
|
||||
crc = (crc >> 8) ^ crc32_table[index];
|
||||
}
|
||||
|
||||
return crc ^ 0xFFFFFFFFUL; /* Final XOR */
|
||||
}
|
||||
|
||||
/**
|
||||
* Update CRC32 with additional data
|
||||
*
|
||||
* @param crc Previous CRC value (already XORed with 0xFFFFFFFF)
|
||||
* @param data New data
|
||||
* @param len Data length
|
||||
* @return Updated CRC value
|
||||
*/
|
||||
uint32_t chunk_crc32_update(uint32_t crc, const uint8_t *data, size_t len) {
|
||||
crc32_init_table();
|
||||
|
||||
/* Undo final XOR from previous calculation */
|
||||
crc ^= 0xFFFFFFFFUL;
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uint8_t index = (crc ^ data[i]) & 0xFF;
|
||||
crc = (crc >> 8) ^ crc32_table[index];
|
||||
}
|
||||
|
||||
return crc ^ 0xFFFFFFFFUL; /* Final XOR */
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
/**
|
||||
* @file protocol.h
|
||||
* @brief OTA Chunk Transfer Protocol Definitions
|
||||
*/
|
||||
|
||||
#ifndef OTA_CHUNK_PROTOCOL_H
|
||||
#define OTA_CHUNK_PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* =========================================================================
|
||||
* Protocol Constants
|
||||
* ========================================================================= */
|
||||
|
||||
#define CHUNK_PROTOCOL_VERSION 1
|
||||
#define CHUNK_DEFAULT_SIZE 1024
|
||||
#define CHUNK_MIN_SIZE 256
|
||||
#define CHUNK_MAX_SIZE 65535
|
||||
#define CHUNK_INDEX_MAX 65535 /* 2-byte unsigned */
|
||||
|
||||
/* Maximum file size: CHUNK_INDEX_MAX * CHUNK_MAX_SIZE ≈ 4.29 GB */
|
||||
#define CHUNK_MAX_FILE_SIZE ((uint32_t)CHUNK_INDEX_MAX * CHUNK_MAX_SIZE)
|
||||
|
||||
/* =========================================================================
|
||||
* Protocol Structures
|
||||
* ========================================================================= */
|
||||
|
||||
/**
|
||||
* Chunk packet structure (network format)
|
||||
*
|
||||
* Layout:
|
||||
* +------------+----------------+----------+
|
||||
* | chunk_idx | payload (N B) | crc32 |
|
||||
* | 2 B | N B | 4 B |
|
||||
* +------------+----------------+----------+
|
||||
*
|
||||
* Note: This structure includes padding. Use packing for network transmission.
|
||||
*/
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint16_t chunk_idx; /* Chunk index (little-endian) */
|
||||
/* payload follows immediately */
|
||||
/* crc32 follows payload */
|
||||
} chunk_header_t;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
/**
|
||||
* Chunk data descriptor (in-memory representation)
|
||||
*/
|
||||
typedef struct {
|
||||
uint16_t chunk_idx; /* Chunk index */
|
||||
uint16_t payload_len; /* Payload length */
|
||||
uint8_t *payload; /* Payload pointer */
|
||||
uint32_t crc32; /* CRC32 of payload */
|
||||
} chunk_data_t;
|
||||
|
||||
/**
|
||||
* Transfer state
|
||||
*/
|
||||
typedef enum {
|
||||
CHUNK_STATE_IDLE,
|
||||
CHUNK_STATE_FETCHING_SIZE,
|
||||
CHUNK_STATE_TRANSFERRING,
|
||||
CHUNK_STATE_RESUMING,
|
||||
CHUNK_STATE_COMPLETE,
|
||||
CHUNK_STATE_ERROR
|
||||
} chunk_state_t;
|
||||
|
||||
/**
|
||||
* Error codes
|
||||
*/
|
||||
typedef enum {
|
||||
CHUNK_ERR_OK = 0,
|
||||
CHUNK_ERR_INVALID_PARAM = -1,
|
||||
CHUNK_ERR_MEMORY = -2,
|
||||
CHUNK_ERR_NETWORK = -3,
|
||||
CHUNK_ERR_CRC = -4,
|
||||
CHUNK_ERR_FILE_NOT_FOUND = -5,
|
||||
CHUNK_ERR_RANGE_INVALID = -6,
|
||||
CHUNK_ERR_TIMEOUT = -7,
|
||||
CHUNK_ERR_SERVER_ERROR = -8,
|
||||
} chunk_err_t;
|
||||
|
||||
/* =========================================================================
|
||||
* Callback Types
|
||||
* ========================================================================= */
|
||||
|
||||
/**
|
||||
* Chunk received callback
|
||||
*
|
||||
* @param chunk_idx Chunk index
|
||||
* @param data Payload data
|
||||
* @param len Payload length
|
||||
* @param user_ctx User context
|
||||
*/
|
||||
typedef void (*chunk_received_cb)(uint16_t chunk_idx, const uint8_t *data,
|
||||
uint16_t len, void *user_ctx);
|
||||
|
||||
/**
|
||||
* Transfer complete callback
|
||||
*
|
||||
* @param total_bytes Total bytes transferred
|
||||
* @param user_ctx User context
|
||||
*/
|
||||
typedef void (*transfer_complete_cb)(uint32_t total_bytes, void *user_ctx);
|
||||
|
||||
/**
|
||||
* Transfer error callback
|
||||
*
|
||||
* @param err_code Error code
|
||||
* @param user_ctx User context
|
||||
*/
|
||||
typedef void (*transfer_error_cb)(chunk_err_t err_code, void *user_ctx);
|
||||
|
||||
/**
|
||||
* Progress update callback
|
||||
*
|
||||
* @param current_chunk Current chunk index
|
||||
* @param total_chunks Total chunks
|
||||
* @param user_ctx User context
|
||||
*/
|
||||
typedef void (*progress_cb)(uint16_t current_chunk, uint16_t total_chunks,
|
||||
void *user_ctx);
|
||||
|
||||
/* =========================================================================
|
||||
* Configuration Structures
|
||||
* ========================================================================= */
|
||||
|
||||
/**
|
||||
* Chunk client configuration
|
||||
*/
|
||||
typedef struct {
|
||||
const char *server_url; /* Server base URL */
|
||||
uint16_t chunk_size; /* Chunk size in bytes */
|
||||
uint32_t timeout_ms; /* Timeout in milliseconds */
|
||||
|
||||
/* Callbacks */
|
||||
chunk_received_cb on_chunk_received;
|
||||
transfer_complete_cb on_complete;
|
||||
transfer_error_cb on_error;
|
||||
progress_cb on_progress;
|
||||
|
||||
void *user_ctx; /* User context passed to callbacks */
|
||||
} chunk_client_config_t;
|
||||
|
||||
/**
|
||||
* Chunk server configuration
|
||||
*/
|
||||
typedef struct {
|
||||
uint16_t port; /* Server port */
|
||||
const char *root_path; /* Root directory for files */
|
||||
uint16_t chunk_size; /* Chunk size in bytes */
|
||||
} chunk_server_config_t;
|
||||
|
||||
/* =========================================================================
|
||||
* CRC32 Functions
|
||||
* ========================================================================= */
|
||||
|
||||
/**
|
||||
* Calculate CRC32 (IEEE 802.3 polynomial)
|
||||
*
|
||||
* @param data Input data
|
||||
* @param len Input length
|
||||
* @return CRC32 value
|
||||
*/
|
||||
uint32_t chunk_crc32(const uint8_t *data, size_t len);
|
||||
|
||||
/**
|
||||
* Update CRC32 with additional data
|
||||
*
|
||||
* @param crc Previous CRC value
|
||||
* @param data New data
|
||||
* @param len Data length
|
||||
* @return Updated CRC value
|
||||
*/
|
||||
uint32_t chunk_crc32_update(uint32_t crc, const uint8_t *data, size_t len);
|
||||
|
||||
/* =========================================================================
|
||||
* Client Functions
|
||||
* ========================================================================= */
|
||||
|
||||
/**
|
||||
* Initialize chunk client
|
||||
*
|
||||
* @param config Configuration structure
|
||||
* @return CHUNK_ERR_OK on success, error code otherwise
|
||||
*/
|
||||
chunk_err_t chunk_client_init(const chunk_client_config_t *config);
|
||||
|
||||
/**
|
||||
* Start file download
|
||||
*
|
||||
* @param filename Filename to download
|
||||
* @return CHUNK_ERR_OK on success, error code otherwise
|
||||
*/
|
||||
chunk_err_t chunk_client_download(const char *filename);
|
||||
|
||||
/**
|
||||
* Resume download from specific chunk
|
||||
*
|
||||
* @param filename Filename to download
|
||||
* @param start_chunk Chunk index to resume from
|
||||
* @return CHUNK_ERR_OK on success, error code otherwise
|
||||
*/
|
||||
chunk_err_t chunk_client_resume(const char *filename, uint16_t start_chunk);
|
||||
|
||||
/**
|
||||
* Cancel current transfer
|
||||
*/
|
||||
void chunk_client_cancel(void);
|
||||
|
||||
/**
|
||||
* Get current transfer state
|
||||
*
|
||||
* @return Current state
|
||||
*/
|
||||
chunk_state_t chunk_client_get_state(void);
|
||||
|
||||
/**
|
||||
* Get current progress
|
||||
*
|
||||
* @param current_chunk Current chunk index (output)
|
||||
* @param total_chunks Total chunks (output)
|
||||
* @return CHUNK_ERR_OK on success
|
||||
*/
|
||||
chunk_err_t chunk_client_get_progress(uint16_t *current_chunk,
|
||||
uint16_t *total_chunks);
|
||||
|
||||
/**
|
||||
* Cleanup client resources
|
||||
*/
|
||||
void chunk_client_cleanup(void);
|
||||
|
||||
/* =========================================================================
|
||||
* Server Functions (for reference / embedded use)
|
||||
* ========================================================================= */
|
||||
|
||||
/**
|
||||
* Initialize chunk server
|
||||
*
|
||||
* @param config Configuration structure
|
||||
* @return CHUNK_ERR_OK on success, error code otherwise
|
||||
*/
|
||||
chunk_err_t chunk_server_init(const chunk_server_config_t *config);
|
||||
|
||||
/**
|
||||
* Start server (blocking)
|
||||
*
|
||||
* @return CHUNK_ERR_OK on success, error code otherwise
|
||||
*/
|
||||
chunk_err_t chunk_server_start(void);
|
||||
|
||||
/**
|
||||
* Stop server
|
||||
*/
|
||||
void chunk_server_stop(void);
|
||||
|
||||
/* =========================================================================
|
||||
* Utility Functions
|
||||
* ========================================================================= */
|
||||
|
||||
/**
|
||||
* Calculate total chunks for a file
|
||||
*
|
||||
* @param file_size File size in bytes
|
||||
* @param chunk_size Chunk size in bytes
|
||||
* @return Total number of chunks
|
||||
*/
|
||||
static inline uint16_t chunk_calc_total_chunks(uint32_t file_size,
|
||||
uint16_t chunk_size) {
|
||||
return (uint16_t)((file_size + chunk_size - 1) / chunk_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate payload size for a chunk
|
||||
*
|
||||
* @param file_size File size in bytes
|
||||
* @param chunk_idx Chunk index
|
||||
* @param chunk_size Chunk size in bytes
|
||||
* @return Payload size for this chunk
|
||||
*/
|
||||
static inline uint16_t chunk_calc_payload_size(uint32_t file_size,
|
||||
uint16_t chunk_idx,
|
||||
uint16_t chunk_size) {
|
||||
uint32_t remaining = file_size - (uint32_t)chunk_idx * chunk_size;
|
||||
return (uint16_t)(remaining < chunk_size ? remaining : chunk_size);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OTA_CHUNK_PROTOCOL_H */
|
||||
Reference in New Issue
Block a user