Skip to content

File syn_settings.c

File List > src > syntropic > storage > syn_settings.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_SETTINGS) || SYN_USE_SETTINGS

#include "../util/syn_assert.h"
#include "../util/syn_crc.h"
#include "syn_settings.h"
#include "syn_vfs.h"

#include <string.h>

/* ── Helpers ────────────────────────────────────────────────────────────── */

static uint16_t compute_crc(const void *data, uint16_t size)
{
    return syn_crc16_ccitt((const uint8_t *)data, size);
}

/* ── API ────────────────────────────────────────────────────────────────── */

SYN_Status syn_settings_init(SYN_Settings *s, uint32_t flash_base, uint8_t sector_count, void *data,
                             uint16_t data_size, const void *defaults)
{
    if (s == NULL || data == NULL || defaults == NULL || data_size == 0) {
        return SYN_INVALID_PARAM;
    }

    SYN_ASSERT(s != NULL);
    SYN_ASSERT(data != NULL);
    SYN_ASSERT(defaults != NULL);
    SYN_ASSERT(data_size > 0);

    s->data = data;
    s->data_size = data_size;
    s->defaults = defaults;
    s->on_change = NULL;
    s->on_change_ctx = NULL;

    /* Initialize the backing param store */
    SYN_Status st = syn_param_init(&s->store, flash_base, sector_count, data_size);

    if (st == SYN_OK) {
        /* Flash has valid data — load it */
        st = syn_param_load(&s->store, data);
        if (st != SYN_OK) {
            /* Load failed — apply defaults */
            memcpy(data, defaults, data_size);
        }
    } else {
        /* Flash is blank or corrupt — apply defaults and write them */
        memcpy(data, defaults, data_size);
        syn_param_save(&s->store, data);
    }

    s->checksum = compute_crc(data, data_size);
    return SYN_OK;
}

SYN_Status syn_settings_save(SYN_Settings *s)
{
    if (s == NULL || s->data == NULL) {
        return SYN_INVALID_PARAM;
    }

    SYN_ASSERT(s != NULL);

    uint16_t new_crc = compute_crc(s->data, s->data_size);

    /* Only write flash if data actually changed */
    if (new_crc == s->checksum) {
        return SYN_OK; /* No change — skip flash write */
    }

    SYN_Status st = syn_param_save(&s->store, s->data);
    if (st == SYN_OK) {
        s->checksum = new_crc;

        if (s->on_change != NULL) {
            s->on_change(s->data, s->on_change_ctx);
        }
    }

    return st;
}

bool syn_settings_changed(const SYN_Settings *s)
{
    SYN_ASSERT(s != NULL);
    uint16_t live_crc = compute_crc(s->data, s->data_size);
    return live_crc != s->checksum;
}

SYN_Status syn_settings_reset(SYN_Settings *s)
{
    SYN_ASSERT(s != NULL);
    memcpy(s->data, s->defaults, s->data_size);
    return syn_settings_save(s);
}

void syn_settings_on_change(SYN_Settings *s, SYN_SettingsChangeCallback cb, void *ctx)
{
    SYN_ASSERT(s != NULL);
    s->on_change = cb;
    s->on_change_ctx = ctx;
}

SYN_Status syn_settings_reload(SYN_Settings *s)
{
    SYN_ASSERT(s != NULL);
    SYN_Status st = syn_param_load(&s->store, s->data);
    if (st == SYN_OK) {
        s->checksum = compute_crc(s->data, s->data_size);
    }
    return st;
}

int syn_settings_export(const SYN_Settings *s, void *buf, size_t len)
{
    if (s == NULL || buf == NULL)
        return -1;
    if (len < s->data_size)
        return -2;

    memcpy(buf, s->data, s->data_size);
    return (int)s->data_size;
}

SYN_Status syn_settings_import(SYN_Settings *s, const void *buf, size_t len, bool save)
{
    if (s == NULL || buf == NULL || len != s->data_size)
        return SYN_INVALID_PARAM;

    memcpy(s->data, buf, s->data_size);
    if (save) {
        return syn_settings_save(s);
    }

    s->checksum = compute_crc(s->data, s->data_size);
    return SYN_OK;
}

SYN_Status syn_settings_export_vfs(const SYN_Settings *s, const char *filepath)
{
    if (s == NULL || filepath == NULL)
        return SYN_INVALID_PARAM;

    int fd = syn_vfs_open(filepath, SYN_O_WRONLY | SYN_O_CREAT | SYN_O_TRUNC);
    if (fd < 0)
        return SYN_ERROR;

    int written = syn_vfs_write(fd, s->data, s->data_size);
    syn_vfs_close(fd);

    if (written < 0 || (size_t)written != s->data_size)
        return SYN_ERROR;

    return SYN_OK;
}

SYN_Status syn_settings_import_vfs(SYN_Settings *s, const char *filepath, bool save)
{
    if (s == NULL || filepath == NULL)
        return SYN_INVALID_PARAM;

    int fd = syn_vfs_open(filepath, SYN_O_RDONLY);
    if (fd < 0)
        return SYN_ERROR;

    int read_bytes = syn_vfs_read(fd, s->data, s->data_size);
    syn_vfs_close(fd);

    if (read_bytes < 0 || (size_t)read_bytes != s->data_size)
        return SYN_ERROR;

    if (save) {
        return syn_settings_save(s);
    }

    s->checksum = compute_crc(s->data, s->data_size);
    return SYN_OK;
}

/* ── Dual-Bank Transactional Settings Implementation ─────────────────────── */

SYN_Status syn_settings_dual_bank_init(SYN_DualBankSettings *db, uint32_t flash_base_a,
                                       uint32_t flash_base_b, uint8_t sector_count, void *data,
                                       uint16_t data_size, const void *defaults)
{
    SYN_ASSERT(db != NULL);
    SYN_ASSERT(data != NULL);
    SYN_ASSERT(defaults != NULL);
    SYN_ASSERT(data_size > 0);

    memset(db, 0, sizeof(*db));

    SYN_Status st_a =
        syn_settings_init(&db->bank_a, flash_base_a, sector_count, data, data_size, defaults);
    SYN_Status st_b =
        syn_settings_init(&db->bank_b, flash_base_b, sector_count, data, data_size, defaults);

    if (st_a == SYN_OK) {
        db->active_bank = 0;
        db->active_crc32 = syn_crc32((const uint8_t *)data, data_size);
    } else if (st_b == SYN_OK) {
        db->active_bank = 1;
        db->active_crc32 = syn_crc32((const uint8_t *)data, data_size);
    } else {
        memcpy(data, defaults, data_size);
        syn_settings_save(&db->bank_a);
        db->active_bank = 0;
        db->active_crc32 = syn_crc32((const uint8_t *)data, data_size);
    }

    return SYN_OK;
}

SYN_Status syn_settings_dual_bank_save(SYN_DualBankSettings *db)
{
    SYN_ASSERT(db != NULL);

    SYN_Settings *active = (db->active_bank == 0) ? &db->bank_a : &db->bank_b;
    SYN_Settings *inactive = (db->active_bank == 0) ? &db->bank_b : &db->bank_a;

    uint32_t current_crc = syn_crc32((const uint8_t *)active->data, active->data_size);
    if (current_crc == db->active_crc32) {
        return SYN_OK; /* Unchanged */
    }

    /* Write to inactive bank */
    SYN_Status st = syn_settings_save(inactive);
    if (st == SYN_OK) {
        /* Switch active bank */
        db->active_bank = (db->active_bank == 0) ? 1 : 0;
        db->active_crc32 = current_crc;
    }

    return st;
}

#endif /* SYN_USE_SETTINGS */