Skip to content

feat: integrate toml-c, add server.toml config system (A4 #124) - #173

Merged
Cadacious merged 3 commits into
mainfrom
agent/124-toml-parser
Feb 24, 2026
Merged

Cadacious merged 3 commits into
mainfrom
agent/124-toml-parser

Conversation

@aurablacklight

@aurablacklight aurablacklight commented Feb 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Vendor toml-c (arp242/toml-c, MIT) as src/toml/toml.c + toml.h — no build-time dependency, zero external downloads
  • Implement a layered config system: hardcoded defaults → server.toml → CLI args; CLI always wins
  • Expose g_server_cfg (type obc_server_cfg_t) as a server global; future module loaders (A2) and the engine API table (A3) will pass a pointer to it into loaded modules

Changes

File Description
src/toml/toml.c, toml.h, LICENSE Vendored toml-c library (MIT)
include/openbc/config.h obc_server_cfg_t, obc_module_cfg_t, full public API
src/server/config.c obc_config_defaults/load/load_str/free + obc_config_mod_string/int/float/bool
include/openbc/server_state.h Added extern obc_server_cfg_t g_server_cfg
src/server/server_state.c Definition of g_server_cfg
src/server/main.c Load server.toml before CLI arg parsing
server.toml Default stock configuration file (all sections, all options commented)
LICENSE-THIRD-PARTY MIT attribution for toml-c (Copyright CK Tan)
tests/test_config.c 17 unit tests: defaults, load_str, all sections, module accessors, error paths
Makefile -Isrc flag; TOML_OBJ + CONFIG_OBJ added to SERVER_LIB_OBJ

Testing

make all && make test
# 25 passed, 0 failed — zero warnings

New test suite test_config covers:

  • obc_config_defaults() values match documented defaults
  • obc_config_load_str() for every TOML section ([server], [game], [data], [gamespy], [master], [[modules]])
  • Absent fields leave defaults untouched
  • Missing file returns false silently
  • Malformed TOML returns false
  • obc_config_mod_{string,int,float,bool}() found/missing key/missing module paths
  • obc_config_free() no-op safety check

Notes

  • obc_server_cfg_t.name and .map are capped at 64 bytes to match the GameSpy hostname/missionscript wire fields — prevents -Wformat-truncation warnings from the main.c snprintf calls that copy these into g_info.
  • Module config values are stored as strings regardless of TOML type, enabling uniform access via the obc_config_mod_* accessors without requiring callers to know the original type.
  • [[modules]] entries with lua = "..." are parsed and stored; the Lua runtime (A14) will consume them.

Closes #124

Summary by CodeRabbit

  • New Features

    • TOML-based server configuration support with server, game, data, gamespy, masters and per-module settings; defaults applied at startup and CLI options retain precedence.
    • Runtime config layer exposes settings for port, player limits, map, log settings, mod packs, masters and module-specific key/value overrides.
  • Documentation

    • Added third-party license documentation.
  • Tests

    • Added comprehensive config tests for defaults, TOML parsing, overrides, ranges and module accessors.

@coderabbitai

coderabbitai Bot commented Feb 24, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6699347 and fe12859.

📒 Files selected for processing (12)
  • LICENSE-THIRD-PARTY
  • Makefile
  • include/openbc/config.h
  • include/openbc/server_state.h
  • server.toml
  • src/server/config.c
  • src/server/main.c
  • src/server/server_state.c
  • src/toml/LICENSE
  • src/toml/toml.c
  • src/toml/toml.h
  • tests/test_config.c

📝 Walkthrough

Walkthrough

Adds a vendored TOML library, a fixed-size TOML-backed server configuration subsystem, integrates config loading into server startup and global state, updates the build to compile new sources, adds unit tests for configuration, and documents the third‑party license.

Changes

Cohort / File(s) Summary
TOML library
src/toml/toml.h, src/toml/toml.c, src/toml/LICENSE
Adds a vendored single-file TOML parser and header with full parsing API, typed accessors, memory management, and MIT license text. Review for API correctness, memory safety, and licensing attribution.
Configuration API & Implementation
include/openbc/config.h, src/server/config.c
Adds public fixed-size config structs and module cfg types, default init, file/string loaders, TOML processing, per-module accessors (string/int/float/bool), and bounded storage with validation and warnings. Audit parsing logic, bounds/truncation handling, and error paths.
Server integration & state
src/server/main.c, src/server/server_state.c, include/openbc/server_state.h, server.toml
Introduces g_server_cfg, loads server.toml during startup (defaults → TOML → CLI), maps cfg into runtime globals, and adds server.toml template. Verify ordering of overrides and integration with CLI logic.
Build system
Makefile
Adds src/toml/toml.c and src/server/config.c to build, exposes -Isrc, updates object lists, and alters header-dependency inclusion. Check build on target platforms and dependency tracking changes.
Tests
tests/test_config.c
Adds comprehensive unit tests covering defaults, per-section TOML parsing, modules, bounds/validation, error cases, and accessor behavior. Run and inspect tests for coverage and flakiness.
Third‑party license
LICENSE-THIRD-PARTY
Adds attribution and full MIT license text for the vendored TOML library. Confirm license placement and wording.

Sequence Diagram(s)

sequenceDiagram
    participant Main as Server Main
    participant ConfigAPI as obc_config_*()
    participant TOML as toml_parse/_file
    participant CFG as g_server_cfg (in-memory)
    participant Runtime as Game Runtime

    Main->>ConfigAPI: obc_config_defaults(&g_server_cfg)
    Main->>ConfigAPI: obc_config_load(path?, &g_server_cfg)
    ConfigAPI->>TOML: toml_parse_file()/toml_parse()
    TOML-->>ConfigAPI: toml_table_t* root
    ConfigAPI->>ConfigAPI: process_toml(root) (server, game, data, gamespy, master, modules)
    ConfigAPI->>CFG: populate fields & arrays
    ConfigAPI-->>Main: return success/failure
    Main->>Runtime: map g_server_cfg -> runtime globals (port, players, map, masters, logs, etc.)
    Runtime->>Runtime: use globals during server lifecycle
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐇 I nibbled TOML leaves by lamplight,

Keys and tables tucked in neat and tight,
Modules whisper their names at dawn,
Defaults hum softly on the lawn,
Hooray — the server wakes at night!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: integrate toml-c, add server.toml config system (A4 #124)' clearly and concisely describes the main change: integration of a TOML parser and implementation of a server configuration system via TOML files.
Linked Issues check ✅ Passed The PR fully addresses issue #124 requirements: TOML parser integrated (toml-c vendored), server.toml loading implemented with layered defaults/TOML/CLI precedence, module config accessible via API, unit tests included, and all acceptance criteria met.
Out of Scope Changes check ✅ Passed All changes are in scope for issue #124. File changes include TOML parser integration, server config header/implementation, main.c CLI integration, server state updates, build configuration, example config file, and comprehensive tests—all directly supporting the TOML parsing and config system objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch agent/124-toml-parser

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/server/config.c`:
- Around line 24-205: Split the large process_toml function into smaller
section-specific static helpers to reduce complexity: create static void
process_server(toml_table_t *root, obc_server_cfg_t *cfg), process_game(...),
process_data(...), process_gamespy(...), process_master(...), and
process_modules(...), move the corresponding parsing blocks from process_toml
into each helper (preserving all field assignments, str_copy/free logic, array
bounds checks, and kv handling for modules), keep the helper functions static
for internal linkage, and replace the inline blocks in process_toml with calls
to these new helpers (ensuring any initialization like cfg->module_count or
cfg->mod_pack_count remains correct and behavior is unchanged).
- Around line 341-351: The function obc_config_mod_float uses strtod(v, NULL)
which returns 0.0 on parse failure, so replace the NULL end-pointer usage with
an endptr and validate conversion: set errno = 0, call strtod(v, &endptr), check
that endptr != v (meaning at least one char was parsed), ensure errno is not
ERANGE (or handle range errors appropriately), and that trailing characters are
acceptable (e.g., only whitespace). If any check fails, return default_val;
otherwise return the parsed double. Use the existing helpers find_module and
find_kv to locate v before parsing.
- Around line 353-363: obc_config_mod_bool currently treats any unrecognized
string as false instead of honoring default_val; update obc_config_mod_bool to
explicitly handle both true-y values ("true","1") returning true and false-y
values ("false","0") returning false, and for any other/unrecognized string
return default_val; locate the function obc_config_mod_bool (and helper calls
find_module and find_kv) and change the final return logic so unrecognized v
falls back to default_val rather than returning false.
- Around line 329-339: obc_config_mod_int currently calls strtol(v, NULL, 10)
and thus treats parse failures (e.g., "abc") as 0; update obc_config_mod_int to
use a char *endptr and call strtol(v, &endptr, 10), check that endptr != v (no
digits parsed) and that errno is not set to ERANGE for overflow/underflow, and
if any of these indicate failure return default_val; keep the existing
short-circuit checks using find_module and find_kv unchanged.
- Around line 159-199: The loop that reads module config keys using
toml_table_len/toml_table_key may iterate non-scalar entries (arrays/tables)
which will fail all scalar probes (toml_table_string/int/bool/double) and
silently skip them; add a concise comment above the loop (or immediately before
the scalar-type probes) explaining that nested tables/arrays in the
[modules.config] sub-table are intentionally unsupported and therefore skipped,
referencing the loop that uses toml_table_key(), toml_table_len(), and the m->kv
storage (obc_mod_kv_t, m->kv_count) so future readers understand this behavior
and it is not an accidental omission.

In `@src/server/main.c`:
- Around line 182-218: The TOML-loaded masters in g_server_cfg.masters are being
appended into user_masters[] (via user_master_count) so later CLI --master
entries end up added instead of replacing them; change the CLI parsing for
--master so that on the very first CLI-provided master you reset
user_master_count to 0 (clearing any TOML entries) before adding the CLI
entries, ensuring user_masters and user_master_count reflect only CLI masters
when any --master is supplied.

In `@src/toml/toml.c`:
- Around line 51-92: The helper functions read_unicode_escape,
toml_table_unparsed, toml_array_unparsed, toml_value_string, toml_value_bool,
toml_value_int, toml_value_double, toml_value_timestamp and is_leap should be
given internal linkage: add the static keyword to their forward declarations
near the top (the typedef and prototypes around
toml_table_unparsed/toml_array_unparsed/toml_value_* and read_unicode_escape)
and also add static to their corresponding definitions later in the file (the
definitions around lines where read_unicode_escape and the toml_value_*/is_leap
implementations appear) so these symbols are file-local and won’t pollute the
global symbol table.

In `@tests/test_config.c`:
- Around line 332-345: The obc_config_mod_bool function currently treats any
value other than "true"/"1" as false which makes unrecognized strings silently
override the default; update obc_config_mod_bool in src/server/config.c to
explicitly accept "true"/"1" as true, explicitly accept "false"/"0" as false,
and in all other cases return the provided default_val; ensure comparisons are
case-insensitive or normalize input if appropriate and reference the
cfg/module/key lookup logic already in obc_config_mod_bool when applying the
fix.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 139d31b and 031b492.

📒 Files selected for processing (12)
  • LICENSE-THIRD-PARTY
  • Makefile
  • include/openbc/config.h
  • include/openbc/server_state.h
  • server.toml
  • src/server/config.c
  • src/server/main.c
  • src/server/server_state.c
  • src/toml/LICENSE
  • src/toml/toml.c
  • src/toml/toml.h
  • tests/test_config.c

Comment thread src/server/config.c Resolved
Comment thread src/server/config.c Resolved
Comment thread src/server/config.c Resolved
Comment thread src/server/config.c Resolved
Comment thread src/server/config.c Resolved
Comment thread src/server/main.c Resolved
Comment thread src/toml/toml.c Resolved
Comment thread tests/test_config.c Resolved
@Cadacious

Copy link
Copy Markdown
Contributor

🔴 H1: Integer config values lack range validation

File: src/server/config.c (process_toml function)

Several integer fields are cast from int64_t to narrower types with no bounds checking:

Port (line ~32):

v = toml_table_int(server, "port");
if (v.ok) cfg->port = (uint16_t)v.u.i;

Values like -1, 0, 70000, or 99999 silently wrap via (uint16_t) cast. Port 0 causes bind() to select an ephemeral port. Negative values wrap to high port numbers.

difficulty -- cast (int) with no validation anywhere (not even in main.c). difficulty = -5 or difficulty = 100 flows into game logic unchecked.

respawn_time -- no validation. A negative value could cause immediate respawn or underflow.

heartbeat_interval -- no validation. A value of 0 causes rapid-fire heartbeats flooding the master server.

Recommended fix -- validate in process_toml:

v = toml_table_int(server, "port");
if (v.ok && v.u.i >= 1 && v.u.i <= 65535)
    cfg->port = (uint16_t)v.u.i;
else if (v.ok)
    fprintf(stderr, "config: invalid port %lld (must be 1-65535)\n", (long long)v.u.i);

v = toml_table_int(game, "difficulty");
if (v.ok && v.u.i >= 0 && v.u.i <= 2)
    cfg->difficulty = (int)v.u.i;

v = toml_table_int(game, "respawn_time");
if (v.ok && v.u.i >= 0 && v.u.i <= 3600)
    cfg->respawn_time = (int)v.u.i;

v = toml_table_int(master, "heartbeat_interval");
if (v.ok && v.u.i >= 10 && v.u.i <= 3600)
    cfg->heartbeat_interval = (int)v.u.i;

Severity: High -- invalid values silently propagate into server behavior with no warning.

@Cadacious

Copy link
Copy Markdown
Contributor

🔴 H2: strtol/strtod in mod accessors have no error checking

File: src/server/config.c (mod accessor functions)

// obc_config_mod_int (line ~338):
return (int)strtol(v, NULL, 10);

// obc_config_mod_float (line ~350):
return strtod(v, NULL);

Three problems:

  1. NULL endptr: No way to detect non-numeric input. strtol("hello", NULL, 10) returns 0 silently, indistinguishable from a legitimate "0". strtol("123abc", NULL, 10) returns 123, silently ignoring trailing garbage.

  2. Overflow: strtol with "99999999999999" returns LONG_MAX and sets errno = ERANGE, but errno is never checked, so it silently clips.

  3. strtod edge cases: strtod("inf") returns INFINITY, strtod("nan") returns NaN. Both are valid IEEE 754 but not valid config values.

Recommended fix:

int obc_config_mod_int(const obc_server_cfg_t *cfg,
                        const char *module_name,
                        const char *key, int default_val)
{
    // ... find_module / find_kv ...
    if (!v || !*v) return default_val;
    char *end;
    errno = 0;
    long val = strtol(v, &end, 10);
    if (end == v || *end != '\0' || errno == ERANGE)
        return default_val;
    if (val < INT_MIN || val > INT_MAX)
        return default_val;
    return (int)val;
}

Similar pattern for obc_config_mod_float -- check end == v, *end != '\0', and isinf()/isnan().

Severity: High -- silent incorrect conversions from malformed config values.

@Cadacious

Copy link
Copy Markdown
Contributor

🟡 M1: Path traversal risk in DLL/Lua module paths (document now, enforce later)

File: src/server/config.c (process_toml -- module dll/lua parsing), include/openbc/config.h

Config fields dll, lua, mode_file, registry, manifest, log_file, and mod_packs[] all accept arbitrary file paths from the TOML file with no validation against path traversal sequences like ../../etc/passwd.

Current risk: LOW -- server.toml is a local admin-controlled file, and the dll/lua fields are not yet consumed by any loader.

Future risk: HIGH -- the dll field is explicitly designed for dlopen(cfg->modules[i].dll). When the module loader (A2) lands, a malicious TOML file could load arbitrary code from anywhere on disk.

Recommended action for this PR: Add a comment in config.h on the module struct:

/* SECURITY: dll/lua paths will be used for code loading in A2.
 * When the module loader is implemented, validate paths against
 * an allowlist directory and reject traversal sequences (../).  */
char dll[256];
char lua[256];

@Cadacious

Copy link
Copy Markdown
Contributor

🟡 M2: Silent string truncation with no warning

File: src/server/config.c (str_copy helper, line ~12)

static void str_copy(char *dst, size_t dstsz, const char *src)
{
    if (!dst || dstsz == 0 || !src) return;
    strncpy(dst, src, dstsz - 1);
    dst[dstsz - 1] = '\0';
}

Correct and safe (always NUL-terminates), but truncation is silent. A server name longer than 63 bytes, a path longer than 255 bytes, or a module key longer than 63 bytes is clipped with no indication to the operator.

Recommended enhancement:

static void str_copy(char *dst, size_t dstsz, const char *src)
{
    if (!dst || dstsz == 0 || !src) return;
    if (strlen(src) >= dstsz)
        fprintf(stderr, "config: warning: value truncated to %zu chars: '%.32s...'\n",
                dstsz - 1, src);
    strncpy(dst, src, dstsz - 1);
    dst[dstsz - 1] = '\0';
}

@Cadacious

Copy link
Copy Markdown
Contributor

🟡 M3: Capacity overflows silently truncate with no warning

File: src/server/config.c (process_toml)

When arrays exceed their maximums, excess entries are silently dropped:

  • [[modules]] > 16: excess modules silently ignored (line ~141: if (n > OBC_CFG_MODULES_MAX) n = OBC_CFG_MODULES_MAX)
  • master > 16: excess masters silently ignored
  • mod_packs > 8: excess packs silently ignored
  • [modules.config] > 32 keys: excess keys silently dropped (line ~163)

The operator gets a working but incomplete config with no indication anything was dropped.

Recommended fix: Add a warning after each clamping:

int n = toml_array_len(modules);
if (n > OBC_CFG_MODULES_MAX) {
    fprintf(stderr, "config: warning: %d modules exceeds max %d; extras ignored\n",
            n, OBC_CFG_MODULES_MAX);
    n = OBC_CFG_MODULES_MAX;
}

Apply same pattern to masters, mod_packs, and per-module kv_count.

@Cadacious

Copy link
Copy Markdown
Contributor

🟡 M4: Add file size guard before TOML parsing

File: src/server/config.c (obc_config_load, line ~248)

The vendored toml-c library reads the entire file into a heap buffer with no size limit. A symlinked server.toml pointing to a multi-gigabyte file or /dev/urandom would cause unbounded memory growth.

Recommended fix: Add a size check before parsing:

bool obc_config_load(const char *path, obc_server_cfg_t *cfg)
{
    if (!path || !cfg) return false;
    FILE *fp = fopen(path, "r");
    if (!fp) return false;

    /* Config files should be small; reject anything over 1 MB */
    fseek(fp, 0, SEEK_END);
    long sz = ftell(fp);
    if (sz > 1024 * 1024) {
        fprintf(stderr, "config: '%s' too large (%ld bytes, max 1MB)\n", path, sz);
        fclose(fp);
        return false;
    }
    rewind(fp);

    // ... existing parse logic ...
}

@Cadacious

Copy link
Copy Markdown
Contributor

🟡 M5: obc_config_mod_bool only recognizes "true" and "1"

File: src/server/config.c (line ~362)

return strcmp(v, "true") == 0 || strcmp(v, "1") == 0;

TOML booleans are stored as strings "true" or "false" by the config system. This check is correct for TOML-originated values. However, if a module config key is set to "yes", "on", "TRUE", or any other truthy-looking string, it silently evaluates to false.

This is probably correct as designed (TOML bools are always lowercase true/false), but worth a brief comment documenting the contract:

/* Only "true" and "1" are truthy. TOML boolean values are always
 * stored as lowercase "true"/"false". Other truthy strings like
 * "yes", "on", "TRUE" are NOT recognized. */
return strcmp(v, "true") == 0 || strcmp(v, "1") == 0;

@Cadacious

Copy link
Copy Markdown
Contributor

🟡 M6: Makefile CRLF-to-LF conversion inflates diff

File: Makefile

The Makefile on main uses CRLF line endings. This commit converted the entire file to LF. This is the sole cause of the massive diff (134 deletions / 81 additions for ~7 actual semantic changes). Using git diff -w reveals only these real changes:

  1. Added -Isrc to CFLAGS
  2. Added TOML_SRC and CONFIG_SRC
  3. Added TOML_OBJ and CONFIG_OBJ
  4. Added both to SERVER_LIB_OBJ

The CRLF-to-LF conversion is actually correct (LF is proper for Makefiles -- GNU Make can be picky about CRLF in recipes). But it should ideally be a separate commit so git blame stays clean.

Recommendation: Consider adding a .gitattributes file with Makefile text eol=lf to prevent this from recurring.

@Cadacious

Copy link
Copy Markdown
Contributor

🔵 L1: -Isrc global include path -- acceptable but sets a precedent

File: Makefile (CFLAGS line)

CFLAGS := -std=c11 -Wall -Wextra -Wpedantic -Iinclude -Isrc -g -O2 $(POSIX_DEFS)

Adding -Isrc globally means all source files can now #include "toml/toml.h" (resolving to src/toml/toml.h). Current directories under src/ (client/, server/, shared/, toml/) don't collide with system headers. But any future file placed directly in src/ (e.g., src/string.h, src/time.h) would shadow system headers.

Recommendation: This is fine today. Just be aware of the convention: no file under src/ should share a name with a system header.

@Cadacious

Copy link
Copy Markdown
Contributor

🔵 L2: Vendored toml-c placement in src/toml/ vs vendor/

File: src/toml/toml.c, src/toml/toml.h, src/toml/LICENSE

The project currently has no vendor/ or third_party/ directory. Third-party code (toml-c) lives at src/toml/, mixed in with project source at the same level as src/server/. The LICENSE-THIRD-PARTY and src/toml/LICENSE files properly attribute the code.

Consideration: If more third-party libraries are vendored in the future, establishing a vendor/ convention now would keep the tree clean. But for a single vendored library, src/toml/ is pragmatic and the LICENSE files handle discoverability.

Not blocking -- just a convention question for the project.

@Cadacious

Copy link
Copy Markdown
Contributor

🔵 L3: toml-c CALLOC uses int for allocation size (upstream issue)

File: src/toml/toml.c (lines 16-23)

static void* CALLOC(size_t nmemb, size_t sz) {
    int   nb = ALIGN8(sz) * nmemb;  // <-- int, not size_t
    void* p  = malloc(nb);
    ...
}

The multiplication ALIGN8(sz) * nmemb is computed in int (32-bit signed). On the i686 target, a crafted TOML file with huge numbers of keys could theoretically overflow nb to a small value, causing a too-small allocation. In practice, this requires gigabytes of TOML data, so the risk is negligible for a local config file.

This is an upstream toml-c issue, not something to fix in vendored code. The M4 file size guard (1MB limit) would prevent this from being exploitable. If toml-c is ever used to parse untrusted TOML (e.g., from network), this would need attention.

@Cadacious

Copy link
Copy Markdown
Contributor

🔵 L4: Memory leak analysis -- CLEAN

I traced all toml_table_string and toml_array_string calls in process_toml. Every heap-allocated string from toml-c is freed in the same if (v.ok) block:

v = toml_table_string(server, "name");
if (v.ok) { str_copy(cfg->name, sizeof(cfg->name), v.u.s); free(v.u.s); }

All 14 string-access sites are correctly paired with free(). The toml_table_t *root is freed via toml_free(root) in both obc_config_load and obc_config_load_str. The strdup in obc_config_load_str is freed regardless of parse success.

No memory leaks found.

@Cadacious

Copy link
Copy Markdown
Contributor

📋 Review Summary

PR scope vs issue #124: All acceptance criteria met -- TOML parser integrated, server.toml loading replaces hardcoded globals, module config sections accessible via obc_config_mod_* accessors, and 17 unit tests cover all API surfaces. The layered config model (defaults -> TOML -> CLI) is clean and well-integrated into main.c.

Area Grade Notes
API Design Good Clean struct, sensible fixed-size arrays, layered override model
TOML Integration Good Vendored toml-c compiles clean, MIT license properly attributed
Config Parsing Needs work Integer fields lack range validation (H1), mod accessors lack error checking (H2)
Security Acceptable Path traversal documented for future (M1), file size guard recommended (M4)
Memory Safety Clean No leaks, all toml-c strings properly freed
Test Coverage Good 17 tests cover all sections, absent fields, error paths, mod accessors
Build System Good Only 7 real changes hidden behind CRLF cleanup noise

Recommendation: Fix H1 (integer range validation) and H2 (strtol/strtod error checking) before merge. These are small changes that prevent silent misconfiguration from flowing into runtime behavior. The rest (M1-M6, L1-L4) can be follow-up.

aurablacklight added a commit that referenced this pull request Feb 24, 2026
@aurablacklight

Copy link
Copy Markdown
Contributor Author

@Cadacious Addressed the latest PR #173 review round in commit 9301a2e.

Summary of fixes:

  • H1 (integer range validation): added explicit range checks in TOML processing for:
    • [server].port (1..65535)
    • [game].difficulty (0..2)
    • [game].respawn_time (0..3600)
    • [master].heartbeat_interval (10..3600)
    • plus safe int64->int guards on other integer casts
  • H2 (mod accessor parse hardening):
    • obc_config_mod_int now validates strtol with endptr, ERANGE, and INT_MIN/INT_MAX bounds
    • obc_config_mod_float now validates strtod with endptr, ERANGE, and rejects NaN/Inf
  • M1: documented loader-path security expectations for dll/lua in config.h.
  • M2: str_copy now emits truncation warnings when values exceed fixed field sizes.
  • M3: added warnings when [[modules]], [gamespy].master, [data].mod_packs, or [modules.config] exceed capacity and get clamped.
  • M4: added file-size guard in obc_config_load (rejects files larger than 1MB before TOML parse).
  • M5: obc_config_mod_bool now recognizes true/1 and false/0; unrecognized strings return default_val.
  • CodeRabbit unresolved threads addressed:
    • refactored process_toml into per-section helpers (process_server_section, process_game_section, etc.)
    • documented intentional skip of nested arrays/tables inside [modules.config]
    • fixed CLI behavior so first --master replaces TOML-loaded masters instead of appending
    • marked internal TOML helpers as static in src/toml/toml.c

Test updates:

  • Added regression coverage in tests/test_config.c for:
    • int-range validation boundaries/rejections
    • robust int/float/bool module accessor parsing and default fallback behavior

Validation run:

  • make all && make test
  • Result: all suites passed (25 passed, 0 failed).

@Cadacious

Copy link
Copy Markdown
Contributor

Re-review: PR #173 -- TOML Config System (Issue #124)

Summary

PR #173 adds a TOML-based configuration system. Vendors toml-c (MIT), implements config.h/config.c (497 lines total), ships a stock server.toml, integrates three-tier config layering (defaults -> TOML -> CLI) into main.c, and includes 17 unit tests. All string fields use fixed-size arrays (zero heap allocation for the config struct itself). Memory management is leak-free -- all 13 toml-c string allocations have matching frees.

Clean-Room Status

PASS. No RE artifacts. All protocol constants (port = 22101, system indices, mission strings) are from the existing clean-room codebase. Vendored library is MIT-licensed with proper attribution in LICENSE-THIRD-PARTY.

Security

  • String bounds: SAFE. str_copy() uses strncpy + explicit NUL termination. No overflows possible.
  • Format strings: SAFE. No config values used as format arguments.
  • Path traversal: DEFERRED. DLL/Lua/registry paths are stored as strings only. No file access occurs from these in this PR. Must be validated when module loading (A14) is implemented.
  • Port range: LOW RISK. (uint16_t)v.u.i truncates without validation. port = -1 becomes 65535, port = 99999 wraps. Pre-existing pattern from CLI path, but TOML is a natural place to add a range check.
  • max_players/system: ACCEPTABLE. Clamped in main.c after TOML load, but g_server_cfg fields remain unclamped. Latent risk if future code reads the raw config.

Game Correctness

4 issues found:

G1 -- MUST FIX: Master server accumulation breaks "CLI wins" contract.
TOML masters pre-load into user_masters[] before CLI parsing. When --master appears on CLI, entries are appended rather than replacing TOML entries. Every other field follows "CLI wins" semantics. Fix: track whether CLI provided --master, and if so, reset TOML entries first.

G2 -- MUST FIX: obc_config_mod_int returns 0 instead of default_val on parse failure.
strtol(v, NULL, 10) returns 0 for unparseable input "abc", which is indistinguishable from legitimate 0 and ignores default_val. Fix: use strtol(v, &end, 10) and check end != v.

G3 -- MUST FIX: obc_config_mod_float same issue. strtod(v, NULL) returns 0.0 for unparseable input. Same fix pattern.

G4 -- MUST FIX: obc_config_mod_bool returns false for unrecognized strings instead of default_val.
Current: return strcmp(v, "true") == 0 || strcmp(v, "1") == 0; -- any unrecognized string returns false regardless of default_val. Fix: recognize "true"/"1" as true, "false"/"0" as false, return default_val for anything else.

G5 -- 5 dead config fields. difficulty, respawn_time, gamespy.enabled, lan_discovery, heartbeat_interval are parsed but not wired to behavior. Acceptable for Phase 2 forward-looking, but should be documented in server.toml comments (users will expect enabled = false to disable GameSpy).

Code Quality

  • C11 compliant, zero warnings
  • Memory leak-free: 13 toml-c string allocations, 13 matching frees. strdup in load_str properly freed. toml_free(root) in both load paths.
  • Error handling good: missing file returns false silently, parse failure prints to stderr with context, NULL cfg checked in all public functions
  • Naming consistent: obc_config_* prefix, obc_*_t types, snake_case throughout
  • Struct size ~176 KB: Dominated by modules[16] with kv[32] each. Global/static storage is fine, but tests declare this on the stack 17 times. Latent MinGW __chkstk risk at -O1.
  • Makefile noise: +81/-77 lines but only 6 semantic changes. Line-ending normalization inflates the diff.

Suggestions

  1. F1 (must fix): Master accumulation -- see G1 above
  2. F2-F4 (must fix): Accessor parse failures -- see G2-G4 above
  3. F5 (should fix): Add stack-size warning comment to config.h above the struct
  4. F6 (should fix): Document dead fields in server.toml with "(parsed, not yet active)" comments
  5. F7 (nice-to-have): Port range validation in process_toml()
  6. F8 (nice-to-have): Consider reducing OBC_CFG_MODCFG_MAX from 32 to 8 (drops struct from ~176 KB to ~45 KB)
  7. F9 (nice-to-have): Add boundary tests (>16 modules, oversized strings, port edge values)

Verdict

REQUEST CHANGES. Architecture is sound, memory management is clean, test coverage is strong. Four issues must be fixed:

  1. Master accumulation breaks "CLI wins" contract (G1)
  2. mod_int returns 0 instead of default_val on parse failure (G2)
  3. mod_float returns 0.0 instead of default_val on parse failure (G3)
  4. mod_bool returns false instead of default_val for unrecognized strings (G4)

All four are small, localized fixes (a few lines each). F2-F4 affect accessor functions no module consumes yet, but the API contract should be correct from day one.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/server/config.c`:
- Around line 132-138: The [game].system value is parsed into cfg->system
without enforcing the documented 1..9 range; modify the toml_table_int handling
after parse_i64_for_int to check that parsed_system is between 1 and 9, assign
cfg->system only on success, and call warn_invalid_i64 (or a new range-specific
warning) when the value is out of range so g_server_cfg.system cannot remain
invalid for other consumers; keep existing parse_i64_for_int and toml_table_int
usage and ensure you don't duplicate clamping done in main.c.
- Around line 454-464: The use of strdup in obc_config_load_str is not portable
under -std=c11; replace the strdup(src) call with an explicit malloc of
strlen(src)+1 followed by memcpy and manual null-termination, assigning that
buffer to the same local variable (buf) and keeping the existing free(buf)
usage; update obc_config_load_str to use this malloc+memcpy pattern (or the
project's existing portable string helper if available) so the toml_parse call
still receives a writable copy.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 031b492 and 9301a2e.

📒 Files selected for processing (5)
  • include/openbc/config.h
  • src/server/config.c
  • src/server/main.c
  • src/toml/toml.c
  • tests/test_config.c

Comment thread src/server/config.c Resolved
Comment thread src/server/config.c Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_config.c`:
- Around line 1-5: Remove the unused header include <math.h> from the top of the
file (the include list that currently has "test_util.h" and "openbc/config.h");
it is not used by any functions or comparisons in this test (no fabs/sqrt/etc.),
so delete the line containing `#include` <math.h> to avoid unnecessary
dependencies and -Wpedantic noise.
- Around line 181-204: Add an explicit lower-bound check for port=1 in the TEST
function test_load_str_int_range_valid_boundaries by calling obc_config_load_str
with a TOML string where "[server]\nport = 1\n" (keep the other fields
identical) and asserting ASSERT(obc_config_load_str(toml, &cfg) == true)
followed by ASSERT_EQ_INT(1, (int)cfg.port); this can be done by either
replacing the existing TOML in test_load_str_int_range_valid_boundaries or
appending a second load/assert block in the same test to cover the lower
boundary while still validating cfg.system, cfg.difficulty, cfg.respawn_time,
and cfg.heartbeat_interval via their existing assertions.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9301a2e and 6699347.

📒 Files selected for processing (2)
  • src/server/config.c
  • tests/test_config.c

Comment thread tests/test_config.c
Comment on lines +1 to +5
#include "test_util.h"
#include "openbc/config.h"

#include <string.h>
#include <math.h>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Unused #include <math.h>.

No math functions (fabs, sqrt, etc.) are used in this file. The float comparisons rely on relational operators. Consider removing this include to keep the dependency set minimal and avoid -Wpedantic noise if the header pulls in platform-specific extensions.

Proposed fix
 `#include` "test_util.h"
 `#include` "openbc/config.h"
 
 `#include` <string.h>
-#include <math.h>
🧰 Tools
🪛 Cppcheck (2.19.0)

[information] 4-4: Include file

(missingIncludeSystem)


[information] 5-5: Include file

(missingIncludeSystem)


[information] 4-4: Include file

(missingIncludeSystem)


[information] 5-5: Include file

(missingIncludeSystem)


[information] 4-4: Include file

(missingIncludeSystem)


[information] 5-5: Include file

(missingIncludeSystem)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_config.c` around lines 1 - 5, Remove the unused header include
<math.h> from the top of the file (the include list that currently has
"test_util.h" and "openbc/config.h"); it is not used by any functions or
comparisons in this test (no fabs/sqrt/etc.), so delete the line containing
`#include` <math.h> to avoid unnecessary dependencies and -Wpedantic noise.

Comment thread tests/test_config.c
Comment on lines +181 to +204
TEST(test_load_str_int_range_valid_boundaries)
{
obc_server_cfg_t cfg;
obc_config_defaults(&cfg);

const char *toml =
"[server]\n"
"port = 65535\n"
"\n"
"[game]\n"
"system = 9\n"
"difficulty = 0\n"
"respawn_time = 3600\n"
"\n"
"[master]\n"
"heartbeat_interval = 10\n";

ASSERT(obc_config_load_str(toml, &cfg) == true);
ASSERT_EQ_INT(65535, (int)cfg.port);
ASSERT_EQ_INT(9, cfg.system);
ASSERT_EQ_INT(0, cfg.difficulty);
ASSERT_EQ_INT(3600, cfg.respawn_time);
ASSERT_EQ_INT(10, cfg.heartbeat_interval);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider adding a lower-boundary test for port = 1.

The test validates the upper boundary (port = 65535) but not the lower boundary (port = 1). While port = 0 is implicitly tested via the range-rejection test (port must be 1..65535), an explicit port = 1 acceptance test would complete the boundary pair.

Add to test_load_str_int_range_valid_boundaries or as a separate assertion
     ASSERT(obc_config_load_str(toml, &cfg) == true);
     ASSERT_EQ_INT(65535, (int)cfg.port);
+
+    /* Also verify lower boundary */
+    obc_config_defaults(&cfg);
+    ASSERT(obc_config_load_str("[server]\nport = 1\n", &cfg) == true);
+    ASSERT_EQ_INT(1, (int)cfg.port);
+
     ASSERT_EQ_INT(9, cfg.system);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_config.c` around lines 181 - 204, Add an explicit lower-bound
check for port=1 in the TEST function test_load_str_int_range_valid_boundaries
by calling obc_config_load_str with a TOML string where "[server]\nport = 1\n"
(keep the other fields identical) and asserting ASSERT(obc_config_load_str(toml,
&cfg) == true) followed by ASSERT_EQ_INT(1, (int)cfg.port); this can be done by
either replacing the existing TOML in test_load_str_int_range_valid_boundaries
or appending a second load/assert block in the same test to cover the lower
boundary while still validating cfg.system, cfg.difficulty, cfg.respawn_time,
and cfg.heartbeat_interval via their existing assertions.

Cadacious and others added 3 commits February 23, 2026 23:39
…124)

Vendor toml-c (MIT, arp242/toml-c) into src/toml/ and implement a
layered configuration system: hardcoded defaults -> server.toml -> CLI args.
Modules loaded in later phases will read per-module config via the
obc_config_mod_* accessors on the global g_server_cfg.

- src/toml/toml.c, toml.h, LICENSE: vendored toml-c library
- include/openbc/config.h: obc_server_cfg_t, obc_module_cfg_t, full API
- src/server/config.c: obc_config_defaults/load/load_str/free + mod accessors
- include/openbc/server_state.h: expose g_server_cfg global
- src/server/server_state.c: define g_server_cfg
- src/server/main.c: load server.toml before CLI arg parsing
- server.toml: stock default configuration file
- LICENSE-THIRD-PARTY: MIT attribution for toml-c (Copyright CK Tan)
- tests/test_config.c: 17 unit tests covering all API surfaces
- Makefile: add -Isrc, TOML_OBJ, CONFIG_OBJ to SERVER_LIB_OBJ

Closes #124

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Validate [game].system range 1..9 (was unchecked int)
- Replace strdup with malloc+memcpy for C11 portability
- Add system range tests to validation and boundary test cases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Cadacious
Cadacious force-pushed the agent/124-toml-parser branch from 6699347 to fe12859 Compare February 24, 2026 05:42
@Cadacious
Cadacious merged commit c367975 into main Feb 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A4: Integrate TOML parser

2 participants