Make chat settings reads immutable and validation failures atomic

This commit is contained in:
evgeny committed 2026-10-27 03:15:15 +01:00
1 parent 27aa76f914
commit 43f9608fbf
2 files changed
+43 -30

No files matched your search

+22 -21
View File
@@ -86,29 +86,27 @@ typedef char chat_setting_capacity_check[CHAT_SETTING_COUNT <= CHAT_SETTING_MAX_
/* ─── state helpers ─── */
static void chat_setting_reset_defaults(struct chat_setting_state* s) {
for (size_t i = 0; i < CHAT_SETTING_COUNT; i++)
memset(s, 0, sizeof(*s));
for (size_t i = 0; i < CHAT_SETTING_COUNT; i++) {
s->values[i] = g_setting_defs[i].default_val;
snprintf(s->int_text[i], sizeof(s->int_text[i]), "%d", s->values[i]);
}
s->initialized = 1;
}
static int chat_setting_find(const char* name) {
if (!name) return -1;
for (size_t i = 0; i < CHAT_SETTING_COUNT; i++)
if (strcmp(g_setting_defs[i].name, name) == 0) return (int)i;
return -1;
}
static void chat_setting_apply(struct chat_setting_state* s) {
(void)s;
/* будет заполняться по мере появления runtime-setter'ов в модулях:
if (idx == ...) some_module_set_xxx(value); */
}
/* ─── state-level API ─── */
int chat_setting_set_state(struct chat_setting_state* s, const char* name, const char* value) {
if (!s || !name || !value || !*name || !*value) return -2;
if (!s->initialized) chat_setting_reset_defaults(s);
if (!s || !name || !value || !*name) {
DEBUG_ERROR(CS_ID, "chat_setting: invalid state/name/value argument"); return -2;
}
int idx = chat_setting_find(name);
if (idx < 0) {
@@ -122,18 +120,20 @@ int chat_setting_set_state(struct chat_setting_state* s, const char* name, const
if (def->type == CHAT_SETTING_STRING) {
if (strcmp(name, "call_video_bitrate_range_kbps") == 0) {
uint32_t minimum, maximum;
if (call_video_rate_parse_range(value, &minimum, &maximum)) return -2;
if (call_video_rate_parse_range(value, &minimum, &maximum)) {
DEBUG_ERROR(CS_ID, "chat_setting: '%s' invalid bitrate range '%s'", name, value); return -2;
}
}
if (strlen(value) >= sizeof(s->string_val[0])) {
DEBUG_ERROR(CS_ID, "chat_setting: '%s' string too long bytes=%zu limit=%zu", name, strlen(value), sizeof(s->string_val[0]) - 1);
return -2;
}
if (!s->initialized) chat_setting_reset_defaults(s);
for (int i = 0; i < s->string_count; i++) {
if (s->string_idx[i] == idx) {
if (strcmp(s->string_val[i], value) == 0) return 0;
snprintf(s->string_val[i], sizeof(s->string_val[i]), "%s", value);
DEBUG_INFO(CS_ID, "chat_setting: %s = %s", name, value);
chat_setting_apply(s);
return 0;
}
}
@@ -145,7 +145,6 @@ int chat_setting_set_state(struct chat_setting_state* s, const char* name, const
snprintf(s->string_val[s->string_count], sizeof(s->string_val[0]), "%s", value);
s->string_count++;
DEBUG_INFO(CS_ID, "chat_setting: %s = %s", name, value);
chat_setting_apply(s);
return 0;
}
@@ -156,18 +155,19 @@ int chat_setting_set_state(struct chat_setting_state* s, const char* name, const
} else {
char* end = NULL;
long lv = strtol(value, &end, 10);
if (!end || *end != '\0' || lv < def->min_val || lv > def->max_val) {
if (end == value || *end != '\0' || lv < def->min_val || lv > def->max_val) {
DEBUG_ERROR(CS_ID, "chat_setting: '%s' invalid int '%s' (range %d..%d)", name, value, def->min_val, def->max_val);
return -2;
}
v = (int)lv;
}
if (!s->initialized) chat_setting_reset_defaults(s);
if (s->values[idx] == v) return 0;
s->values[idx] = v;
snprintf(s->int_text[idx], sizeof(s->int_text[idx]), "%d", v);
DEBUG_INFO(CS_ID, "chat_setting: %s = %s", name, value);
chat_setting_apply(s);
return 0;
}
@@ -190,14 +190,13 @@ const char* chat_setting_get_state(const struct chat_setting_state* s, const cha
return "";
}
snprintf((char*)s->int_buf, sizeof(s->int_buf), "%d", s->values[idx]);
return s->int_buf;
return s->int_text[idx];
}
int chat_setting_get_int_state(const struct chat_setting_state* s, const char* name, int def) {
if (!s || !s->initialized) return def;
int idx = chat_setting_find(name);
return idx >= 0 ? s->values[idx] : def;
return idx >= 0 && g_setting_defs[idx].type != CHAT_SETTING_STRING ? s->values[idx] : def;
}
/* ─── instance-level API ─── */
@@ -208,12 +207,14 @@ static struct chat_setting_state* cs_state(struct UTUN_INSTANCE* inst) {
int chat_setting_set(struct UTUN_INSTANCE* inst, const char* name, const char* value) {
struct chat_setting_state* s = cs_state(inst);
if (!s) return -2;
if (!s) { DEBUG_ERROR(CS_ID, "chat_setting: instance is NULL"); return -2; }
int result = chat_setting_set_state(s, name, value);
if (!result && !strcmp(name, "incoming_call_policy"))
call_set_phone_state(inst, inst->phone_state, (unsigned)chat_setting_get_int(inst, name, CALL_POLICY_PUBLISH_PHONE));
if (!result && (!strcmp(name, "local_invites_enabled") || !strcmp(name, "local_broadcasts_enabled")))
return chat_local_apply_settings(inst);
if (!result && (!strcmp(name, "local_invites_enabled") || !strcmp(name, "local_broadcasts_enabled")) &&
chat_local_apply_settings(inst)) {
DEBUG_ERROR(CS_ID, "chat_setting: '%s' stored but LAN apply failed; retry setting to reapply", name); return -3;
}
return result;
}
+21 -9
View File
@@ -2,12 +2,13 @@
* chat_setting.h — chat-related settings (key-value API)
*
* Один API для всех потребителей: парсер конфига (headless) и GUI (через bridge).
* chat_setting_set(name, value) — валидация + хранение + apply к модулям.
* Возвращает 0=OK, -1=неизвестная настройка, -2=невалидное значение.
* Нулевое состояние инициализируется значениями реестра при первом успешном set.
* Ошибка валидации не меняет состояние. Чтение const, не выделяет память и не пишет в хранилище.
*
* Состояние — per-instance. Конфиг-парсер заполняет struct chat_setting_state
* из [chatserver] ещё до создания instance (state-уровень), затем runtime-модули
* читают через instance-уровень (chat_setting_get_int(inst, ...)).
* читают через instance-уровень (chat_setting_get_int(inst, ...)). State не синхронизирован:
* до старта им владеет парсер, runtime-вызовы выполняются в uasync-потоке instance.
*/
#ifndef CHAT_SETTING_H
@@ -27,23 +28,34 @@ extern "C" {
#define CHAT_SETTING_STRING_MAX 16
struct chat_setting_state {
int values[CHAT_SETTING_MAX_SETTINGS];
int string_idx[CHAT_SETTING_STRING_MAX];
char string_val[CHAT_SETTING_STRING_MAX][256];
int string_count;
int initialized;
char int_buf[16]; /* буфер для chat_setting_get_state у int-настроек */
int values[CHAT_SETTING_MAX_SETTINGS]; /* Числовые значения по индексу реестра. */
int string_idx[CHAT_SETTING_STRING_MAX]; /* Индексы занятых строковых настроек. */
char string_val[CHAT_SETTING_STRING_MAX][256]; /* NUL-строки, не более255 байт. */
int string_count; /* Число занятых string_val. */
int initialized; /* 0 до первого успешного set. */
char int_text[CHAT_SETTING_MAX_SETTINGS][16]; /* Текст числа, обновляемый только setter. */
};
/* ─── state-уровень (конфиг-парсер, до создания instance) ─── */
/* s изначально обнулён. name/value заимствованы до возврата; значение копируется.
* 0 — сохранено, -1 — неизвестное имя, -2 — неверные аргументы/значение/нет места.
* Числа — decimal в диапазоне реестра; bool — 0/1,no/yes,false/true; пути допускают пустую строку. */
int chat_setting_set_state(struct chat_setting_state* s, const char* name, const char* value);
/* Заимствованная NUL-строка до изменения этой настройки/уничтожения s; другие getters её не меняют.
* NULL — неизвестное имя/NULL s/неинициализированное состояние. Для bitrate range даже до set — "300:4000".
* Незаполненный строковый путь после инициализации — "". */
const char* chat_setting_get_state(const struct chat_setting_state* s, const char* name);
/* Число/bool либо def для неизвестного/строкового имени, NULL s и неинициализированного состояния. */
int chat_setting_get_int_state(const struct chat_setting_state* s, const char* name, int def);
/* ─── instance-уровень (runtime) ─── */
/* Те же коды; NULL inst — -2. Большинство потребителей читают значение при следующем обращении/старте.
* incoming_call_policy сразу обновляет телефонную политику, local_* — LAN permissions.
* -3 — значение сохранено, применение LAN не удалось; повтор set того же значения повторяет apply. */
int chat_setting_set(struct UTUN_INSTANCE* inst, const char* name, const char* value);
/* Instance-варианты чтения с теми же lifetime/default; NULL inst даёт NULL/def. */
const char* chat_setting_get(struct UTUN_INSTANCE* inst, const char* name);
int chat_setting_get_int(struct UTUN_INSTANCE* inst, const char* name, int def);