From b9c48d9f4e69bb03049a65cdbe380b341a3e247e Mon Sep 17 00:00:00 2001 From: Alex Stewart Date: Wed, 2 Mar 2016 10:48:28 -0800 Subject: [PATCH 01/13] Initial sysparam implementation Problems with inability to write individual bytes to flash. Need to reorganize to read/write word-multiples instead. --- core/include/esp/types.h | 1 + core/include/sysparam.h | 42 +++ core/sysparam.c | 616 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 659 insertions(+) create mode 100644 core/include/sysparam.h create mode 100644 core/sysparam.c diff --git a/core/include/esp/types.h b/core/include/esp/types.h index cb816da..53c4cd4 100644 --- a/core/include/esp/types.h +++ b/core/include/esp/types.h @@ -3,6 +3,7 @@ #include #include +#include typedef volatile uint32_t *esp_reg_t; diff --git a/core/include/sysparam.h b/core/include/sysparam.h new file mode 100644 index 0000000..08bf506 --- /dev/null +++ b/core/include/sysparam.h @@ -0,0 +1,42 @@ +#ifndef _SYSPARAM_H_ +#define _SYSPARAM_H_ + +#include + +#ifndef SYSPARAM_REGION_SECTORS +// Number of (4K) sectors that make up a sysparam region. Total sysparam data +// cannot be larger than this. Note that the actual amount of used flash +// space will be *twice* this amount. +#define SYSPARAM_REGION_SECTORS 1 +#endif + +typedef struct { + char *key; + uint8_t *value; + size_t key_len; + size_t value_len; + size_t bufsize; + struct sysparam_context *ctx; +} sysparam_iter_t; + +typedef enum { + SYSPARAM_ERR_NOMEM = -5, + SYSPARAM_ERR_BADDATA = -4, + SYSPARAM_ERR_IO = -3, + SYSPARAM_ERR_FULL = -2, + SYSPARAM_ERR_BADARGS = -1, + SYSPARAM_OK = 0, + SYSPARAM_NOTFOUND = 1, +} sysparam_status_t; + +sysparam_status_t sysparam_init(uint32_t base_addr, bool create); +sysparam_status_t sysparam_get_raw(const char *key, uint8_t **destptr, size_t *actual_length); +sysparam_status_t sysparam_get_string(const char *key, char **destptr); +sysparam_status_t sysparam_get_raw_static(const char *key, uint8_t *buffer, size_t buffer_size, size_t *actual_length); +sysparam_status_t sysparam_put_raw(const char *key, const uint8_t *value, size_t value_len); +sysparam_status_t sysparam_put_string(const char *key, const char *value); +sysparam_status_t sysparam_iter_start(sysparam_iter_t *iter); +sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter); +void sysparam_iter_end(sysparam_iter_t *iter); + +#endif /* _SYSPARAM_H_ */ diff --git a/core/sysparam.c b/core/sysparam.c new file mode 100644 index 0000000..65bfda5 --- /dev/null +++ b/core/sysparam.c @@ -0,0 +1,616 @@ +#include +#include +#include +#include + +//TODO: make this properly threadsafe + +#define SYSPARAM_MAGIC 0x70524f45 // "EORp" in little-endian +#define SYSPARAM_REGION_HEADER_SIZE 4 +#define ENTRY_OVERHEAD 4 +#define DEFAULT_ITER_BUF_SIZE 64 +#define MAX_KEY_ID 0x7e + +typedef struct sysparam_context { + uint32_t key_addr; + uint32_t value_addr; + uint32_t end_addr; + size_t key_len; + size_t value_len; + size_t compactable; + uint8_t key_id; + uint8_t max_key_id; + bool length_error; +} sysparam_context_t; + +//#define CHECK_FLASH_OP(x) if ((x) != SPI_FLASH_RESULT_OK) return SYSPARAM_ERR_IO; +#include +#define CHECK_FLASH_OP(x) do { int __x = (x); if ((__x) != SPI_FLASH_RESULT_OK) { printf("FLASH ERR: %s: %d\n", #x, __x); return SYSPARAM_ERR_IO; } } while (0); + +/* Internal data structures */ + +static struct { + uint32_t cur_addr; + uint32_t alt_addr; + size_t region_size; +} sysparam_info; + +/* Internal routines */ + +#define max(x, y) ((x) > (y) ? (x) : (y)) +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static sysparam_status_t format_region(uint32_t addr) { + uint16_t sector = addr / sdk_flashchip.sector_size; + int i; + + for (i = 0; i < SYSPARAM_REGION_SECTORS; i++) { + CHECK_FLASH_OP(sdk_spi_flash_erase_sector(sector + i)); + } + return SYSPARAM_OK; +} + +static sysparam_status_t write_region_header(uint32_t addr) { + uint32_t magic = SYSPARAM_MAGIC; + CHECK_FLASH_OP(sdk_spi_flash_write(addr, &magic, 4)); + return SYSPARAM_OK; +} + +static void init_context(sysparam_context_t *ctx) { + memset(ctx, 0, sizeof(*ctx)); + ctx->key_addr = sysparam_info.cur_addr + SYSPARAM_REGION_HEADER_SIZE; +} + +// Scan through the region to find the location of the key entry matching the +// specified name. +static sysparam_status_t find_key(sysparam_context_t *ctx, const char *key, size_t key_len, uint8_t *buffer) { + size_t payload_len; + uint8_t entry_id; + + // Are we already at the end? + if (ctx->key_addr == ctx->end_addr) return SYSPARAM_NOTFOUND; + + while (ctx->key_addr < sysparam_info.cur_addr + sysparam_info.region_size - 2) { + if (ctx->length_error) { + // Our last entry's length fields didn't match, which means we have + // no idea whether we're in the right place anymore. Can't + // continue. + //FIXME: print an error + return SYSPARAM_ERR_BADDATA; + } + if (ctx->key_len) { + ctx->key_addr += ctx->key_len + ENTRY_OVERHEAD; + } + + CHECK_FLASH_OP(sdk_spi_flash_read(ctx->key_addr, buffer, 2)); + payload_len = buffer[0]; + entry_id = buffer[1]; + ctx->key_len = payload_len; + + CHECK_FLASH_OP(sdk_spi_flash_read(ctx->key_addr + payload_len + 2, buffer, 2)); + // checksum = buffer[0]; //FIXME + if (buffer[1] != payload_len) { + ctx->length_error = true; + } + + if (entry_id == 0xff) { + // End of entries + break; + } else if (!entry_id) { + // Deleted entry + } else if (!(entry_id & 0x80)) { + // Key definition + ctx->max_key_id = max(ctx->max_key_id, entry_id); + if (!key) { + ctx->key_id = entry_id; + return SYSPARAM_OK; + } + if (payload_len == key_len) { + CHECK_FLASH_OP(sdk_spi_flash_read(ctx->key_addr + 2, buffer, key_len)); + //FIXME: check checksum + // If checksum checks out, clear length_error + if (!memcmp(key, buffer, key_len)) { + ctx->key_id = entry_id; + return SYSPARAM_OK; + } + } + } + } + ctx->end_addr = ctx->key_addr; + ctx->key_len = 0; + return SYSPARAM_NOTFOUND; +} + +// Scan through the region to find the location of the value entry matching the +// key found by `find_key` +static sysparam_status_t find_value(sysparam_context_t *ctx) { + uint32_t addr = ctx->key_addr; + size_t payload_len = ctx->key_len; + bool length_error = ctx->length_error; + uint8_t value_id = ctx->key_id | 0x80; + uint8_t entry_id; + uint8_t buffer[2]; + + while (addr < sysparam_info.cur_addr + sysparam_info.region_size - 2) { + if (length_error) { + // Our last entry's length fields didn't match, which means we have + // no idea whether we're in the right place anymore. Can't + // continue. + //FIXME: print an error + return SYSPARAM_ERR_BADDATA; + } + addr += payload_len + ENTRY_OVERHEAD; + + CHECK_FLASH_OP(sdk_spi_flash_read(addr, buffer, 2)); + payload_len = buffer[0]; + entry_id = buffer[1]; + + CHECK_FLASH_OP(sdk_spi_flash_read(addr + payload_len + 2, buffer, 2)); + //checksum = buffer[0]; //FIXME + if (buffer[1] != payload_len) { + length_error = true; + } + + if (entry_id == 0xff) { + // End of entries + break; + } else if (!entry_id) { + // Deleted entry + } else if (!(entry_id & 0x80)) { + // Key entry. Make sure we update max_key_id. + ctx->max_key_id = max(ctx->max_key_id, entry_id); + } else if (entry_id == value_id) { + // We found our value + ctx->value_addr = addr; + ctx->value_len = payload_len; + return SYSPARAM_OK; + } + } + ctx->end_addr = addr; + ctx->value_len = 0; + return SYSPARAM_NOTFOUND; +} + +// Scan through any remaining data in the region until we get to the end, +// updating ctx as we go. +// NOTE: This assumes you've already called `find_key`/`find_value` (if you +// care about those things). It only looks for the end. +static sysparam_status_t find_end(sysparam_context_t *ctx) { + uint32_t addr; + size_t payload_len; + bool length_error = ctx->length_error; + uint8_t entry_id; + uint8_t buffer[2]; + + if (ctx->end_addr) { + return SYSPARAM_OK; + } + if (ctx->value_addr) { + addr = ctx->value_addr; + payload_len = ctx->value_len; + } else { + addr = ctx->key_addr; + payload_len = ctx->key_len; + } + while (addr < sysparam_info.cur_addr + sysparam_info.region_size - 2) { + if (length_error) { + // Our last entry's length fields didn't match, which means we have + // no idea whether we're in the right place anymore. Can't + // continue. + //FIXME: print an error + return SYSPARAM_ERR_BADDATA; + } + addr += payload_len + ENTRY_OVERHEAD; + + CHECK_FLASH_OP(sdk_spi_flash_read(addr, buffer, 2)); + payload_len = buffer[0]; + entry_id = buffer[1]; + + CHECK_FLASH_OP(sdk_spi_flash_read(addr + payload_len + 2, buffer, 2)); + //checksum = buffer[0]; //FIXME + if (buffer[1] != payload_len) { + length_error = true; + } + + if (entry_id == 0xff) { + // End of entries + break; + } else if (!entry_id) { + // Deleted entry + } else if (!(entry_id & 0x80)) { + // Key entry. Make sure we update max_key_id. + ctx->max_key_id = max(ctx->max_key_id, entry_id); + } + } + ctx->end_addr = addr; + ctx->value_len = 0; + return SYSPARAM_OK; +} + +static sysparam_status_t read_key(sysparam_context_t *ctx, char *buffer, size_t buffer_size) { + if (!ctx->key_len) { + return SYSPARAM_NOTFOUND; + } + CHECK_FLASH_OP(sdk_spi_flash_read(ctx->key_addr + 2, buffer, min(buffer_size, ctx->key_len))); + //FIXME: check checksum + return SYSPARAM_OK; +} + +static sysparam_status_t read_value(sysparam_context_t *ctx, uint8_t *buffer, size_t buffer_size) { + if (!ctx->value_len) { + return SYSPARAM_NOTFOUND; + } + CHECK_FLASH_OP(sdk_spi_flash_read(ctx->value_addr + 2, buffer, min(buffer_size, ctx->value_len))); + //FIXME: check checksum + return SYSPARAM_OK; +} + +static inline sysparam_status_t write_entry(uint32_t addr, uint8_t id, const uint8_t *payload, size_t payload_len) { + uint8_t buffer[2]; + + buffer[0] = payload_len; + buffer[1] = id; + CHECK_FLASH_OP(sdk_spi_flash_write(addr, buffer, 2)); + CHECK_FLASH_OP(sdk_spi_flash_write(addr + 2, payload, payload_len)); + buffer[0] = 0; //FIXME: calculate checksum + buffer[1] = payload_len; + CHECK_FLASH_OP(sdk_spi_flash_write(addr + 2 + payload_len, buffer, 2)); + + return SYSPARAM_OK; +} + +static sysparam_status_t compact_params(sysparam_context_t *ctx, bool delete_current_value) { + uint32_t new_base = sysparam_info.alt_addr; + sysparam_status_t status; + uint32_t addr = new_base + SYSPARAM_REGION_HEADER_SIZE; + uint8_t current_key_id = 0; + uint32_t zero = 0; + sysparam_iter_t iter; + + status = format_region(new_base); + if (status < 0) return status; + status = sysparam_iter_start(&iter); + if (status < 0) return status; + + while (true) { + status = sysparam_iter_next(&iter); + if (status < 0) break; + + current_key_id++; + + if (iter.ctx->key_addr == ctx->key_addr) { + ctx->key_addr = addr; + } + if (iter.ctx->key_id == ctx->key_id) { + ctx->key_id = current_key_id; + } + + // Write the key to the new region + status = write_entry(addr, current_key_id, (uint8_t *)iter.key, iter.key_len); + if (status < 0) break; + addr += iter.key_len + ENTRY_OVERHEAD; + + if (iter.ctx->value_addr == ctx->value_addr) { + if (delete_current_value) { + // Don't copy the old value, since we'll just be deleting it + // and writing a new one as soon as we return. + ctx->value_addr = 0; + ctx->value_len = 0; + continue; + } else { + ctx->value_addr = addr; + } + } + // Copy the value to the new region + status = write_entry(addr, current_key_id | 0x80, iter.value, iter.value_len); + if (status < 0) break; + addr += iter.value_len + ENTRY_OVERHEAD; + } + sysparam_iter_end(&iter); + + // If we broke out with an error, return the error instead of continuing. + if (status < 0) return status; + + // Fix up all the remaining bits of ctx to have correct values for the new + // (compacted) region. + ctx->end_addr = addr; + ctx->compactable = 0; + ctx->max_key_id = current_key_id; + ctx->length_error = false; + + // Switch to officially using the new region. + status = write_region_header(new_base); + if (status < 0) return status; + sysparam_info.alt_addr = sysparam_info.cur_addr; + sysparam_info.cur_addr = new_base; + // Zero out the old header, so future calls to sysparam_init() will know + // that the new one is the correct region to be looking at. + CHECK_FLASH_OP(sdk_spi_flash_write(sysparam_info.alt_addr, &zero, 4)); + + return SYSPARAM_OK; +} + +/* Public Functions */ + +sysparam_status_t sysparam_init(uint32_t base_addr, bool create) { + sysparam_status_t status; + size_t region_size = SYSPARAM_REGION_SECTORS * sdk_flashchip.sector_size; + uint32_t magic; + int i; + + sysparam_info.region_size = region_size; + for (i = 0; i < 2; i++) { + CHECK_FLASH_OP(sdk_spi_flash_read(base_addr + (i * region_size), &magic, 4)); + if (magic == SYSPARAM_MAGIC) { + sysparam_info.cur_addr = base_addr + i * region_size; + sysparam_info.alt_addr = base_addr + (i ^ 1) * region_size; + return SYSPARAM_OK; + } + } + // We couldn't find one already present, so create a new one at the + // specified address (if `create` is set). + if (create) { + sysparam_info.cur_addr = base_addr; + sysparam_info.alt_addr = base_addr + region_size; + status = format_region(base_addr); + if (status != SYSPARAM_OK) return status; + return write_region_header(base_addr); + } else { + return SYSPARAM_NOTFOUND; + } +} + +sysparam_status_t sysparam_get_raw(const char *key, uint8_t **destptr, size_t *actual_length) { + sysparam_context_t ctx; + sysparam_status_t status; + size_t key_len = strlen(key); + uint8_t *buffer = malloc(key_len + 2); + + if (!buffer) return SYSPARAM_ERR_NOMEM; + do { + init_context(&ctx); + status = find_key(&ctx, key, key_len, buffer); + if (status != SYSPARAM_OK) break; + + status = find_value(&ctx); + if (status != SYSPARAM_OK) break; + + buffer = realloc(buffer, ctx.value_len + 1); + if (!buffer) { + return SYSPARAM_ERR_NOMEM; + } + status = read_value(&ctx, buffer, ctx.value_len); + if (status != SYSPARAM_OK) break; + + // Zero-terminate the result, just in case (doesn't hurt anything for + // non-string data, and can avoid nasty mistakes if the caller wants to + // interpret the result as a string). + buffer[ctx.value_len] = 0; + + *destptr = buffer; + if (actual_length) *actual_length = ctx.value_len; + return SYSPARAM_OK; + } while (false); + + free(buffer); + *destptr = NULL; + if (actual_length) *actual_length = 0; + return status; +} + +sysparam_status_t sysparam_get_string(const char *key, char **destptr) { + // `sysparam_get_raw` will zero-terminate the result as a matter of course, + // so no need to do that here. + return sysparam_get_raw(key, (uint8_t **)destptr, NULL); +} + +sysparam_status_t sysparam_get_raw_static(const char *key, uint8_t *buffer, size_t buffer_size, size_t *actual_length) { + sysparam_context_t ctx; + sysparam_status_t status = SYSPARAM_OK; + size_t key_len = strlen(key); + + // Supplied buffer must be at least as large as the key, or 2 bytes, + // whichever is larger. + if (buffer_size < max(key_len, 2)) return SYSPARAM_ERR_NOMEM; + + if (actual_length) *actual_length = 0; + + init_context(&ctx); + status = find_key(&ctx, key, key_len, buffer); + if (status != SYSPARAM_OK) return status; + status = find_value(&ctx); + if (status != SYSPARAM_OK) return status; + status = read_value(&ctx, buffer, buffer_size); + if (status != SYSPARAM_OK) return status; + + if (actual_length) *actual_length = ctx.value_len; + return SYSPARAM_OK; +} + +sysparam_status_t sysparam_put_raw(const char *key, const uint8_t *value, size_t value_len) { + sysparam_context_t ctx; + sysparam_status_t status = SYSPARAM_OK; + size_t key_len = strlen(key); + uint8_t *buffer; + size_t free_space; + size_t needed_space; + bool free_value = false; + + if (!key_len) return SYSPARAM_ERR_BADARGS; + if (value_len && (value & 0x3)) { + // The passed value isn't word-aligned. This will be a problem later + // when calling `sdk_spi_flash_write`, so make a word-aligned copy. + buffer = malloc(value_len); + if (!buffer) return SYSPARAM_ERR_NOMEM; + memcpy(buffer, value, value_len); + value = buffer; + free_value = true; + } + // Create a working buffer for `find_key` to use. + buffer = malloc(key_len); + if (!buffer) { + if (free_value) free(value); + return SYSPARAM_ERR_NOMEM; + } + + + do { + init_context(&ctx); + status = find_key(&ctx, key, key_len, buffer); + if (status == SYSPARAM_OK) { + // Key already exists, see if there's a current value. + status = find_value(&ctx); + } + if (status < 0) break; + + if (value_len) { + if (ctx.value_len == value_len) { + // Are we trying to write the same value that's already there? + if (value_len > key_len) { + buffer = realloc(buffer, value_len); + if (!buffer) return SYSPARAM_ERR_NOMEM; + } + status = read_value(&ctx, buffer, value_len); + if (status < 0) break; + if (!memcmp(buffer, value, value_len)) { + // Yup! No need to do anything further, just leave the + // current value as-is. + status = SYSPARAM_OK; + break; + } + } + + // Write the new/updated value + status = find_end(&ctx); + if (status < 0) break; + + // Since we will be deleting the old value (if any) make sure that + // the compactable count includes the space taken up by that entry + // too (even though it's not actually deleted yet) + if (ctx.value_addr) { + ctx.compactable += ctx.value_len + ENTRY_OVERHEAD; + } + + // Append new value to the end, but first make sure we have enough + // space. + free_space = sysparam_info.region_size - (ctx.end_addr - sysparam_info.cur_addr); + needed_space = ENTRY_OVERHEAD + value_len; + if (!ctx.key_id) { + // We did not find a previous key entry matching this key. We will + // need to add a key entry as well. + key_len = strlen(key); + needed_space += ENTRY_OVERHEAD + key_len; + } + if (needed_space > free_space) { + if (needed_space > free_space + ctx.compactable) { + // Nothing we can do here.. We're full. + // (at least full enough that compacting won't help us store + // this value) + //FIXME: debug message + status = SYSPARAM_ERR_FULL; + break; + } + // We should have enough space after we compact things. + status = compact_params(&ctx, true); + if (status < 0) break; + } + + if (!ctx.key_id) { + // Write key entry for new key + ctx.key_id = ctx.max_key_id + 1; + if (ctx.key_id > MAX_KEY_ID) { + status = SYSPARAM_ERR_FULL; + break; + } + status = write_entry(ctx.end_addr, ctx.key_id, (uint8_t *)key, key_len); + if (status < 0) break; + ctx.end_addr += key_len + ENTRY_OVERHEAD; + } + + // Write new value + status = write_entry(ctx.end_addr, ctx.key_id | 0x80, value, value_len); + if (status < 0) break; + } + + // Delete old value (if present) by setting it's id to 0x00 + if (ctx.value_addr) { + buffer[0] = 0; + if (sdk_spi_flash_write(ctx.value_addr + 1, buffer, 1) != SPI_FLASH_RESULT_OK) { + status = SYSPARAM_ERR_IO; + break; + } + } + } while (false); + + if (free_value) free(value); + free(buffer); + return status; +} + +sysparam_status_t sysparam_put_string(const char *key, const char *value) { + return sysparam_put_raw(key, (const uint8_t *)value, strlen(value)); +} + +sysparam_status_t sysparam_iter_start(sysparam_iter_t *iter) { + iter->bufsize = DEFAULT_ITER_BUF_SIZE; + iter->key = malloc(iter->bufsize); + if (!iter->key) { + iter->bufsize = 0; + return SYSPARAM_ERR_NOMEM; + } + iter->key_len = 0; + iter->value_len = 0; + iter->ctx = malloc(sizeof(sysparam_context_t)); + if (!iter->ctx) { + free(iter->key); + iter->bufsize = 0; + return SYSPARAM_ERR_NOMEM; + } + init_context(iter->ctx); + + return SYSPARAM_OK; +} + +sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter) { + uint8_t buffer[2]; + sysparam_status_t status; + size_t required_len; + + while (true) { + status = find_key(iter->ctx, NULL, 0, buffer); + if (status != 0) return status; + status = find_value(iter->ctx); + if (status < 0) return status; + if (status == SYSPARAM_NOTFOUND) continue; + required_len = iter->ctx->key_len + 1 + iter->ctx->value_len + 1; + if (required_len < iter->bufsize) { + iter->key = realloc(iter->key, required_len); + if (!iter->key) { + iter->bufsize = 0; + return SYSPARAM_ERR_NOMEM; + } + iter->bufsize = required_len; + } + + status = read_key(iter->ctx, iter->key, iter->bufsize); + if (status < 0) return status; + // Null-terminate the key + iter->key[iter->ctx->key_len] = 0; + iter->key_len = iter->ctx->key_len; + + iter->value = (uint8_t *)iter->key + iter->ctx->key_len + 1; + status = read_value(iter->ctx, iter->value, iter->bufsize - iter->ctx->key_len - 1); + if (status < 0) return status; + // Null-terminate the value (just in case) + iter->value[iter->ctx->value_len] = 0; + iter->value_len = iter->ctx->value_len; + + return SYSPARAM_OK; + } +} + +void sysparam_iter_end(sysparam_iter_t *iter) { + if (iter->key) free(iter->key); + if (iter->ctx) free(iter->ctx); +} + From ae16299f4a031f5942b70acd742f6b04d80b36b3 Mon Sep 17 00:00:00 2001 From: Alex Stewart Date: Mon, 7 Mar 2016 09:14:14 -0800 Subject: [PATCH 02/13] sysparam improvements Mostly done, a few minor cleanups left. --- core/include/sysparam.h | 37 +- core/sysparam.c | 894 +++++++++++++++++++++++----------------- 2 files changed, 538 insertions(+), 393 deletions(-) diff --git a/core/include/sysparam.h b/core/include/sysparam.h index 08bf506..6f158ee 100644 --- a/core/include/sysparam.h +++ b/core/include/sysparam.h @@ -10,6 +10,18 @@ #define SYSPARAM_REGION_SECTORS 1 #endif +typedef enum { + SYSPARAM_ERR_NOMEM = -6, + SYSPARAM_ERR_CORRUPT = -5, + SYSPARAM_ERR_IO = -4, + SYSPARAM_ERR_FULL = -3, + SYSPARAM_ERR_BADVALUE = -2, + SYSPARAM_ERR_NOINIT = -1, + SYSPARAM_OK = 0, + SYSPARAM_NOTFOUND = 1, + SYSPARAM_PARSEFAILED = 2, +} sysparam_status_t; + typedef struct { char *key; uint8_t *value; @@ -19,22 +31,17 @@ typedef struct { struct sysparam_context *ctx; } sysparam_iter_t; -typedef enum { - SYSPARAM_ERR_NOMEM = -5, - SYSPARAM_ERR_BADDATA = -4, - SYSPARAM_ERR_IO = -3, - SYSPARAM_ERR_FULL = -2, - SYSPARAM_ERR_BADARGS = -1, - SYSPARAM_OK = 0, - SYSPARAM_NOTFOUND = 1, -} sysparam_status_t; - -sysparam_status_t sysparam_init(uint32_t base_addr, bool create); -sysparam_status_t sysparam_get_raw(const char *key, uint8_t **destptr, size_t *actual_length); +sysparam_status_t sysparam_init(uint32_t base_addr); +sysparam_status_t sysparam_create_area(uint32_t base_addr, bool force); +sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t *actual_length); +sysparam_status_t sysparam_get_data_static(const char *key, uint8_t *buffer, size_t buffer_size, size_t *actual_length); sysparam_status_t sysparam_get_string(const char *key, char **destptr); -sysparam_status_t sysparam_get_raw_static(const char *key, uint8_t *buffer, size_t buffer_size, size_t *actual_length); -sysparam_status_t sysparam_put_raw(const char *key, const uint8_t *value, size_t value_len); -sysparam_status_t sysparam_put_string(const char *key, const char *value); +sysparam_status_t sysparam_get_int(const char *key, int32_t *result); +sysparam_status_t sysparam_get_bool(const char *key, bool *result); +sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_t value_len); +sysparam_status_t sysparam_set_string(const char *key, const char *value); +sysparam_status_t sysparam_set_int(const char *key, int32_t value); +sysparam_status_t sysparam_set_bool(const char *key, bool value); sysparam_status_t sysparam_iter_start(sysparam_iter_t *iter); sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter); void sysparam_iter_end(sysparam_iter_t *iter); diff --git a/core/sysparam.c b/core/sysparam.c index 65bfda5..d6fd4b1 100644 --- a/core/sysparam.c +++ b/core/sysparam.c @@ -1,37 +1,54 @@ #include #include +#include #include #include //TODO: make this properly threadsafe +//FIXME: reduce stack usage +//TODO: make stderr work for debug output + +#ifndef SYSPARAM_DEBUG +#define SYSPARAM_DEBUG 0 +#endif + +#define debug(level, format, ...) if (SYSPARAM_DEBUG >= (level)) { printf("%s" format "\n", "sysparam: ", ## __VA_ARGS__); } #define SYSPARAM_MAGIC 0x70524f45 // "EORp" in little-endian -#define SYSPARAM_REGION_HEADER_SIZE 4 -#define ENTRY_OVERHEAD 4 +#define SYSPARAM_STALEMAGIC 0x40524f45 // "EOR@" in little-endian +#define REGION_HEADER_SIZE 4 // NOTE: Must be multiple of 4 +#define ENTRY_HEADER_SIZE 4 // NOTE: Must be multiple of 4 #define DEFAULT_ITER_BUF_SIZE 64 #define MAX_KEY_ID 0x7e +#define SCAN_BUFFER_SIZE 8 // words -typedef struct sysparam_context { - uint32_t key_addr; - uint32_t value_addr; - uint32_t end_addr; - size_t key_len; - size_t value_len; +#define ROUND_TO_WORD_BOUNDARY(x) (((x) + 3) & 0xfffffffc) +#define ENTRY_SIZE(payload_len) (ENTRY_HEADER_SIZE + ROUND_TO_WORD_BOUNDARY(payload_len)) + +#define CHECK_FLASH_OP(x) do { int __x = (x); if ((__x) != SPI_FLASH_RESULT_OK) { debug(1, "FLASH ERR: %d", __x); return SYSPARAM_ERR_IO; } } while (0); + + +struct entry_header { + uint8_t prev_len; + uint8_t len; + uint8_t id; + uint8_t crc; +} __attribute__ ((packed)); + +struct sysparam_context { + uint32_t addr; + struct entry_header entry; + uint64_t unused_keys[2]; size_t compactable; - uint8_t key_id; uint8_t max_key_id; - bool length_error; -} sysparam_context_t; - -//#define CHECK_FLASH_OP(x) if ((x) != SPI_FLASH_RESULT_OK) return SYSPARAM_ERR_IO; -#include -#define CHECK_FLASH_OP(x) do { int __x = (x); if ((__x) != SPI_FLASH_RESULT_OK) { printf("FLASH ERR: %s: %d\n", #x, __x); return SYSPARAM_ERR_IO; } } while (0); +}; /* Internal data structures */ -static struct { - uint32_t cur_addr; - uint32_t alt_addr; +struct { + uint32_t cur_base; + uint32_t alt_base; + uint32_t end_addr; size_t region_size; } sysparam_info; @@ -50,223 +67,156 @@ static sysparam_status_t format_region(uint32_t addr) { return SYSPARAM_OK; } -static sysparam_status_t write_region_header(uint32_t addr) { - uint32_t magic = SYSPARAM_MAGIC; +static inline sysparam_status_t write_region_header(uint32_t addr, bool active) { + uint32_t magic = active ? SYSPARAM_MAGIC : SYSPARAM_STALEMAGIC; + debug(3, "write region header (0x%08x) @ 0x%08x", magic, addr); CHECK_FLASH_OP(sdk_spi_flash_write(addr, &magic, 4)); return SYSPARAM_OK; } -static void init_context(sysparam_context_t *ctx) { +static void init_context(struct sysparam_context *ctx) { memset(ctx, 0, sizeof(*ctx)); - ctx->key_addr = sysparam_info.cur_addr + SYSPARAM_REGION_HEADER_SIZE; + ctx->addr = sysparam_info.cur_base; +} + +static sysparam_status_t init_write_context(struct sysparam_context *ctx) { + memset(ctx, 0, sizeof(*ctx)); + ctx->addr = sysparam_info.end_addr; + debug(3, "read entry header @ 0x%08x", ctx->addr); + CHECK_FLASH_OP(sdk_spi_flash_read(ctx->addr, &ctx->entry, ENTRY_HEADER_SIZE)); + return SYSPARAM_OK; +} + +static sysparam_status_t find_entry(struct sysparam_context *ctx, uint8_t match_id) { + uint8_t prev_len; + + while (ctx->addr + ENTRY_SIZE(ctx->entry.len) < sysparam_info.end_addr) { + prev_len = ctx->entry.len; + ctx->addr += ENTRY_SIZE(ctx->entry.len); + + debug(3, "read entry header @ 0x%08x", ctx->addr); + CHECK_FLASH_OP(sdk_spi_flash_read(ctx->addr, &ctx->entry, ENTRY_HEADER_SIZE)); + if (ctx->entry.prev_len != prev_len) { + // Uh oh.. This should match the entry.len field from the + // previous entry. If it doesn't, it means that field may have + // been corrupted and we don't even know if we're in the right + // place anymore. We have to bail out. + debug(1, "prev_len mismatch at 0x%08x (%d != %d)", ctx->addr, ctx->entry.prev_len, prev_len); + ctx->addr = sysparam_info.end_addr; + return SYSPARAM_ERR_CORRUPT; + } + + if (ctx->entry.id) { + if (!(ctx->entry.id & 0x80)) { + // Key definition + ctx->max_key_id = ctx->entry.id; + ctx->unused_keys[ctx->entry.id >> 6] |= (1 << (ctx->entry.id & 0x3f)); + if (!match_id) { + // We're looking for any key, so make this a matching key. + match_id = ctx->entry.id; + } + } else { + // Value entry + ctx->unused_keys[(ctx->entry.id >> 6) & 1] &= ~(1 << (ctx->entry.id & 0x3f)); + } + if (ctx->entry.id == match_id) { + return SYSPARAM_OK; + } + } else { + // Deleted entry + ctx->compactable += ENTRY_SIZE(ctx->entry.len); + } + } + ctx->entry.len = 0; + ctx->entry.id = 0; + return SYSPARAM_NOTFOUND; +} + +static inline sysparam_status_t read_payload(struct sysparam_context *ctx, uint8_t *buffer, size_t buffer_size) { + debug(3, "read payload (%d) @ 0x%08x", min(buffer_size, ctx->entry.len), ctx->addr); + CHECK_FLASH_OP(sdk_spi_flash_read(ctx->addr + ENTRY_HEADER_SIZE, buffer, min(buffer_size, ctx->entry.len))); + //FIXME: check crc + return SYSPARAM_OK; } // Scan through the region to find the location of the key entry matching the // specified name. -static sysparam_status_t find_key(sysparam_context_t *ctx, const char *key, size_t key_len, uint8_t *buffer) { - size_t payload_len; - uint8_t entry_id; - - // Are we already at the end? - if (ctx->key_addr == ctx->end_addr) return SYSPARAM_NOTFOUND; - - while (ctx->key_addr < sysparam_info.cur_addr + sysparam_info.region_size - 2) { - if (ctx->length_error) { - // Our last entry's length fields didn't match, which means we have - // no idea whether we're in the right place anymore. Can't - // continue. - //FIXME: print an error - return SYSPARAM_ERR_BADDATA; - } - if (ctx->key_len) { - ctx->key_addr += ctx->key_len + ENTRY_OVERHEAD; - } - - CHECK_FLASH_OP(sdk_spi_flash_read(ctx->key_addr, buffer, 2)); - payload_len = buffer[0]; - entry_id = buffer[1]; - ctx->key_len = payload_len; - - CHECK_FLASH_OP(sdk_spi_flash_read(ctx->key_addr + payload_len + 2, buffer, 2)); - // checksum = buffer[0]; //FIXME - if (buffer[1] != payload_len) { - ctx->length_error = true; - } - - if (entry_id == 0xff) { - // End of entries - break; - } else if (!entry_id) { - // Deleted entry - } else if (!(entry_id & 0x80)) { - // Key definition - ctx->max_key_id = max(ctx->max_key_id, entry_id); - if (!key) { - ctx->key_id = entry_id; - return SYSPARAM_OK; - } - if (payload_len == key_len) { - CHECK_FLASH_OP(sdk_spi_flash_read(ctx->key_addr + 2, buffer, key_len)); - //FIXME: check checksum - // If checksum checks out, clear length_error - if (!memcmp(key, buffer, key_len)) { - ctx->key_id = entry_id; - return SYSPARAM_OK; - } - } - } - } - ctx->end_addr = ctx->key_addr; - ctx->key_len = 0; - return SYSPARAM_NOTFOUND; -} - -// Scan through the region to find the location of the value entry matching the -// key found by `find_key` -static sysparam_status_t find_value(sysparam_context_t *ctx) { - uint32_t addr = ctx->key_addr; - size_t payload_len = ctx->key_len; - bool length_error = ctx->length_error; - uint8_t value_id = ctx->key_id | 0x80; - uint8_t entry_id; - uint8_t buffer[2]; - - while (addr < sysparam_info.cur_addr + sysparam_info.region_size - 2) { - if (length_error) { - // Our last entry's length fields didn't match, which means we have - // no idea whether we're in the right place anymore. Can't - // continue. - //FIXME: print an error - return SYSPARAM_ERR_BADDATA; - } - addr += payload_len + ENTRY_OVERHEAD; - - CHECK_FLASH_OP(sdk_spi_flash_read(addr, buffer, 2)); - payload_len = buffer[0]; - entry_id = buffer[1]; - - CHECK_FLASH_OP(sdk_spi_flash_read(addr + payload_len + 2, buffer, 2)); - //checksum = buffer[0]; //FIXME - if (buffer[1] != payload_len) { - length_error = true; - } - - if (entry_id == 0xff) { - // End of entries - break; - } else if (!entry_id) { - // Deleted entry - } else if (!(entry_id & 0x80)) { - // Key entry. Make sure we update max_key_id. - ctx->max_key_id = max(ctx->max_key_id, entry_id); - } else if (entry_id == value_id) { - // We found our value - ctx->value_addr = addr; - ctx->value_len = payload_len; - return SYSPARAM_OK; - } - } - ctx->end_addr = addr; - ctx->value_len = 0; - return SYSPARAM_NOTFOUND; -} - -// Scan through any remaining data in the region until we get to the end, -// updating ctx as we go. -// NOTE: This assumes you've already called `find_key`/`find_value` (if you -// care about those things). It only looks for the end. -static sysparam_status_t find_end(sysparam_context_t *ctx) { - uint32_t addr; - size_t payload_len; - bool length_error = ctx->length_error; - uint8_t entry_id; - uint8_t buffer[2]; - - if (ctx->end_addr) { - return SYSPARAM_OK; - } - if (ctx->value_addr) { - addr = ctx->value_addr; - payload_len = ctx->value_len; - } else { - addr = ctx->key_addr; - payload_len = ctx->key_len; - } - while (addr < sysparam_info.cur_addr + sysparam_info.region_size - 2) { - if (length_error) { - // Our last entry's length fields didn't match, which means we have - // no idea whether we're in the right place anymore. Can't - // continue. - //FIXME: print an error - return SYSPARAM_ERR_BADDATA; - } - addr += payload_len + ENTRY_OVERHEAD; - - CHECK_FLASH_OP(sdk_spi_flash_read(addr, buffer, 2)); - payload_len = buffer[0]; - entry_id = buffer[1]; - - CHECK_FLASH_OP(sdk_spi_flash_read(addr + payload_len + 2, buffer, 2)); - //checksum = buffer[0]; //FIXME - if (buffer[1] != payload_len) { - length_error = true; - } - - if (entry_id == 0xff) { - // End of entries - break; - } else if (!entry_id) { - // Deleted entry - } else if (!(entry_id & 0x80)) { - // Key entry. Make sure we update max_key_id. - ctx->max_key_id = max(ctx->max_key_id, entry_id); - } - } - ctx->end_addr = addr; - ctx->value_len = 0; - return SYSPARAM_OK; -} - -static sysparam_status_t read_key(sysparam_context_t *ctx, char *buffer, size_t buffer_size) { - if (!ctx->key_len) { - return SYSPARAM_NOTFOUND; - } - CHECK_FLASH_OP(sdk_spi_flash_read(ctx->key_addr + 2, buffer, min(buffer_size, ctx->key_len))); - //FIXME: check checksum - return SYSPARAM_OK; -} - -static sysparam_status_t read_value(sysparam_context_t *ctx, uint8_t *buffer, size_t buffer_size) { - if (!ctx->value_len) { - return SYSPARAM_NOTFOUND; - } - CHECK_FLASH_OP(sdk_spi_flash_read(ctx->value_addr + 2, buffer, min(buffer_size, ctx->value_len))); - //FIXME: check checksum - return SYSPARAM_OK; -} - -static inline sysparam_status_t write_entry(uint32_t addr, uint8_t id, const uint8_t *payload, size_t payload_len) { - uint8_t buffer[2]; - - buffer[0] = payload_len; - buffer[1] = id; - CHECK_FLASH_OP(sdk_spi_flash_write(addr, buffer, 2)); - CHECK_FLASH_OP(sdk_spi_flash_write(addr + 2, payload, payload_len)); - buffer[0] = 0; //FIXME: calculate checksum - buffer[1] = payload_len; - CHECK_FLASH_OP(sdk_spi_flash_write(addr + 2 + payload_len, buffer, 2)); - - return SYSPARAM_OK; -} - -static sysparam_status_t compact_params(sysparam_context_t *ctx, bool delete_current_value) { - uint32_t new_base = sysparam_info.alt_addr; +static sysparam_status_t find_key(struct sysparam_context *ctx, const char *key, uint8_t key_len, uint8_t *buffer) { sysparam_status_t status; - uint32_t addr = new_base + SYSPARAM_REGION_HEADER_SIZE; - uint8_t current_key_id = 0; - uint32_t zero = 0; - sysparam_iter_t iter; + while (true) { + // Find the next key entry + status = find_entry(ctx, 0); + if (status != SYSPARAM_OK) return status; + if (!key) { + // We're looking for the next (any) key, so we're done. + break; + } + if (ctx->entry.len == key_len) { + status = read_payload(ctx, buffer, key_len); + if (status < 0) return status; + if (!memcmp(key, buffer, key_len)) { + // We have a match + break; + } + } + } + + return SYSPARAM_OK; +} + +static inline sysparam_status_t write_entry(uint32_t addr, uint8_t id, const uint8_t *payload, uint8_t len, uint8_t prev_len) { + struct entry_header entry; + + debug(2, "Writing entry 0x%02x @ 0x%08x", id, addr); + entry.prev_len = prev_len; + entry.len = len; + entry.id = id; + entry.crc = 0; //FIXME: calculate crc + debug(3, "write entry header @ 0x%08x", addr); + CHECK_FLASH_OP(sdk_spi_flash_write(addr, &entry, ENTRY_HEADER_SIZE)); + debug(3, "write payload (%d) @ 0x%08x", len, addr + ENTRY_HEADER_SIZE); + CHECK_FLASH_OP(sdk_spi_flash_write(addr + ENTRY_HEADER_SIZE, payload, len)); + + return SYSPARAM_OK; +} + +static inline sysparam_status_t write_entry_tail(uint32_t addr, uint8_t prev_len) { + struct entry_header entry; + + entry.prev_len = prev_len; + entry.len = 0xff; + entry.id = 0xff; + entry.crc = 0xff; + debug(3, "write entry tail @ 0x%08x", addr); + CHECK_FLASH_OP(sdk_spi_flash_write(addr, &entry, ENTRY_HEADER_SIZE)); + + return SYSPARAM_OK; +} + +static inline sysparam_status_t delete_entry(uint32_t addr) { + struct entry_header entry; + + debug(2, "Deleting entry @ 0x%08x", addr); + debug(3, "read entry header @ 0x%08x", addr); + CHECK_FLASH_OP(sdk_spi_flash_read(addr, &entry, ENTRY_HEADER_SIZE)); + // Set the ID to zero to mark it as "deleted" + entry.id = 0x00; + debug(3, "write entry header @ 0x%08x", addr); + CHECK_FLASH_OP(sdk_spi_flash_write(addr, &entry, ENTRY_HEADER_SIZE)); + + return SYSPARAM_OK; +} + +static sysparam_status_t compact_params(struct sysparam_context *ctx, uint8_t *key_id) { + uint32_t new_base = sysparam_info.alt_base; + sysparam_status_t status; + uint32_t addr = new_base + REGION_HEADER_SIZE; + uint8_t current_key_id = 0; + sysparam_iter_t iter; + uint8_t prev_len = 0; + + debug(1, "compacting region (current size %d, expect to recover %d%s bytes)...", sysparam_info.end_addr - sysparam_info.cur_base, ctx->compactable, (ctx->unused_keys[0] || ctx->unused_keys[1]) ? "+ (unused keys present)" : ""); status = format_region(new_base); if (status < 0) return status; status = sysparam_iter_start(&iter); @@ -274,121 +224,191 @@ static sysparam_status_t compact_params(sysparam_context_t *ctx, bool delete_cur while (true) { status = sysparam_iter_next(&iter); - if (status < 0) break; + if (status != SYSPARAM_OK) break; current_key_id++; - if (iter.ctx->key_addr == ctx->key_addr) { - ctx->key_addr = addr; - } - if (iter.ctx->key_id == ctx->key_id) { - ctx->key_id = current_key_id; - } - // Write the key to the new region - status = write_entry(addr, current_key_id, (uint8_t *)iter.key, iter.key_len); + debug(2, "writing %d key @ 0x%08x", current_key_id, addr); + status = write_entry(addr, current_key_id, (uint8_t *)iter.key, iter.key_len, prev_len); if (status < 0) break; - addr += iter.key_len + ENTRY_OVERHEAD; + prev_len = iter.key_len; + addr += ENTRY_SIZE(iter.key_len); - if (iter.ctx->value_addr == ctx->value_addr) { - if (delete_current_value) { - // Don't copy the old value, since we'll just be deleting it - // and writing a new one as soon as we return. - ctx->value_addr = 0; - ctx->value_len = 0; - continue; - } else { - ctx->value_addr = addr; - } + if (iter.ctx->entry.id == *key_id) { + // Update key_id to have the correct id for the compacted result + *key_id = current_key_id; + // Don't copy the old value, since we'll just be deleting it + // and writing a new one as soon as we return. + continue; } + // Copy the value to the new region - status = write_entry(addr, current_key_id | 0x80, iter.value, iter.value_len); + debug(2, "writing %d value @ 0x%08x", current_key_id, addr); + status = write_entry(addr, current_key_id | 0x80, iter.value, iter.value_len, prev_len); if (status < 0) break; - addr += iter.value_len + ENTRY_OVERHEAD; + prev_len = iter.value_len; + addr += ENTRY_SIZE(iter.value_len); } sysparam_iter_end(&iter); - // If we broke out with an error, return the error instead of continuing. - if (status < 0) return status; + if (status >= 0) { + status = write_entry_tail(addr, prev_len); + } - // Fix up all the remaining bits of ctx to have correct values for the new - // (compacted) region. - ctx->end_addr = addr; - ctx->compactable = 0; - ctx->max_key_id = current_key_id; - ctx->length_error = false; + // If we broke out with an error, return the error instead of continuing. + if (status < 0) { + debug(1, "error encountered during compacting (%d)", status); + return status; + } // Switch to officially using the new region. - status = write_region_header(new_base); + status = write_region_header(new_base, true); if (status < 0) return status; - sysparam_info.alt_addr = sysparam_info.cur_addr; - sysparam_info.cur_addr = new_base; - // Zero out the old header, so future calls to sysparam_init() will know - // that the new one is the correct region to be looking at. - CHECK_FLASH_OP(sdk_spi_flash_write(sysparam_info.alt_addr, &zero, 4)); + status = write_region_header(sysparam_info.cur_base, false); + if (status < 0) return status; + + sysparam_info.alt_base = sysparam_info.cur_base; + sysparam_info.cur_base = new_base; + sysparam_info.end_addr = addr; + + // Fix up ctx so it doesn't point to invalid stuff + memset(ctx, 0, sizeof(*ctx)); + ctx->addr = addr; + ctx->entry.prev_len = prev_len; + ctx->max_key_id = current_key_id; + + debug(1, "done compacting (current size %d)", sysparam_info.end_addr - sysparam_info.cur_base); return SYSPARAM_OK; } /* Public Functions */ -sysparam_status_t sysparam_init(uint32_t base_addr, bool create) { +sysparam_status_t sysparam_init(uint32_t base_addr) { sysparam_status_t status; - size_t region_size = SYSPARAM_REGION_SECTORS * sdk_flashchip.sector_size; - uint32_t magic; - int i; + uint32_t magic0, magic1; + struct sysparam_context ctx; - sysparam_info.region_size = region_size; - for (i = 0; i < 2; i++) { - CHECK_FLASH_OP(sdk_spi_flash_read(base_addr + (i * region_size), &magic, 4)); - if (magic == SYSPARAM_MAGIC) { - sysparam_info.cur_addr = base_addr + i * region_size; - sysparam_info.alt_addr = base_addr + (i ^ 1) * region_size; - return SYSPARAM_OK; - } - } - // We couldn't find one already present, so create a new one at the - // specified address (if `create` is set). - if (create) { - sysparam_info.cur_addr = base_addr; - sysparam_info.alt_addr = base_addr + region_size; - status = format_region(base_addr); - if (status != SYSPARAM_OK) return status; - return write_region_header(base_addr); + sysparam_info.region_size = SYSPARAM_REGION_SECTORS * sdk_flashchip.sector_size; + // First, see if we can find an existing one. + debug(3, "read magic @ 0x%08x", base_addr); + CHECK_FLASH_OP(sdk_spi_flash_read(base_addr, &magic0, 4)); + debug(3, "read magic @ 0x%08x", base_addr + sysparam_info.region_size); + CHECK_FLASH_OP(sdk_spi_flash_read(base_addr + sysparam_info.region_size, &magic1, 4)); + if (magic0 == SYSPARAM_MAGIC && magic1 == SYSPARAM_STALEMAGIC) { + // Sysparam area found, first region is active + sysparam_info.cur_base = base_addr; + sysparam_info.alt_base = base_addr + sysparam_info.region_size; + } else if (magic0 == SYSPARAM_STALEMAGIC && magic1 == SYSPARAM_MAGIC) { + // Sysparam area found, second region is active + sysparam_info.cur_base = base_addr + sysparam_info.region_size; + sysparam_info.alt_base = base_addr; + } else if (magic0 == SYSPARAM_MAGIC && magic1 == SYSPARAM_MAGIC) { + // Both regions are marked as active. Not sure which to use. + // This can theoretically happen if something goes wrong at exactly the + // wrong time during compacting. + return SYSPARAM_ERR_CORRUPT; + } else if (magic0 == SYSPARAM_STALEMAGIC && magic1 == SYSPARAM_STALEMAGIC) { + // Both regions are marked as inactive. This shouldn't ever happen. + return SYSPARAM_ERR_CORRUPT; } else { + // Looks like there's something else at that location entirely. return SYSPARAM_NOTFOUND; } + + // Find the actual end + sysparam_info.end_addr = sysparam_info.cur_base + sysparam_info.region_size; + init_context(&ctx); + status = find_entry(&ctx, 0xff); + if (status < 0) { + sysparam_info.cur_base = 0; + sysparam_info.alt_base = 0; + sysparam_info.end_addr = 0; + return status; + } + if (status == SYSPARAM_OK) { + sysparam_info.end_addr = ctx.addr; + } + + return SYSPARAM_OK; } -sysparam_status_t sysparam_get_raw(const char *key, uint8_t **destptr, size_t *actual_length) { - sysparam_context_t ctx; +sysparam_status_t sysparam_create_area(uint32_t base_addr, bool force) { + sysparam_status_t status; + uint32_t buffer[SCAN_BUFFER_SIZE]; + uint32_t addr; + int i; + size_t region_size = SYSPARAM_REGION_SECTORS * sdk_flashchip.sector_size; + + if (!force) { + // First, scan through the area and make sure it's actually empty and + // we're not going to be clobbering something else important. + for (addr = base_addr; addr < base_addr + SYSPARAM_REGION_SECTORS * 2 * sdk_flashchip.sector_size; addr += SCAN_BUFFER_SIZE) { + debug(3, "read %d words @ 0x%08x", SCAN_BUFFER_SIZE, addr); + CHECK_FLASH_OP(sdk_spi_flash_read(addr, buffer, SCAN_BUFFER_SIZE * 4)); + for (i = 0; i < SCAN_BUFFER_SIZE; i++) { + if (buffer[i] != 0xffffffff) { + // Uh oh, not empty. + return SYSPARAM_NOTFOUND; + } + } + } + } + + if (sysparam_info.cur_base == base_addr || sysparam_info.alt_base == base_addr) { + // We're reformating the same region we're already using. + // De-initialize everything to force the caller to do a clean + // `sysparam_init()` afterwards. + memset(&sysparam_info, 0, sizeof(sysparam_info)); + } + status = format_region(base_addr); + if (status < 0) return status; + status = format_region(base_addr + region_size); + if (status < 0) return status; + status = write_entry_tail(base_addr + REGION_HEADER_SIZE, 0); + if (status < 0) return status; + status = write_region_header(base_addr + region_size, false); + if (status < 0) return status; + status = write_region_header(base_addr, true); + if (status < 0) return status; + + return SYSPARAM_OK; +} + +sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t *actual_length) { + struct sysparam_context ctx; sysparam_status_t status; size_t key_len = strlen(key); - uint8_t *buffer = malloc(key_len + 2); + uint8_t *buffer; + + if (!sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; + buffer = malloc(key_len + 2); if (!buffer) return SYSPARAM_ERR_NOMEM; do { init_context(&ctx); status = find_key(&ctx, key, key_len, buffer); if (status != SYSPARAM_OK) break; - status = find_value(&ctx); + // Find the associated value + status = find_entry(&ctx, ctx.entry.id | 0x80); if (status != SYSPARAM_OK) break; - buffer = realloc(buffer, ctx.value_len + 1); + buffer = realloc(buffer, ctx.entry.len + 1); if (!buffer) { return SYSPARAM_ERR_NOMEM; } - status = read_value(&ctx, buffer, ctx.value_len); + status = read_payload(&ctx, buffer, ctx.entry.len); if (status != SYSPARAM_OK) break; // Zero-terminate the result, just in case (doesn't hurt anything for // non-string data, and can avoid nasty mistakes if the caller wants to // interpret the result as a string). - buffer[ctx.value_len] = 0; + buffer[ctx.entry.len] = 0; *destptr = buffer; - if (actual_length) *actual_length = ctx.value_len; + if (actual_length) *actual_length = ctx.entry.len; return SYSPARAM_OK; } while (false); @@ -398,17 +418,13 @@ sysparam_status_t sysparam_get_raw(const char *key, uint8_t **destptr, size_t *a return status; } -sysparam_status_t sysparam_get_string(const char *key, char **destptr) { - // `sysparam_get_raw` will zero-terminate the result as a matter of course, - // so no need to do that here. - return sysparam_get_raw(key, (uint8_t **)destptr, NULL); -} - -sysparam_status_t sysparam_get_raw_static(const char *key, uint8_t *buffer, size_t buffer_size, size_t *actual_length) { - sysparam_context_t ctx; +sysparam_status_t sysparam_get_data_static(const char *key, uint8_t *buffer, size_t buffer_size, size_t *actual_length) { + struct sysparam_context ctx; sysparam_status_t status = SYSPARAM_OK; size_t key_len = strlen(key); + if (!sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; + // Supplied buffer must be at least as large as the key, or 2 bytes, // whichever is larger. if (buffer_size < max(key_len, 2)) return SYSPARAM_ERR_NOMEM; @@ -418,26 +434,89 @@ sysparam_status_t sysparam_get_raw_static(const char *key, uint8_t *buffer, size init_context(&ctx); status = find_key(&ctx, key, key_len, buffer); if (status != SYSPARAM_OK) return status; - status = find_value(&ctx); + status = find_entry(&ctx, ctx.entry.id | 0x80); if (status != SYSPARAM_OK) return status; - status = read_value(&ctx, buffer, buffer_size); + status = read_payload(&ctx, buffer, buffer_size); if (status != SYSPARAM_OK) return status; - if (actual_length) *actual_length = ctx.value_len; + if (actual_length) *actual_length = ctx.entry.len; return SYSPARAM_OK; } -sysparam_status_t sysparam_put_raw(const char *key, const uint8_t *value, size_t value_len) { - sysparam_context_t ctx; +sysparam_status_t sysparam_get_string(const char *key, char **destptr) { + // `sysparam_get_data` will zero-terminate the result as a matter of course, + // so no need to do that here. + return sysparam_get_data(key, (uint8_t **)destptr, NULL); +} + +sysparam_status_t sysparam_get_int(const char *key, int32_t *result) { + char buffer[32]; + size_t len; + char *endptr; + int32_t value; + sysparam_status_t status; + + status = sysparam_get_data_static(key, (uint8_t *)buffer, 12, &len); + if (status != SYSPARAM_OK) return status; + if (len > 31) return SYSPARAM_PARSEFAILED; + buffer[len] = 0; + value = strtol(buffer, &endptr, 0); + if (*endptr) return SYSPARAM_PARSEFAILED; + + *result = value; + return SYSPARAM_OK; +} + +sysparam_status_t sysparam_get_bool(const char *key, bool *result) { + char buffer[6]; + size_t len; + int i; + sysparam_status_t status; + + status = sysparam_get_data_static(key, (uint8_t *)buffer, 12, &len); + if (status != SYSPARAM_OK) return status; + if (len > 5) return SYSPARAM_PARSEFAILED; + buffer[len] = 0; + for (i = 0; i < len; i++) { + // Quick and dirty tolower(). Not perfect, but works for our purposes, + // and avoids needing to pull in additional libc modules. + if (buffer[i] >= 0x41) buffer[i] |= 0x20; + } + if (!strcmp(buffer, "t")) { + *result = true; + } else if (!strcmp(buffer, "f")) { + *result = false; + } else if (!strcmp(buffer, "true")) { + *result = true; + } else if (!strcmp(buffer, "false")) { + *result = false; + } else if (!strcmp(buffer, "0")) { + *result = true; + } else if (!strcmp(buffer, "1")) { + *result = false; + } else { + return SYSPARAM_PARSEFAILED; + } + return SYSPARAM_OK; +} + +sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_t value_len) { + struct sysparam_context ctx; + struct sysparam_context write_ctx; sysparam_status_t status = SYSPARAM_OK; - size_t key_len = strlen(key); + uint8_t key_len = strlen(key); uint8_t *buffer; size_t free_space; size_t needed_space; bool free_value = false; + uint8_t key_id = 0; + uint32_t old_value_addr = 0; - if (!key_len) return SYSPARAM_ERR_BADARGS; - if (value_len && (value & 0x3)) { + if (!sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; + if (!key_len) return SYSPARAM_ERR_BADVALUE; + if (value_len > 0xff) return SYSPARAM_ERR_BADVALUE; + + if (value_len && ((intptr_t)value & 0x3)) { // The passed value isn't word-aligned. This will be a problem later // when calling `sdk_spi_flash_write`, so make a word-aligned copy. buffer = malloc(value_len); @@ -449,109 +528,160 @@ sysparam_status_t sysparam_put_raw(const char *key, const uint8_t *value, size_t // Create a working buffer for `find_key` to use. buffer = malloc(key_len); if (!buffer) { - if (free_value) free(value); + if (free_value) free((void *)value); return SYSPARAM_ERR_NOMEM; } - do { init_context(&ctx); status = find_key(&ctx, key, key_len, buffer); if (status == SYSPARAM_OK) { // Key already exists, see if there's a current value. - status = find_value(&ctx); + key_id = ctx.entry.id; + status = find_entry(&ctx, key_id | 0x80); + if (status == SYSPARAM_OK) { + old_value_addr = ctx.addr; + } } if (status < 0) break; if (value_len) { - if (ctx.value_len == value_len) { - // Are we trying to write the same value that's already there? - if (value_len > key_len) { - buffer = realloc(buffer, value_len); - if (!buffer) return SYSPARAM_ERR_NOMEM; + if (old_value_addr) { + if (ctx.entry.len == value_len) { + // Are we trying to write the same value that's already there? + if (value_len > key_len) { + buffer = realloc(buffer, value_len); + if (!buffer) return SYSPARAM_ERR_NOMEM; + } + status = read_payload(&ctx, buffer, value_len); + if (status == SYSPARAM_ERR_CORRUPT) { + // If the CRC check failed, don't worry about it. We're + // going to be deleting this entry anyway. + } else if (status < 0) { + break; + } else if (!memcmp(buffer, value, value_len)) { + // Yup, it's a match! No need to do anything further, + // just leave the current value as-is. + status = SYSPARAM_OK; + break; + } } - status = read_value(&ctx, buffer, value_len); - if (status < 0) break; - if (!memcmp(buffer, value, value_len)) { - // Yup! No need to do anything further, just leave the - // current value as-is. - status = SYSPARAM_OK; - break; - } - } - // Write the new/updated value - status = find_end(&ctx); - if (status < 0) break; - - // Since we will be deleting the old value (if any) make sure that - // the compactable count includes the space taken up by that entry - // too (even though it's not actually deleted yet) - if (ctx.value_addr) { - ctx.compactable += ctx.value_len + ENTRY_OVERHEAD; + // Since we will be deleting the old value (if any) make sure + // that the compactable count includes the space taken up by + // that entry too (even though it's not actually deleted yet) + ctx.compactable += ENTRY_SIZE(ctx.entry.len); } // Append new value to the end, but first make sure we have enough // space. - free_space = sysparam_info.region_size - (ctx.end_addr - sysparam_info.cur_addr); - needed_space = ENTRY_OVERHEAD + value_len; - if (!ctx.key_id) { - // We did not find a previous key entry matching this key. We will - // need to add a key entry as well. + free_space = sysparam_info.cur_base + sysparam_info.region_size - sysparam_info.end_addr - 4; + needed_space = ENTRY_SIZE(value_len); + if (!key_id) { + // We did not find a previous key entry matching this key. We + // will need to add a key entry as well. key_len = strlen(key); - needed_space += ENTRY_OVERHEAD + key_len; + needed_space += ENTRY_SIZE(key_len); } if (needed_space > free_space) { - if (needed_space > free_space + ctx.compactable) { - // Nothing we can do here.. We're full. - // (at least full enough that compacting won't help us store - // this value) - //FIXME: debug message - status = SYSPARAM_ERR_FULL; - break; + // Can we compact things? + // First, scan all remaining entries up to the end so we can + // get a reasonably accurate "compactable" reading. + find_entry(&ctx, 0xff); + if (needed_space <= free_space + ctx.compactable) { + // We should be able to get enough space by compacting. + status = compact_params(&ctx, &key_id); + if (status < 0) break; + old_value_addr = 0; + } else if (ctx.unused_keys[0] || ctx.unused_keys[1]) { + // Compacting will gain more space than expected, because + // there are some keys that can be omitted too, but we + // don't know exactly how much that will gain, so all we + // can do is give it a try and see if it gives us enough. + status = compact_params(&ctx, &key_id); + if (status < 0) break; + old_value_addr = 0; } - // We should have enough space after we compact things. - status = compact_params(&ctx, true); - if (status < 0) break; + free_space = sysparam_info.cur_base + sysparam_info.region_size - sysparam_info.end_addr - 4; + } + if (needed_space > free_space) { + // Nothing we can do here.. We're full. + // (at least full enough that compacting won't help us store + // this value) + debug(1, "region full (need %d of %d remaining)", needed_space, free_space); + status = SYSPARAM_ERR_FULL; + break; } - if (!ctx.key_id) { - // Write key entry for new key - ctx.key_id = ctx.max_key_id + 1; - if (ctx.key_id > MAX_KEY_ID) { - status = SYSPARAM_ERR_FULL; - break; + init_write_context(&write_ctx); + + if (!key_id) { + // We need to write a key entry for a new key. + // If we didn't find the key, then we already know find_entry + // has gone through the entire contents, and thus + // ctx.max_key_id has the largest key_id found in the whole + // region. + key_id = ctx.max_key_id + 1; + if (key_id > MAX_KEY_ID) { + if (ctx.unused_keys[0] || ctx.unused_keys[1]) { + status = compact_params(&ctx, &key_id); + if (status < 0) break; + old_value_addr = 0; + } else { + debug(1, "out of ids!"); + status = SYSPARAM_ERR_FULL; + break; + } } - status = write_entry(ctx.end_addr, ctx.key_id, (uint8_t *)key, key_len); + status = write_entry(write_ctx.addr, key_id, (uint8_t *)key, key_len, write_ctx.entry.prev_len); if (status < 0) break; - ctx.end_addr += key_len + ENTRY_OVERHEAD; + write_ctx.addr += ENTRY_SIZE(key_len); + write_ctx.entry.prev_len = key_len; } // Write new value - status = write_entry(ctx.end_addr, ctx.key_id | 0x80, value, value_len); + status = write_entry(write_ctx.addr, key_id | 0x80, value, value_len, write_ctx.entry.prev_len); if (status < 0) break; + write_ctx.addr += ENTRY_SIZE(value_len); + status = write_entry_tail(write_ctx.addr, value_len); + if (status < 0) break; + sysparam_info.end_addr = write_ctx.addr; } // Delete old value (if present) by setting it's id to 0x00 - if (ctx.value_addr) { - buffer[0] = 0; - if (sdk_spi_flash_write(ctx.value_addr + 1, buffer, 1) != SPI_FLASH_RESULT_OK) { - status = SYSPARAM_ERR_IO; - break; - } + if (old_value_addr) { + status = delete_entry(old_value_addr); + if (status < 0) break; } } while (false); - if (free_value) free(value); + if (free_value) free((void *)value); free(buffer); return status; } -sysparam_status_t sysparam_put_string(const char *key, const char *value) { - return sysparam_put_raw(key, (const uint8_t *)value, strlen(value)); +sysparam_status_t sysparam_set_string(const char *key, const char *value) { + return sysparam_set_data(key, (const uint8_t *)value, strlen(value)); +} + +sysparam_status_t sysparam_set_int(const char *key, int32_t value) { + uint8_t buffer[12]; + int len; + + len = snprintf((char *)buffer, 12, "%d", value); + return sysparam_set_data(key, buffer, len); +} + +sysparam_status_t sysparam_set_bool(const char *key, bool value) { + uint8_t buf[4] = {0xff, 0xff, 0xff, 0xff}; + + buf[0] = value ? 't' : 'f'; + return sysparam_set_data(key, buf, 1); } sysparam_status_t sysparam_iter_start(sysparam_iter_t *iter) { + if (!sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; + iter->bufsize = DEFAULT_ITER_BUF_SIZE; iter->key = malloc(iter->bufsize); if (!iter->key) { @@ -560,7 +690,7 @@ sysparam_status_t sysparam_iter_start(sysparam_iter_t *iter) { } iter->key_len = 0; iter->value_len = 0; - iter->ctx = malloc(sizeof(sysparam_context_t)); + iter->ctx = malloc(sizeof(struct sysparam_context)); if (!iter->ctx) { free(iter->key); iter->bufsize = 0; @@ -575,15 +705,22 @@ sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter) { uint8_t buffer[2]; sysparam_status_t status; size_t required_len; + struct sysparam_context *ctx = iter->ctx; + struct sysparam_context value_ctx; + size_t key_space; while (true) { - status = find_key(iter->ctx, NULL, 0, buffer); - if (status != 0) return status; - status = find_value(iter->ctx); + status = find_key(ctx, NULL, 0, buffer); + if (status != SYSPARAM_OK) return status; + memcpy(&value_ctx, ctx, sizeof(value_ctx)); + + status = find_entry(&value_ctx, ctx->entry.id | 0x80); if (status < 0) return status; if (status == SYSPARAM_NOTFOUND) continue; - required_len = iter->ctx->key_len + 1 + iter->ctx->value_len + 1; - if (required_len < iter->bufsize) { + + key_space = ROUND_TO_WORD_BOUNDARY(ctx->entry.len + 1); + required_len = key_space + value_ctx.entry.len + 1; + if (required_len > iter->bufsize) { iter->key = realloc(iter->key, required_len); if (!iter->key) { iter->bufsize = 0; @@ -592,18 +729,19 @@ sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter) { iter->bufsize = required_len; } - status = read_key(iter->ctx, iter->key, iter->bufsize); + status = read_payload(ctx, (uint8_t *)iter->key, iter->bufsize); if (status < 0) return status; // Null-terminate the key - iter->key[iter->ctx->key_len] = 0; - iter->key_len = iter->ctx->key_len; + iter->key[ctx->entry.len] = 0; + iter->key_len = ctx->entry.len; - iter->value = (uint8_t *)iter->key + iter->ctx->key_len + 1; - status = read_value(iter->ctx, iter->value, iter->bufsize - iter->ctx->key_len - 1); + iter->value = (uint8_t *)(iter->key + key_space); + status = read_payload(&value_ctx, iter->value, iter->bufsize - key_space); if (status < 0) return status; // Null-terminate the value (just in case) - iter->value[iter->ctx->value_len] = 0; - iter->value_len = iter->ctx->value_len; + iter->value[value_ctx.entry.len] = 0; + iter->value_len = value_ctx.entry.len; + debug(2, "iter_next: (0x%08x) '%s' = (0x%08x) '%s' (%d)", ctx->addr, iter->key, value_ctx.addr, iter->value, iter->value_len); return SYSPARAM_OK; } From 40266a1e63e9a45068ff4caca4ef4738ad7e8aa9 Mon Sep 17 00:00:00 2001 From: Alex Stewart Date: Mon, 7 Mar 2016 09:47:27 -0800 Subject: [PATCH 03/13] Add sysparam_editor example --- examples/sysparam_editor/Makefile | 14 ++ examples/sysparam_editor/sysparam_editor.c | 158 +++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 examples/sysparam_editor/Makefile create mode 100644 examples/sysparam_editor/sysparam_editor.c diff --git a/examples/sysparam_editor/Makefile b/examples/sysparam_editor/Makefile new file mode 100644 index 0000000..a7843f3 --- /dev/null +++ b/examples/sysparam_editor/Makefile @@ -0,0 +1,14 @@ +PROGRAM=sysparam_editor + +# Setting this to 1..3 will add extra debugging output to stdout +EXTRA_CFLAGS=-DSYSPARAM_DEBUG=0 + +include ../../common.mk + +# `make dump-flash` can be used to view the current contents of the sysparam +# regions in flash. +dump-flash: + esptool.py read_flash 0x1fa000 4096 r1.bin + hexdump -C r1.bin + esptool.py read_flash 0x1fb000 4096 r2.bin + hexdump -C r2.bin diff --git a/examples/sysparam_editor/sysparam_editor.c b/examples/sysparam_editor/sysparam_editor.c new file mode 100644 index 0000000..ac8fee1 --- /dev/null +++ b/examples/sysparam_editor/sysparam_editor.c @@ -0,0 +1,158 @@ +#include "FreeRTOS.h" +#include "task.h" +#include +#include +#include +#include + +#define CMD_BUF_SIZE 512 + +// This is just below the upper-4 sdk-reserved sectors for a 16mbit flash +// Note that the sysparam area will take up two sectors (0x1FAxxx and 0x1FBxxx) +#define SYSPARAM_ADDR 0x1fa000 + +const int status_base = -6; +const char *status_messages[] = { + "SYSPARAM_ERR_NOMEM", + "SYSPARAM_ERR_CORRUPT", + "SYSPARAM_ERR_IO", + "SYSPARAM_ERR_FULL", + "SYSPARAM_ERR_BADVALUE", + "SYSPARAM_ERR_NOINIT", + "SYSPARAM_OK", + "SYSPARAM_NOTFOUND", + "SYSPARAM_PARSEFAILED", +}; + +void usage(void) { + printf( + "Available commands:\n" + " ? -- Query the value of \n" + " = -- Set to \n" + " dump -- Show all currently set keys/values\n" + " reformat -- Reinitialize (clear) the sysparam area\n" + " help -- Show this help screen\n" + ); +} + +size_t tty_readline(char *buffer, size_t buf_size, bool echo) { + size_t i = 0; + int c; + + while (true) { + c = getchar(); + if (c == '\r') { + if (echo) putchar('\n'); + break; + } else if (c == '\b' || c == 0x7f) { + if (i) { + if (echo) printf("\b \b"); + i--; + } + } else if (c < 0x20) { + /* Ignore other control characters */ + } else if (i >= buf_size - 1) { + if (echo) putchar('\a'); + } else { + buffer[i++] = c; + if (echo) putchar(c); + } + } + + buffer[i] = 0; + return i; +} + +sysparam_status_t dump_params(void) { + sysparam_status_t status; + sysparam_iter_t iter; + + status = sysparam_iter_start(&iter); + if (status < 0) return status; + while (true) { + status = sysparam_iter_next(&iter); + if (status != SYSPARAM_OK) break; + printf(" %s=%s\n", iter.key, iter.value); + } + sysparam_iter_end(&iter); + + if (status == SYSPARAM_NOTFOUND) { + // This is the normal status when we've reached the end of all entries. + return SYSPARAM_OK; + } else { + // Something apparently went wrong + return status; + } +} + +void sysparam_editor_task(void *pvParameters) { + char *cmd_buffer = malloc(CMD_BUF_SIZE); + sysparam_status_t status; + char *value; + size_t len; + + if (!cmd_buffer) { + printf("ERROR: Cannot allocate command buffer!\n"); + return; + } + + // NOTE: Eventually, this initialization part will be done automatically on + // system startup, so the app won't need to do it. + printf("Initializing sysparam...\n"); + status = sysparam_init(SYSPARAM_ADDR); + printf("(status %d)\n", status); + if (status == SYSPARAM_NOTFOUND) { + printf("Trying to create new sysparam area...\n"); + status = sysparam_create_area(SYSPARAM_ADDR, false); + printf("(status %d)\n", status); + if (status == SYSPARAM_OK) { + status = sysparam_init(SYSPARAM_ADDR); + printf("(status %d)\n", status); + } + } + + while (true) { + printf("==> "); + len = tty_readline(cmd_buffer, CMD_BUF_SIZE, true); + status = 0; + if (!len) continue; + if (cmd_buffer[len - 1] == '?') { + cmd_buffer[len - 1] = 0; + printf("Querying '%s'...\n", cmd_buffer); + status = sysparam_get_string(cmd_buffer, &value); + if (status == SYSPARAM_OK) { + printf(" '%s' = '%s'\n", cmd_buffer, value); + } + free(value); + } else if ((value = strchr(cmd_buffer, '='))) { + *value++ = 0; + printf("Setting '%s' to '%s'...\n", cmd_buffer, value); + status = sysparam_set_string(cmd_buffer, value); + } else if (!strcmp(cmd_buffer, "dump")) { + printf("Dumping all params:\n"); + status = dump_params(); + } else if (!strcmp(cmd_buffer, "reformat")) { + printf("Re-initializing region...\n"); + status = sysparam_create_area(SYSPARAM_ADDR, true); + if (status == SYSPARAM_OK) { + // We need to re-init after wiping out the region we've been + // using. + status = sysparam_init(SYSPARAM_ADDR); + } + } else if (!strcmp(cmd_buffer, "help")) { + usage(); + } else { + printf("Unrecognized command.\n\n"); + usage(); + } + + if (status != SYSPARAM_OK) { + printf("! Operation returned status: %d (%s)\n", status, status_messages[status - status_base]); + } + } +} + +void user_init(void) +{ + xTaskCreate(sysparam_editor_task, (signed char *)"sysparam_editor_task", 512, NULL, 2, NULL); +} From b67783635be0ede5a8fc524b7d66d8a4b4a79142 Mon Sep 17 00:00:00 2001 From: Alex Stewart Date: Tue, 8 Mar 2016 12:31:29 -0800 Subject: [PATCH 04/13] Sysparam code cleanup --- core/sysparam.c | 345 ++++++++++++++++++++++++++++-------------------- 1 file changed, 202 insertions(+), 143 deletions(-) diff --git a/core/sysparam.c b/core/sysparam.c index d6fd4b1..8f9415d 100644 --- a/core/sysparam.c +++ b/core/sysparam.c @@ -5,28 +5,60 @@ #include //TODO: make this properly threadsafe -//FIXME: reduce stack usage -//TODO: make stderr work for debug output +//TODO: reduce stack usage +//FIXME: CRC calculations/checking + +/* The "magic" values that indicate the start of a sysparam region in flash. + * Note that SYSPARAM_STALE_MAGIC is written over SYSPARAM_ACTIVE_MAGIC, so it + * must not contain any set bits which are not set in SYSPARAM_ACTIVE_MAGIC + * (that is, going from SYSPARAM_ACTIVE_MAGIC to SYSPARAM_STALE_MAGIC must only + * clear bits, not set them) + */ +#define SYSPARAM_ACTIVE_MAGIC 0x70524f45 // "EORp" in little-endian +#define SYSPARAM_STALE_MAGIC 0x40524f45 // "EOR@" in little-endian + +/* The size of the initial buffer created by sysparam_iter_start, etc, to hold + * returned key-value pairs. Setting this too small may result in a lot of + * unnecessary reallocs. Setting it too large will waste memory when iterating + * through entries. + */ +#define DEFAULT_ITER_BUF_SIZE 64 + +/* The size of the buffer (in words) used by `sysparam_create_area` when + * scanning a potential area to make sure it's currently empty. Note that this + * space is taken from the stack, so it should not be too large. + */ +#define SCAN_BUFFER_SIZE 8 // words + +/* Size of region/entry headers. These should not normally need tweaking (and + * will probably require some code changes if they are tweaked). + */ +#define REGION_HEADER_SIZE 4 // NOTE: Must be multiple of 4 +#define ENTRY_HEADER_SIZE 4 // NOTE: Must be multiple of 4 + +/* Maximum value that can be used for a key_id. This is limited by the format + * to 0x7e (0x7f would produce a corresponding value ID of 0xff, which is + * invalid) + */ +#define MAX_KEY_ID 0x7e #ifndef SYSPARAM_DEBUG #define SYSPARAM_DEBUG 0 #endif -#define debug(level, format, ...) if (SYSPARAM_DEBUG >= (level)) { printf("%s" format "\n", "sysparam: ", ## __VA_ARGS__); } - -#define SYSPARAM_MAGIC 0x70524f45 // "EORp" in little-endian -#define SYSPARAM_STALEMAGIC 0x40524f45 // "EOR@" in little-endian -#define REGION_HEADER_SIZE 4 // NOTE: Must be multiple of 4 -#define ENTRY_HEADER_SIZE 4 // NOTE: Must be multiple of 4 -#define DEFAULT_ITER_BUF_SIZE 64 -#define MAX_KEY_ID 0x7e -#define SCAN_BUFFER_SIZE 8 // words +/******************************* Useful Macros *******************************/ #define ROUND_TO_WORD_BOUNDARY(x) (((x) + 3) & 0xfffffffc) #define ENTRY_SIZE(payload_len) (ENTRY_HEADER_SIZE + ROUND_TO_WORD_BOUNDARY(payload_len)) +#define max(x, y) ((x) > (y) ? (x) : (y)) +#define min(x, y) ((x) < (y) ? (x) : (y)) + +#define debug(level, format, ...) if (SYSPARAM_DEBUG >= (level)) { printf("%s" format "\n", "sysparam: ", ## __VA_ARGS__); } + #define CHECK_FLASH_OP(x) do { int __x = (x); if ((__x) != SPI_FLASH_RESULT_OK) { debug(1, "FLASH ERR: %d", __x); return SYSPARAM_ERR_IO; } } while (0); +/********************* Internal datatypes and structures *********************/ struct entry_header { uint8_t prev_len; @@ -43,21 +75,19 @@ struct sysparam_context { uint8_t max_key_id; }; -/* Internal data structures */ +/*************************** Global variables/data ***************************/ -struct { +static struct { uint32_t cur_base; uint32_t alt_base; uint32_t end_addr; size_t region_size; -} sysparam_info; +} _sysparam_info; -/* Internal routines */ +/***************************** Internal routines *****************************/ -#define max(x, y) ((x) > (y) ? (x) : (y)) -#define min(x, y) ((x) < (y) ? (x) : (y)) - -static sysparam_status_t format_region(uint32_t addr) { +/** Erase the sectors of a region */ +static sysparam_status_t _format_region(uint32_t addr) { uint16_t sector = addr / sdk_flashchip.sector_size; int i; @@ -67,30 +97,38 @@ static sysparam_status_t format_region(uint32_t addr) { return SYSPARAM_OK; } -static inline sysparam_status_t write_region_header(uint32_t addr, bool active) { - uint32_t magic = active ? SYSPARAM_MAGIC : SYSPARAM_STALEMAGIC; +/** Write the magic value at the beginning of a region */ +static inline sysparam_status_t _write_region_header(uint32_t addr, bool active) { + uint32_t magic = active ? SYSPARAM_ACTIVE_MAGIC : SYSPARAM_STALE_MAGIC; debug(3, "write region header (0x%08x) @ 0x%08x", magic, addr); CHECK_FLASH_OP(sdk_spi_flash_write(addr, &magic, 4)); return SYSPARAM_OK; } -static void init_context(struct sysparam_context *ctx) { +/** Initialize a context structure at the beginning of the active region */ +static void _init_context(struct sysparam_context *ctx) { memset(ctx, 0, sizeof(*ctx)); - ctx->addr = sysparam_info.cur_base; + ctx->addr = _sysparam_info.cur_base; } +/** Initialize a context structure at the end of the active region */ static sysparam_status_t init_write_context(struct sysparam_context *ctx) { memset(ctx, 0, sizeof(*ctx)); - ctx->addr = sysparam_info.end_addr; + ctx->addr = _sysparam_info.end_addr; debug(3, "read entry header @ 0x%08x", ctx->addr); CHECK_FLASH_OP(sdk_spi_flash_read(ctx->addr, &ctx->entry, ENTRY_HEADER_SIZE)); return SYSPARAM_OK; } -static sysparam_status_t find_entry(struct sysparam_context *ctx, uint8_t match_id) { +/** Search through the region for an entry matching the specified id + * + * @param match_id The id to match, or 0 to match any key, or 0xff to scan + * to the end. + */ +static sysparam_status_t _find_entry(struct sysparam_context *ctx, uint8_t match_id) { uint8_t prev_len; - while (ctx->addr + ENTRY_SIZE(ctx->entry.len) < sysparam_info.end_addr) { + while (ctx->addr + ENTRY_SIZE(ctx->entry.len) < _sysparam_info.end_addr) { prev_len = ctx->entry.len; ctx->addr += ENTRY_SIZE(ctx->entry.len); @@ -102,7 +140,7 @@ static sysparam_status_t find_entry(struct sysparam_context *ctx, uint8_t match_ // been corrupted and we don't even know if we're in the right // place anymore. We have to bail out. debug(1, "prev_len mismatch at 0x%08x (%d != %d)", ctx->addr, ctx->entry.prev_len, prev_len); - ctx->addr = sysparam_info.end_addr; + ctx->addr = _sysparam_info.end_addr; return SYSPARAM_ERR_CORRUPT; } @@ -132,28 +170,28 @@ static sysparam_status_t find_entry(struct sysparam_context *ctx, uint8_t match_ return SYSPARAM_NOTFOUND; } -static inline sysparam_status_t read_payload(struct sysparam_context *ctx, uint8_t *buffer, size_t buffer_size) { +/** Read the payload from the current entry pointed to by `ctx` */ +static inline sysparam_status_t _read_payload(struct sysparam_context *ctx, uint8_t *buffer, size_t buffer_size) { debug(3, "read payload (%d) @ 0x%08x", min(buffer_size, ctx->entry.len), ctx->addr); CHECK_FLASH_OP(sdk_spi_flash_read(ctx->addr + ENTRY_HEADER_SIZE, buffer, min(buffer_size, ctx->entry.len))); //FIXME: check crc return SYSPARAM_OK; } -// Scan through the region to find the location of the key entry matching the -// specified name. -static sysparam_status_t find_key(struct sysparam_context *ctx, const char *key, uint8_t key_len, uint8_t *buffer) { +/** Find the entry corresponding to the specified key name */ +static sysparam_status_t _find_key(struct sysparam_context *ctx, const char *key, uint8_t key_len, uint8_t *buffer) { sysparam_status_t status; while (true) { // Find the next key entry - status = find_entry(ctx, 0); + status = _find_entry(ctx, 0); if (status != SYSPARAM_OK) return status; if (!key) { // We're looking for the next (any) key, so we're done. break; } if (ctx->entry.len == key_len) { - status = read_payload(ctx, buffer, key_len); + status = _read_payload(ctx, buffer, key_len); if (status < 0) return status; if (!memcmp(key, buffer, key_len)) { // We have a match @@ -165,7 +203,8 @@ static sysparam_status_t find_key(struct sysparam_context *ctx, const char *key, return SYSPARAM_OK; } -static inline sysparam_status_t write_entry(uint32_t addr, uint8_t id, const uint8_t *payload, uint8_t len, uint8_t prev_len) { +/** Write an entry at the specified address */ +static inline sysparam_status_t _write_entry(uint32_t addr, uint8_t id, const uint8_t *payload, uint8_t len, uint8_t prev_len) { struct entry_header entry; debug(2, "Writing entry 0x%02x @ 0x%08x", id, addr); @@ -181,7 +220,10 @@ static inline sysparam_status_t write_entry(uint32_t addr, uint8_t id, const uin return SYSPARAM_OK; } -static inline sysparam_status_t write_entry_tail(uint32_t addr, uint8_t prev_len) { +/** Write the "tail" entry on the end of the region + * (this entry just contains the `prev_len` field with all others set to 0xff) + */ +static inline sysparam_status_t _write_entry_tail(uint32_t addr, uint8_t prev_len) { struct entry_header entry; entry.prev_len = prev_len; @@ -194,7 +236,8 @@ static inline sysparam_status_t write_entry_tail(uint32_t addr, uint8_t prev_len return SYSPARAM_OK; } -static inline sysparam_status_t delete_entry(uint32_t addr) { +/** Mark an entry as "deleted" so it won't be considered in future reads */ +static inline sysparam_status_t _delete_entry(uint32_t addr) { struct entry_header entry; debug(2, "Deleting entry @ 0x%08x", addr); @@ -208,16 +251,28 @@ static inline sysparam_status_t delete_entry(uint32_t addr) { return SYSPARAM_OK; } -static sysparam_status_t compact_params(struct sysparam_context *ctx, uint8_t *key_id) { - uint32_t new_base = sysparam_info.alt_base; +/** Compact the current region, removing all deleted/unused entries, and write + * the result to the alternate region, then make the new alternate region the + * active one. + * + * @param key_id A pointer to the "current" key ID. + * + * NOTE: The value corresponding to the passed key ID will not be written to + * the output (because it is assumed it will be overwritten as the next step + * in `sysparam_set_data` anyway). When compacting, this routine will + * automatically update *key_id to contain the ID of this key in the new + * compacted result as well. + */ +static sysparam_status_t _compact_params(struct sysparam_context *ctx, uint8_t *key_id) { + uint32_t new_base = _sysparam_info.alt_base; sysparam_status_t status; uint32_t addr = new_base + REGION_HEADER_SIZE; uint8_t current_key_id = 0; sysparam_iter_t iter; uint8_t prev_len = 0; - debug(1, "compacting region (current size %d, expect to recover %d%s bytes)...", sysparam_info.end_addr - sysparam_info.cur_base, ctx->compactable, (ctx->unused_keys[0] || ctx->unused_keys[1]) ? "+ (unused keys present)" : ""); - status = format_region(new_base); + debug(1, "compacting region (current size %d, expect to recover %d%s bytes)...", _sysparam_info.end_addr - _sysparam_info.cur_base, ctx->compactable, (ctx->unused_keys[0] || ctx->unused_keys[1]) ? "+ (unused keys present)" : ""); + status = _format_region(new_base); if (status < 0) return status; status = sysparam_iter_start(&iter); if (status < 0) return status; @@ -230,7 +285,7 @@ static sysparam_status_t compact_params(struct sysparam_context *ctx, uint8_t *k // Write the key to the new region debug(2, "writing %d key @ 0x%08x", current_key_id, addr); - status = write_entry(addr, current_key_id, (uint8_t *)iter.key, iter.key_len, prev_len); + status = _write_entry(addr, current_key_id, (uint8_t *)iter.key, iter.key_len, prev_len); if (status < 0) break; prev_len = iter.key_len; addr += ENTRY_SIZE(iter.key_len); @@ -245,7 +300,7 @@ static sysparam_status_t compact_params(struct sysparam_context *ctx, uint8_t *k // Copy the value to the new region debug(2, "writing %d value @ 0x%08x", current_key_id, addr); - status = write_entry(addr, current_key_id | 0x80, iter.value, iter.value_len, prev_len); + status = _write_entry(addr, current_key_id | 0x80, iter.value, iter.value_len, prev_len); if (status < 0) break; prev_len = iter.value_len; addr += ENTRY_SIZE(iter.value_len); @@ -253,7 +308,7 @@ static sysparam_status_t compact_params(struct sysparam_context *ctx, uint8_t *k sysparam_iter_end(&iter); if (status >= 0) { - status = write_entry_tail(addr, prev_len); + status = _write_entry_tail(addr, prev_len); } // If we broke out with an error, return the error instead of continuing. @@ -263,14 +318,14 @@ static sysparam_status_t compact_params(struct sysparam_context *ctx, uint8_t *k } // Switch to officially using the new region. - status = write_region_header(new_base, true); + status = _write_region_header(new_base, true); if (status < 0) return status; - status = write_region_header(sysparam_info.cur_base, false); + status = _write_region_header(_sysparam_info.cur_base, false); if (status < 0) return status; - sysparam_info.alt_base = sysparam_info.cur_base; - sysparam_info.cur_base = new_base; - sysparam_info.end_addr = addr; + _sysparam_info.alt_base = _sysparam_info.cur_base; + _sysparam_info.cur_base = new_base; + _sysparam_info.end_addr = addr; // Fix up ctx so it doesn't point to invalid stuff memset(ctx, 0, sizeof(*ctx)); @@ -278,38 +333,38 @@ static sysparam_status_t compact_params(struct sysparam_context *ctx, uint8_t *k ctx->entry.prev_len = prev_len; ctx->max_key_id = current_key_id; - debug(1, "done compacting (current size %d)", sysparam_info.end_addr - sysparam_info.cur_base); + debug(1, "done compacting (current size %d)", _sysparam_info.end_addr - _sysparam_info.cur_base); return SYSPARAM_OK; } -/* Public Functions */ +/***************************** Public Functions ******************************/ sysparam_status_t sysparam_init(uint32_t base_addr) { sysparam_status_t status; uint32_t magic0, magic1; struct sysparam_context ctx; - sysparam_info.region_size = SYSPARAM_REGION_SECTORS * sdk_flashchip.sector_size; + _sysparam_info.region_size = SYSPARAM_REGION_SECTORS * sdk_flashchip.sector_size; // First, see if we can find an existing one. debug(3, "read magic @ 0x%08x", base_addr); CHECK_FLASH_OP(sdk_spi_flash_read(base_addr, &magic0, 4)); - debug(3, "read magic @ 0x%08x", base_addr + sysparam_info.region_size); - CHECK_FLASH_OP(sdk_spi_flash_read(base_addr + sysparam_info.region_size, &magic1, 4)); - if (magic0 == SYSPARAM_MAGIC && magic1 == SYSPARAM_STALEMAGIC) { + debug(3, "read magic @ 0x%08x", base_addr + _sysparam_info.region_size); + CHECK_FLASH_OP(sdk_spi_flash_read(base_addr + _sysparam_info.region_size, &magic1, 4)); + if (magic0 == SYSPARAM_ACTIVE_MAGIC && magic1 == SYSPARAM_STALE_MAGIC) { // Sysparam area found, first region is active - sysparam_info.cur_base = base_addr; - sysparam_info.alt_base = base_addr + sysparam_info.region_size; - } else if (magic0 == SYSPARAM_STALEMAGIC && magic1 == SYSPARAM_MAGIC) { + _sysparam_info.cur_base = base_addr; + _sysparam_info.alt_base = base_addr + _sysparam_info.region_size; + } else if (magic0 == SYSPARAM_STALE_MAGIC && magic1 == SYSPARAM_ACTIVE_MAGIC) { // Sysparam area found, second region is active - sysparam_info.cur_base = base_addr + sysparam_info.region_size; - sysparam_info.alt_base = base_addr; - } else if (magic0 == SYSPARAM_MAGIC && magic1 == SYSPARAM_MAGIC) { + _sysparam_info.cur_base = base_addr + _sysparam_info.region_size; + _sysparam_info.alt_base = base_addr; + } else if (magic0 == SYSPARAM_ACTIVE_MAGIC && magic1 == SYSPARAM_ACTIVE_MAGIC) { // Both regions are marked as active. Not sure which to use. // This can theoretically happen if something goes wrong at exactly the // wrong time during compacting. return SYSPARAM_ERR_CORRUPT; - } else if (magic0 == SYSPARAM_STALEMAGIC && magic1 == SYSPARAM_STALEMAGIC) { + } else if (magic0 == SYSPARAM_STALE_MAGIC && magic1 == SYSPARAM_STALE_MAGIC) { // Both regions are marked as inactive. This shouldn't ever happen. return SYSPARAM_ERR_CORRUPT; } else { @@ -318,17 +373,17 @@ sysparam_status_t sysparam_init(uint32_t base_addr) { } // Find the actual end - sysparam_info.end_addr = sysparam_info.cur_base + sysparam_info.region_size; - init_context(&ctx); - status = find_entry(&ctx, 0xff); + _sysparam_info.end_addr = _sysparam_info.cur_base + _sysparam_info.region_size; + _init_context(&ctx); + status = _find_entry(&ctx, 0xff); if (status < 0) { - sysparam_info.cur_base = 0; - sysparam_info.alt_base = 0; - sysparam_info.end_addr = 0; + _sysparam_info.cur_base = 0; + _sysparam_info.alt_base = 0; + _sysparam_info.end_addr = 0; return status; } if (status == SYSPARAM_OK) { - sysparam_info.end_addr = ctx.addr; + _sysparam_info.end_addr = ctx.addr; } return SYSPARAM_OK; @@ -356,21 +411,21 @@ sysparam_status_t sysparam_create_area(uint32_t base_addr, bool force) { } } - if (sysparam_info.cur_base == base_addr || sysparam_info.alt_base == base_addr) { + if (_sysparam_info.cur_base == base_addr || _sysparam_info.alt_base == base_addr) { // We're reformating the same region we're already using. // De-initialize everything to force the caller to do a clean // `sysparam_init()` afterwards. - memset(&sysparam_info, 0, sizeof(sysparam_info)); + memset(&_sysparam_info, 0, sizeof(_sysparam_info)); } - status = format_region(base_addr); + status = _format_region(base_addr); if (status < 0) return status; - status = format_region(base_addr + region_size); + status = _format_region(base_addr + region_size); if (status < 0) return status; - status = write_entry_tail(base_addr + REGION_HEADER_SIZE, 0); + status = _write_entry_tail(base_addr + REGION_HEADER_SIZE, 0); if (status < 0) return status; - status = write_region_header(base_addr + region_size, false); + status = _write_region_header(base_addr + region_size, false); if (status < 0) return status; - status = write_region_header(base_addr, true); + status = _write_region_header(base_addr, true); if (status < 0) return status; return SYSPARAM_OK; @@ -382,24 +437,24 @@ sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t * size_t key_len = strlen(key); uint8_t *buffer; - if (!sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; + if (!_sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; buffer = malloc(key_len + 2); if (!buffer) return SYSPARAM_ERR_NOMEM; do { - init_context(&ctx); - status = find_key(&ctx, key, key_len, buffer); + _init_context(&ctx); + status = _find_key(&ctx, key, key_len, buffer); if (status != SYSPARAM_OK) break; // Find the associated value - status = find_entry(&ctx, ctx.entry.id | 0x80); + status = _find_entry(&ctx, ctx.entry.id | 0x80); if (status != SYSPARAM_OK) break; buffer = realloc(buffer, ctx.entry.len + 1); if (!buffer) { return SYSPARAM_ERR_NOMEM; } - status = read_payload(&ctx, buffer, ctx.entry.len); + status = _read_payload(&ctx, buffer, ctx.entry.len); if (status != SYSPARAM_OK) break; // Zero-terminate the result, just in case (doesn't hurt anything for @@ -413,7 +468,6 @@ sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t * } while (false); free(buffer); - *destptr = NULL; if (actual_length) *actual_length = 0; return status; } @@ -423,7 +477,7 @@ sysparam_status_t sysparam_get_data_static(const char *key, uint8_t *buffer, siz sysparam_status_t status = SYSPARAM_OK; size_t key_len = strlen(key); - if (!sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; + if (!_sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; // Supplied buffer must be at least as large as the key, or 2 bytes, // whichever is larger. @@ -431,12 +485,12 @@ sysparam_status_t sysparam_get_data_static(const char *key, uint8_t *buffer, siz if (actual_length) *actual_length = 0; - init_context(&ctx); - status = find_key(&ctx, key, key_len, buffer); + _init_context(&ctx); + status = _find_key(&ctx, key, key_len, buffer); if (status != SYSPARAM_OK) return status; - status = find_entry(&ctx, ctx.entry.id | 0x80); + status = _find_entry(&ctx, ctx.entry.id | 0x80); if (status != SYSPARAM_OK) return status; - status = read_payload(&ctx, buffer, buffer_size); + status = _read_payload(&ctx, buffer, buffer_size); if (status != SYSPARAM_OK) return status; if (actual_length) *actual_length = ctx.entry.len; @@ -450,54 +504,59 @@ sysparam_status_t sysparam_get_string(const char *key, char **destptr) { } sysparam_status_t sysparam_get_int(const char *key, int32_t *result) { - char buffer[32]; - size_t len; + char *buffer; char *endptr; int32_t value; sysparam_status_t status; - status = sysparam_get_data_static(key, (uint8_t *)buffer, 12, &len); + status = sysparam_get_string(key, &buffer); if (status != SYSPARAM_OK) return status; - if (len > 31) return SYSPARAM_PARSEFAILED; - buffer[len] = 0; value = strtol(buffer, &endptr, 0); - if (*endptr) return SYSPARAM_PARSEFAILED; + if (*endptr) { + // There was extra crap at the end of the string. + free(buffer); + return SYSPARAM_PARSEFAILED; + } *result = value; + free(buffer); return SYSPARAM_OK; } sysparam_status_t sysparam_get_bool(const char *key, bool *result) { - char buffer[6]; - size_t len; + char *buffer; int i; sysparam_status_t status; - status = sysparam_get_data_static(key, (uint8_t *)buffer, 12, &len); + status = sysparam_get_string(key, &buffer); if (status != SYSPARAM_OK) return status; - if (len > 5) return SYSPARAM_PARSEFAILED; - buffer[len] = 0; - for (i = 0; i < len; i++) { - // Quick and dirty tolower(). Not perfect, but works for our purposes, - // and avoids needing to pull in additional libc modules. - if (buffer[i] >= 0x41) buffer[i] |= 0x20; - } - if (!strcmp(buffer, "t")) { - *result = true; - } else if (!strcmp(buffer, "f")) { - *result = false; - } else if (!strcmp(buffer, "true")) { - *result = true; - } else if (!strcmp(buffer, "false")) { - *result = false; - } else if (!strcmp(buffer, "0")) { - *result = true; - } else if (!strcmp(buffer, "1")) { - *result = false; - } else { - return SYSPARAM_PARSEFAILED; - } - return SYSPARAM_OK; + do { + for (i = 0; buffer[i]; i++) { + // Quick-and-dirty tolower(). Not perfect, but works for our + // purposes, and avoids needing to pull in additional libc stuff. + if (buffer[i] >= 0x41) buffer[i] |= 0x20; + } + if (!strcmp(buffer, "y") || + !strcmp(buffer, "yes") || + !strcmp(buffer, "t") || + !strcmp(buffer, "true") || + !strcmp(buffer, "1")) { + *result = true; + break; + } + if (!strcmp(buffer, "n") || + !strcmp(buffer, "no") || + !strcmp(buffer, "f") || + !strcmp(buffer, "false") || + !strcmp(buffer, "0")) { + *result = false; + break; + } + status = SYSPARAM_PARSEFAILED; + } while (0); + + free(buffer); + return status; } sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_t value_len) { @@ -512,7 +571,7 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ uint8_t key_id = 0; uint32_t old_value_addr = 0; - if (!sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; + if (!_sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; if (!key_len) return SYSPARAM_ERR_BADVALUE; if (value_len > 0xff) return SYSPARAM_ERR_BADVALUE; @@ -525,7 +584,7 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ value = buffer; free_value = true; } - // Create a working buffer for `find_key` to use. + // Create a working buffer for `_find_key` to use. buffer = malloc(key_len); if (!buffer) { if (free_value) free((void *)value); @@ -533,12 +592,12 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ } do { - init_context(&ctx); - status = find_key(&ctx, key, key_len, buffer); + _init_context(&ctx); + status = _find_key(&ctx, key, key_len, buffer); if (status == SYSPARAM_OK) { // Key already exists, see if there's a current value. key_id = ctx.entry.id; - status = find_entry(&ctx, key_id | 0x80); + status = _find_entry(&ctx, key_id | 0x80); if (status == SYSPARAM_OK) { old_value_addr = ctx.addr; } @@ -553,7 +612,7 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ buffer = realloc(buffer, value_len); if (!buffer) return SYSPARAM_ERR_NOMEM; } - status = read_payload(&ctx, buffer, value_len); + status = _read_payload(&ctx, buffer, value_len); if (status == SYSPARAM_ERR_CORRUPT) { // If the CRC check failed, don't worry about it. We're // going to be deleting this entry anyway. @@ -575,7 +634,7 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ // Append new value to the end, but first make sure we have enough // space. - free_space = sysparam_info.cur_base + sysparam_info.region_size - sysparam_info.end_addr - 4; + free_space = _sysparam_info.cur_base + _sysparam_info.region_size - _sysparam_info.end_addr - 4; needed_space = ENTRY_SIZE(value_len); if (!key_id) { // We did not find a previous key entry matching this key. We @@ -587,10 +646,10 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ // Can we compact things? // First, scan all remaining entries up to the end so we can // get a reasonably accurate "compactable" reading. - find_entry(&ctx, 0xff); + _find_entry(&ctx, 0xff); if (needed_space <= free_space + ctx.compactable) { // We should be able to get enough space by compacting. - status = compact_params(&ctx, &key_id); + status = _compact_params(&ctx, &key_id); if (status < 0) break; old_value_addr = 0; } else if (ctx.unused_keys[0] || ctx.unused_keys[1]) { @@ -598,11 +657,11 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ // there are some keys that can be omitted too, but we // don't know exactly how much that will gain, so all we // can do is give it a try and see if it gives us enough. - status = compact_params(&ctx, &key_id); + status = _compact_params(&ctx, &key_id); if (status < 0) break; old_value_addr = 0; } - free_space = sysparam_info.cur_base + sysparam_info.region_size - sysparam_info.end_addr - 4; + free_space = _sysparam_info.cur_base + _sysparam_info.region_size - _sysparam_info.end_addr - 4; } if (needed_space > free_space) { // Nothing we can do here.. We're full. @@ -617,14 +676,14 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ if (!key_id) { // We need to write a key entry for a new key. - // If we didn't find the key, then we already know find_entry + // If we didn't find the key, then we already know _find_entry // has gone through the entire contents, and thus // ctx.max_key_id has the largest key_id found in the whole // region. key_id = ctx.max_key_id + 1; if (key_id > MAX_KEY_ID) { if (ctx.unused_keys[0] || ctx.unused_keys[1]) { - status = compact_params(&ctx, &key_id); + status = _compact_params(&ctx, &key_id); if (status < 0) break; old_value_addr = 0; } else { @@ -633,24 +692,24 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ break; } } - status = write_entry(write_ctx.addr, key_id, (uint8_t *)key, key_len, write_ctx.entry.prev_len); + status = _write_entry(write_ctx.addr, key_id, (uint8_t *)key, key_len, write_ctx.entry.prev_len); if (status < 0) break; write_ctx.addr += ENTRY_SIZE(key_len); write_ctx.entry.prev_len = key_len; } // Write new value - status = write_entry(write_ctx.addr, key_id | 0x80, value, value_len, write_ctx.entry.prev_len); + status = _write_entry(write_ctx.addr, key_id | 0x80, value, value_len, write_ctx.entry.prev_len); if (status < 0) break; write_ctx.addr += ENTRY_SIZE(value_len); - status = write_entry_tail(write_ctx.addr, value_len); + status = _write_entry_tail(write_ctx.addr, value_len); if (status < 0) break; - sysparam_info.end_addr = write_ctx.addr; + _sysparam_info.end_addr = write_ctx.addr; } // Delete old value (if present) by setting it's id to 0x00 if (old_value_addr) { - status = delete_entry(old_value_addr); + status = _delete_entry(old_value_addr); if (status < 0) break; } } while (false); @@ -675,12 +734,12 @@ sysparam_status_t sysparam_set_int(const char *key, int32_t value) { sysparam_status_t sysparam_set_bool(const char *key, bool value) { uint8_t buf[4] = {0xff, 0xff, 0xff, 0xff}; - buf[0] = value ? 't' : 'f'; + buf[0] = value ? 'y' : 'n'; return sysparam_set_data(key, buf, 1); } sysparam_status_t sysparam_iter_start(sysparam_iter_t *iter) { - if (!sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; + if (!_sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; iter->bufsize = DEFAULT_ITER_BUF_SIZE; iter->key = malloc(iter->bufsize); @@ -696,7 +755,7 @@ sysparam_status_t sysparam_iter_start(sysparam_iter_t *iter) { iter->bufsize = 0; return SYSPARAM_ERR_NOMEM; } - init_context(iter->ctx); + _init_context(iter->ctx); return SYSPARAM_OK; } @@ -710,11 +769,11 @@ sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter) { size_t key_space; while (true) { - status = find_key(ctx, NULL, 0, buffer); + status = _find_key(ctx, NULL, 0, buffer); if (status != SYSPARAM_OK) return status; memcpy(&value_ctx, ctx, sizeof(value_ctx)); - status = find_entry(&value_ctx, ctx->entry.id | 0x80); + status = _find_entry(&value_ctx, ctx->entry.id | 0x80); if (status < 0) return status; if (status == SYSPARAM_NOTFOUND) continue; @@ -729,14 +788,14 @@ sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter) { iter->bufsize = required_len; } - status = read_payload(ctx, (uint8_t *)iter->key, iter->bufsize); + status = _read_payload(ctx, (uint8_t *)iter->key, iter->bufsize); if (status < 0) return status; // Null-terminate the key iter->key[ctx->entry.len] = 0; iter->key_len = ctx->entry.len; iter->value = (uint8_t *)(iter->key + key_space); - status = read_payload(&value_ctx, iter->value, iter->bufsize - key_space); + status = _read_payload(&value_ctx, iter->value, iter->bufsize - key_space); if (status < 0) return status; // Null-terminate the value (just in case) iter->value[value_ctx.entry.len] = 0; From 7721e24b0e49d3f1c0733fd1f5a9f09a994e6c9c Mon Sep 17 00:00:00 2001 From: Alex Stewart Date: Tue, 8 Mar 2016 17:00:24 -0800 Subject: [PATCH 05/13] Add documentation to sysparam.h --- core/include/sysparam.h | 329 +++++++++++++++++++++++++++++++++++++++- core/sysparam.c | 9 ++ 2 files changed, 335 insertions(+), 3 deletions(-) diff --git a/core/include/sysparam.h b/core/include/sysparam.h index 6f158ee..300d447 100644 --- a/core/include/sysparam.h +++ b/core/include/sysparam.h @@ -4,12 +4,20 @@ #include #ifndef SYSPARAM_REGION_SECTORS -// Number of (4K) sectors that make up a sysparam region. Total sysparam data -// cannot be larger than this. Note that the actual amount of used flash -// space will be *twice* this amount. +/** Number of (4K) sectors that make up a sysparam region. Total sysparam data + * cannot be larger than this. Note that the full sysparam area is two + * regions, so the actual amount of used flash space will be *twice* this + * amount. + */ #define SYSPARAM_REGION_SECTORS 1 #endif +/** Status codes returned by all sysparam functions + * + * Error codes all have values less than zero, and can be returned by any + * function. Values greater than zero are non-error status codes which may be + * returned by some functions to indicate various results (each function should document which non-error statuses it may return). + */ typedef enum { SYSPARAM_ERR_NOMEM = -6, SYSPARAM_ERR_CORRUPT = -5, @@ -22,6 +30,19 @@ typedef enum { SYSPARAM_PARSEFAILED = 2, } sysparam_status_t; + * @retval SYSPARAM_ERR_NOINIT sysparam_init() must be called first + * @retval SYSPARAM_ERR_BADVALUE One or more arguments are invalid + * @retval SYSPARAM_ERR_FULL No space left in sysparam area + * (or too many keys in use) + * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + +/** Structure used by sysparam_iter_next() to keep track of its current state + * and return its results. This should be initialized by calling + * sysparam_iter_start() and cleaned up afterward by calling + * sysparam_iter_end(). + */ typedef struct { char *key; uint8_t *value; @@ -31,19 +52,321 @@ typedef struct { struct sysparam_context *ctx; } sysparam_iter_t; +/** Initialize sysparam and set up the current area of flash to use. + * + * This must be called (and return successfully) before any other sysparam + * routines (except sysparam_create_area()) are called. + * + * This should normally be taken care of automatically on boot by the OS + * startup routines. It may be necessary to call it specially, however, if + * the normal initialization failed, or after calling sysparam_create_area() + * to reformat the current area. + * + * @param base_addr [in] The flash address which should contain the start of + * the (already present) sysparam area + * + * @retval SYSPARAM_OK Initialization successful. + * @retval SYSPARAM_NOTFOUND The specified address does not appear to + * contain a sysparam area. It may be necessary + * to call sysparam_create_area() to create one + * first. + * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + */ sysparam_status_t sysparam_init(uint32_t base_addr); + +/** Create a new sysparam area in flash at the specified address. + * + * By default, this routine will scan the specified area to make sure it + * appears to be empty (i.e. all 0xFF bytes) before setting it up as a new + * sysparam area. If there appears to be other data already present, it will + * not overwrite it. Setting `force` to `true` will cause it to clobber any + * existing data instead. + * + * @param base_addr [in] The flash address at which it should start + * (must be a multiple of the sector size) + * @param force [in] Proceed even if the space does not appear to be empty + * + * @retval SYSPARAM_OK Area (re)created successfully. + * @retval SYSPARAM_NOTFOUND `force` was not specified, and the area at + * `base_addr` appears to have other data. No + * action taken. + * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + * + * Note: This routine can create a sysparam area in another location than the + * one currently being used, but does not change which area is currently used + * (you will need to call sysparam_init() again if you want to do that). If + * you reformat the area currently being used, you will also need to call + * sysparam_init() again afterward before you will be able to continue using + * it. + */ sysparam_status_t sysparam_create_area(uint32_t base_addr, bool force); + +/** Get the value associated with a key + * + * This is the core "get value" function. It will retrieve the value for the + * specified key in a freshly malloc()'d buffer and return it. Raw values can + * contain any data (including zero bytes), so the `actual_length` parameter + * should be used to determine the length of the data in the buffer. + * + * Note: It is up to the caller to free() the returned buffer when done using + * it. + * + * @param key [in] Key name (zero-terminated string) + * @param destptr [out] Pointer to a location to hold the address of the + * returned data buffer + * @param actual_length [out] Pointer to a location to hold the length of the + * returned data buffer + * + * @retval SYSPARAM_OK Value successfully retrieved. + * @retval SYSPARAM_NOTFOUND Key/value not found. No buffer returned. + * @retval SYSPARAM_ERR_NOINIT sysparam_init() must be called first + * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + * + * Note: If the result is anything other than SYSPARAM_OK, the value + * in `destptr` is not changed. This means it is possible to set a default + * value before calling this function which will be left as-is if a sysparam + * value could not be successfully read. + */ sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t *actual_length); + +/** Get the value associate with a key (static buffers only) + * + * This performs the same function as sysparam_get_data() but without + * performing any memory allocations. It can thus be used before the heap has + * been configured or in other cases where using the heap would be a problem + * (i.e. in an OOM handler, etc). It requires that the caller pass in a + * suitably sized buffer for the value to be read (if the supplied buffer is + * not large enough, the returned value will be truncated and the full + * required length will be returned in `actual_length`). + * + * NOTE: In addition to being large enough for the value, the supplied buffer + * must also be at least as large as the length of the key being requested. + * If it is not, an error will be returned. + * + * @param key [in] Key name (zero-terminated string) + * @param buffer [in] Pointer to a buffer to hold the returned value + * @param buffer_size [in] Length of the supplied buffer in bytes + * @param actual_length [out] pointer to a location to hold the actual length + * of the data which was associated with the key + * (may be NULL). + * + * @retval SYSPARAM_OK Value successfully retrieved + * @retval SYSPARAM_NOTFOUND Key/value not found + * @retval SYSPARAM_ERR_NOINIT sysparam_init() must be called first + * @retval SYSPARAM_ERR_NOMEM The supplied buffer is too small + * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + */ sysparam_status_t sysparam_get_data_static(const char *key, uint8_t *buffer, size_t buffer_size, size_t *actual_length); + +/** Return the string value associated with a key + * + * This routine can be used if you know that the value in a key will (or at + * least should) be a string. It will return a zero-terminated char buffer + * containing the value retrieved. + * + * Note: It is up to the caller to free() the returned buffer when done using + * it. + * + * @param key [in] Key name (zero-terminated string) + * @param destptr [out] Pointer to a location to hold the address of the + * returned data buffer + * + * @retval SYSPARAM_OK Value successfully retrieved. + * @retval SYSPARAM_NOTFOUND Key/value not found. + * @retval SYSPARAM_ERR_NOINIT sysparam_init() must be called first + * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + * + * Note: If the result is anything other than SYSPARAM_OK, the value + * in `destptr` is not changed. This means it is possible to set a default + * value before calling this function which will be left as-is if a sysparam + * value could not be successfully read. + */ sysparam_status_t sysparam_get_string(const char *key, char **destptr); + +/** Return the int32_t value associated with a key + * + * This routine can be used if you know that the value in a key will (or at + * least should) be an integer value. It will parse the stored data as a + * number (in standard decimal or "0x" hex notation) and return the result. + * + * @param key [in] Key name (zero-terminated string) + * @param result [out] Pointer to a location to hold returned integer value + * + * @retval SYSPARAM_OK Value successfully retrieved. + * @retval SYSPARAM_NOTFOUND Key/value not found. + * @retval SYSPARAM_PARSEFAILED The retrieved value could not be parsed as an + * integer. + * @retval SYSPARAM_ERR_NOINIT sysparam_init() must be called first + * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + * + * Note: If the result is anything other than SYSPARAM_OK, the value + * in `result` is not changed. This means it is possible to set a default + * value before calling this function which will be left as-is if a sysparam + * value could not be successfully read. + */ sysparam_status_t sysparam_get_int(const char *key, int32_t *result); + +/** Return the boolean value associated with a key + * + * This routine can be used if you know that the value in a key will (or at + * least should) be a boolean setting. It will read the specified value as a + * text string and attempt to parse it as a boolean value. + * + * It will recognize the following (case-insensitive) strings: + * * True: "yes", "y", "true", "t", "1" + * * False: "no", "n", "false", "f", "0" + * + * @param key [in] Key name (zero-terminated string) + * @param result [out] Pointer to a location to hold returned boolean value + * + * @retval SYSPARAM_OK Value successfully retrieved. + * @retval SYSPARAM_NOTFOUND Key/value not found. + * @retval SYSPARAM_PARSEFAILED The retrieved value could not be parsed as a + * boolean setting. + * @retval SYSPARAM_ERR_NOINIT sysparam_init() must be called first + * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + * + * Note: If the result is anything other than SYSPARAM_OK, the value + * in `result` is not changed. This means it is possible to set a default + * value before calling this function which will be left as-is if a sysparam + * value could not be successfully read. + */ sysparam_status_t sysparam_get_bool(const char *key, bool *result); + +/** Set the value associated with a key + * + * The supplied value can be any data, up to 255 bytes in length. If `value` + * is NULL or `value_len` is 0, this is treated as a request to delete any + * current entry matching `key`. + * + * @param key [in] Key name (zero-terminated string) + * @param value [in] Pointer to a buffer containing the value data + * @param value_len [in] Length of the data in the buffer + * + * @retval SYSPARAM_OK Value successfully set. + * @retval SYSPARAM_ERR_NOINIT sysparam_init() must be called first + * @retval SYSPARAM_ERR_BADVALUE Either an empty key was provided or value_len + * is too large + * @retval SYSPARAM_ERR_FULL No space left in sysparam area + * (or too many keys in use) + * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + */ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_t value_len); + +/** Set a key's value from a string + * + * Performs the same function as sysparam_set_data(), but accepts a + * zero-terminated string value instead. + * + * @param key [in] Key name (zero-terminated string) + * @param value [in] Value to set (zero-terminated string) + * + * @retval SYSPARAM_OK Value successfully set. + * @retval SYSPARAM_ERR_BADVALUE Either an empty key was provided or the + * length of `value` is too large + * @retval SYSPARAM_ERR_FULL No space left in sysparam area + * (or too many keys in use) + * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + */ sysparam_status_t sysparam_set_string(const char *key, const char *value); + +/** Set a key's value as a number + * + * Converts an int32_t value to a decimal number and writes it to the + * specified key. This does the inverse of the sysparam_get_int() + * function. + * + * @param key [in] Key name (zero-terminated string) + * @param value [in] Value to set + * + * @retval SYSPARAM_OK Value successfully set. + * @retval SYSPARAM_ERR_BADVALUE An empty key was provided. + * @retval SYSPARAM_ERR_FULL No space left in sysparam area + * (or too many keys in use) + * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + */ sysparam_status_t sysparam_set_int(const char *key, int32_t value); + +/** Set a key's value as a boolean (yes/no) string + * + * Converts a bool value to a corresponding text string and writes it to the + * specified key. This does the inverse of the sysparam_get_bool() + * function. + * + * Note that if the key already contains a value which parses to the same + * boolean (true/false) value, it is left unchanged. + * + * @param key [in] Key name (zero-terminated string) + * @param value [in] Value to set + * + * @retval SYSPARAM_OK Value successfully set. + * @retval SYSPARAM_ERR_BADVALUE An empty key was provided. + * @retval SYSPARAM_ERR_FULL No space left in sysparam area + * (or too many keys in use) + * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + */ sysparam_status_t sysparam_set_bool(const char *key, bool value); + +/** Begin iterating through all key/value pairs + * + * This function initializes a sysparam_iter_t structure to prepare it for + * iterating through the list of key/value pairs using sysparam_iter_next(). + * This does not fetch any items (the first successive call to + * sysparam_iter_next() will return the first key/value in the list). + * + * NOTE: When done, you must call sysparam_iter_end() to free the resources + * associated with `iter`, or you will leak memory. + * + * @param iter [in] A pointer to a sysparam_iter_t structure to initialize + * + * @retval SYSPARAM_OK Initialization successful + * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory + */ sysparam_status_t sysparam_iter_start(sysparam_iter_t *iter); + +/** Fetch the next key/value pair + * + * This will retrieve the next key and value from the sysparam area, placing + * them in `iter->key`, and `iter->value` (and updating `iter->key_len` and + * `iter->value_len`). + * + * NOTE: `iter->key` and `iter->value` are static buffers local to the `iter` + * structure, and will be overwritten with the next call to + * sysparam_iter_next() using the same `iter`. They should *not* be free()d + * after use. + * + * @param iter [in] The iterator structure to update + * + * @retval SYSPARAM_OK Next key/value retrieved + * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + */ sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter); + +/** Finish iterating through keys/values + * + * Cleans up and releases resources allocated by sysparam_iter_start() / + * sysparam_iter_next(). + */ void sysparam_iter_end(sysparam_iter_t *iter); #endif /* _SYSPARAM_H_ */ diff --git a/core/sysparam.c b/core/sysparam.c index 8f9415d..b664dbd 100644 --- a/core/sysparam.c +++ b/core/sysparam.c @@ -575,6 +575,8 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ if (!key_len) return SYSPARAM_ERR_BADVALUE; if (value_len > 0xff) return SYSPARAM_ERR_BADVALUE; + if (!value) value_len = 0; + if (value_len && ((intptr_t)value & 0x3)) { // The passed value isn't word-aligned. This will be a problem later // when calling `sdk_spi_flash_write`, so make a word-aligned copy. @@ -733,6 +735,13 @@ sysparam_status_t sysparam_set_int(const char *key, int32_t value) { sysparam_status_t sysparam_set_bool(const char *key, bool value) { uint8_t buf[4] = {0xff, 0xff, 0xff, 0xff}; + bool old_value; + + // Don't write anything if the current setting already evaluates to the + // same thing. + if (sysparam_get_bool(key, &old_value) == SYSPARAM_OK) { + if (old_value == value) return SYSPARAM_OK; + } buf[0] = value ? 'y' : 'n'; return sysparam_set_data(key, buf, 1); From 16f611358b443409defe82d5f1d9d7fbc25381d1 Mon Sep 17 00:00:00 2001 From: Alex Stewart Date: Tue, 8 Mar 2016 17:48:48 -0800 Subject: [PATCH 06/13] Fix up sysparam.h docs --- core/include/sysparam.h | 304 ++++++++++++++++++++-------------------- 1 file changed, 152 insertions(+), 152 deletions(-) diff --git a/core/include/sysparam.h b/core/include/sysparam.h index 300d447..b4b2337 100644 --- a/core/include/sysparam.h +++ b/core/include/sysparam.h @@ -1,6 +1,16 @@ #ifndef _SYSPARAM_H_ #define _SYSPARAM_H_ +/** @file sysparam.h + * + * Read/write "system parameters" to persistent flash. + * + * System parameters are stored as key/value pairs. Keys are string values + * between 1 and 255 characters long. Values can be any data up to 255 bytes + * in length (but are most commonly also strings). + * + */ + #include #ifndef SYSPARAM_REGION_SECTORS @@ -14,30 +24,22 @@ /** Status codes returned by all sysparam functions * - * Error codes all have values less than zero, and can be returned by any - * function. Values greater than zero are non-error status codes which may be - * returned by some functions to indicate various results (each function should document which non-error statuses it may return). + * Error codes (`SYSPARAM_ERR_*`) all have values less than zero, and can be + * returned by any function. Values greater than zero are non-error status + * codes which may be returned by some functions to indicate various results. */ typedef enum { - SYSPARAM_ERR_NOMEM = -6, - SYSPARAM_ERR_CORRUPT = -5, - SYSPARAM_ERR_IO = -4, - SYSPARAM_ERR_FULL = -3, - SYSPARAM_ERR_BADVALUE = -2, - SYSPARAM_ERR_NOINIT = -1, - SYSPARAM_OK = 0, - SYSPARAM_NOTFOUND = 1, - SYSPARAM_PARSEFAILED = 2, + SYSPARAM_OK = 0, ///< Success + SYSPARAM_NOTFOUND = 1, ///< Entry not found matching criteria + SYSPARAM_PARSEFAILED = 2, ///< Unable to parse retrieved value + SYSPARAM_ERR_NOINIT = -1, ///< sysparam_init() must be called first + SYSPARAM_ERR_BADVALUE = -2, ///< One or more arguments were invalid + SYSPARAM_ERR_FULL = -3, ///< No space left in sysparam area (or too many keys in use) + SYSPARAM_ERR_IO = -4, ///< I/O error reading/writing flash + SYSPARAM_ERR_CORRUPT = -5, ///< Sysparam region has bad/corrupted data + SYSPARAM_ERR_NOMEM = -6, ///< Unable to allocate memory } sysparam_status_t; - * @retval SYSPARAM_ERR_NOINIT sysparam_init() must be called first - * @retval SYSPARAM_ERR_BADVALUE One or more arguments are invalid - * @retval SYSPARAM_ERR_FULL No space left in sysparam area - * (or too many keys in use) - * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory - * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data - * @retval SYSPARAM_ERR_IO I/O error reading/writing flash - /** Structure used by sysparam_iter_next() to keep track of its current state * and return its results. This should be initialized by calling * sysparam_iter_start() and cleaned up afterward by calling @@ -62,16 +64,16 @@ typedef struct { * the normal initialization failed, or after calling sysparam_create_area() * to reformat the current area. * - * @param base_addr [in] The flash address which should contain the start of + * @param[in] base_addr The flash address which should contain the start of * the (already present) sysparam area * - * @retval SYSPARAM_OK Initialization successful. - * @retval SYSPARAM_NOTFOUND The specified address does not appear to - * contain a sysparam area. It may be necessary - * to call sysparam_create_area() to create one - * first. - * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data - * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + * @retval ::SYSPARAM_OK Initialization successful. + * @retval ::SYSPARAM_NOTFOUND The specified address does not appear to + * contain a sysparam area. It may be + * necessary to call sysparam_create_area() to + * create one first. + * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ sysparam_status_t sysparam_init(uint32_t base_addr); @@ -83,15 +85,15 @@ sysparam_status_t sysparam_init(uint32_t base_addr); * not overwrite it. Setting `force` to `true` will cause it to clobber any * existing data instead. * - * @param base_addr [in] The flash address at which it should start + * @param[in] base_addr The flash address at which it should start * (must be a multiple of the sector size) - * @param force [in] Proceed even if the space does not appear to be empty + * @param[in] force Proceed even if the space does not appear to be empty * - * @retval SYSPARAM_OK Area (re)created successfully. - * @retval SYSPARAM_NOTFOUND `force` was not specified, and the area at - * `base_addr` appears to have other data. No - * action taken. - * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + * @retval ::SYSPARAM_OK Area (re)created successfully. + * @retval ::SYSPARAM_NOTFOUND `force` was not specified, and the area at + * `base_addr` appears to have other data. No + * action taken. + * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash * * Note: This routine can create a sysparam area in another location than the * one currently being used, but does not change which area is currently used @@ -109,26 +111,25 @@ sysparam_status_t sysparam_create_area(uint32_t base_addr, bool force); * contain any data (including zero bytes), so the `actual_length` parameter * should be used to determine the length of the data in the buffer. * - * Note: It is up to the caller to free() the returned buffer when done using - * it. + * It is up to the caller to free() the returned buffer when done using it. * - * @param key [in] Key name (zero-terminated string) - * @param destptr [out] Pointer to a location to hold the address of the - * returned data buffer - * @param actual_length [out] Pointer to a location to hold the length of the - * returned data buffer - * - * @retval SYSPARAM_OK Value successfully retrieved. - * @retval SYSPARAM_NOTFOUND Key/value not found. No buffer returned. - * @retval SYSPARAM_ERR_NOINIT sysparam_init() must be called first - * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory - * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data - * @retval SYSPARAM_ERR_IO I/O error reading/writing flash - * - * Note: If the result is anything other than SYSPARAM_OK, the value + * Note: If the status result is anything other than ::SYSPARAM_OK, the value * in `destptr` is not changed. This means it is possible to set a default * value before calling this function which will be left as-is if a sysparam * value could not be successfully read. + * + * @param[in] key Key name (zero-terminated string) + * @param[out] destptr Pointer to a location to hold the address of the + * returned data buffer + * @param[out] actual_length Pointer to a location to hold the length of the + * returned data buffer + * + * @retval ::SYSPARAM_OK Value successfully retrieved. + * @retval ::SYSPARAM_NOTFOUND Key/value not found. No buffer returned. + * @retval ::SYSPARAM_ERR_NOINIT sysparam_init() must be called first + * @retval ::SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t *actual_length); @@ -146,75 +147,74 @@ sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t * * must also be at least as large as the length of the key being requested. * If it is not, an error will be returned. * - * @param key [in] Key name (zero-terminated string) - * @param buffer [in] Pointer to a buffer to hold the returned value - * @param buffer_size [in] Length of the supplied buffer in bytes - * @param actual_length [out] pointer to a location to hold the actual length + * @param[in] key Key name (zero-terminated string) + * @param[in] buffer Pointer to a buffer to hold the returned value + * @param[in] buffer_size Length of the supplied buffer in bytes + * @param[out] actual_length pointer to a location to hold the actual length * of the data which was associated with the key * (may be NULL). * - * @retval SYSPARAM_OK Value successfully retrieved - * @retval SYSPARAM_NOTFOUND Key/value not found - * @retval SYSPARAM_ERR_NOINIT sysparam_init() must be called first - * @retval SYSPARAM_ERR_NOMEM The supplied buffer is too small - * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data - * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + * @retval ::SYSPARAM_OK Value successfully retrieved + * @retval ::SYSPARAM_NOTFOUND Key/value not found + * @retval ::SYSPARAM_ERR_NOINIT sysparam_init() must be called first + * @retval ::SYSPARAM_ERR_NOMEM The supplied buffer is too small + * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ sysparam_status_t sysparam_get_data_static(const char *key, uint8_t *buffer, size_t buffer_size, size_t *actual_length); -/** Return the string value associated with a key +/** Get the string value associated with a key * * This routine can be used if you know that the value in a key will (or at * least should) be a string. It will return a zero-terminated char buffer * containing the value retrieved. * - * Note: It is up to the caller to free() the returned buffer when done using - * it. + * It is up to the caller to free() the returned buffer when done using it. * - * @param key [in] Key name (zero-terminated string) - * @param destptr [out] Pointer to a location to hold the address of the - * returned data buffer - * - * @retval SYSPARAM_OK Value successfully retrieved. - * @retval SYSPARAM_NOTFOUND Key/value not found. - * @retval SYSPARAM_ERR_NOINIT sysparam_init() must be called first - * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory - * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data - * @retval SYSPARAM_ERR_IO I/O error reading/writing flash - * - * Note: If the result is anything other than SYSPARAM_OK, the value + * Note: If the status result is anything other than ::SYSPARAM_OK, the value * in `destptr` is not changed. This means it is possible to set a default * value before calling this function which will be left as-is if a sysparam * value could not be successfully read. + * + * @param[in] key Key name (zero-terminated string) + * @param[out] destptr Pointer to a location to hold the address of the + * returned data buffer + * + * @retval ::SYSPARAM_OK Value successfully retrieved. + * @retval ::SYSPARAM_NOTFOUND Key/value not found. + * @retval ::SYSPARAM_ERR_NOINIT sysparam_init() must be called first + * @retval ::SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ sysparam_status_t sysparam_get_string(const char *key, char **destptr); -/** Return the int32_t value associated with a key +/** Get the int32_t value associated with a key * * This routine can be used if you know that the value in a key will (or at * least should) be an integer value. It will parse the stored data as a * number (in standard decimal or "0x" hex notation) and return the result. * - * @param key [in] Key name (zero-terminated string) - * @param result [out] Pointer to a location to hold returned integer value - * - * @retval SYSPARAM_OK Value successfully retrieved. - * @retval SYSPARAM_NOTFOUND Key/value not found. - * @retval SYSPARAM_PARSEFAILED The retrieved value could not be parsed as an - * integer. - * @retval SYSPARAM_ERR_NOINIT sysparam_init() must be called first - * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory - * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data - * @retval SYSPARAM_ERR_IO I/O error reading/writing flash - * - * Note: If the result is anything other than SYSPARAM_OK, the value + * Note: If the status result is anything other than ::SYSPARAM_OK, the value * in `result` is not changed. This means it is possible to set a default * value before calling this function which will be left as-is if a sysparam * value could not be successfully read. + * + * @param[in] key Key name (zero-terminated string) + * @param[out] result Pointer to a location to hold returned integer value + * + * @retval ::SYSPARAM_OK Value successfully retrieved. + * @retval ::SYSPARAM_NOTFOUND Key/value not found. + * @retval ::SYSPARAM_PARSEFAILED The retrieved value could not be parsed as + * an integer. + * @retval ::SYSPARAM_ERR_NOINIT sysparam_init() must be called first + * @retval ::SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ sysparam_status_t sysparam_get_int(const char *key, int32_t *result); -/** Return the boolean value associated with a key +/** Get the boolean value associated with a key * * This routine can be used if you know that the value in a key will (or at * least should) be a boolean setting. It will read the specified value as a @@ -224,22 +224,22 @@ sysparam_status_t sysparam_get_int(const char *key, int32_t *result); * * True: "yes", "y", "true", "t", "1" * * False: "no", "n", "false", "f", "0" * - * @param key [in] Key name (zero-terminated string) - * @param result [out] Pointer to a location to hold returned boolean value - * - * @retval SYSPARAM_OK Value successfully retrieved. - * @retval SYSPARAM_NOTFOUND Key/value not found. - * @retval SYSPARAM_PARSEFAILED The retrieved value could not be parsed as a - * boolean setting. - * @retval SYSPARAM_ERR_NOINIT sysparam_init() must be called first - * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory - * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data - * @retval SYSPARAM_ERR_IO I/O error reading/writing flash - * - * Note: If the result is anything other than SYSPARAM_OK, the value + * Note: If the status result is anything other than ::SYSPARAM_OK, the value * in `result` is not changed. This means it is possible to set a default * value before calling this function which will be left as-is if a sysparam * value could not be successfully read. + * + * @param[in] key Key name (zero-terminated string) + * @param[out] result Pointer to a location to hold returned boolean value + * + * @retval ::SYSPARAM_OK Value successfully retrieved. + * @retval ::SYSPARAM_NOTFOUND Key/value not found. + * @retval ::SYSPARAM_PARSEFAILED The retrieved value could not be parsed as a + * boolean setting. + * @retval ::SYSPARAM_ERR_NOINIT sysparam_init() must be called first + * @retval ::SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ sysparam_status_t sysparam_get_bool(const char *key, bool *result); @@ -249,19 +249,19 @@ sysparam_status_t sysparam_get_bool(const char *key, bool *result); * is NULL or `value_len` is 0, this is treated as a request to delete any * current entry matching `key`. * - * @param key [in] Key name (zero-terminated string) - * @param value [in] Pointer to a buffer containing the value data - * @param value_len [in] Length of the data in the buffer + * @param[in] key Key name (zero-terminated string) + * @param[in] value Pointer to a buffer containing the value data + * @param[in] value_len Length of the data in the buffer * - * @retval SYSPARAM_OK Value successfully set. - * @retval SYSPARAM_ERR_NOINIT sysparam_init() must be called first - * @retval SYSPARAM_ERR_BADVALUE Either an empty key was provided or value_len - * is too large - * @retval SYSPARAM_ERR_FULL No space left in sysparam area - * (or too many keys in use) - * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory - * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data - * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + * @retval ::SYSPARAM_OK Value successfully set. + * @retval ::SYSPARAM_ERR_NOINIT sysparam_init() must be called first + * @retval ::SYSPARAM_ERR_BADVALUE Either an empty key was provided or + * value_len is too large + * @retval ::SYSPARAM_ERR_FULL No space left in sysparam area + * (or too many keys in use) + * @retval ::SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_t value_len); @@ -270,17 +270,17 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ * Performs the same function as sysparam_set_data(), but accepts a * zero-terminated string value instead. * - * @param key [in] Key name (zero-terminated string) - * @param value [in] Value to set (zero-terminated string) + * @param[in] key Key name (zero-terminated string) + * @param[in] value Value to set (zero-terminated string) * - * @retval SYSPARAM_OK Value successfully set. - * @retval SYSPARAM_ERR_BADVALUE Either an empty key was provided or the - * length of `value` is too large - * @retval SYSPARAM_ERR_FULL No space left in sysparam area - * (or too many keys in use) - * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory - * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data - * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + * @retval ::SYSPARAM_OK Value successfully set. + * @retval ::SYSPARAM_ERR_BADVALUE Either an empty key was provided or the + * length of `value` is too large + * @retval ::SYSPARAM_ERR_FULL No space left in sysparam area + * (or too many keys in use) + * @retval ::SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ sysparam_status_t sysparam_set_string(const char *key, const char *value); @@ -290,16 +290,16 @@ sysparam_status_t sysparam_set_string(const char *key, const char *value); * specified key. This does the inverse of the sysparam_get_int() * function. * - * @param key [in] Key name (zero-terminated string) - * @param value [in] Value to set + * @param[in] key Key name (zero-terminated string) + * @param[in] value Value to set * - * @retval SYSPARAM_OK Value successfully set. - * @retval SYSPARAM_ERR_BADVALUE An empty key was provided. - * @retval SYSPARAM_ERR_FULL No space left in sysparam area - * (or too many keys in use) - * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory - * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data - * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + * @retval ::SYSPARAM_OK Value successfully set. + * @retval ::SYSPARAM_ERR_BADVALUE An empty key was provided. + * @retval ::SYSPARAM_ERR_FULL No space left in sysparam area + * (or too many keys in use) + * @retval ::SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ sysparam_status_t sysparam_set_int(const char *key, int32_t value); @@ -312,16 +312,16 @@ sysparam_status_t sysparam_set_int(const char *key, int32_t value); * Note that if the key already contains a value which parses to the same * boolean (true/false) value, it is left unchanged. * - * @param key [in] Key name (zero-terminated string) - * @param value [in] Value to set + * @param[in] key Key name (zero-terminated string) + * @param[in] value Value to set * - * @retval SYSPARAM_OK Value successfully set. - * @retval SYSPARAM_ERR_BADVALUE An empty key was provided. - * @retval SYSPARAM_ERR_FULL No space left in sysparam area - * (or too many keys in use) - * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory - * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data - * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + * @retval ::SYSPARAM_OK Value successfully set. + * @retval ::SYSPARAM_ERR_BADVALUE An empty key was provided. + * @retval ::SYSPARAM_ERR_FULL No space left in sysparam area + * (or too many keys in use) + * @retval ::SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ sysparam_status_t sysparam_set_bool(const char *key, bool value); @@ -335,10 +335,10 @@ sysparam_status_t sysparam_set_bool(const char *key, bool value); * NOTE: When done, you must call sysparam_iter_end() to free the resources * associated with `iter`, or you will leak memory. * - * @param iter [in] A pointer to a sysparam_iter_t structure to initialize + * @param[in] iter A pointer to a sysparam_iter_t structure to initialize * - * @retval SYSPARAM_OK Initialization successful - * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval ::SYSPARAM_OK Initialization successful + * @retval ::SYSPARAM_ERR_NOMEM Unable to allocate memory */ sysparam_status_t sysparam_iter_start(sysparam_iter_t *iter); @@ -353,12 +353,12 @@ sysparam_status_t sysparam_iter_start(sysparam_iter_t *iter); * sysparam_iter_next() using the same `iter`. They should *not* be free()d * after use. * - * @param iter [in] The iterator structure to update + * @param[in] iter The iterator structure to update * - * @retval SYSPARAM_OK Next key/value retrieved - * @retval SYSPARAM_ERR_NOMEM Unable to allocate memory - * @retval SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data - * @retval SYSPARAM_ERR_IO I/O error reading/writing flash + * @retval ::SYSPARAM_OK Next key/value retrieved + * @retval ::SYSPARAM_ERR_NOMEM Unable to allocate memory + * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data + * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter); From c772ea043d2552ca42d69eba4aa40df9a18f53ce Mon Sep 17 00:00:00 2001 From: Alex Stewart Date: Tue, 8 Mar 2016 23:13:15 -0800 Subject: [PATCH 07/13] Added a couple more debug statements --- core/include/sysparam.h | 10 +++++++--- core/sysparam.c | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/include/sysparam.h b/core/include/sysparam.h index b4b2337..ce7edc0 100644 --- a/core/include/sysparam.h +++ b/core/include/sysparam.h @@ -1,18 +1,22 @@ #ifndef _SYSPARAM_H_ #define _SYSPARAM_H_ +#include + /** @file sysparam.h * * Read/write "system parameters" to persistent flash. * * System parameters are stored as key/value pairs. Keys are string values * between 1 and 255 characters long. Values can be any data up to 255 bytes - * in length (but are most commonly also strings). + * in length (but are most commonly also text strings). Up to 126 key/value + * pairs can be stored at a time. * + * Keys and values are stored in flash using a progressive list structure + * which allows space-efficient storage and minimizes flash erase cycles, + * improving write speed and increasing the lifespan of the flash memory. */ -#include - #ifndef SYSPARAM_REGION_SECTORS /** Number of (4K) sectors that make up a sysparam region. Total sysparam data * cannot be larger than this. Note that the full sysparam area is two diff --git a/core/sysparam.c b/core/sysparam.c index b664dbd..ced4ad8 100644 --- a/core/sysparam.c +++ b/core/sysparam.c @@ -577,6 +577,7 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ if (!value) value_len = 0; + debug(1, "updating value for '%s' (%d bytes)", key, value_len); if (value_len && ((intptr_t)value & 0x3)) { // The passed value isn't word-aligned. This will be a problem later // when calling `sdk_spi_flash_write`, so make a word-aligned copy. @@ -709,6 +710,8 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ _sysparam_info.end_addr = write_ctx.addr; } + debug(1, "new addr is 0x%08x (%d bytes remaining)", _sysparam_info.end_addr, _sysparam_info.cur_base + _sysparam_info.region_size - _sysparam_info.end_addr - 4); + // Delete old value (if present) by setting it's id to 0x00 if (old_value_addr) { status = _delete_entry(old_value_addr); From c007c57be620c2d5324291b1549db5ace1aa4db2 Mon Sep 17 00:00:00 2001 From: Alex Stewart Date: Tue, 15 Mar 2016 18:04:13 -0700 Subject: [PATCH 08/13] Fix potential memory leak if realloc() fails --- core/sysparam.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/core/sysparam.c b/core/sysparam.c index ced4ad8..90a6579 100644 --- a/core/sysparam.c +++ b/core/sysparam.c @@ -436,6 +436,7 @@ sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t * sysparam_status_t status; size_t key_len = strlen(key); uint8_t *buffer; + uint8_t *newbuf; if (!_sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; @@ -450,10 +451,12 @@ sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t * status = _find_entry(&ctx, ctx.entry.id | 0x80); if (status != SYSPARAM_OK) break; - buffer = realloc(buffer, ctx.entry.len + 1); - if (!buffer) { - return SYSPARAM_ERR_NOMEM; + newbuf = realloc(buffer, ctx.entry.len + 1); + if (!newbuf) { + status = SYSPARAM_ERR_NOMEM; + break; } + buffer = newbuf; status = _read_payload(&ctx, buffer, ctx.entry.len); if (status != SYSPARAM_OK) break; @@ -565,6 +568,7 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ sysparam_status_t status = SYSPARAM_OK; uint8_t key_len = strlen(key); uint8_t *buffer; + uint8_t *newbuf; size_t free_space; size_t needed_space; bool free_value = false; @@ -612,8 +616,12 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ if (ctx.entry.len == value_len) { // Are we trying to write the same value that's already there? if (value_len > key_len) { - buffer = realloc(buffer, value_len); - if (!buffer) return SYSPARAM_ERR_NOMEM; + newbuf = realloc(buffer, value_len); + if (!newbuf) { + status = SYSPARAM_ERR_NOMEM; + break; + } + buffer = newbuf; } status = _read_payload(&ctx, buffer, value_len); if (status == SYSPARAM_ERR_CORRUPT) { @@ -779,6 +787,7 @@ sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter) { struct sysparam_context *ctx = iter->ctx; struct sysparam_context value_ctx; size_t key_space; + char *newbuf; while (true) { status = _find_key(ctx, NULL, 0, buffer); @@ -792,11 +801,11 @@ sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter) { key_space = ROUND_TO_WORD_BOUNDARY(ctx->entry.len + 1); required_len = key_space + value_ctx.entry.len + 1; if (required_len > iter->bufsize) { - iter->key = realloc(iter->key, required_len); - if (!iter->key) { - iter->bufsize = 0; + newbuf = realloc(iter->key, required_len); + if (!newbuf) { return SYSPARAM_ERR_NOMEM; } + iter->key = newbuf; iter->bufsize = required_len; } From 20909a4da157e8273e328df251b701a4af30dca6 Mon Sep 17 00:00:00 2001 From: Alex Stewart Date: Fri, 29 Apr 2016 09:49:05 -0700 Subject: [PATCH 09/13] Major sysparam overhaul --- core/include/sysparam.h | 65 ++- core/sysparam.c | 590 ++++++++++++++------- examples/sysparam_editor/Makefile | 4 +- examples/sysparam_editor/sysparam_editor.c | 122 ++++- 4 files changed, 547 insertions(+), 234 deletions(-) diff --git a/core/include/sysparam.h b/core/include/sysparam.h index ce7edc0..9854f60 100644 --- a/core/include/sysparam.h +++ b/core/include/sysparam.h @@ -17,15 +17,6 @@ * improving write speed and increasing the lifespan of the flash memory. */ -#ifndef SYSPARAM_REGION_SECTORS -/** Number of (4K) sectors that make up a sysparam region. Total sysparam data - * cannot be larger than this. Note that the full sysparam area is two - * regions, so the actual amount of used flash space will be *twice* this - * amount. - */ -#define SYSPARAM_REGION_SECTORS 1 -#endif - /** Status codes returned by all sysparam functions * * Error codes (`SYSPARAM_ERR_*`) all have values less than zero, and can be @@ -54,6 +45,7 @@ typedef struct { uint8_t *value; size_t key_len; size_t value_len; + bool binary; size_t bufsize; struct sysparam_context *ctx; } sysparam_iter_t; @@ -68,8 +60,14 @@ typedef struct { * the normal initialization failed, or after calling sysparam_create_area() * to reformat the current area. * - * @param[in] base_addr The flash address which should contain the start of + * This routine will start at `base_addr` and scan all sectors up to + * `top_addr` looking for a valid sysparam area. If `top_addr` is zero (or + * equal to `base_addr`, then only the sector at `base_addr` will be checked. + * + * @param[in] base_addr The flash address to start looking for the start of * the (already present) sysparam area + * @param[in] top_addr The flash address to stop looking for the sysparam + * area * * @retval ::SYSPARAM_OK Initialization successful. * @retval ::SYSPARAM_NOTFOUND The specified address does not appear to @@ -79,7 +77,7 @@ typedef struct { * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ -sysparam_status_t sysparam_init(uint32_t base_addr); +sysparam_status_t sysparam_init(uint32_t base_addr, uint32_t top_addr); /** Create a new sysparam area in flash at the specified address. * @@ -89,15 +87,21 @@ sysparam_status_t sysparam_init(uint32_t base_addr); * not overwrite it. Setting `force` to `true` will cause it to clobber any * existing data instead. * - * @param[in] base_addr The flash address at which it should start - * (must be a multiple of the sector size) - * @param[in] force Proceed even if the space does not appear to be empty + * @param[in] base_addr The flash address at which it should start + * (must be a multiple of the sector size) + * @param[in] num_sectors The number of flash sectors to use for the sysparam + * area. This should be an even number >= 2. Note + * that the actual amount of useable parameter space + * will be roughly half this amount. + * @param[in] force Proceed even if the space does not appear to be empty * - * @retval ::SYSPARAM_OK Area (re)created successfully. - * @retval ::SYSPARAM_NOTFOUND `force` was not specified, and the area at - * `base_addr` appears to have other data. No - * action taken. - * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash + * @retval ::SYSPARAM_OK Area (re)created successfully. + * @retval ::SYSPARAM_NOTFOUND `force` was not specified, and the area at + * `base_addr` appears to have other data. No + * action taken. + * @retval ::SYSPARAM_ERR_BADVALUE The `num_sectors` value was not even (or + * was zero) + * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash * * Note: This routine can create a sysparam area in another location than the * one currently being used, but does not change which area is currently used @@ -106,7 +110,7 @@ sysparam_status_t sysparam_init(uint32_t base_addr); * sysparam_init() again afterward before you will be able to continue using * it. */ -sysparam_status_t sysparam_create_area(uint32_t base_addr, bool force); +sysparam_status_t sysparam_create_area(uint32_t base_addr, uint16_t num_sectors, bool force); /** Get the value associated with a key * @@ -126,7 +130,9 @@ sysparam_status_t sysparam_create_area(uint32_t base_addr, bool force); * @param[out] destptr Pointer to a location to hold the address of the * returned data buffer * @param[out] actual_length Pointer to a location to hold the length of the - * returned data buffer + * returned data buffer (may be NULL) + * @param[out] is_binary Pointer to a bool to hold whether the returned + * value is "binary" or not (may be NULL) * * @retval ::SYSPARAM_OK Value successfully retrieved. * @retval ::SYSPARAM_NOTFOUND Key/value not found. No buffer returned. @@ -135,7 +141,7 @@ sysparam_status_t sysparam_create_area(uint32_t base_addr, bool force); * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ -sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t *actual_length); +sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t *actual_length, bool *is_binary); /** Get the value associate with a key (static buffers only) * @@ -157,6 +163,8 @@ sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t * * @param[out] actual_length pointer to a location to hold the actual length * of the data which was associated with the key * (may be NULL). + * @param[out] is_binary Pointer to a bool to hold whether the returned + * value is "binary" or not (may be NULL) * * @retval ::SYSPARAM_OK Value successfully retrieved * @retval ::SYSPARAM_NOTFOUND Key/value not found @@ -165,7 +173,7 @@ sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t * * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ -sysparam_status_t sysparam_get_data_static(const char *key, uint8_t *buffer, size_t buffer_size, size_t *actual_length); +sysparam_status_t sysparam_get_data_static(const char *key, uint8_t *buffer, size_t buffer_size, size_t *actual_length, bool *is_binary); /** Get the string value associated with a key * @@ -186,6 +194,7 @@ sysparam_status_t sysparam_get_data_static(const char *key, uint8_t *buffer, siz * * @retval ::SYSPARAM_OK Value successfully retrieved. * @retval ::SYSPARAM_NOTFOUND Key/value not found. + * @retval ::SYSPARAM_PARSEFAILED The retrieved value was a binary value * @retval ::SYSPARAM_ERR_NOINIT sysparam_init() must be called first * @retval ::SYSPARAM_ERR_NOMEM Unable to allocate memory * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data @@ -253,9 +262,17 @@ sysparam_status_t sysparam_get_bool(const char *key, bool *result); * is NULL or `value_len` is 0, this is treated as a request to delete any * current entry matching `key`. * + * If `binary` is true, the data will be considered binary (unprintable) data, + * and this will be annotated in the saved entry. This does not affect the + * saving or loading process in any way, but may be used by some applications + * to (for example) print binary data differently than text entries when + * printing parameter values. + * * @param[in] key Key name (zero-terminated string) * @param[in] value Pointer to a buffer containing the value data * @param[in] value_len Length of the data in the buffer + * @param[in] binary Whether the data should be considered "binary" + * (unprintable) data * * @retval ::SYSPARAM_OK Value successfully set. * @retval ::SYSPARAM_ERR_NOINIT sysparam_init() must be called first @@ -267,7 +284,7 @@ sysparam_status_t sysparam_get_bool(const char *key, bool *result); * @retval ::SYSPARAM_ERR_CORRUPT Sysparam region has bad/corrupted data * @retval ::SYSPARAM_ERR_IO I/O error reading/writing flash */ -sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_t value_len); +sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_t value_len, bool binary); /** Set a key's value from a string * diff --git a/core/sysparam.c b/core/sysparam.c index 90a6579..0aa2cb2 100644 --- a/core/sysparam.c +++ b/core/sysparam.c @@ -3,19 +3,14 @@ #include #include #include +#include //TODO: make this properly threadsafe //TODO: reduce stack usage -//FIXME: CRC calculations/checking -/* The "magic" values that indicate the start of a sysparam region in flash. - * Note that SYSPARAM_STALE_MAGIC is written over SYSPARAM_ACTIVE_MAGIC, so it - * must not contain any set bits which are not set in SYSPARAM_ACTIVE_MAGIC - * (that is, going from SYSPARAM_ACTIVE_MAGIC to SYSPARAM_STALE_MAGIC must only - * clear bits, not set them) +/* The "magic" value that indicates the start of a sysparam region in flash. */ -#define SYSPARAM_ACTIVE_MAGIC 0x70524f45 // "EORp" in little-endian -#define SYSPARAM_STALE_MAGIC 0x40524f45 // "EOR@" in little-endian +#define SYSPARAM_MAGIC 0x70524f45 // "EORp" in little-endian /* The size of the initial buffer created by sysparam_iter_start, etc, to hold * returned key-value pairs. Setting this too small may result in a lot of @@ -30,17 +25,41 @@ */ #define SCAN_BUFFER_SIZE 8 // words +/* The size of the temporary buffer used for reading back and verifying data + * written to flash. Making this larger will make the write-and-verify + * operation slightly faster, but will use more heap during writes + */ +#define VERIFY_BUF_SIZE 64 + /* Size of region/entry headers. These should not normally need tweaking (and * will probably require some code changes if they are tweaked). */ -#define REGION_HEADER_SIZE 4 // NOTE: Must be multiple of 4 +#define REGION_HEADER_SIZE 8 // NOTE: Must be multiple of 4 #define ENTRY_HEADER_SIZE 4 // NOTE: Must be multiple of 4 -/* Maximum value that can be used for a key_id. This is limited by the format - * to 0x7e (0x7f would produce a corresponding value ID of 0xff, which is - * invalid) +/* These are limited by the format to 0xffff, but could be set lower if desired */ -#define MAX_KEY_ID 0x7e +#define MAX_KEY_LEN 0xffff +#define MAX_VALUE_LEN 0xffff + +/* Maximum value that can be used for a key_id. This is limited by the format + * to 0xffe (0xfff indicates end/unwritten space) + */ +#define MAX_KEY_ID 0x0ffe + +#define REGION_FLAG_SECOND 0x8000 // First (0) or second (1) region +#define REGION_FLAG_ACTIVE 0x4000 // Stale (0) or active (1) region +#define REGION_MASK_SIZE 0x0fff // Region size in sectors + +#define ENTRY_FLAG_ALIVE 0x8000 // Deleted (0) or active (1) +#define ENTRY_FLAG_INVALID 0x4000 // Valid (0) or invalid (1) entry +#define ENTRY_FLAG_VALUE 0x2000 // Key (0) or value (1) +#define ENTRY_FLAG_BINARY 0x1000 // Text (0) or binary (1) data + +#define ENTRY_MASK_ID 0xfff + +#define ENTRY_ID_END 0xfff +#define ENTRY_ID_ANY 0x1000 #ifndef SYSPARAM_DEBUG #define SYSPARAM_DEBUG 0 @@ -60,19 +79,23 @@ /********************* Internal datatypes and structures *********************/ +struct region_header { + uint32_t magic; + uint16_t flags_size; + uint16_t reserved; +} __attribute__ ((packed)); + struct entry_header { - uint8_t prev_len; - uint8_t len; - uint8_t id; - uint8_t crc; + uint16_t idflags; + uint16_t len; } __attribute__ ((packed)); struct sysparam_context { uint32_t addr; struct entry_header entry; - uint64_t unused_keys[2]; + int unused_keys; size_t compactable; - uint8_t max_key_id; + uint16_t max_key_id; }; /*************************** Global variables/data ***************************/ @@ -82,26 +105,90 @@ static struct { uint32_t alt_base; uint32_t end_addr; size_t region_size; + bool force_compact; } _sysparam_info; /***************************** Internal routines *****************************/ +static inline IRAM sysparam_status_t _do_write(uint32_t addr, const void *data, size_t data_size) { + CHECK_FLASH_OP(sdk_spi_flash_write(addr, data, data_size)); + return SYSPARAM_OK; +} + +static inline IRAM sysparam_status_t _do_verify(uint32_t addr, const void *data, void *buffer, size_t len) { + CHECK_FLASH_OP(sdk_spi_flash_read(addr, buffer, len)); + if (memcmp(data, buffer, len)) { + return SYSPARAM_ERR_IO; + } + return SYSPARAM_OK; +} + +/*FIXME: Eventually, this should probably be implemented down at the SPI flash library layer, where it can just compare bytes/words straight from the SPI hardware buffer instead of allocating a whole separate temp buffer, reading chunks into that, and then doing a memcmp.. */ +static IRAM sysparam_status_t _write_and_verify(uint32_t addr, const void *data, size_t data_size) { + int i; + size_t count; + sysparam_status_t status = SYSPARAM_OK; + uint8_t *verify_buf = malloc(VERIFY_BUF_SIZE); + + if (!verify_buf) return SYSPARAM_ERR_NOMEM; + do { + status = _do_write(addr, data, data_size); + if (status != SYSPARAM_OK) break; + for (i = 0; i < data_size; i += VERIFY_BUF_SIZE) { + count = min(data_size - i, VERIFY_BUF_SIZE); + status = _do_verify(addr + i, data + i, verify_buf, count); + if (status != SYSPARAM_OK) { + debug(1, "Flash write (@ 0x%08x) verify failed!", addr); + break; + } + } + } while (false); + free(verify_buf); + return status; +} + /** Erase the sectors of a region */ -static sysparam_status_t _format_region(uint32_t addr) { +static sysparam_status_t _format_region(uint32_t addr, uint16_t num_sectors) { uint16_t sector = addr / sdk_flashchip.sector_size; int i; - for (i = 0; i < SYSPARAM_REGION_SECTORS; i++) { + for (i = 0; i < num_sectors; i++) { CHECK_FLASH_OP(sdk_spi_flash_erase_sector(sector + i)); } return SYSPARAM_OK; } -/** Write the magic value at the beginning of a region */ -static inline sysparam_status_t _write_region_header(uint32_t addr, bool active) { - uint32_t magic = active ? SYSPARAM_ACTIVE_MAGIC : SYSPARAM_STALE_MAGIC; - debug(3, "write region header (0x%08x) @ 0x%08x", magic, addr); - CHECK_FLASH_OP(sdk_spi_flash_write(addr, &magic, 4)); +/** Write the magic data at the beginning of a region */ +static inline sysparam_status_t _write_region_header(uint32_t addr, uint32_t other, bool active) { + struct region_header header; + sysparam_status_t status; + int16_t num_sectors; + + header.magic = SYSPARAM_MAGIC; + if (addr < other) { + num_sectors = (other - addr) / sdk_flashchip.sector_size; + header.flags_size = num_sectors & REGION_MASK_SIZE; + } else { + num_sectors = (addr - other) / sdk_flashchip.sector_size; + header.flags_size = num_sectors & REGION_MASK_SIZE; + header.flags_size |= REGION_FLAG_SECOND; + } + if (active) { + header.flags_size |= REGION_FLAG_ACTIVE; + } + header.reserved = 0; + + debug(3, "write region header (0x%04x) @ 0x%08x", header.flags_size, addr); + status = _write_and_verify(addr, &header, REGION_HEADER_SIZE); + if (status != SYSPARAM_OK) { + // Uh oh.. Something failed, so we don't know whether what we wrote is + // actually in the flash or not. Try to zero it out to be sure and + // return an error. + debug(3, "zero region header @ 0x%08x", addr); + memset(&header, 0, REGION_HEADER_SIZE); + _write_and_verify(addr, &header, REGION_HEADER_SIZE); + return SYSPARAM_ERR_IO; + } return SYSPARAM_OK; } @@ -122,51 +209,79 @@ static sysparam_status_t init_write_context(struct sysparam_context *ctx) { /** Search through the region for an entry matching the specified id * - * @param match_id The id to match, or 0 to match any key, or 0xff to scan + * @param match_id The id to match, or 0 to match any key, or 0xfff to scan * to the end. */ -static sysparam_status_t _find_entry(struct sysparam_context *ctx, uint8_t match_id) { - uint8_t prev_len; +static sysparam_status_t _find_entry(struct sysparam_context *ctx, uint16_t match_id, bool find_value) { + uint16_t id; - while (ctx->addr + ENTRY_SIZE(ctx->entry.len) < _sysparam_info.end_addr) { - prev_len = ctx->entry.len; - ctx->addr += ENTRY_SIZE(ctx->entry.len); + while (true) { + if (ctx->addr == _sysparam_info.cur_base) { + ctx->addr += REGION_HEADER_SIZE; + } else { + uint32_t next_addr = ctx->addr + ENTRY_SIZE(ctx->entry.len); + if (next_addr > _sysparam_info.cur_base + _sysparam_info.region_size) { + // This entry has an obviously impossible length, so we need to + // stop reading here. + // We can report this as the end of the valid entries, but then + // any future writes (to the end) will write over + // previously-written data and result in garbage. The best + // workaround is to make sure that the next write operation + // will always start with a compaction, which will leave off + // the invalid data at the end and fix the issue going forward. + debug(1, "Encountered entry with invalid length (0x%04x) @ 0x%08x (region end is 0x%08x). Truncating entries.", ctx->entry.len, ctx->addr, _sysparam_info.end_addr); + _sysparam_info.force_compact = true; + break; + } + ctx->addr = next_addr; + if (ctx->addr == _sysparam_info.cur_base + _sysparam_info.region_size) { + // This is the last entry in the available space, but it + // exactly fits. Stop reading here. + break; + } + } debug(3, "read entry header @ 0x%08x", ctx->addr); CHECK_FLASH_OP(sdk_spi_flash_read(ctx->addr, &ctx->entry, ENTRY_HEADER_SIZE)); - if (ctx->entry.prev_len != prev_len) { - // Uh oh.. This should match the entry.len field from the - // previous entry. If it doesn't, it means that field may have - // been corrupted and we don't even know if we're in the right - // place anymore. We have to bail out. - debug(1, "prev_len mismatch at 0x%08x (%d != %d)", ctx->addr, ctx->entry.prev_len, prev_len); - ctx->addr = _sysparam_info.end_addr; - return SYSPARAM_ERR_CORRUPT; + debug(3, " idflags = 0x%04x", ctx->entry.idflags); + if (ctx->entry.idflags == 0xffff) { + // 0xffff is never a valid id field, so this means we've hit the + // end and are looking at unwritten flash space from here on. + break; } - if (ctx->entry.id) { - if (!(ctx->entry.id & 0x80)) { - // Key definition - ctx->max_key_id = ctx->entry.id; - ctx->unused_keys[ctx->entry.id >> 6] |= (1 << (ctx->entry.id & 0x3f)); - if (!match_id) { - // We're looking for any key, so make this a matching key. - match_id = ctx->entry.id; + id = ctx->entry.idflags & ENTRY_MASK_ID; + if ((ctx->entry.idflags & (ENTRY_FLAG_ALIVE | ENTRY_FLAG_INVALID)) == ENTRY_FLAG_ALIVE) { + debug(3, " entry is alive and valid"); + if (!(ctx->entry.idflags & ENTRY_FLAG_VALUE)) { + debug(3, " entry is a key"); + ctx->max_key_id = id; + ctx->unused_keys++; + if (!find_value) { + if ((id == match_id) || (match_id == ENTRY_ID_ANY)) { + return SYSPARAM_OK; + } } } else { - // Value entry - ctx->unused_keys[(ctx->entry.id >> 6) & 1] &= ~(1 << (ctx->entry.id & 0x3f)); - } - if (ctx->entry.id == match_id) { - return SYSPARAM_OK; + debug(3, " entry is a value"); + ctx->unused_keys--; + if (find_value) { + if ((id == match_id) || (match_id == ENTRY_ID_ANY)) { + return SYSPARAM_OK; + } + } } + debug(3, " (not a match)"); } else { - // Deleted entry + debug(3, " entry is deleted or invalid"); ctx->compactable += ENTRY_SIZE(ctx->entry.len); } } + if (match_id == ENTRY_ID_END) { + return SYSPARAM_OK; + } ctx->entry.len = 0; - ctx->entry.id = 0; + ctx->entry.idflags = 0; return SYSPARAM_NOTFOUND; } @@ -174,18 +289,19 @@ static sysparam_status_t _find_entry(struct sysparam_context *ctx, uint8_t match static inline sysparam_status_t _read_payload(struct sysparam_context *ctx, uint8_t *buffer, size_t buffer_size) { debug(3, "read payload (%d) @ 0x%08x", min(buffer_size, ctx->entry.len), ctx->addr); CHECK_FLASH_OP(sdk_spi_flash_read(ctx->addr + ENTRY_HEADER_SIZE, buffer, min(buffer_size, ctx->entry.len))); - //FIXME: check crc return SYSPARAM_OK; } /** Find the entry corresponding to the specified key name */ -static sysparam_status_t _find_key(struct sysparam_context *ctx, const char *key, uint8_t key_len, uint8_t *buffer) { +static sysparam_status_t _find_key(struct sysparam_context *ctx, const char *key, uint16_t key_len, uint8_t *buffer) { sysparam_status_t status; + debug(3, "find key: %s", key ? key : "(null)"); while (true) { // Find the next key entry - status = _find_entry(ctx, 0); + status = _find_entry(ctx, ENTRY_ID_ANY, false); if (status != SYSPARAM_OK) return status; + debug(3, "found a key entry @ 0x%08x", ctx->addr); if (!key) { // We're looking for the next (any) key, so we're done. break; @@ -197,43 +313,70 @@ static sysparam_status_t _find_key(struct sysparam_context *ctx, const char *key // We have a match break; } + debug(3, "entry payload does not match"); + } else { + debug(3, "key length (%d) does not match (%d)", ctx->entry.len, key_len); } } + debug(3, "key match @ 0x%08x (idflags = 0x%04x)", ctx->addr, ctx->entry.idflags); return SYSPARAM_OK; } +/** Find the value entry matching the id field from a particular key */ +static inline sysparam_status_t _find_value(struct sysparam_context *ctx, uint16_t id_field) { + debug(3, "find value: 0x%04x", id_field); + return _find_entry(ctx, id_field & ENTRY_MASK_ID, true); +} + /** Write an entry at the specified address */ -static inline sysparam_status_t _write_entry(uint32_t addr, uint8_t id, const uint8_t *payload, uint8_t len, uint8_t prev_len) { +static inline sysparam_status_t _write_entry(uint32_t addr, uint16_t id, const uint8_t *payload, uint16_t len) { struct entry_header entry; + sysparam_status_t status; debug(2, "Writing entry 0x%02x @ 0x%08x", id, addr); - entry.prev_len = prev_len; + entry.idflags = id | ENTRY_FLAG_ALIVE | ENTRY_FLAG_INVALID; entry.len = len; - entry.id = id; - entry.crc = 0; //FIXME: calculate crc - debug(3, "write entry header @ 0x%08x", addr); - CHECK_FLASH_OP(sdk_spi_flash_write(addr, &entry, ENTRY_HEADER_SIZE)); + debug(3, "write initial entry header @ 0x%08x", addr); + status = _write_and_verify(addr, &entry, ENTRY_HEADER_SIZE); + if (status == SYSPARAM_ERR_IO) { + // Uh-oh.. Either the flash call failed in some way or we didn't get + // back what we wrote. This could be a problem because depending on + // how it went wrong it could screw up all reads/writes from this point + // forward. Try to salvage the on-flash structure by overwriting the + // failed header with all zeros, which (if successful) will be + // interpreted on later reads as a deleted empty-payload entry (and it + // will just skip to the next spot). + memset(&entry, 0, ENTRY_HEADER_SIZE); + debug(3, "zeroing entry header @ 0x%08x", addr); + status = _write_and_verify(addr, &entry, ENTRY_HEADER_SIZE); + if (status != SYSPARAM_OK) return status; + + // Make sure future writes skip past this zeroed bit + if (_sysparam_info.end_addr == addr) { + _sysparam_info.end_addr += ENTRY_HEADER_SIZE; + } + // We could just skip to the next space and try again, but + // unfortunately now we can't be sure there's enough space remaining to + // fit the entry, so we just have to fail this operation. Hopefully, + // at least, future requests will still succeed, though. + status = SYSPARAM_ERR_IO; + } + if (status != SYSPARAM_OK) return status; + + // If we've gotten this far, we've committed to writing the full entry. + if (_sysparam_info.end_addr == addr) { + _sysparam_info.end_addr += ENTRY_SIZE(len); + } debug(3, "write payload (%d) @ 0x%08x", len, addr + ENTRY_HEADER_SIZE); - CHECK_FLASH_OP(sdk_spi_flash_write(addr + ENTRY_HEADER_SIZE, payload, len)); + status = _write_and_verify(addr + ENTRY_HEADER_SIZE, payload, len); + if (status != SYSPARAM_OK) return status; - return SYSPARAM_OK; -} + debug(3, "set entry valid @ 0x%08x", addr); + entry.idflags &= ~ENTRY_FLAG_INVALID; + status = _write_and_verify(addr, &entry, ENTRY_HEADER_SIZE); -/** Write the "tail" entry on the end of the region - * (this entry just contains the `prev_len` field with all others set to 0xff) - */ -static inline sysparam_status_t _write_entry_tail(uint32_t addr, uint8_t prev_len) { - struct entry_header entry; - - entry.prev_len = prev_len; - entry.len = 0xff; - entry.id = 0xff; - entry.crc = 0xff; - debug(3, "write entry tail @ 0x%08x", addr); - CHECK_FLASH_OP(sdk_spi_flash_write(addr, &entry, ENTRY_HEADER_SIZE)); - - return SYSPARAM_OK; + return status; } /** Mark an entry as "deleted" so it won't be considered in future reads */ @@ -244,7 +387,7 @@ static inline sysparam_status_t _delete_entry(uint32_t addr) { debug(3, "read entry header @ 0x%08x", addr); CHECK_FLASH_OP(sdk_spi_flash_read(addr, &entry, ENTRY_HEADER_SIZE)); // Set the ID to zero to mark it as "deleted" - entry.id = 0x00; + entry.idflags &= ~ENTRY_FLAG_ALIVE; debug(3, "write entry header @ 0x%08x", addr); CHECK_FLASH_OP(sdk_spi_flash_write(addr, &entry, ENTRY_HEADER_SIZE)); @@ -263,16 +406,17 @@ static inline sysparam_status_t _delete_entry(uint32_t addr) { * automatically update *key_id to contain the ID of this key in the new * compacted result as well. */ -static sysparam_status_t _compact_params(struct sysparam_context *ctx, uint8_t *key_id) { +static sysparam_status_t _compact_params(struct sysparam_context *ctx, int *key_id) { uint32_t new_base = _sysparam_info.alt_base; sysparam_status_t status; uint32_t addr = new_base + REGION_HEADER_SIZE; - uint8_t current_key_id = 0; + uint16_t current_key_id = 0; sysparam_iter_t iter; - uint8_t prev_len = 0; + uint16_t binary_flag; + uint16_t num_sectors = _sysparam_info.region_size / sdk_flashchip.sector_size; - debug(1, "compacting region (current size %d, expect to recover %d%s bytes)...", _sysparam_info.end_addr - _sysparam_info.cur_base, ctx->compactable, (ctx->unused_keys[0] || ctx->unused_keys[1]) ? "+ (unused keys present)" : ""); - status = _format_region(new_base); + debug(1, "compacting region (current size %d, expect to recover %d%s bytes)...", _sysparam_info.end_addr - _sysparam_info.cur_base, ctx->compactable, (ctx->unused_keys > 0) ? "+ (unused keys present)" : ""); + status = _format_region(new_base, num_sectors); if (status < 0) return status; status = sysparam_iter_start(&iter); if (status < 0) return status; @@ -285,12 +429,11 @@ static sysparam_status_t _compact_params(struct sysparam_context *ctx, uint8_t * // Write the key to the new region debug(2, "writing %d key @ 0x%08x", current_key_id, addr); - status = _write_entry(addr, current_key_id, (uint8_t *)iter.key, iter.key_len, prev_len); + status = _write_entry(addr, current_key_id, (uint8_t *)iter.key, iter.key_len); if (status < 0) break; - prev_len = iter.key_len; addr += ENTRY_SIZE(iter.key_len); - if (iter.ctx->entry.id == *key_id) { + if ((iter.ctx->entry.idflags & ENTRY_MASK_ID) == *key_id) { // Update key_id to have the correct id for the compacted result *key_id = current_key_id; // Don't copy the old value, since we'll just be deleting it @@ -300,17 +443,13 @@ static sysparam_status_t _compact_params(struct sysparam_context *ctx, uint8_t * // Copy the value to the new region debug(2, "writing %d value @ 0x%08x", current_key_id, addr); - status = _write_entry(addr, current_key_id | 0x80, iter.value, iter.value_len, prev_len); + binary_flag = iter.binary ? ENTRY_FLAG_BINARY : 0; + status = _write_entry(addr, current_key_id | ENTRY_FLAG_VALUE | binary_flag, iter.value, iter.value_len); if (status < 0) break; - prev_len = iter.value_len; addr += ENTRY_SIZE(iter.value_len); } sysparam_iter_end(&iter); - if (status >= 0) { - status = _write_entry_tail(addr, prev_len); - } - // If we broke out with an error, return the error instead of continuing. if (status < 0) { debug(1, "error encountered during compacting (%d)", status); @@ -318,19 +457,19 @@ static sysparam_status_t _compact_params(struct sysparam_context *ctx, uint8_t * } // Switch to officially using the new region. - status = _write_region_header(new_base, true); + status = _write_region_header(new_base, _sysparam_info.cur_base, true); if (status < 0) return status; - status = _write_region_header(_sysparam_info.cur_base, false); + status = _write_region_header(_sysparam_info.cur_base, new_base, false); if (status < 0) return status; _sysparam_info.alt_base = _sysparam_info.cur_base; _sysparam_info.cur_base = new_base; _sysparam_info.end_addr = addr; + _sysparam_info.force_compact = false; // Fix up ctx so it doesn't point to invalid stuff memset(ctx, 0, sizeof(*ctx)); ctx->addr = addr; - ctx->entry.prev_len = prev_len; ctx->max_key_id = current_key_id; debug(1, "done compacting (current size %d)", _sysparam_info.end_addr - _sysparam_info.cur_base); @@ -340,42 +479,82 @@ static sysparam_status_t _compact_params(struct sysparam_context *ctx, uint8_t * /***************************** Public Functions ******************************/ -sysparam_status_t sysparam_init(uint32_t base_addr) { +sysparam_status_t sysparam_init(uint32_t base_addr, uint32_t top_addr) { sysparam_status_t status; - uint32_t magic0, magic1; + uint32_t addr0, addr1; + struct region_header header0, header1; struct sysparam_context ctx; + uint16_t num_sectors; - _sysparam_info.region_size = SYSPARAM_REGION_SECTORS * sdk_flashchip.sector_size; - // First, see if we can find an existing one. - debug(3, "read magic @ 0x%08x", base_addr); - CHECK_FLASH_OP(sdk_spi_flash_read(base_addr, &magic0, 4)); - debug(3, "read magic @ 0x%08x", base_addr + _sysparam_info.region_size); - CHECK_FLASH_OP(sdk_spi_flash_read(base_addr + _sysparam_info.region_size, &magic1, 4)); - if (magic0 == SYSPARAM_ACTIVE_MAGIC && magic1 == SYSPARAM_STALE_MAGIC) { - // Sysparam area found, first region is active - _sysparam_info.cur_base = base_addr; - _sysparam_info.alt_base = base_addr + _sysparam_info.region_size; - } else if (magic0 == SYSPARAM_STALE_MAGIC && magic1 == SYSPARAM_ACTIVE_MAGIC) { - // Sysparam area found, second region is active - _sysparam_info.cur_base = base_addr + _sysparam_info.region_size; - _sysparam_info.alt_base = base_addr; - } else if (magic0 == SYSPARAM_ACTIVE_MAGIC && magic1 == SYSPARAM_ACTIVE_MAGIC) { - // Both regions are marked as active. Not sure which to use. - // This can theoretically happen if something goes wrong at exactly the - // wrong time during compacting. - return SYSPARAM_ERR_CORRUPT; - } else if (magic0 == SYSPARAM_STALE_MAGIC && magic1 == SYSPARAM_STALE_MAGIC) { - // Both regions are marked as inactive. This shouldn't ever happen. - return SYSPARAM_ERR_CORRUPT; - } else { - // Looks like there's something else at that location entirely. + // Make sure we're starting at the beginning of the sector + base_addr -= (base_addr % sdk_flashchip.sector_size); + + if (!top_addr || top_addr == base_addr) { + // Only scan the specified sector, nowhere else. + top_addr = base_addr + sdk_flashchip.sector_size; + } + for (addr0 = base_addr; addr0 < top_addr; addr0 += sdk_flashchip.sector_size) { + CHECK_FLASH_OP(sdk_spi_flash_read(addr0, &header0, REGION_HEADER_SIZE)); + if (header0.magic == SYSPARAM_MAGIC) { + // Found a starting point... + break; + } + } + if (addr0 >= top_addr) { return SYSPARAM_NOTFOUND; } + // We've found a valid header at addr0. Now find the other half of the sysparam area. + num_sectors = header0.flags_size & REGION_MASK_SIZE; + + if (header0.flags_size & REGION_FLAG_SECOND) { + addr1 = addr0 - num_sectors * sdk_flashchip.sector_size; + } else { + addr1 = addr0 + num_sectors * sdk_flashchip.sector_size; + } + CHECK_FLASH_OP(sdk_spi_flash_read(addr1, &header1, REGION_HEADER_SIZE)); + + if (header1.magic == SYSPARAM_MAGIC) { + // Yay! Found the other one. Sanity-check it.. + if ((header0.flags_size & REGION_FLAG_SECOND) == (header1.flags_size & REGION_FLAG_SECOND)) { + // Hmm.. they both say they're the same region. That can't be right... + debug(1, "Found region headers @ 0x%08x and 0x%08x, but both claim to be the same region.", addr0, addr1); + return SYSPARAM_ERR_CORRUPT; + } + } else { + // Didn't find a valid header at the alternate location (which probably means something clobbered it or something went wrong at a critical point when rewriting it. Is the one we did find the active or stale one? + if (header0.flags_size & REGION_FLAG_ACTIVE) { + // Found the active one. We can work with this. Try to recreate the missing stale region... + debug(2, "Found active region header @ 0x%08x but no stale region @ 0x%08x. Trying to recreate stale region.", addr0, addr1); + status = _format_region(addr1, num_sectors); + if (status != SYSPARAM_OK) return status; + status = _write_region_header(addr1, addr0, false); + if (status != SYSPARAM_OK) return status; + } else { + // Found the stale one. We have no idea how old it is, so we shouldn't use it without some sort of confirmation/recovery. We'll have to bail for now. + debug(1, "Found stale-region header @ 0x%08x, but no active region.", addr0); + return SYSPARAM_ERR_CORRUPT; + } + } + // At this point we have confirmed valid regions at addr0 and addr1. + + _sysparam_info.region_size = num_sectors * sdk_flashchip.sector_size; + if (header0.flags_size & REGION_FLAG_ACTIVE) { + _sysparam_info.cur_base = addr0; + _sysparam_info.alt_base = addr1; + debug(3, "Active region @ 0x%08x (0x%04x). Stale region @ 0x%08x (0x%04x).", addr0, header0.flags_size, addr1, header1.flags_size); + + } else { + _sysparam_info.cur_base = addr1; + _sysparam_info.alt_base = addr0; + debug(3, "Active region @ 0x%08x (0x%04x). Stale region @ 0x%08x (0x%04x).", addr1, header1.flags_size, addr0, header0.flags_size); + } + // Find the actual end _sysparam_info.end_addr = _sysparam_info.cur_base + _sysparam_info.region_size; + _sysparam_info.force_compact = false; _init_context(&ctx); - status = _find_entry(&ctx, 0xff); + status = _find_entry(&ctx, ENTRY_ID_END, false); if (status < 0) { _sysparam_info.cur_base = 0; _sysparam_info.alt_base = 0; @@ -389,17 +568,24 @@ sysparam_status_t sysparam_init(uint32_t base_addr) { return SYSPARAM_OK; } -sysparam_status_t sysparam_create_area(uint32_t base_addr, bool force) { +sysparam_status_t sysparam_create_area(uint32_t base_addr, uint16_t num_sectors, bool force) { + size_t region_size; sysparam_status_t status; uint32_t buffer[SCAN_BUFFER_SIZE]; uint32_t addr; int i; - size_t region_size = SYSPARAM_REGION_SECTORS * sdk_flashchip.sector_size; + + // Convert "number of sectors for area" into "number of sectors per region" + if (num_sectors < 1 || (num_sectors & 1)) { + return SYSPARAM_ERR_BADVALUE; + } + num_sectors >>= 1; + region_size = num_sectors * sdk_flashchip.sector_size; if (!force) { // First, scan through the area and make sure it's actually empty and // we're not going to be clobbering something else important. - for (addr = base_addr; addr < base_addr + SYSPARAM_REGION_SECTORS * 2 * sdk_flashchip.sector_size; addr += SCAN_BUFFER_SIZE) { + for (addr = base_addr; addr < base_addr + region_size * 2; addr += SCAN_BUFFER_SIZE) { debug(3, "read %d words @ 0x%08x", SCAN_BUFFER_SIZE, addr); CHECK_FLASH_OP(sdk_spi_flash_read(addr, buffer, SCAN_BUFFER_SIZE * 4)); for (i = 0; i < SCAN_BUFFER_SIZE; i++) { @@ -417,21 +603,19 @@ sysparam_status_t sysparam_create_area(uint32_t base_addr, bool force) { // `sysparam_init()` afterwards. memset(&_sysparam_info, 0, sizeof(_sysparam_info)); } - status = _format_region(base_addr); + status = _format_region(base_addr, num_sectors); if (status < 0) return status; - status = _format_region(base_addr + region_size); + status = _format_region(base_addr + region_size, num_sectors); if (status < 0) return status; - status = _write_entry_tail(base_addr + REGION_HEADER_SIZE, 0); + status = _write_region_header(base_addr, base_addr + region_size, true); if (status < 0) return status; - status = _write_region_header(base_addr + region_size, false); - if (status < 0) return status; - status = _write_region_header(base_addr, true); + status = _write_region_header(base_addr + region_size, base_addr, false); if (status < 0) return status; return SYSPARAM_OK; } -sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t *actual_length) { +sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t *actual_length, bool *is_binary) { struct sysparam_context ctx; sysparam_status_t status; size_t key_len = strlen(key); @@ -448,7 +632,7 @@ sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t * if (status != SYSPARAM_OK) break; // Find the associated value - status = _find_entry(&ctx, ctx.entry.id | 0x80); + status = _find_value(&ctx, ctx.entry.idflags); if (status != SYSPARAM_OK) break; newbuf = realloc(buffer, ctx.entry.len + 1); @@ -467,6 +651,7 @@ sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t * *destptr = buffer; if (actual_length) *actual_length = ctx.entry.len; + if (is_binary) *is_binary = (bool)(ctx.entry.idflags & ENTRY_FLAG_BINARY); return SYSPARAM_OK; } while (false); @@ -475,7 +660,7 @@ sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t * return status; } -sysparam_status_t sysparam_get_data_static(const char *key, uint8_t *buffer, size_t buffer_size, size_t *actual_length) { +sysparam_status_t sysparam_get_data_static(const char *key, uint8_t *buffer, size_t buffer_size, size_t *actual_length, bool *is_binary) { struct sysparam_context ctx; sysparam_status_t status = SYSPARAM_OK; size_t key_len = strlen(key); @@ -491,19 +676,33 @@ sysparam_status_t sysparam_get_data_static(const char *key, uint8_t *buffer, siz _init_context(&ctx); status = _find_key(&ctx, key, key_len, buffer); if (status != SYSPARAM_OK) return status; - status = _find_entry(&ctx, ctx.entry.id | 0x80); + status = _find_value(&ctx, ctx.entry.idflags); if (status != SYSPARAM_OK) return status; status = _read_payload(&ctx, buffer, buffer_size); if (status != SYSPARAM_OK) return status; if (actual_length) *actual_length = ctx.entry.len; + if (is_binary) *is_binary = (bool)(ctx.entry.idflags & ENTRY_FLAG_BINARY); return SYSPARAM_OK; } sysparam_status_t sysparam_get_string(const char *key, char **destptr) { + bool is_binary; + sysparam_status_t status; + uint8_t *buf; + + status = sysparam_get_data(key, &buf, NULL, &is_binary); + if (status != SYSPARAM_OK) return status; + if (is_binary) { + // Value was saved as binary data, which means we shouldn't try to + // interpret it as a string. + free(buf); + return SYSPARAM_PARSEFAILED; + } // `sysparam_get_data` will zero-terminate the result as a matter of course, // so no need to do that here. - return sysparam_get_data(key, (uint8_t **)destptr, NULL); + *destptr = (char *)buf; + return SYSPARAM_OK; } sysparam_status_t sysparam_get_int(const char *key, int32_t *result) { @@ -528,29 +727,23 @@ sysparam_status_t sysparam_get_int(const char *key, int32_t *result) { sysparam_status_t sysparam_get_bool(const char *key, bool *result) { char *buffer; - int i; sysparam_status_t status; status = sysparam_get_string(key, &buffer); if (status != SYSPARAM_OK) return status; do { - for (i = 0; buffer[i]; i++) { - // Quick-and-dirty tolower(). Not perfect, but works for our - // purposes, and avoids needing to pull in additional libc stuff. - if (buffer[i] >= 0x41) buffer[i] |= 0x20; - } - if (!strcmp(buffer, "y") || - !strcmp(buffer, "yes") || - !strcmp(buffer, "t") || - !strcmp(buffer, "true") || + if (!strcasecmp(buffer, "y") || + !strcasecmp(buffer, "yes") || + !strcasecmp(buffer, "t") || + !strcasecmp(buffer, "true") || !strcmp(buffer, "1")) { *result = true; break; } - if (!strcmp(buffer, "n") || - !strcmp(buffer, "no") || - !strcmp(buffer, "f") || - !strcmp(buffer, "false") || + if (!strcasecmp(buffer, "n") || + !strcasecmp(buffer, "no") || + !strcasecmp(buffer, "f") || + !strcasecmp(buffer, "false") || !strcmp(buffer, "0")) { *result = false; break; @@ -562,22 +755,24 @@ sysparam_status_t sysparam_get_bool(const char *key, bool *result) { return status; } -sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_t value_len) { +sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_t value_len, bool is_binary) { struct sysparam_context ctx; struct sysparam_context write_ctx; sysparam_status_t status = SYSPARAM_OK; - uint8_t key_len = strlen(key); + uint16_t key_len = strlen(key); uint8_t *buffer; uint8_t *newbuf; size_t free_space; size_t needed_space; bool free_value = false; - uint8_t key_id = 0; + int key_id = -1; uint32_t old_value_addr = 0; + uint16_t binary_flag; if (!_sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; if (!key_len) return SYSPARAM_ERR_BADVALUE; - if (value_len > 0xff) return SYSPARAM_ERR_BADVALUE; + if (key_len > MAX_KEY_LEN) return SYSPARAM_ERR_BADVALUE; + if (value_len > MAX_VALUE_LEN) return SYSPARAM_ERR_BADVALUE; if (!value) value_len = 0; @@ -603,17 +798,19 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ status = _find_key(&ctx, key, key_len, buffer); if (status == SYSPARAM_OK) { // Key already exists, see if there's a current value. - key_id = ctx.entry.id; - status = _find_entry(&ctx, key_id | 0x80); + key_id = ctx.entry.idflags & ENTRY_MASK_ID; + status = _find_value(&ctx, key_id); if (status == SYSPARAM_OK) { old_value_addr = ctx.addr; } } if (status < 0) break; + binary_flag = is_binary ? ENTRY_FLAG_BINARY : 0; + if (value_len) { if (old_value_addr) { - if (ctx.entry.len == value_len) { + if ((ctx.entry.idflags & ENTRY_FLAG_BINARY) == binary_flag && ctx.entry.len == value_len) { // Are we trying to write the same value that's already there? if (value_len > key_len) { newbuf = realloc(buffer, value_len); @@ -624,12 +821,8 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ buffer = newbuf; } status = _read_payload(&ctx, buffer, value_len); - if (status == SYSPARAM_ERR_CORRUPT) { - // If the CRC check failed, don't worry about it. We're - // going to be deleting this entry anyway. - } else if (status < 0) { - break; - } else if (!memcmp(buffer, value, value_len)) { + if (status < 0) break; + if (!memcmp(buffer, value, value_len)) { // Yup, it's a match! No need to do anything further, // just leave the current value as-is. status = SYSPARAM_OK; @@ -645,9 +838,9 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ // Append new value to the end, but first make sure we have enough // space. - free_space = _sysparam_info.cur_base + _sysparam_info.region_size - _sysparam_info.end_addr - 4; + free_space = _sysparam_info.cur_base + _sysparam_info.region_size - _sysparam_info.end_addr; needed_space = ENTRY_SIZE(value_len); - if (!key_id) { + if (key_id < 0) { // We did not find a previous key entry matching this key. We // will need to add a key entry as well. key_len = strlen(key); @@ -657,13 +850,13 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ // Can we compact things? // First, scan all remaining entries up to the end so we can // get a reasonably accurate "compactable" reading. - _find_entry(&ctx, 0xff); + _find_entry(&ctx, ENTRY_ID_END, false); if (needed_space <= free_space + ctx.compactable) { // We should be able to get enough space by compacting. status = _compact_params(&ctx, &key_id); if (status < 0) break; old_value_addr = 0; - } else if (ctx.unused_keys[0] || ctx.unused_keys[1]) { + } else if (ctx.unused_keys > 0) { // Compacting will gain more space than expected, because // there are some keys that can be omitted too, but we // don't know exactly how much that will gain, so all we @@ -672,7 +865,7 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ if (status < 0) break; old_value_addr = 0; } - free_space = _sysparam_info.cur_base + _sysparam_info.region_size - _sysparam_info.end_addr - 4; + free_space = _sysparam_info.cur_base + _sysparam_info.region_size - _sysparam_info.end_addr; } if (needed_space > free_space) { // Nothing we can do here.. We're full. @@ -683,17 +876,14 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ break; } - init_write_context(&write_ctx); - - if (!key_id) { + if (key_id < 0) { // We need to write a key entry for a new key. // If we didn't find the key, then we already know _find_entry // has gone through the entire contents, and thus // ctx.max_key_id has the largest key_id found in the whole // region. - key_id = ctx.max_key_id + 1; - if (key_id > MAX_KEY_ID) { - if (ctx.unused_keys[0] || ctx.unused_keys[1]) { + if (ctx.max_key_id >= MAX_KEY_ID) { + if (ctx.unused_keys > 0) { status = _compact_params(&ctx, &key_id); if (status < 0) break; old_value_addr = 0; @@ -703,28 +893,40 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ break; } } - status = _write_entry(write_ctx.addr, key_id, (uint8_t *)key, key_len, write_ctx.entry.prev_len); + } + + if (_sysparam_info.force_compact) { + // We didn't need to compact above, but due to previously + // detected inconsistencies, we should compact anyway before + // writing anything new, so do that. + status = _compact_params(&ctx, &key_id); + if (status < 0) break; + } + + init_write_context(&write_ctx); + + if (key_id < 0) { + // Write a new key entry + key_id = ctx.max_key_id + 1; + status = _write_entry(write_ctx.addr, key_id, (uint8_t *)key, key_len); if (status < 0) break; write_ctx.addr += ENTRY_SIZE(key_len); - write_ctx.entry.prev_len = key_len; } // Write new value - status = _write_entry(write_ctx.addr, key_id | 0x80, value, value_len, write_ctx.entry.prev_len); + status = _write_entry(write_ctx.addr, key_id | ENTRY_FLAG_VALUE | binary_flag, value, value_len); if (status < 0) break; write_ctx.addr += ENTRY_SIZE(value_len); - status = _write_entry_tail(write_ctx.addr, value_len); - if (status < 0) break; _sysparam_info.end_addr = write_ctx.addr; } - debug(1, "new addr is 0x%08x (%d bytes remaining)", _sysparam_info.end_addr, _sysparam_info.cur_base + _sysparam_info.region_size - _sysparam_info.end_addr - 4); - - // Delete old value (if present) by setting it's id to 0x00 + // Delete old value (if present) by clearing its "alive" flag if (old_value_addr) { status = _delete_entry(old_value_addr); if (status < 0) break; } + + debug(1, "New addr is 0x%08x (%d bytes remaining)", _sysparam_info.end_addr, _sysparam_info.cur_base + _sysparam_info.region_size - _sysparam_info.end_addr); } while (false); if (free_value) free((void *)value); @@ -733,7 +935,7 @@ sysparam_status_t sysparam_set_data(const char *key, const uint8_t *value, size_ } sysparam_status_t sysparam_set_string(const char *key, const char *value) { - return sysparam_set_data(key, (const uint8_t *)value, strlen(value)); + return sysparam_set_data(key, (const uint8_t *)value, strlen(value), false); } sysparam_status_t sysparam_set_int(const char *key, int32_t value) { @@ -741,7 +943,7 @@ sysparam_status_t sysparam_set_int(const char *key, int32_t value) { int len; len = snprintf((char *)buffer, 12, "%d", value); - return sysparam_set_data(key, buffer, len); + return sysparam_set_data(key, buffer, len, false); } sysparam_status_t sysparam_set_bool(const char *key, bool value) { @@ -755,7 +957,7 @@ sysparam_status_t sysparam_set_bool(const char *key, bool value) { } buf[0] = value ? 'y' : 'n'; - return sysparam_set_data(key, buf, 1); + return sysparam_set_data(key, buf, 1, false); } sysparam_status_t sysparam_iter_start(sysparam_iter_t *iter) { @@ -794,7 +996,7 @@ sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter) { if (status != SYSPARAM_OK) return status; memcpy(&value_ctx, ctx, sizeof(value_ctx)); - status = _find_entry(&value_ctx, ctx->entry.id | 0x80); + status = _find_value(&value_ctx, ctx->entry.idflags); if (status < 0) return status; if (status == SYSPARAM_NOTFOUND) continue; @@ -821,7 +1023,13 @@ sysparam_status_t sysparam_iter_next(sysparam_iter_t *iter) { // Null-terminate the value (just in case) iter->value[value_ctx.entry.len] = 0; iter->value_len = value_ctx.entry.len; - debug(2, "iter_next: (0x%08x) '%s' = (0x%08x) '%s' (%d)", ctx->addr, iter->key, value_ctx.addr, iter->value, iter->value_len); + if (value_ctx.entry.idflags & ENTRY_FLAG_BINARY) { + iter->binary = true; + debug(2, "iter_next: (0x%08x) '%s' = (0x%08x) (%d)", ctx->addr, iter->key, value_ctx.addr, iter->value_len); + } else { + iter->binary = false; + debug(2, "iter_next: (0x%08x) '%s' = (0x%08x) '%s' (%d)", ctx->addr, iter->key, value_ctx.addr, iter->value, iter->value_len); + } return SYSPARAM_OK; } diff --git a/examples/sysparam_editor/Makefile b/examples/sysparam_editor/Makefile index a7843f3..a774b68 100644 --- a/examples/sysparam_editor/Makefile +++ b/examples/sysparam_editor/Makefile @@ -8,7 +8,7 @@ include ../../common.mk # `make dump-flash` can be used to view the current contents of the sysparam # regions in flash. dump-flash: - esptool.py read_flash 0x1fa000 4096 r1.bin + esptool.py read_flash 0x1f8000 8192 r1.bin hexdump -C r1.bin - esptool.py read_flash 0x1fb000 4096 r2.bin + esptool.py read_flash 0x1fa000 8192 r2.bin hexdump -C r2.bin diff --git a/examples/sysparam_editor/sysparam_editor.c b/examples/sysparam_editor/sysparam_editor.c index ac8fee1..d290eab 100644 --- a/examples/sysparam_editor/sysparam_editor.c +++ b/examples/sysparam_editor/sysparam_editor.c @@ -5,11 +5,19 @@ #include #include -#define CMD_BUF_SIZE 512 +#define CMD_BUF_SIZE 5000 -// This is just below the upper-4 sdk-reserved sectors for a 16mbit flash -// Note that the sysparam area will take up two sectors (0x1FAxxx and 0x1FBxxx) -#define SYSPARAM_ADDR 0x1fa000 +// Number of (4K) sectors that make up a sysparam area. Total sysparam data +// cannot be larger than half this amount. +// Note that if there is already a sysparam area created with a different size, +// that will continue to be used (if it can be found). This value is only used +// when creating/reformatting the sysparam area. +#define SYSPARAM_SECTORS 4 + +// This places the sysparam region just below the upper-4 sdk-reserved sectors +// for a 16mbit flash +#define FLASH_TOP 0x1fc000 +#define SYSPARAM_ADDR (FLASH_TOP - (SYSPARAM_SECTORS * 4096)) const int status_base = -6; const char *status_messages[] = { @@ -27,11 +35,12 @@ const char *status_messages[] = { void usage(void) { printf( "Available commands:\n" - " ? -- Query the value of \n" - " = -- Set to \n" - " dump -- Show all currently set keys/values\n" - " reformat -- Reinitialize (clear) the sysparam area\n" - " help -- Show this help screen\n" + " ? -- Query the value of \n" + " = -- Set to text \n" + " : -- Set to binary value represented as hex\n" + " dump -- Show all currently set keys/values\n" + " reformat -- Reinitialize (clear) the sysparam area\n" + " help -- Show this help screen\n" ); } @@ -63,6 +72,23 @@ size_t tty_readline(char *buffer, size_t buf_size, bool echo) { return i; } +void print_text_value(char *key, char *value) { + printf(" '%s' = '%s'\n", key, value); +} + +void print_binary_value(char *key, uint8_t *value, size_t len) { + size_t i; + + printf(" %s:", key); + for (i = 0; i < len; i++) { + if (!(i & 0x0f)) { + printf("\n "); + } + printf(" %02x", value[i]); + } + printf("\n"); +} + sysparam_status_t dump_params(void) { sysparam_status_t status; sysparam_iter_t iter; @@ -72,7 +98,11 @@ sysparam_status_t dump_params(void) { while (true) { status = sysparam_iter_next(&iter); if (status != SYSPARAM_OK) break; - printf(" %s=%s\n", iter.key, iter.value); + if (!iter.binary) { + print_text_value(iter.key, (char *)iter.value); + } else { + print_binary_value(iter.key, iter.value, iter.value_len); + } } sysparam_iter_end(&iter); @@ -85,28 +115,69 @@ sysparam_status_t dump_params(void) { } } +uint8_t *parse_hexdata(char *string, size_t *result_length) { + size_t string_len = strlen(string); + uint8_t *buf = malloc(string_len / 2); + uint8_t c; + int i, j; + bool digit = false; + + j = 0; + for (i = 0; string[i]; i++) { + c = string[i]; + if (c >= 0x30 && c <= 0x39) { + c &= 0x0f; + } else if (c >= 0x41 && c <= 0x46) { + c -= 0x37; + } else if (c >= 0x61 && c <= 0x66) { + c -= 0x57; + } else if (c == ' ') { + continue; + } else { + free(buf); + return NULL; + } + if (!digit) { + buf[j] = c << 4; + } else { + buf[j++] |= c; + } + digit = !digit; + } + if (digit) { + free(buf); + return NULL; + } + *result_length = j; + return buf; +} + void sysparam_editor_task(void *pvParameters) { char *cmd_buffer = malloc(CMD_BUF_SIZE); sysparam_status_t status; char *value; + uint8_t *bin_value; size_t len; + uint8_t *data; if (!cmd_buffer) { printf("ERROR: Cannot allocate command buffer!\n"); return; } + printf("\nWelcome to the system parameter editor! Enter 'help' for more information.\n\n"); + // NOTE: Eventually, this initialization part will be done automatically on // system startup, so the app won't need to do it. printf("Initializing sysparam...\n"); - status = sysparam_init(SYSPARAM_ADDR); + status = sysparam_init(SYSPARAM_ADDR, FLASH_TOP); printf("(status %d)\n", status); if (status == SYSPARAM_NOTFOUND) { printf("Trying to create new sysparam area...\n"); - status = sysparam_create_area(SYSPARAM_ADDR, false); + status = sysparam_create_area(SYSPARAM_ADDR, SYSPARAM_SECTORS, false); printf("(status %d)\n", status); if (status == SYSPARAM_OK) { - status = sysparam_init(SYSPARAM_ADDR); + status = sysparam_init(SYSPARAM_ADDR, 0); printf("(status %d)\n", status); } } @@ -121,23 +192,40 @@ void sysparam_editor_task(void *pvParameters) { printf("Querying '%s'...\n", cmd_buffer); status = sysparam_get_string(cmd_buffer, &value); if (status == SYSPARAM_OK) { - printf(" '%s' = '%s'\n", cmd_buffer, value); + print_text_value(cmd_buffer, value); + free(value); + } else if (status == SYSPARAM_PARSEFAILED) { + // This means it's actually a binary value + status = sysparam_get_data(cmd_buffer, &bin_value, &len, NULL); + if (status == SYSPARAM_OK) { + print_binary_value(cmd_buffer, bin_value, len); + free(value); + } } - free(value); } else if ((value = strchr(cmd_buffer, '='))) { *value++ = 0; printf("Setting '%s' to '%s'...\n", cmd_buffer, value); status = sysparam_set_string(cmd_buffer, value); + } else if ((value = strchr(cmd_buffer, ':'))) { + *value++ = 0; + data = parse_hexdata(value, &len); + if (value) { + printf("Setting '%s' to binary data...\n", cmd_buffer); + status = sysparam_set_data(cmd_buffer, data, len, true); + free(data); + } else { + printf("Error: Unable to parse hex data\n"); + } } else if (!strcmp(cmd_buffer, "dump")) { printf("Dumping all params:\n"); status = dump_params(); } else if (!strcmp(cmd_buffer, "reformat")) { printf("Re-initializing region...\n"); - status = sysparam_create_area(SYSPARAM_ADDR, true); + status = sysparam_create_area(SYSPARAM_ADDR, SYSPARAM_SECTORS, true); if (status == SYSPARAM_OK) { // We need to re-init after wiping out the region we've been // using. - status = sysparam_init(SYSPARAM_ADDR); + status = sysparam_init(SYSPARAM_ADDR, 0); } } else if (!strcmp(cmd_buffer, "help")) { usage(); From 66589f5d3f83dbab745cfedb79680aaac583ac6b Mon Sep 17 00:00:00 2001 From: Alex Stewart Date: Mon, 9 May 2016 20:47:30 -0700 Subject: [PATCH 10/13] Add sysparam_get_info function --- core/include/sysparam.h | 20 ++++++++++++++++++++ core/sysparam.c | 8 ++++++++ 2 files changed, 28 insertions(+) diff --git a/core/include/sysparam.h b/core/include/sysparam.h index 9854f60..9260e73 100644 --- a/core/include/sysparam.h +++ b/core/include/sysparam.h @@ -3,6 +3,10 @@ #include +#ifndef DEFAULT_SYSPARAM_SECTORS +#define DEFAULT_SYSPARAM_SECTORS 4 +#endif + /** @file sysparam.h * * Read/write "system parameters" to persistent flash. @@ -112,6 +116,22 @@ sysparam_status_t sysparam_init(uint32_t base_addr, uint32_t top_addr); */ sysparam_status_t sysparam_create_area(uint32_t base_addr, uint16_t num_sectors, bool force); +/** Get the start address and size of the currently active sysparam area + * + * Fills in `base_addr` and `num_sectors` with the location and size of the + * currently active sysparam area. The returned values correspond to the + * arguments passed to the sysparam_create_area() call when the area was + * originally created. + * + * @param[out] base_addr The flash address at which the sysparam area starts + * @param[out] num_sectors The number of flash sectors used by the sysparam + * area + * + * @retval ::SYSPARAM_OK Completed successfully + * @retval ::SYSPARAM_ERR_NOINIT No current sysparam area is active + */ +sysparam_status_t sysparam_get_info(uint32_t *base_addr, uint32_t *num_sectors); + /** Get the value associated with a key * * This is the core "get value" function. It will retrieve the value for the diff --git a/core/sysparam.c b/core/sysparam.c index 0aa2cb2..cb08ac3 100644 --- a/core/sysparam.c +++ b/core/sysparam.c @@ -615,6 +615,14 @@ sysparam_status_t sysparam_create_area(uint32_t base_addr, uint16_t num_sectors, return SYSPARAM_OK; } +sysparam_status_t sysparam_get_info(uint32_t *base_addr, uint32_t *num_sectors) { + if (!_sysparam_info.cur_base) return SYSPARAM_ERR_NOINIT; + + *base_addr = min(_sysparam_info.cur_base, _sysparam_info.alt_base); + *num_sectors = (_sysparam_info.region_size / sdk_flashchip.sector_size) * 2; + return SYSPARAM_OK; +} + sysparam_status_t sysparam_get_data(const char *key, uint8_t **destptr, size_t *actual_length, bool *is_binary) { struct sysparam_context ctx; sysparam_status_t status; From 23f13db3aedeb7c6e8207b5467cd9399fbb16f57 Mon Sep 17 00:00:00 2001 From: Alex Stewart Date: Mon, 9 May 2016 20:48:06 -0700 Subject: [PATCH 11/13] Add sysparam initialization to app_main.c --- core/app_main.c | 17 +++++++++ examples/sysparam_editor/sysparam_editor.c | 43 ++++++++-------------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/core/app_main.c b/core/app_main.c index 7a9bf8d..34a39b9 100644 --- a/core/app_main.c +++ b/core/app_main.c @@ -25,6 +25,7 @@ #include "espressif/esp_common.h" #include "sdk_internal.h" +#include "sysparam.h" /* This is not declared in any header file (but arguably should be) */ @@ -180,6 +181,8 @@ void IRAM sdk_user_start(void) { uint32_t cksum_len; uint32_t cksum_value; uint32_t ic_flash_addr; + uint32_t sysparam_addr; + sysparam_status_t status; SPI(0).USER0 |= SPI_USER0_CS_SETUP; sdk_SPIRead(0, buf32, 4); @@ -245,6 +248,20 @@ void IRAM sdk_user_start(void) { } memcpy(&sdk_g_ic.s, buf32, sizeof(struct sdk_g_ic_saved_st)); + // By default, put the sysparam region just below the config sectors at the + // top of the flash space + sysparam_addr = flash_size - (4 + DEFAULT_SYSPARAM_SECTORS) * sdk_flashchip.sector_size; + status = sysparam_init(sysparam_addr, flash_size); + if (status == SYSPARAM_NOTFOUND) { + status = sysparam_create_area(sysparam_addr, DEFAULT_SYSPARAM_SECTORS, false); + if (status == SYSPARAM_OK) { + status = sysparam_init(sysparam_addr, 0); + } + } + if (status != SYSPARAM_OK) { + printf("WARNING: Could not initialize sysparams (%d)!\n", status); + } + user_start_phase2(); } diff --git a/examples/sysparam_editor/sysparam_editor.c b/examples/sysparam_editor/sysparam_editor.c index d290eab..22f6190 100644 --- a/examples/sysparam_editor/sysparam_editor.c +++ b/examples/sysparam_editor/sysparam_editor.c @@ -5,20 +5,10 @@ #include #include +#include + #define CMD_BUF_SIZE 5000 -// Number of (4K) sectors that make up a sysparam area. Total sysparam data -// cannot be larger than half this amount. -// Note that if there is already a sysparam area created with a different size, -// that will continue to be used (if it can be found). This value is only used -// when creating/reformatting the sysparam area. -#define SYSPARAM_SECTORS 4 - -// This places the sysparam region just below the upper-4 sdk-reserved sectors -// for a 16mbit flash -#define FLASH_TOP 0x1fc000 -#define SYSPARAM_ADDR (FLASH_TOP - (SYSPARAM_SECTORS * 4096)) - const int status_base = -6; const char *status_messages[] = { "SYSPARAM_ERR_NOMEM", @@ -159,6 +149,7 @@ void sysparam_editor_task(void *pvParameters) { uint8_t *bin_value; size_t len; uint8_t *data; + uint32_t base_addr, num_sectors; if (!cmd_buffer) { printf("ERROR: Cannot allocate command buffer!\n"); @@ -167,21 +158,17 @@ void sysparam_editor_task(void *pvParameters) { printf("\nWelcome to the system parameter editor! Enter 'help' for more information.\n\n"); - // NOTE: Eventually, this initialization part will be done automatically on - // system startup, so the app won't need to do it. - printf("Initializing sysparam...\n"); - status = sysparam_init(SYSPARAM_ADDR, FLASH_TOP); - printf("(status %d)\n", status); - if (status == SYSPARAM_NOTFOUND) { - printf("Trying to create new sysparam area...\n"); - status = sysparam_create_area(SYSPARAM_ADDR, SYSPARAM_SECTORS, false); - printf("(status %d)\n", status); - if (status == SYSPARAM_OK) { - status = sysparam_init(SYSPARAM_ADDR, 0); - printf("(status %d)\n", status); - } + status = sysparam_get_info(&base_addr, &num_sectors); + if (status == SYSPARAM_OK) { + printf("[current sysparam region is at 0x%08x (%d sectors)]\n", base_addr, num_sectors); + } else { + printf("[NOTE: No current sysparam region (initialization problem during boot?)]\n"); + // Default to the same place/size as the normal system initialization + // stuff, so if the user uses this utility to reformat it, it will put + // it somewhere the system will find it later + num_sectors = DEFAULT_SYSPARAM_SECTORS; + base_addr = sdk_flashchip.chip_size - (4 + num_sectors) * sdk_flashchip.sector_size; } - while (true) { printf("==> "); len = tty_readline(cmd_buffer, CMD_BUF_SIZE, true); @@ -221,11 +208,11 @@ void sysparam_editor_task(void *pvParameters) { status = dump_params(); } else if (!strcmp(cmd_buffer, "reformat")) { printf("Re-initializing region...\n"); - status = sysparam_create_area(SYSPARAM_ADDR, SYSPARAM_SECTORS, true); + status = sysparam_create_area(base_addr, num_sectors, true); if (status == SYSPARAM_OK) { // We need to re-init after wiping out the region we've been // using. - status = sysparam_init(SYSPARAM_ADDR, 0); + status = sysparam_init(base_addr, 0); } } else if (!strcmp(cmd_buffer, "help")) { usage(); From 71f4609cb56f6fb8de3f8d18f27080e81061d7c0 Mon Sep 17 00:00:00 2001 From: luisbebop Date: Sat, 4 Jun 2016 16:35:40 -0300 Subject: [PATCH 12/13] Add connect task to the makefile --- common.mk | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common.mk b/common.mk index 172fbe2..6f9e0d7 100644 --- a/common.mk +++ b/common.mk @@ -220,6 +220,9 @@ size: $(PROGRAM_OUT) test: flash $(FILTEROUTPUT) --port $(ESPPORT) --baud 115200 --elf $(PROGRAM_OUT) + +connect: + $(FILTEROUTPUT) --port $(ESPPORT) --baud 115200 --elf $(PROGRAM_OUT) # the rebuild target is written like this so it can be run in a parallel build # environment without causing weird side effects From 145479f0955e637477f71272bf443a8babe2eb27 Mon Sep 17 00:00:00 2001 From: luisbebop Date: Sat, 4 Jun 2016 16:36:16 -0300 Subject: [PATCH 13/13] Add websocket example that supports permessage-deflate extension --- examples/websocket_mbedtls/Makefile | 4 + examples/websocket_mbedtls/adler32.c | 78 +++ examples/websocket_mbedtls/cert.c | 44 ++ examples/websocket_mbedtls/conn.c | 229 ++++++++ examples/websocket_mbedtls/conn.h | 11 + examples/websocket_mbedtls/crc32.c | 64 +++ examples/websocket_mbedtls/defl_static.c | 308 +++++++++++ examples/websocket_mbedtls/defl_static.h | 14 + examples/websocket_mbedtls/genlz77.c | 116 ++++ .../include/mbedtls/config.h | 27 + examples/websocket_mbedtls/tinf.h | 102 ++++ examples/websocket_mbedtls/tinfgzip.c | 124 +++++ examples/websocket_mbedtls/tinflate.c | 518 ++++++++++++++++++ examples/websocket_mbedtls/tinfzlib.c | 101 ++++ examples/websocket_mbedtls/util.h | 28 + .../websocket_mbedtls/websocket_mbedtls.c | 108 ++++ examples/websocket_mbedtls/ws.c | 270 +++++++++ examples/websocket_mbedtls/ws.h | 27 + lwip/include/lwipopts.h | 3 +- 19 files changed, 2175 insertions(+), 1 deletion(-) create mode 100644 examples/websocket_mbedtls/Makefile create mode 100644 examples/websocket_mbedtls/adler32.c create mode 100644 examples/websocket_mbedtls/cert.c create mode 100644 examples/websocket_mbedtls/conn.c create mode 100644 examples/websocket_mbedtls/conn.h create mode 100644 examples/websocket_mbedtls/crc32.c create mode 100644 examples/websocket_mbedtls/defl_static.c create mode 100644 examples/websocket_mbedtls/defl_static.h create mode 100644 examples/websocket_mbedtls/genlz77.c create mode 100644 examples/websocket_mbedtls/include/mbedtls/config.h create mode 100644 examples/websocket_mbedtls/tinf.h create mode 100644 examples/websocket_mbedtls/tinfgzip.c create mode 100644 examples/websocket_mbedtls/tinflate.c create mode 100644 examples/websocket_mbedtls/tinfzlib.c create mode 100644 examples/websocket_mbedtls/util.h create mode 100644 examples/websocket_mbedtls/websocket_mbedtls.c create mode 100644 examples/websocket_mbedtls/ws.c create mode 100644 examples/websocket_mbedtls/ws.h diff --git a/examples/websocket_mbedtls/Makefile b/examples/websocket_mbedtls/Makefile new file mode 100644 index 0000000..3570a79 --- /dev/null +++ b/examples/websocket_mbedtls/Makefile @@ -0,0 +1,4 @@ +PROGRAM=websocket_mbedtls +COMPONENTS = FreeRTOS lwip core extras/mbedtls + +include ../../common.mk diff --git a/examples/websocket_mbedtls/adler32.c b/examples/websocket_mbedtls/adler32.c new file mode 100644 index 0000000..f99b2d7 --- /dev/null +++ b/examples/websocket_mbedtls/adler32.c @@ -0,0 +1,78 @@ +/* + * Adler-32 checksum + * + * Copyright (c) 2003 by Joergen Ibsen / Jibz + * All Rights Reserved + * + * http://www.ibsensoftware.com/ + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ + +/* + * Adler-32 algorithm taken from the zlib source, which is + * Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + */ + +#include "tinf.h" + +#define A32_BASE 65521 +#define A32_NMAX 5552 + +unsigned int tinf_adler32(const void *data, unsigned int length) +{ + const unsigned char *buf = (const unsigned char *)data; + + unsigned int s1 = 1; + unsigned int s2 = 0; + + while (length > 0) + { + int k = length < A32_NMAX ? length : A32_NMAX; + int i; + + for (i = k / 16; i; --i, buf += 16) + { + s1 += buf[0]; s2 += s1; s1 += buf[1]; s2 += s1; + s1 += buf[2]; s2 += s1; s1 += buf[3]; s2 += s1; + s1 += buf[4]; s2 += s1; s1 += buf[5]; s2 += s1; + s1 += buf[6]; s2 += s1; s1 += buf[7]; s2 += s1; + + s1 += buf[8]; s2 += s1; s1 += buf[9]; s2 += s1; + s1 += buf[10]; s2 += s1; s1 += buf[11]; s2 += s1; + s1 += buf[12]; s2 += s1; s1 += buf[13]; s2 += s1; + s1 += buf[14]; s2 += s1; s1 += buf[15]; s2 += s1; + } + + for (i = k % 16; i; --i) { s1 += *buf++; s2 += s1; } + + s1 %= A32_BASE; + s2 %= A32_BASE; + + length -= k; + } + + return (s2 << 16) | s1; +} diff --git a/examples/websocket_mbedtls/cert.c b/examples/websocket_mbedtls/cert.c new file mode 100644 index 0000000..7acc438 --- /dev/null +++ b/examples/websocket_mbedtls/cert.c @@ -0,0 +1,44 @@ +/* This is the CA certificate for the CA trust chain of + www.howsmyssl.com in PEM format, as dumped via: + + openssl s_client -showcerts -connect www.howsmyssl.com:443 +#include +#include + +/* + 1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3 + i:/O=Digital Signature Trust Co./CN=DST Root CA X3 + */ +const char *server_root_cert = "-----BEGIN CERTIFICATE-----\r\n" +"MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\r\n" +"MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\r\n" +"DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\r\n" +"SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\r\n" +"GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\r\n" +"AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\r\n" +"q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\r\n" +"SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0\r\n" +"Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\r\n" +"a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\r\n" +"/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\r\n" +"AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\r\n" +"CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\r\n" +"bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\r\n" +"c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\r\n" +"VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\r\n" +"ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\r\n" +"MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\r\n" +"Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\r\n" +"AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\r\n" +"uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\r\n" +"wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\r\n" +"X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\r\n" +"PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\r\n" +"KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\r\n" +"-----END CERTIFICATE-----\r\n"; + + diff --git a/examples/websocket_mbedtls/conn.c b/examples/websocket_mbedtls/conn.c new file mode 100644 index 0000000..6daeff4 --- /dev/null +++ b/examples/websocket_mbedtls/conn.c @@ -0,0 +1,229 @@ +#include "espressif/esp_common.h" +#include "esp/uart.h" + +#include + +#include "FreeRTOS.h" +#include "task.h" + +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include "lwip/netdb.h" +#include "lwip/dns.h" +#include "lwip/api.h" + +#include "ssid_config.h" + +/* mbedtls/config.h MUST appear before all other mbedtls headers, or + you'll get the default config. + + (Although mostly that isn't a big problem, you just might get + errors at link time if functions don't exist.) */ +#include "mbedtls/config.h" + +#include "mbedtls/net.h" +#include "mbedtls/debug.h" +#include "mbedtls/ssl.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/error.h" +#include "mbedtls/certs.h" + +#include +#include + +#include "conn.h" + +/* SSL file descriptors */ +#define SSL_HANDLES_SIZE 4 +mbedtls_ssl_context* sslHandles[SSL_HANDLES_SIZE] = {NULL, NULL, NULL, NULL}; +unsigned char ctxC = 0; + +mbedtls_ssl_config conf; +mbedtls_x509_crt cacert; +mbedtls_ctr_drbg_context ctr_drbg; +mbedtls_entropy_context entropy; + +/* Connect to a hostname and port using TLS 1.2 and +return a index for one SSL connection on the pool or -1 if error +*/ +int ConnConnect(char *hostname, int port) { + int ret = 0; + mbedtls_ssl_context *sslFD; + mbedtls_net_context *socketFD; + int sslHandle = 0; + char s_port[10]; + + // configure mbedtls + if (!ctxC) { + mbedtls_ssl_config_init( &conf ); + mbedtls_x509_crt_init( &cacert ); + mbedtls_ctr_drbg_init( &ctr_drbg ); + mbedtls_entropy_init( &entropy ); + + if( ( ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy, + (const unsigned char *) "ssl_client1", + strlen( "ssl_client1" ) ) ) != 0 ) + { + printf( " failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret ); + return -1; + } + + ret = mbedtls_x509_crt_parse( &cacert, (const unsigned char *) mbedtls_test_cas_pem, + mbedtls_test_cas_pem_len ); + if( ret < 0 ) + { + printf( " failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", -ret ); + return -1; + } + + + if( ( ret = mbedtls_ssl_config_defaults( &conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT ) ) != 0 ) + { + printf( " failed\n ! mbedtls_ssl_config_defaults returned %d\n\n", ret ); + return -1; + } + + mbedtls_ssl_conf_authmode( &conf, MBEDTLS_SSL_VERIFY_OPTIONAL ); + mbedtls_ssl_conf_ca_chain( &conf, &cacert, NULL ); + mbedtls_ssl_conf_rng( &conf, mbedtls_ctr_drbg_random, &ctr_drbg ); + + ctxC = 1; + } + + // find a free slot at sslHandles + while(sslHandle < SSL_HANDLES_SIZE) { + if (sslHandles[sslHandle] == NULL) { + sslFD = malloc(sizeof(mbedtls_ssl_context)); + socketFD = malloc(sizeof(mbedtls_net_context)); + mbedtls_ssl_init( sslFD ); + mbedtls_net_init( socketFD ); + sslHandles[sslHandle] = sslFD; + break; + } + sslHandle++; + } + + // no free slot at sslHandles + if (sslHandle == SSL_HANDLES_SIZE) { + return -1; + } + + // connect mbedtls socket + memset(s_port, 0, sizeof(s_port)); + sprintf(s_port, "%d", port); + ret = mbedtls_net_connect(socketFD, hostname, s_port, MBEDTLS_NET_PROTO_TCP); + if (ret != 0) { + ConnClose(sslHandle); + return -1; + } + + if(( ret = mbedtls_ssl_setup( sslFD, &conf ) ) != 0 ) + { + printf( " failed\n ! mbedtls_ssl_setup returned %d\n\n", ret ); + ConnClose(sslHandle); + return -1; + } + + if( ( ret = mbedtls_ssl_set_hostname( sslFD, "mbed TLS Server 1" ) ) != 0 ) + { + printf( " failed\n ! mbedtls_ssl_set_hostname returned %d\n\n", ret ); + ConnClose(sslHandle); + return -1; + } + + mbedtls_ssl_set_bio( sslFD, socketFD, mbedtls_net_send, mbedtls_net_recv, NULL ); + + while( ( ret = mbedtls_ssl_handshake( sslFD ) ) != 0 ) + { + if( ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) + { + printf( " failed\n ! mbedtls_ssl_handshake returned -0x%x\n\n", -ret ); + ConnClose(sslHandle); + return -1; + } + } + + return sslHandle; +} + +int ConnReadBytesAvailable(int sslHandle) { + mbedtls_ssl_context* sslFD = NULL; + mbedtls_net_context* socketFD = NULL; + int count = 0; + + if (sslHandle >= 0) sslFD = sslHandles[sslHandle]; + else return -1; + + if (sslFD) { + socketFD = (mbedtls_net_context *) sslFD->p_bio; + lwip_ioctl(socketFD->fd, FIONREAD, &count); + return count; + } else { + return -1; + } +} + +/* Read bytes from a valid SSL connection on the pool. Blocking! */ +int ConnRead(int sslHandle, void *buf, int num) { + mbedtls_ssl_context* sslFD = NULL; + int ret; + + if (sslHandle >= 0) sslFD = sslHandles[sslHandle]; + else return -1; + + if (sslFD) { + if (num == 0) return 0; + ret = mbedtls_ssl_read(sslFD, buf, num); + return ret; + } else { + return -1; + } +} + +/* Write bytes to a valid SSL connection on the pool */ +int ConnWrite(int sslHandle, const void *buf, int num) { + mbedtls_ssl_context* sslFD = NULL; + int ret; + + if (sslHandle >= 0) sslFD = sslHandles[sslHandle]; + else return -1; + + if (sslFD) { + if (num == 0) return 0; + ret = mbedtls_ssl_write(sslFD, buf, num); + return ret; + } else { + return -1; + } +} + +/* Close a valid SSL connection on the pool, release the resources and +open the slot to another connection */ +void ConnClose(int sslHandle) { + mbedtls_ssl_context *sslFD = NULL; + + if (sslHandle >= 0) sslFD = sslHandles[sslHandle]; + else return; + + if (sslFD) { + mbedtls_ssl_close_notify( sslFD ); + + mbedtls_net_free( sslFD->p_bio ); + free(sslFD->p_bio); + + mbedtls_ssl_free( sslFD ); + free(sslFD); + } + + sslHandles[sslHandle] = NULL; +} + +void sleep_ms(int milliseconds) +{ + vTaskDelay(milliseconds / portTICK_RATE_MS); +} diff --git a/examples/websocket_mbedtls/conn.h b/examples/websocket_mbedtls/conn.h new file mode 100644 index 0000000..77d9fca --- /dev/null +++ b/examples/websocket_mbedtls/conn.h @@ -0,0 +1,11 @@ +#ifndef CONN +#define CONN + +int ConnConnect(char *host, int port); +int ConnReadBytesAvailable(int sslHandle); +int ConnRead(int sslHandle, void *buf, int num); +int ConnWrite(int sslHandle, const void *buf, int num); +void ConnClose(int sslHandle); +void sleep_ms(int milliseconds); + +#endif \ No newline at end of file diff --git a/examples/websocket_mbedtls/crc32.c b/examples/websocket_mbedtls/crc32.c new file mode 100644 index 0000000..fce7d33 --- /dev/null +++ b/examples/websocket_mbedtls/crc32.c @@ -0,0 +1,64 @@ +/* + * CRC32 checksum + * + * Copyright (c) 1998-2003 by Joergen Ibsen / Jibz + * All Rights Reserved + * + * http://www.ibsensoftware.com/ + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ + +/* + * CRC32 algorithm taken from the zlib source, which is + * Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + */ + +#include "tinf.h" + +static const unsigned int tinf_crc32tab[16] = { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, + 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, + 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, + 0xbdbdf21c +}; + +unsigned int tinf_crc32(const void *data, unsigned int length) +{ + const unsigned char *buf = (const unsigned char *)data; + unsigned int crc = 0xffffffff; + unsigned int i; + + if (length == 0) return 0; + + for (i = 0; i < length; ++i) + { + crc ^= buf[i]; + crc = tinf_crc32tab[crc & 0x0f] ^ (crc >> 4); + crc = tinf_crc32tab[crc & 0x0f] ^ (crc >> 4); + } + + return crc ^ 0xffffffff; +} diff --git a/examples/websocket_mbedtls/defl_static.c b/examples/websocket_mbedtls/defl_static.c new file mode 100644 index 0000000..f797847 --- /dev/null +++ b/examples/websocket_mbedtls/defl_static.c @@ -0,0 +1,308 @@ +/* + +Routines in this file are based on: +Zlib (RFC1950 / RFC1951) compression for PuTTY. + +PuTTY is copyright 1997-2014 Simon Tatham. + +Portions copyright Robert de Bath, Joris van Rantwijk, Delian +Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, +Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus +Kuhn, Colin Watson, and CORE SDI S.A. + +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 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. +*/ + +#include +#include +#include +#include "defl_static.h" + +#define snew(type) ( (type *) malloc(sizeof(type)) ) +#define snewn(n, type) ( (type *) malloc((n) * sizeof(type)) ) +#define sresize(x, n, type) ( (type *) realloc((x), (n) * sizeof(type)) ) +#define sfree(x) ( free((x)) ) + +#ifndef FALSE +#define FALSE 0 +#define TRUE (!FALSE) +#endif + +/* ---------------------------------------------------------------------- + * Zlib compression. We always use the static Huffman tree option. + * Mostly this is because it's hard to scan a block in advance to + * work out better trees; dynamic trees are great when you're + * compressing a large file under no significant time constraint, + * but when you're compressing little bits in real time, things get + * hairier. + * + * I suppose it's possible that I could compute Huffman trees based + * on the frequencies in the _previous_ block, as a sort of + * heuristic, but I'm not confident that the gain would balance out + * having to transmit the trees. + */ + +void outbits(struct Outbuf *out, unsigned long bits, int nbits) +{ + assert(out->noutbits + nbits <= 32); + out->outbits |= bits << out->noutbits; + out->noutbits += nbits; + while (out->noutbits >= 8) { + if (out->outlen >= out->outsize) { + out->outsize = out->outlen + 64; + out->outbuf = sresize(out->outbuf, out->outsize, unsigned char); + } + out->outbuf[out->outlen++] = (unsigned char) (out->outbits & 0xFF); + out->outbits >>= 8; + out->noutbits -= 8; + } +} + +static const unsigned char mirrorbytes[256] = { + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, + 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, + 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, + 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, + 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, + 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, + 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, + 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, + 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, + 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, + 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, + 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, + 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, + 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, + 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, + 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, + 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, +}; + +typedef struct { + short code, extrabits; + int min, max; +} coderecord; + +static const coderecord lencodes[] = { + {257, 0, 3, 3}, + {258, 0, 4, 4}, + {259, 0, 5, 5}, + {260, 0, 6, 6}, + {261, 0, 7, 7}, + {262, 0, 8, 8}, + {263, 0, 9, 9}, + {264, 0, 10, 10}, + {265, 1, 11, 12}, + {266, 1, 13, 14}, + {267, 1, 15, 16}, + {268, 1, 17, 18}, + {269, 2, 19, 22}, + {270, 2, 23, 26}, + {271, 2, 27, 30}, + {272, 2, 31, 34}, + {273, 3, 35, 42}, + {274, 3, 43, 50}, + {275, 3, 51, 58}, + {276, 3, 59, 66}, + {277, 4, 67, 82}, + {278, 4, 83, 98}, + {279, 4, 99, 114}, + {280, 4, 115, 130}, + {281, 5, 131, 162}, + {282, 5, 163, 194}, + {283, 5, 195, 226}, + {284, 5, 227, 257}, + {285, 0, 258, 258}, +}; + +static const coderecord distcodes[] = { + {0, 0, 1, 1}, + {1, 0, 2, 2}, + {2, 0, 3, 3}, + {3, 0, 4, 4}, + {4, 1, 5, 6}, + {5, 1, 7, 8}, + {6, 2, 9, 12}, + {7, 2, 13, 16}, + {8, 3, 17, 24}, + {9, 3, 25, 32}, + {10, 4, 33, 48}, + {11, 4, 49, 64}, + {12, 5, 65, 96}, + {13, 5, 97, 128}, + {14, 6, 129, 192}, + {15, 6, 193, 256}, + {16, 7, 257, 384}, + {17, 7, 385, 512}, + {18, 8, 513, 768}, + {19, 8, 769, 1024}, + {20, 9, 1025, 1536}, + {21, 9, 1537, 2048}, + {22, 10, 2049, 3072}, + {23, 10, 3073, 4096}, + {24, 11, 4097, 6144}, + {25, 11, 6145, 8192}, + {26, 12, 8193, 12288}, + {27, 12, 12289, 16384}, + {28, 13, 16385, 24576}, + {29, 13, 24577, 32768}, +}; + +void zlib_literal(struct Outbuf *out, unsigned char c) +{ + if (out->comp_disabled) { + /* + * We're in an uncompressed block, so just output the byte. + */ + outbits(out, c, 8); + return; + } + + if (c <= 143) { + /* 0 through 143 are 8 bits long starting at 00110000. */ + outbits(out, mirrorbytes[0x30 + c], 8); + } else { + /* 144 through 255 are 9 bits long starting at 110010000. */ + outbits(out, 1 + 2 * mirrorbytes[0x90 - 144 + c], 9); + } +} + +void zlib_match(struct Outbuf *out, int distance, int len) +{ + const coderecord *d, *l; + int i, j, k; + + assert(!out->comp_disabled); + + while (len > 0) { + int thislen; + + /* + * We can transmit matches of lengths 3 through 258 + * inclusive. So if len exceeds 258, we must transmit in + * several steps, with 258 or less in each step. + * + * Specifically: if len >= 261, we can transmit 258 and be + * sure of having at least 3 left for the next step. And if + * len <= 258, we can just transmit len. But if len == 259 + * or 260, we must transmit len-3. + */ + thislen = (len > 260 ? 258 : len <= 258 ? len : len - 3); + len -= thislen; + + /* + * Binary-search to find which length code we're + * transmitting. + */ + i = -1; + j = sizeof(lencodes) / sizeof(*lencodes); + while (1) { + assert(j - i >= 2); + k = (j + i) / 2; + if (thislen < lencodes[k].min) + j = k; + else if (thislen > lencodes[k].max) + i = k; + else { + l = &lencodes[k]; + break; /* found it! */ + } + } + + /* + * Transmit the length code. 256-279 are seven bits + * starting at 0000000; 280-287 are eight bits starting at + * 11000000. + */ + if (l->code <= 279) { + outbits(out, mirrorbytes[(l->code - 256) * 2], 7); + } else { + outbits(out, mirrorbytes[0xc0 - 280 + l->code], 8); + } + + /* + * Transmit the extra bits. + */ + if (l->extrabits) + outbits(out, thislen - l->min, l->extrabits); + + /* + * Binary-search to find which distance code we're + * transmitting. + */ + i = -1; + j = sizeof(distcodes) / sizeof(*distcodes); + while (1) { + assert(j - i >= 2); + k = (j + i) / 2; + if (distance < distcodes[k].min) + j = k; + else if (distance > distcodes[k].max) + i = k; + else { + d = &distcodes[k]; + break; /* found it! */ + } + } + + /* + * Transmit the distance code. Five bits starting at 00000. + */ + outbits(out, mirrorbytes[d->code * 8], 5); + + /* + * Transmit the extra bits. + */ + if (d->extrabits) + outbits(out, distance - d->min, d->extrabits); + } +} + +void zlib_start_block(struct Outbuf *out) +{ +// outbits(out, 0x9C78, 16); + outbits(out, 1, 1); /* Final block */ + outbits(out, 1, 2); /* Static huffman block */ +} + +void zlib_finish_block(struct Outbuf *out) +{ + outbits(out, 0, 7); /* close block */ + outbits(out, 0, 7); /* Make sure all bits are flushed */ +} + +void zlib_free_block(struct Outbuf *out) { + sfree(out->outbuf); +} diff --git a/examples/websocket_mbedtls/defl_static.h b/examples/websocket_mbedtls/defl_static.h new file mode 100644 index 0000000..b3a349b --- /dev/null +++ b/examples/websocket_mbedtls/defl_static.h @@ -0,0 +1,14 @@ +struct Outbuf { + unsigned char *outbuf; + int outlen, outsize; + unsigned long outbits; + int noutbits; + int comp_disabled; +}; + +void outbits(struct Outbuf *out, unsigned long bits, int nbits); +void zlib_start_block(struct Outbuf *ctx); +void zlib_finish_block(struct Outbuf *ctx); +void zlib_literal(struct Outbuf *ectx, unsigned char c); +void zlib_match(struct Outbuf *ectx, int distance, int len); +void zlib_free_block(struct Outbuf *out); diff --git a/examples/websocket_mbedtls/genlz77.c b/examples/websocket_mbedtls/genlz77.c new file mode 100644 index 0000000..e68d400 --- /dev/null +++ b/examples/websocket_mbedtls/genlz77.c @@ -0,0 +1,116 @@ +/* + * genlz77 - Generic LZ77 compressor + * + * Copyright (c) 2014 by Paul Sokolovsky + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ +#include +#include +#include +#include "defl_static.h" + +#define HASH_BITS 10 +#define HASH_SIZE (1<> (3*8 - HASH_BITS)) - v) & (HASH_SIZE - 1); + return hash; +} + +#ifdef DUMP_LZTXT + +/* Counter for approximate compressed length in LZTXT mode. */ +/* Literal is counted as 1, copy as 2 bytes. */ +unsigned approx_compressed_len; + +void literal(void *data, uint8_t val) +{ + printf("L%02x # %c\n", val, (val >= 0x20 && val <= 0x7e) ? val : '?'); + approx_compressed_len++; +} + +void copy(void *data, unsigned offset, unsigned len) +{ + printf("C-%u,%u\n", offset, len); + approx_compressed_len += 2; +} + +#else + +static void literal(void *data, uint8_t val) +{ + zlib_literal(data, val); +} + +static void copy(void *data, unsigned offset, unsigned len) +{ + zlib_match(data, offset, len); +} + +#endif + + +void tinf_compress(void *data, const uint8_t *src, unsigned slen) +{ + const uint8_t *hashtable[HASH_SIZE] = {0}; + + const uint8_t *top = src + slen - MIN_MATCH; + while (src < top) { + int h = HASH(src); + const uint8_t **bucket = &hashtable[h & (HASH_SIZE - 1)]; + const uint8_t *subs = *bucket; + *bucket = src; + if (subs && src > subs && (src - subs) <= MAX_OFFSET && !memcmp(src, subs, MIN_MATCH)) { + src += MIN_MATCH; + const uint8_t *m = subs + MIN_MATCH; + int len = MIN_MATCH; + while (*src == *m && len < MAX_MATCH) { + src++; m++; len++; + } + copy(data, src - len - subs, len); + } else { + literal(data, *src++); + } + } + // Process buffer tail, which is less than MIN_MATCH + // (and so it doesn't make sense to look for matches there) + top += MIN_MATCH; + while (src < top) { + literal(data, *src++); + } +} diff --git a/examples/websocket_mbedtls/include/mbedtls/config.h b/examples/websocket_mbedtls/include/mbedtls/config.h new file mode 100644 index 0000000..2674292 --- /dev/null +++ b/examples/websocket_mbedtls/include/mbedtls/config.h @@ -0,0 +1,27 @@ +/* Special mbedTLS config file for http_get_mbedtls example, + overrides supported cipher suite list. + + Overriding the set of cipher suites saves small amounts of ROM and + RAM, and is a good practice in general if you know what server(s) + you want to connect to. + + However it's extra important here because the howsmyssl API sends + back the list of ciphers we send it as a JSON list in the, and we + only have a 4096kB receive buffer. If the server supported maximum + fragment length option then we wouldn't have this problem either, + but we do so this is a good workaround. + + The ciphers chosen below are common ECDHE ciphers, the same ones + Firefox uses when connecting to a TLSv1.2 server. +*/ +#ifndef MBEDTLS_CONFIG_H + +/* include_next picks up default config from extras/mbedtls/include/mbedtls/config.h */ +#include_next + +#define MBEDTLS_SSL_CIPHERSUITES MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + +/* uncomment next line to include debug output from example */ +//#define MBEDTLS_DEBUG_C + +#endif diff --git a/examples/websocket_mbedtls/tinf.h b/examples/websocket_mbedtls/tinf.h new file mode 100644 index 0000000..e9401f2 --- /dev/null +++ b/examples/websocket_mbedtls/tinf.h @@ -0,0 +1,102 @@ +/* + * uzlib - tiny deflate/inflate library (deflate, gzip, zlib) + * + * Copyright (c) 2003 by Joergen Ibsen / Jibz + * All Rights Reserved + * http://www.ibsensoftware.com/ + * + * Copyright (c) 2014 by Paul Sokolovsky + */ + +#ifndef TINF_H_INCLUDED +#define TINF_H_INCLUDED + +#include + +/* calling convention */ +#ifndef TINFCC + #ifdef __WATCOMC__ + #define TINFCC __cdecl + #else + #define TINFCC + #endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define TINF_OK 0 +#define TINF_DATA_ERROR (-3) +#define TINF_DEST_OVERFLOW (-4) + +/* data structures */ + +typedef struct { + unsigned short table[16]; /* table of code length counts */ + unsigned short trans[288]; /* code -> symbol translation table */ +} TINF_TREE; + +struct TINF_DATA; +typedef struct TINF_DATA { + const unsigned char *source; + unsigned int tag; + unsigned int bitcount; + + /* Buffer start */ + unsigned char *destStart; + /* Buffer total size */ + unsigned int destSize; + /* Current pointer in buffer */ + unsigned char *dest; + /* Remaining bytes in buffer */ + unsigned int destRemaining; + /* Argument is the allocation size which didn't fit into buffer. Note that + exact mimumum size to grow buffer by is lastAlloc - destRemaining. But + growing by this exact size is ineficient, as the next allocation will + fail again. */ + int (*destGrow)(struct TINF_DATA *data, unsigned int lastAlloc); + + TINF_TREE ltree; /* dynamic length/symbol tree */ + TINF_TREE dtree; /* dynamic distance tree */ +} TINF_DATA; + + +/* low-level API */ + +/* Step 1: Allocate TINF_DATA structure */ +/* Step 2: Set destStart, destSize, and destGrow fields */ +/* Step 3: Set source field */ +/* Step 4: Call tinf_uncompress_dyn() */ +/* Step 5: In response to destGrow callback, update destStart and destSize fields */ +/* Step 6: When tinf_uncompress_dyn() returns, buf.dest points to a byte past last uncompressed byte */ + +int TINFCC tinf_uncompress_dyn(TINF_DATA *d); +int TINFCC tinf_zlib_uncompress_dyn(TINF_DATA *d, unsigned int sourceLen); + +/* high-level API */ + +void TINFCC tinf_init(void); + +int TINFCC tinf_uncompress(void *dest, unsigned int *destLen, + const void *source, unsigned int sourceLen); + +int TINFCC tinf_gzip_uncompress(void *dest, unsigned int *destLen, + const void *source, unsigned int sourceLen); + +int TINFCC tinf_zlib_uncompress(void *dest, unsigned int *destLen, + const void *source, unsigned int sourceLen); + +unsigned int TINFCC tinf_adler32(const void *data, unsigned int length); + +unsigned int TINFCC tinf_crc32(const void *data, unsigned int length); + +/* compression API */ + +void TINFCC tinf_compress(void *data, const uint8_t *src, unsigned slen); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* TINF_H_INCLUDED */ diff --git a/examples/websocket_mbedtls/tinfgzip.c b/examples/websocket_mbedtls/tinfgzip.c new file mode 100644 index 0000000..1a16795 --- /dev/null +++ b/examples/websocket_mbedtls/tinfgzip.c @@ -0,0 +1,124 @@ +/* + * tinfgzip - tiny gzip decompressor + * + * Copyright (c) 2003 by Joergen Ibsen / Jibz + * All Rights Reserved + * + * http://www.ibsensoftware.com/ + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ + +#include "tinf.h" + +#define FTEXT 1 +#define FHCRC 2 +#define FEXTRA 4 +#define FNAME 8 +#define FCOMMENT 16 + +int tinf_gzip_uncompress(void *dest, unsigned int *destLen, + const void *source, unsigned int sourceLen) +{ + unsigned char *src = (unsigned char *)source; + unsigned char *dst = (unsigned char *)dest; + unsigned char *start; + unsigned int dlen, crc32; + int res; + unsigned char flg; + + /* -- check format -- */ + + /* check id bytes */ + if (src[0] != 0x1f || src[1] != 0x8b) return TINF_DATA_ERROR; + + /* check method is deflate */ + if (src[2] != 8) return TINF_DATA_ERROR; + + /* get flag byte */ + flg = src[3]; + + /* check that reserved bits are zero */ + if (flg & 0xe0) return TINF_DATA_ERROR; + + /* -- find start of compressed data -- */ + + /* skip base header of 10 bytes */ + start = src + 10; + + /* skip extra data if present */ + if (flg & FEXTRA) + { + unsigned int xlen = start[1]; + xlen = 256*xlen + start[0]; + start += xlen + 2; + } + + /* skip file name if present */ + if (flg & FNAME) { while (*start) ++start; ++start; } + + /* skip file comment if present */ + if (flg & FCOMMENT) { while (*start) ++start; ++start; } + + /* check header crc if present */ + if (flg & FHCRC) + { + unsigned int hcrc = start[1]; + hcrc = 256*hcrc + start[0]; + + if (hcrc != (tinf_crc32(src, start - src) & 0x0000ffff)) + return TINF_DATA_ERROR; + + start += 2; + } + + /* -- get decompressed length -- */ + + dlen = src[sourceLen - 1]; + dlen = 256*dlen + src[sourceLen - 2]; + dlen = 256*dlen + src[sourceLen - 3]; + dlen = 256*dlen + src[sourceLen - 4]; + + /* -- get crc32 of decompressed data -- */ + + crc32 = src[sourceLen - 5]; + crc32 = 256*crc32 + src[sourceLen - 6]; + crc32 = 256*crc32 + src[sourceLen - 7]; + crc32 = 256*crc32 + src[sourceLen - 8]; + + /* -- decompress data -- */ + + res = tinf_uncompress(dst, destLen, start, src + sourceLen - start - 8); + + if (res != TINF_OK) return TINF_DATA_ERROR; + + if (*destLen != dlen) return TINF_DATA_ERROR; + + /* -- check CRC32 checksum -- */ + + if (crc32 != tinf_crc32(dst, dlen)) return TINF_DATA_ERROR; + + return TINF_OK; +} diff --git a/examples/websocket_mbedtls/tinflate.c b/examples/websocket_mbedtls/tinflate.c new file mode 100644 index 0000000..76db08c --- /dev/null +++ b/examples/websocket_mbedtls/tinflate.c @@ -0,0 +1,518 @@ +/* + * tinflate - tiny inflate + * + * Copyright (c) 2003 by Joergen Ibsen / Jibz + * All Rights Reserved + * http://www.ibsensoftware.com/ + * + * Copyright (c) 2014 by Paul Sokolovsky + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ + +#include "tinf.h" + +/* --------------------------------------------------- * + * -- uninitialized global data (static structures) -- * + * --------------------------------------------------- */ + +#ifdef RUNTIME_BITS_TABLES + +/* extra bits and base tables for length codes */ +unsigned char length_bits[30]; +unsigned short length_base[30]; + +/* extra bits and base tables for distance codes */ +unsigned char dist_bits[30]; +unsigned short dist_base[30]; + +#else + +const unsigned char length_bits[30] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, + 5, 5, 5, 5 +}; +const unsigned short length_base[30] = { + 3, 4, 5, 6, 7, 8, 9, 10, + 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, + 131, 163, 195, 227, 258 +}; + +const unsigned char dist_bits[30] = { + 0, 0, 0, 0, 1, 1, 2, 2, + 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, + 11, 11, 12, 12, 13, 13 +}; +const unsigned short dist_base[30] = { + 1, 2, 3, 4, 5, 7, 9, 13, + 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, + 4097, 6145, 8193, 12289, 16385, 24577 +}; + +#endif + +/* special ordering of code length codes */ +const unsigned char clcidx[] = { + 16, 17, 18, 0, 8, 7, 9, 6, + 10, 5, 11, 4, 12, 3, 13, 2, + 14, 1, 15 +}; + +/* ----------------------- * + * -- utility functions -- * + * ----------------------- */ + +/* Execute callback to grow destination buffer */ +static int tinf_grow_dest_buf(TINF_DATA *d, unsigned int lastAlloc) +{ + unsigned int oldsize = d->dest - d->destStart; + + /* This will update only destStart and destSize */ + if (!d->destGrow) + { + return TINF_DEST_OVERFLOW; + } + + d->destGrow(d, lastAlloc); + d->dest = d->destStart + oldsize; + d->destRemaining = d->destSize - oldsize; + return 0; +} + +#ifdef RUNTIME_BITS_TABLES +/* build extra bits and base tables */ +static void tinf_build_bits_base(unsigned char *bits, unsigned short *base, int delta, int first) +{ + int i, sum; + + /* build bits table */ + for (i = 0; i < delta; ++i) bits[i] = 0; + for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta; + + /* build base table */ + for (sum = first, i = 0; i < 30; ++i) + { + base[i] = sum; + sum += 1 << bits[i]; + } +} +#endif + +/* build the fixed huffman trees */ +static void tinf_build_fixed_trees(TINF_TREE *lt, TINF_TREE *dt) +{ + int i; + + /* build fixed length tree */ + for (i = 0; i < 7; ++i) lt->table[i] = 0; + + lt->table[7] = 24; + lt->table[8] = 152; + lt->table[9] = 112; + + for (i = 0; i < 24; ++i) lt->trans[i] = 256 + i; + for (i = 0; i < 144; ++i) lt->trans[24 + i] = i; + for (i = 0; i < 8; ++i) lt->trans[24 + 144 + i] = 280 + i; + for (i = 0; i < 112; ++i) lt->trans[24 + 144 + 8 + i] = 144 + i; + + /* build fixed distance tree */ + for (i = 0; i < 5; ++i) dt->table[i] = 0; + + dt->table[5] = 32; + + for (i = 0; i < 32; ++i) dt->trans[i] = i; +} + +/* given an array of code lengths, build a tree */ +static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned int num) +{ + unsigned short offs[16]; + unsigned int i, sum; + + /* clear code length count table */ + for (i = 0; i < 16; ++i) t->table[i] = 0; + + /* scan symbol lengths, and sum code length counts */ + for (i = 0; i < num; ++i) t->table[lengths[i]]++; + + t->table[0] = 0; + + /* compute offset table for distribution sort */ + for (sum = 0, i = 0; i < 16; ++i) + { + offs[i] = sum; + sum += t->table[i]; + } + + /* create code->symbol translation table (symbols sorted by code) */ + for (i = 0; i < num; ++i) + { + if (lengths[i]) t->trans[offs[lengths[i]]++] = i; + } +} + +/* ---------------------- * + * -- decode functions -- * + * ---------------------- */ + +/* get one bit from source stream */ +static int tinf_getbit(TINF_DATA *d) +{ + unsigned int bit; + + /* check if tag is empty */ + if (!d->bitcount--) + { + /* load next tag */ + d->tag = *d->source++; + d->bitcount = 7; + } + + /* shift bit out of tag */ + bit = d->tag & 0x01; + d->tag >>= 1; + + return bit; +} + +/* read a num bit value from a stream and add base */ +static unsigned int tinf_read_bits(TINF_DATA *d, int num, int base) +{ + unsigned int val = 0; + + /* read num bits */ + if (num) + { + unsigned int limit = 1 << (num); + unsigned int mask; + + for (mask = 1; mask < limit; mask *= 2) + if (tinf_getbit(d)) val += mask; + } + + return val + base; +} + +/* given a data stream and a tree, decode a symbol */ +static int tinf_decode_symbol(TINF_DATA *d, TINF_TREE *t) +{ + int sum = 0, cur = 0, len = 0; + + /* get more bits while code value is above sum */ + do { + + cur = 2*cur + tinf_getbit(d); + + ++len; + + sum += t->table[len]; + cur -= t->table[len]; + + } while (cur >= 0); + + return t->trans[sum + cur]; +} + +/* given a data stream, decode dynamic trees from it */ +static void tinf_decode_trees(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) +{ + unsigned char lengths[288+32]; + unsigned int hlit, hdist, hclen; + unsigned int i, num, length; + + /* get 5 bits HLIT (257-286) */ + hlit = tinf_read_bits(d, 5, 257); + + /* get 5 bits HDIST (1-32) */ + hdist = tinf_read_bits(d, 5, 1); + + /* get 4 bits HCLEN (4-19) */ + hclen = tinf_read_bits(d, 4, 4); + + for (i = 0; i < 19; ++i) lengths[i] = 0; + + /* read code lengths for code length alphabet */ + for (i = 0; i < hclen; ++i) + { + /* get 3 bits code length (0-7) */ + unsigned int clen = tinf_read_bits(d, 3, 0); + + lengths[clcidx[i]] = clen; + } + + /* build code length tree, temporarily use length tree */ + tinf_build_tree(lt, lengths, 19); + + /* decode code lengths for the dynamic trees */ + for (num = 0; num < hlit + hdist; ) + { + int sym = tinf_decode_symbol(d, lt); + + switch (sym) + { + case 16: + /* copy previous code length 3-6 times (read 2 bits) */ + { + unsigned char prev = lengths[num - 1]; + for (length = tinf_read_bits(d, 2, 3); length; --length) + { + lengths[num++] = prev; + } + } + break; + case 17: + /* repeat code length 0 for 3-10 times (read 3 bits) */ + for (length = tinf_read_bits(d, 3, 3); length; --length) + { + lengths[num++] = 0; + } + break; + case 18: + /* repeat code length 0 for 11-138 times (read 7 bits) */ + for (length = tinf_read_bits(d, 7, 11); length; --length) + { + lengths[num++] = 0; + } + break; + default: + /* values 0-15 represent the actual code lengths */ + lengths[num++] = sym; + break; + } + } + + /* build dynamic trees */ + tinf_build_tree(lt, lengths, hlit); + tinf_build_tree(dt, lengths + hlit, hdist); +} + +/* ----------------------------- * + * -- block inflate functions -- * + * ----------------------------- */ + +/* given a stream and two trees, inflate a block of data */ +static int tinf_inflate_block_data(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) +{ + while (1) + { + int sym = tinf_decode_symbol(d, lt); + + /* check for end of block */ + if (sym == 256) + { + return TINF_OK; + } + + if (sym < 256) + { + if (d->destRemaining == 0) + { + int res = tinf_grow_dest_buf(d, 1); + if (res) return res; + } + + *d->dest++ = sym; + d->destRemaining--; + + } else { + + unsigned int length, offs, i; + int dist; + + sym -= 257; + + /* possibly get more bits from length code */ + length = tinf_read_bits(d, length_bits[sym], length_base[sym]); + + dist = tinf_decode_symbol(d, dt); + + /* possibly get more bits from distance code */ + offs = tinf_read_bits(d, dist_bits[dist], dist_base[dist]); + + if (d->destRemaining < length) + { + int res = tinf_grow_dest_buf(d, length); + if (res) return res; + } + + /* copy match */ + for (i = 0; i < length; ++i) + { + d->dest[i] = d->dest[(int)(i - offs)]; + } + + d->dest += length; + d->destRemaining -= length; + } + } +} + +/* inflate an uncompressed block of data */ +static int tinf_inflate_uncompressed_block(TINF_DATA *d) +{ + unsigned int length, invlength; + unsigned int i; + + /* get length */ + length = d->source[1]; + length = 256*length + d->source[0]; + + /* get one's complement of length */ + invlength = d->source[3]; + invlength = 256*invlength + d->source[2]; + + /* check length */ + if (length != (~invlength & 0x0000ffff)) return TINF_DATA_ERROR; + + if (d->destRemaining < length) + { + int res = tinf_grow_dest_buf(d, length); + if (res) return res; + } + + d->source += 4; + + /* copy block */ + for (i = length; i; --i) *d->dest++ = *d->source++; + d->destRemaining -= length; + + /* make sure we start next block on a byte boundary */ + d->bitcount = 0; + + return TINF_OK; +} + +/* inflate a block of data compressed with fixed huffman trees */ +static int tinf_inflate_fixed_block(TINF_DATA *d) +{ + /* build fixed huffman trees */ + tinf_build_fixed_trees(&d->ltree, &d->dtree); + + /* decode block using fixed trees */ + return tinf_inflate_block_data(d, &d->ltree, &d->dtree); +} + +/* inflate a block of data compressed with dynamic huffman trees */ +static int tinf_inflate_dynamic_block(TINF_DATA *d) +{ + /* decode trees from stream */ + tinf_decode_trees(d, &d->ltree, &d->dtree); + + /* decode block using decoded trees */ + return tinf_inflate_block_data(d, &d->ltree, &d->dtree); +} + +/* ---------------------- * + * -- public functions -- * + * ---------------------- */ + +/* initialize global (static) data */ +void tinf_init(void) +{ +#ifdef RUNTIME_BITS_TABLES + /* build extra bits and base tables */ + tinf_build_bits_base(length_bits, length_base, 4, 3); + tinf_build_bits_base(dist_bits, dist_base, 2, 1); + + /* fix a special case */ + length_bits[28] = 0; + length_base[28] = 258; +#endif +} + +/* inflate stream from source to dest */ +int tinf_uncompress(void *dest, unsigned int *destLen, + const void *source, unsigned int sourceLen) +{ + (void)sourceLen; + TINF_DATA d; + int res; + + /* initialise data */ + d.source = (const unsigned char *)source; + + d.destStart = (unsigned char *)dest; + d.destRemaining = *destLen; + d.destSize = *destLen; + + res = tinf_uncompress_dyn(&d); + + *destLen = d.dest - d.destStart; + + return res; +} + +/* inflate stream from source to dest */ +int tinf_uncompress_dyn(TINF_DATA *d) +{ + int bfinal; + + /* initialise data */ + d->bitcount = 0; + + d->dest = d->destStart; + d->destRemaining = d->destSize; + + do { + + unsigned int btype; + int res; + + /* read final block flag */ + bfinal = tinf_getbit(d); + + /* read block type (2 bits) */ + btype = tinf_read_bits(d, 2, 0); + + /* decompress block */ + switch (btype) + { + case 0: + /* decompress uncompressed block */ + res = tinf_inflate_uncompressed_block(d); + break; + case 1: + /* decompress block with fixed huffman trees */ + res = tinf_inflate_fixed_block(d); + break; + case 2: + /* decompress block with dynamic huffman trees */ + res = tinf_inflate_dynamic_block(d); + break; + default: + return TINF_DATA_ERROR; + } + + if (res != TINF_OK) return TINF_DATA_ERROR; + + } while (!bfinal); + + return TINF_OK; +} diff --git a/examples/websocket_mbedtls/tinfzlib.c b/examples/websocket_mbedtls/tinfzlib.c new file mode 100644 index 0000000..dbacc1d --- /dev/null +++ b/examples/websocket_mbedtls/tinfzlib.c @@ -0,0 +1,101 @@ +/* + * tinfzlib - tiny zlib decompressor + * + * Copyright (c) 2003 by Joergen Ibsen / Jibz + * All Rights Reserved + * + * http://www.ibsensoftware.com/ + * + * This software is provided 'as-is', without any express + * or implied warranty. In no event will the authors be + * held liable for any damages arising from the use of + * this software. + * + * Permission is granted to anyone to use this software + * for any purpose, including commercial applications, + * and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The origin of this software must not be + * misrepresented; you must not claim that you + * wrote the original software. If you use this + * software in a product, an acknowledgment in + * the product documentation would be appreciated + * but is not required. + * + * 2. Altered source versions must be plainly marked + * as such, and must not be misrepresented as + * being the original software. + * + * 3. This notice may not be removed or altered from + * any source distribution. + */ + +#include "tinf.h" + +int tinf_zlib_uncompress(void *dest, unsigned int *destLen, + const void *source, unsigned int sourceLen) +{ + TINF_DATA d; + int res; + + /* initialise data */ + d.source = (const unsigned char *)source; + + d.destStart = (unsigned char *)dest; + d.destRemaining = *destLen; + + res = tinf_zlib_uncompress_dyn(&d, sourceLen); + + *destLen = d.dest - d.destStart; + + return res; +} + +int tinf_zlib_uncompress_dyn(TINF_DATA *d, unsigned int sourceLen) +{ + unsigned int a32; + int res; + unsigned char cmf, flg; + + /* -- get header bytes -- */ + + cmf = d->source[0]; + flg = d->source[1]; + + /* -- check format -- */ + + /* check checksum */ + if ((256*cmf + flg) % 31) return TINF_DATA_ERROR; + + /* check method is deflate */ + if ((cmf & 0x0f) != 8) return TINF_DATA_ERROR; + + /* check window size is valid */ + if ((cmf >> 4) > 7) return TINF_DATA_ERROR; + + /* check there is no preset dictionary */ + if (flg & 0x20) return TINF_DATA_ERROR; + + /* -- get adler32 checksum -- */ + + a32 = d->source[sourceLen - 4]; + a32 = 256*a32 + d->source[sourceLen - 3]; + a32 = 256*a32 + d->source[sourceLen - 2]; + a32 = 256*a32 + d->source[sourceLen - 1]; + + d->source += 2; + + /* -- inflate -- */ + + res = tinf_uncompress_dyn(d); + + if (res != TINF_OK) return res; + + /* -- check adler32 checksum -- */ + + if (a32 != tinf_adler32(d->destStart, d->dest - d->destStart)) return TINF_DATA_ERROR; + + return TINF_OK; +} + diff --git a/examples/websocket_mbedtls/util.h b/examples/websocket_mbedtls/util.h new file mode 100644 index 0000000..8d29b93 --- /dev/null +++ b/examples/websocket_mbedtls/util.h @@ -0,0 +1,28 @@ +// util +void hex_dump(const char *desc, const void *addr, const size_t len); + +#define Int32 signed long +#define Uint32 unsigned long +#define Byte unsigned char +#define Word unsigned short +#define Bool char +#define true 1 +#define false 0 + +#define MAKEWORD(a, b) ((Word)(((Byte)((a) & 0xff)) | ((Word)((Byte)((b) & 0xff))) << 8)) +#define MAKELONG(low,high) ((Int32)(((Word)(low)) | (((Uint32)((Word)(high))) << 16))) +#define LOWORD(l) ((Word)((l) & 0xffff)) +#define HIWORD(l) ((Word)((l) >> 16)) +#define LOBYTE(w) ((Byte)((w) & 0xff)) +#define HIBYTE(w) ((Byte)((w) >> 8)) +#define HH(x) HIBYTE(HIWORD( x )) +#define HL(x) LOBYTE(HIWORD( x )) +#define LH(x) HIBYTE(LOWORD( x )) +#define LL(x) LOBYTE(LOWORD( x )) +#define LONIBLE(x) (((Byte)x) & 0x0F ) +#define HINIBLE(x) ((((Byte)x) * 0xF0)>>4) + +#define _SWAPS(x) ((unsigned short)( \ + ((((unsigned short) x) & 0x000000FF) << 8) | \ + ((((unsigned short) x) & 0x0000FF00) >> 8) \ + )) diff --git a/examples/websocket_mbedtls/websocket_mbedtls.c b/examples/websocket_mbedtls/websocket_mbedtls.c new file mode 100644 index 0000000..68f5b94 --- /dev/null +++ b/examples/websocket_mbedtls/websocket_mbedtls.c @@ -0,0 +1,108 @@ +/* websocket_mbedtls - Websocket example using mbed TLS. + * + * It creates a websocket with a server running on Heroku. It uses TLS v1.2. + * I already implemented support to the permessage-deflate extension that + * reduces the footprint of the websocket frames using the inflate deflate + * algorithm. + + * The remaining memory when using the websocket on top of mbed TLS 1.2 is ~14Kb + * + * There is room for memory optimization. Feel free to improve the code. + * + * After get the esp8266 connected, go to ruby-websockets-chat.herokuapp.com + * and type some commands like break (to reconnect), turn led on and turn led off. + * + * If you wanna check the source code from the server: + * https://github.com/heroku-examples/ruby-websockets-chat-demo + * + * If you wanna work with the permessage-deflate extension, connect to the + * host serene-escarpment-15149.herokuapp.com and the path /echo . It is a websocket + * server echoing back your messages and supporting the permessage-deflate extension: + * https://github.com/luisbebop/websocket-echo-deflate + */ + +#include "espressif/esp_common.h" +#include "esp/uart.h" +#include +#include "FreeRTOS.h" +#include "task.h" +#include "ssid_config.h" +#include "conn.h" +#include "ws.h" +#include "util.h" + +const int gpio = 2; + +void websocket_task(void *pvParameters) +{ + //char * host = "serene-escarpment-15149.herokuapp.com"; + //char * path = "/echo"; + + char *host = "ruby-websockets-chat.herokuapp.com"; + char *path = "/"; + + int socket = 0, ret = 0; + int port = 443; + Bool compression = false, timeout = false; + char text[2048]; + + gpio_enable(gpio, GPIO_OUTPUT); + gpio_write(gpio, 1); + + while(1) { + vTaskDelay(5000 / portTICK_RATE_MS); + printf("top of loop, free heap = %u\n", xPortGetFreeHeapSize()); + + socket = ConnConnect(host, port); + printf("\nConnConnect socket %d\n", socket); + + ret = wsConnect(socket, host, path, &compression); + printf("wsConnect ret %d compression %d\n", ret, compression); + if ( ret < 0) + { + printf("websocket handshake error ret=%d\n", ret); + } + + strcpy(text, "{\"handle\":\"websocket-c-client\", \"text\": \"hello from low level ~*~ esp8266\"}"); + wsSendText(socket, text, compression); + + while(1) { + printf("top of loop, free heap = %u\n", xPortGetFreeHeapSize()); + + memset(text, 0, sizeof(text)); + wsReceiveText(socket, text, 2048, compression, &timeout, 15); + + if (timeout) { + printf("wsReceiveText timeout\n"); + } else { + printf("no timeout ...\n"); + printf("%s\n", text); + + if(strstr(text, "break") != 0) break; + if(strstr(text, "turn led on") != 0) gpio_write(gpio, 0); + if(strstr(text, "turn led off") != 0) gpio_write(gpio, 1); + } + } + + printf("Before ConnClose top of loop, free heap = %u\n", xPortGetFreeHeapSize()); + ConnClose(socket); + printf("After ConnClose top of loop, free heap = %u\n", xPortGetFreeHeapSize()); + } +} + +void user_init(void) +{ + uart_set_baud(0, 115200); + printf("SDK version:%s\n", sdk_system_get_sdk_version()); + + struct sdk_station_config config = { + .ssid = WIFI_SSID, + .password = WIFI_PASS, + }; + + /* required to call wifi_set_opmode before station_set_config */ + sdk_wifi_set_opmode(STATION_MODE); + sdk_wifi_station_set_config(&config); + + xTaskCreate(&websocket_task, (signed char *)"websocket_task", 2048, NULL, 2, NULL); +} diff --git a/examples/websocket_mbedtls/ws.c b/examples/websocket_mbedtls/ws.c new file mode 100644 index 0000000..b0b4fe2 --- /dev/null +++ b/examples/websocket_mbedtls/ws.c @@ -0,0 +1,270 @@ +#include +#include +#include + +#include "tinf.h" +#include "defl_static.h" +#include "conn.h" +#include "ws.h" + +const static char http_get[] = "GET wss://%s%s HTTP/1.1\r\n"; +const static char http_host[] = "Host: %s\r\n"; +const static char http_origin[] = "Origin: https://%s\r\n"; +const static char http_upgrade[] = "Upgrade: websocket\r\n"; +const static char http_connection[] = "Connection: Upgrade\r\n"; +const static char http_ws_key[] = "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Extensions: permessage-deflate\r\n"; +const static char http_ws_version[] = "Sec-WebSocket-Version: 13\r\n"; + +#define WS_OPCODE_CONTINUATION 0x00 +#define WS_OPCODE_TEXT 0x01 +#define WS_OPCODE_BINARY 0x02 +#define WS_OPCODE_CONECTION_CLOSE 0x08 +#define WS_OPCODE_PING 0x09 +#define WS_OPCODE_PONG 0x0A + +int wsConnect(int socket, const char *host, const char *path, Bool *compression) { + char server_reply[1024]; + char buffer[1024]; + + memset(server_reply, 0, sizeof(server_reply)); + *compression = false; + + memset(buffer, 0, sizeof(buffer)); + sprintf(buffer, http_get, host, path); + ConnWrite(socket, buffer, strlen(buffer)); + + memset(buffer, 0, sizeof(buffer)); + sprintf(buffer, http_host, host); + ConnWrite(socket, buffer, strlen(buffer)); + + memset(buffer, 0, sizeof(buffer)); + sprintf(buffer, http_origin, host); + ConnWrite(socket, buffer, strlen(buffer)); + + ConnWrite(socket, http_upgrade, strlen(http_upgrade)); + ConnWrite(socket, http_connection, strlen(http_connection)); + ConnWrite(socket, http_ws_key, strlen(http_ws_key)); + ConnWrite(socket, http_ws_version, strlen(http_ws_version)); + ConnWrite(socket, "\r\n", 2); + + if (ConnRead(socket, server_reply, 300) <= 0) { + return -1; + } + + if (strstr(server_reply, "HTTP/1.1 101 Switching Protocols") == NULL) { + return -1; + } + + if (strstr(server_reply, "permessage-deflate") != 0) { + *compression = true; + } + + return 1; +} + +void wsInitFrame(WsFrame *frame) { + frame->finFlag = 0; + frame->maskingFlag = 0; + frame->opcode = 0; + frame->payloadLenght = 0; + frame->maskingMap = 0; + frame->payload = 0; +} + +void wsCreateTextFrame(WsFrame *frame, const char *text) { + frame->finFlag = 1; + frame->maskingFlag = 1; + frame->opcode = WS_OPCODE_TEXT; + frame->payloadLenght = strlen(text); + frame->maskingMap = 0x00000000; + frame->payload = (uint8_t *)text; +} + +void wsSendFrame(int socket, WsFrame *frame) { + uint8_t finOpcode = (frame->finFlag) ? frame->opcode | 0x80 : frame->opcode; + uint8_t maskPayloadLength = (frame->maskingFlag) ? frame->payloadLenght | 0x80 : frame->payloadLenght; + unsigned short sizePayload; + + // todo support extended payloadLength + // The length of the "Payload data", in bytes: if 0-125, that is the + // payload length. If 126, the following 2 bytes interpreted as a + // 16-bit unsigned integer are the payload length. If 127, the + // following 8 bytes interpreted as a 64-bit unsigned integer (the + // most significant bit MUST be 0) are the payload length. Multibyte + // length quantities are expressed in network byte order. Note that + // in all cases, the minimal number of bytes MUST be used to encode + // the length, for example, the length of a 124-byte-long string + // can't be encoded as the sequence 126, 0, 124. The payload length + // is the length of the "Extension data" + the length of the + // "Application data". The length of the "Extension data" may be + // zero, in which case the payload length is the length of the + // "Application data". + // FE = 126 + if (frame->payloadLenght > 125) { + maskPayloadLength = 0xfe; + sizePayload = MAKEWORD(LL(frame->payloadLenght), LH(frame->payloadLenght)); + } + + + ConnWrite(socket, &finOpcode, 1); + ConnWrite(socket, &maskPayloadLength, 1); + + if (frame->payloadLenght > 125) { + sizePayload = _SWAPS(sizePayload); + ConnWrite(socket, &sizePayload, 2); + } + + ConnWrite(socket, &frame->maskingMap , 4); + ConnWrite(socket, frame->payload, frame->payloadLenght); +} + +void wsReceiveFrame(int socket, WsFrame *frame, Bool *timeout, int seconds) { + unsigned char inBuffer[2048]; + int receivedTotal = 0; + int receivedLength = 0; + int i = 0, ret = 0; + + *timeout = false; + memset(inBuffer, 0, sizeof(inBuffer)); + + while((ret = ConnReadBytesAvailable(socket)) == 0) { + i++; + sleep_ms(10); + if ((i > (seconds * 100)) && ret == 0) { + *timeout = true; + return; + } + } + + while((receivedLength = ConnRead(socket, inBuffer + receivedTotal, 2 - receivedTotal)) > 0 && receivedTotal < 2) { + receivedTotal += receivedLength; + } + + frame->finFlag = (inBuffer[0] & 0x80) >> 7; + frame->opcode = inBuffer[0] & 0x0F; + frame->maskingFlag = inBuffer[1] & 0x80; + frame->payloadLenght = MAKEWORD(inBuffer[1], 0x00); + + if (frame->payloadLenght > 125) { + receivedLength = ConnRead(socket, inBuffer, 2); + frame->payloadLenght = MAKEWORD(inBuffer[1], inBuffer[0]); + } + + while((receivedLength = ConnRead(socket, inBuffer + receivedTotal, frame->payloadLenght + 2 - receivedTotal)) > 0 && receivedTotal < frame->payloadLenght) { + receivedTotal += receivedLength; + if ((receivedTotal - 2) == frame->payloadLenght) break; + } + + memcpy(frame->payload, inBuffer + 2, receivedTotal - 2); + + if (frame->opcode == WS_OPCODE_PING) { + wsSendPong(socket, frame); + } +} + +unsigned char gzipBuffer[2048]; +void wsInflateFrame(WsFrame *frame) { + unsigned int outlen = 2048; + + memset(gzipBuffer, 0, sizeof(gzipBuffer)); + + // https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-28#page-22 + // 7.2.2 Decompression + frame->payload[frame->payloadLenght-1] = 0x01; + frame->payload[frame->payloadLenght] = 0x00; + frame->payload[frame->payloadLenght+1] = 0x00; + frame->payload[frame->payloadLenght+2] = 0xff; + frame->payload[frame->payloadLenght+3] = 0xff; + + tinf_uncompress(gzipBuffer, &outlen, frame->payload, frame->payloadLenght+4); + + memset(frame->payload, 0, frame->payloadLenght+3); + memcpy(frame->payload, gzipBuffer, outlen); +} + +void wsDeflateFrame(WsFrame *frame) { + struct Outbuf out = {0}; + memset(gzipBuffer, 0, sizeof(gzipBuffer)); + + //Deflate payload + zlib_start_block(&out); + tinf_compress(&out, frame->payload, frame->payloadLenght); + zlib_finish_block(&out); + + memcpy(gzipBuffer, out.outbuf, out.outlen); + + frame->finFlag = 0; + frame->opcode = 0xc1; + frame->payloadLenght = out.outlen; + frame->payload = (uint8_t *)gzipBuffer; + + zlib_free_block(&out); +} + +void wsSendPong(int socket, WsFrame *frame) { + frame->maskingFlag = 1; + frame->opcode = WS_OPCODE_PONG; + frame->payloadLenght = 0; + wsSendFrame(socket, frame); +} + +void wsSendText(int socket, const char *text, Bool compression) { + WsFrame frame; + wsInitFrame(&frame); + wsCreateTextFrame(&frame, text); + if (compression) wsDeflateFrame(&frame); + wsSendFrame(socket, &frame); +} + +void wsReceiveText(int socket, char *buffer, int bufferSize, Bool compression, Bool *timeout, int seconds) { + WsFrame frame; + wsInitFrame(&frame); + memset(buffer, 0, bufferSize); + frame.payload = (uint8_t *)buffer; + wsReceiveFrame(socket, &frame, timeout, seconds); + if (frame.opcode == WS_OPCODE_TEXT && compression) wsInflateFrame(&frame); + if (frame.opcode == WS_OPCODE_PONG) strcpy(buffer, "OPCODE_PING"); +} + +void hex_dump(const char *desc, const void *addr, const size_t len) { + int i; + unsigned char buff[17]; + unsigned char *pc = (unsigned char*)addr; + + // Output description if given. + if (desc != NULL) + printf ("%s:\n", desc); + + // Process every byte in the data. + for (i = 0; i < len; i++) { + // Multiple of 16 means new line (with line offset). + + if ((i % 16) == 0) { + // Just don't print ASCII for the zeroth line. + if (i != 0) + printf (" %s\n", buff); + + // Output the offset. + printf (" %04x ", i); + } + + // Now the hex code for the specific character. + printf (" %02x", pc[i]); + + // And store a printable ASCII character for later. + if ((pc[i] < 0x20) || (pc[i] > 0x7e)) + buff[i % 16] = '.'; + else + buff[i % 16] = pc[i]; + buff[(i % 16) + 1] = '\0'; + } + + // Pad out last line if not exactly 16 characters. + while ((i % 16) != 0) { + printf (" "); + i++; + } + + // And print the final ASCII bit. + printf (" %s\n", buff); +} diff --git a/examples/websocket_mbedtls/ws.h b/examples/websocket_mbedtls/ws.h new file mode 100644 index 0000000..0aa1239 --- /dev/null +++ b/examples/websocket_mbedtls/ws.h @@ -0,0 +1,27 @@ +#ifndef WEBSOCKETS +#define WEBSOCKETS + +#include "util.h" + +typedef struct WsFrame_ { + Byte finFlag; + Byte maskingFlag; + Byte opcode; + Uint32 payloadLenght; + Uint32 maskingMap; + Byte *payload; +} WsFrame; + +int wsConnect(int socket, const char *host, const char *path, Bool *compression); +void wsInitFrame(WsFrame *frame); +void wsCreateTextFrame(WsFrame *frame, const char *text); +void wsSendFrame(int socket, WsFrame *frame); +void wsReceiveFrame(int socket, WsFrame *frame, Bool *timeout, int seconds); +void wsInflateFrame(WsFrame *frame); +void wsDeflateFrame(WsFrame *frame); +void wsSendPong(int socket, WsFrame *frame); +void wsSendText(int socket, const char *text, Bool compression); +void wsReceiveText(int socket, char *buffer, int bufferSize, Bool compression, Bool *timeout, int seconds); +void hex_dump(const char *desc, const void *addr, const size_t len); + +#endif diff --git a/lwip/include/lwipopts.h b/lwip/include/lwipopts.h index 8f46c7d..113c89d 100644 --- a/lwip/include/lwipopts.h +++ b/lwip/include/lwipopts.h @@ -345,7 +345,8 @@ /** * LWIP_SO_RCVBUF==1: Enable SO_RCVBUF processing. */ -#define LWIP_SO_RCVBUF 0 +#define LWIP_SO_RCVBUF 1 +#define INT_MAX __INT_MAX__ /** * SO_REUSE==1: Enable SO_REUSEADDR option.