2024A-SW/components/NVM/Key_Value.c
2025-01-25 14:04:42 -06:00

228 lines
6.5 KiB
C

/*
* This program source code file is part of the KTag project.
*
* 🛡️ <https://ktag.clubk.club> 🃞
*
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* There should be a copy of the GNU Affero General Public License in the LICENSE
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <SystemK.h>
#define MAX_LINE_LENGTH 140
#define MAX_KEY_LENGTH 64
#define MAX_VALUE_LENGTH 64
static const char *TAG = "NVM KV";
static const char *TEMP_FILE = "/usb/esp/temp.txt";
// Trims the whitespace from both ends of a string.
static void KV_Trim(char *str)
{
char *start = str;
char *end = str + strlen(str) - 1;
while (*start && (*start == ' ' || *start == '\t'))
start++;
while (end > start && (*end == ' ' || *end == '\t' || *end == '\n'))
end--;
*(end + 1) = '\0';
memmove(str, start, end - start + 2);
}
// Splits a line around the '=' into a key and value pair.
static int KV_Parse_Line(char *line, char *key, char *value)
{
char *delimiter = strchr(line, '=');
if (delimiter == NULL)
return 0;
*delimiter = '\0';
strncpy(key, line, MAX_KEY_LENGTH - 1);
strncpy(value, delimiter + 1, MAX_VALUE_LENGTH - 1);
key[MAX_KEY_LENGTH - 1] = '\0';
value[MAX_VALUE_LENGTH - 1] = '\0';
KV_Trim(key);
KV_Trim(value);
return 1;
}
SystemKResult_T KV_Get_Value_string(const char *filename, const char *search_key, char *value)
{
FILE *file = fopen(filename, "r");
if (file == NULL)
{
KLOG_ERROR(TAG, "Couldn't open file %s for reading!", filename);
return SYSTEMK_RESULT_FILE_NOT_FOUND;
}
char line[MAX_LINE_LENGTH];
char key[MAX_KEY_LENGTH];
while (fgets(line, sizeof(line), file))
{
if (KV_Parse_Line(line, key, value) && strcmp(key, search_key) == 0)
{
fclose(file);
return SYSTEMK_RESULT_SUCCESS;
}
}
fclose(file);
KLOG_ERROR(TAG, "Couldn't find key %s in file %s!", search_key, filename);
return SYSTEMK_RESULT_KEY_NOT_FOUND;
}
SystemKResult_T KV_Set_Value_string(const char *filename, const char *set_key, const char *set_value)
{
FILE *file = fopen(filename, "r");
if (file == NULL)
{
KLOG_ERROR(TAG, "Couldn't open file %s for reading!", filename);
return SYSTEMK_RESULT_FILE_NOT_FOUND;
}
FILE *temp = fopen(TEMP_FILE, "w");
if (temp == NULL)
{
fclose(file);
KLOG_ERROR(TAG, "Couldn't open file %s for writing!", TEMP_FILE);
return SYSTEMK_RESULT_WRITE_FAILED;
}
char line[MAX_LINE_LENGTH];
char line_copy[MAX_LINE_LENGTH];
char key[MAX_KEY_LENGTH];
char value[MAX_VALUE_LENGTH];
int found = 0;
while (fgets(line, sizeof(line), file))
{
strncpy(line_copy, line, MAX_LINE_LENGTH);
line_copy[MAX_LINE_LENGTH - 1] = '\0'; // Ensure null-termination
if (KV_Parse_Line(line, key, value) && strcmp(key, set_key) == 0)
{
fprintf(temp, "%s = %s\n", set_key, set_value);
found = 1;
}
else
{
fputs(line_copy, temp);
}
}
if (!found)
{
fprintf(temp, "%s = %s\n", set_key, set_value);
}
fclose(file);
fclose(temp);
remove(filename);
rename(TEMP_FILE, filename);
return SYSTEMK_RESULT_SUCCESS;
}
SystemKResult_T KV_Get_Value_uint32(const char *filename, const char *search_key, uint32_t *value)
{
char value_str[MAX_VALUE_LENGTH];
SystemKResult_T result = KV_Get_Value_string(filename, search_key, value_str);
if (result != SYSTEMK_RESULT_SUCCESS)
{
return result;
}
char *endptr;
// Reset errno to check for conversion errors.
errno = 0;
*value = strtoul(value_str, &endptr, 10);
// Check for conversion errors
if ((errno == ERANGE && *value == ULONG_MAX) || (errno != 0))
{
KLOG_ERROR(TAG, "Error converting %s for key %s to uint32_t!", value_str, search_key);
return SYSTEMK_RESULT_WRONG_DATATYPE;
}
if (endptr == value_str)
{
KLOG_ERROR(TAG, "No digits were found in %s (for key %s)!", value_str, search_key);
return SYSTEMK_RESULT_WRONG_DATATYPE;
}
if (*value > UINT32_MAX)
{
KLOG_ERROR(TAG, "Value %s (for key %s) exceeds uint32_t range!", value_str, search_key);
return SYSTEMK_RESULT_OVERFLOW;
}
return SYSTEMK_RESULT_SUCCESS;
}
SystemKResult_T KV_Set_Value_uint32(const char *filename, const char *set_key, uint32_t *set_value)
{
char value_str[MAX_VALUE_LENGTH];
int written = snprintf(value_str, MAX_VALUE_LENGTH, "%lu", *set_value);
if (written < 0 || written >= MAX_VALUE_LENGTH)
{
KLOG_ERROR(TAG, "Error converting uint32_t to string for key %s!", set_key);
return SYSTEMK_RESULT_WRONG_DATATYPE;
}
return KV_Set_Value_string(filename, set_key, value_str);
}
SystemKResult_T KV_Get_Value_uint8(const char *filename, const char *search_key, uint8_t *value)
{
uint32_t value32 = 0;
SystemKResult_T result = KV_Get_Value_uint32(filename, search_key, &value32);
if (result != SYSTEMK_RESULT_SUCCESS)
{
return result;
}
// Check for conversion errors
if (value32 > UINT8_MAX)
{
KLOG_ERROR(TAG, "Value %lu (for key %s) exceeds uint32_t range!", value32, search_key);
return SYSTEMK_RESULT_OVERFLOW;
}
*value = (uint8_t)value32;
return SYSTEMK_RESULT_SUCCESS;
}
SystemKResult_T KV_Set_Value_uint8(const char *filename, const char *set_key, uint8_t *set_value)
{
char value_str[MAX_VALUE_LENGTH];
int written = snprintf(value_str, MAX_VALUE_LENGTH, "%u", *set_value);
if (written < 0 || written >= MAX_VALUE_LENGTH)
{
KLOG_ERROR(TAG, "Error converting uint8_t to string for key %s!", set_key);
return SYSTEMK_RESULT_WRONG_DATATYPE;
}
return KV_Set_Value_string(filename, set_key, value_str);
}