diff --git a/.vscode/settings.json b/.vscode/settings.json index 9c3abc48..c7bb3a88 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "workbench.colorTheme": "Gruvbox Dark Soft", + "workbench.colorTheme": "Dreamweaver Dark Theme", "C_Cpp.default.compileCommands": "${workspaceFolder}/compile_commands.json", "C_Cpp.default.cppStandard": "c17", "C_Cpp.default.intelliSenseMode": "clang-x64", diff --git a/compile_commands.json b/compile_commands.json index 32c7f4c3..c1a534d4 100644 --- a/compile_commands.json +++ b/compile_commands.json @@ -1,4 +1,10 @@ [ + { + "directory": "/workspace/build", + "command": "clang -Ilibgitmind.a.p -I. -I.. -I../core/include -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -std=c2x -O0 -g -std=c2x -pedantic -Wall -Wextra -Werror -Wvla -Wshadow -Wstrict-prototypes -Wconversion -Wsign-conversion -Wfloat-equal -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -O2 -fno-common -fstrict-aliasing -fPIC -pthread -MD -MQ libgitmind.a.p/core_src_cbor_cbor.c.o -MF libgitmind.a.p/core_src_cbor_cbor.c.o.d -o libgitmind.a.p/core_src_cbor_cbor.c.o -c ../core/src/cbor/cbor.c", + "file": "../core/src/cbor/cbor.c", + "output": "libgitmind.a.p/core_src_cbor_cbor.c.o" + }, { "directory": "/workspace/build", "command": "clang -Ilibgitmind.a.p -I. -I.. -I../core/include -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -std=c2x -O0 -g -std=c2x -pedantic -Wall -Wextra -Werror -Wvla -Wshadow -Wstrict-prototypes -Wconversion -Wsign-conversion -Wfloat-equal -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -O2 -fno-common -fstrict-aliasing -fPIC -pthread -MD -MQ libgitmind.a.p/core_src_crypto_backend.c.o -MF libgitmind.a.p/core_src_crypto_backend.c.o.d -o libgitmind.a.p/core_src_crypto_backend.c.o -c ../core/src/crypto/backend.c", @@ -23,6 +29,18 @@ "file": "../core/src/error/error.c", "output": "libgitmind.a.p/core_src_error_error.c.o" }, + { + "directory": "/workspace/build", + "command": "clang -Ilibgitmind.a.p -I. -I.. -I../core/include -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -std=c2x -O0 -g -std=c2x -pedantic -Wall -Wextra -Werror -Wvla -Wshadow -Wstrict-prototypes -Wconversion -Wsign-conversion -Wfloat-equal -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -O2 -fno-common -fstrict-aliasing -fPIC -pthread -MD -MQ libgitmind.a.p/core_src_io_io.c.o -MF libgitmind.a.p/core_src_io_io.c.o.d -o libgitmind.a.p/core_src_io_io.c.o -c ../core/src/io/io.c", + "file": "../core/src/io/io.c", + "output": "libgitmind.a.p/core_src_io_io.c.o" + }, + { + "directory": "/workspace/build", + "command": "clang -Ilibgitmind.a.p -I. -I.. -I../core/include -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -std=c2x -O0 -g -std=c2x -pedantic -Wall -Wextra -Werror -Wvla -Wshadow -Wstrict-prototypes -Wconversion -Wsign-conversion -Wfloat-equal -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -O2 -fno-common -fstrict-aliasing -fPIC -pthread -MD -MQ libgitmind.a.p/core_src_time_time.c.o -MF libgitmind.a.p/core_src_time_time.c.o.d -o libgitmind.a.p/core_src_time_time.c.o -c ../core/src/time/time.c", + "file": "../core/src/time/time.c", + "output": "libgitmind.a.p/core_src_time_time.c.o" + }, { "directory": "/workspace/build", "command": "clang -Ilibgitmind.a.p -I. -I.. -I../core/include -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -std=c2x -O0 -g -std=c2x -pedantic -Wall -Wextra -Werror -Wvla -Wshadow -Wstrict-prototypes -Wconversion -Wsign-conversion -Wfloat-equal -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -O2 -fno-common -fstrict-aliasing -fPIC -pthread -MD -MQ libgitmind.a.p/core_src_types_id.c.o -MF libgitmind.a.p/core_src_types_id.c.o.d -o libgitmind.a.p/core_src_types_id.c.o -c ../core/src/types/id.c", @@ -53,6 +71,12 @@ "file": "../core/src/types/string_utf8.c", "output": "libgitmind.a.p/core_src_types_string_utf8.c.o" }, + { + "directory": "/workspace/build", + "command": "clang -Ilibgitmind.a.p -I. -I.. -I../core/include -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -std=c2x -O0 -g -std=c2x -pedantic -Wall -Wextra -Werror -Wvla -Wshadow -Wstrict-prototypes -Wconversion -Wsign-conversion -Wfloat-equal -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -O2 -fno-common -fstrict-aliasing -fPIC -pthread -MD -MQ libgitmind.a.p/core_src_types_ulid.c.o -MF libgitmind.a.p/core_src_types_ulid.c.o.d -o libgitmind.a.p/core_src_types_ulid.c.o -c ../core/src/types/ulid.c", + "file": "../core/src/types/ulid.c", + "output": "libgitmind.a.p/core_src_types_ulid.c.o" + }, { "directory": "/workspace/build", "command": "clang -Ilibgitmind.a.p -I. -I.. -I../core/include -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -std=c2x -O0 -g -std=c2x -pedantic -Wall -Wextra -Werror -Wvla -Wshadow -Wstrict-prototypes -Wconversion -Wsign-conversion -Wfloat-equal -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -O2 -fno-common -fstrict-aliasing -fPIC -pthread -MD -MQ libgitmind.a.p/core_src_utf8_validate.c.o -MF libgitmind.a.p/core_src_utf8_validate.c.o.d -o libgitmind.a.p/core_src_utf8_validate.c.o -c ../core/src/utf8/validate.c", @@ -118,5 +142,29 @@ "command": "clang -Itest_crypto_backend.p -I. -I.. -I../core/include -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -std=c2x -O0 -g -std=c2x -pedantic -Wall -Wextra -Werror -Wvla -Wshadow -Wstrict-prototypes -Wconversion -Wsign-conversion -Wfloat-equal -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -O2 -fno-common -fstrict-aliasing -pthread -DGITMIND_ENABLE_TEST_BACKEND -MD -MQ test_crypto_backend.p/core_tests_unit_test_crypto_backend.c.o -MF test_crypto_backend.p/core_tests_unit_test_crypto_backend.c.o.d -o test_crypto_backend.p/core_tests_unit_test_crypto_backend.c.o -c ../core/tests/unit/test_crypto_backend.c", "file": "../core/tests/unit/test_crypto_backend.c", "output": "test_crypto_backend.p/core_tests_unit_test_crypto_backend.c.o" + }, + { + "directory": "/workspace/build", + "command": "clang -Itest_ulid.p -I. -I.. -I../core/include -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -std=c2x -O0 -g -std=c2x -pedantic -Wall -Wextra -Werror -Wvla -Wshadow -Wstrict-prototypes -Wconversion -Wsign-conversion -Wfloat-equal -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -O2 -fno-common -fstrict-aliasing -pthread -DGITMIND_ENABLE_TEST_BACKEND -MD -MQ test_ulid.p/core_tests_unit_test_ulid.c.o -MF test_ulid.p/core_tests_unit_test_ulid.c.o.d -o test_ulid.p/core_tests_unit_test_ulid.c.o -c ../core/tests/unit/test_ulid.c", + "file": "../core/tests/unit/test_ulid.c", + "output": "test_ulid.p/core_tests_unit_test_ulid.c.o" + }, + { + "directory": "/workspace/build", + "command": "clang -Itest_io.p -I. -I.. -I../core/include -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -std=c2x -O0 -g -std=c2x -pedantic -Wall -Wextra -Werror -Wvla -Wshadow -Wstrict-prototypes -Wconversion -Wsign-conversion -Wfloat-equal -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -O2 -fno-common -fstrict-aliasing -pthread -MD -MQ test_io.p/core_tests_unit_test_io.c.o -MF test_io.p/core_tests_unit_test_io.c.o.d -o test_io.p/core_tests_unit_test_io.c.o -c ../core/tests/unit/test_io.c", + "file": "../core/tests/unit/test_io.c", + "output": "test_io.p/core_tests_unit_test_io.c.o" + }, + { + "directory": "/workspace/build", + "command": "clang -Itest_time.p -I. -I.. -I../core/include -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -std=c2x -O0 -g -std=c2x -pedantic -Wall -Wextra -Werror -Wvla -Wshadow -Wstrict-prototypes -Wconversion -Wsign-conversion -Wfloat-equal -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -O2 -fno-common -fstrict-aliasing -pthread -MD -MQ test_time.p/core_tests_unit_test_time.c.o -MF test_time.p/core_tests_unit_test_time.c.o.d -o test_time.p/core_tests_unit_test_time.c.o -c ../core/tests/unit/test_time.c", + "file": "../core/tests/unit/test_time.c", + "output": "test_time.p/core_tests_unit_test_time.c.o" + }, + { + "directory": "/workspace/build", + "command": "clang -Itest_cbor.p -I. -I.. -I../core/include -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -std=c2x -O0 -g -std=c2x -pedantic -Wall -Wextra -Werror -Wvla -Wshadow -Wstrict-prototypes -Wconversion -Wsign-conversion -Wfloat-equal -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -O2 -fno-common -fstrict-aliasing -pthread -MD -MQ test_cbor.p/core_tests_unit_test_cbor.c.o -MF test_cbor.p/core_tests_unit_test_cbor.c.o.d -o test_cbor.p/core_tests_unit_test_cbor.c.o -c ../core/tests/unit/test_cbor.c", + "file": "../core/tests/unit/test_cbor.c", + "output": "test_cbor.p/core_tests_unit_test_cbor.c.o" } ] diff --git a/core/include/gitmind/cbor/cbor.h b/core/include/gitmind/cbor/cbor.h new file mode 100644 index 00000000..fdaea118 --- /dev/null +++ b/core/include/gitmind/cbor/cbor.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: LicenseRef-MIND-UCAL-1.0 */ +/* © 2025 J. Kirby Ross / Neuroglyph Collective */ + +#ifndef GITMIND_CBOR_CBOR_H +#define GITMIND_CBOR_CBOR_H + +#include +#include +#include + +/* Result types for CBOR operations */ +GM_RESULT_DEF(gm_result_uint64, uint64_t); + +/** + * CBOR read operations with Result types. + * All operations return Result types for proper error handling. + */ + +/** + * Read a CBOR unsigned integer from buffer. + * @param buf Buffer to read from + * @param offset Pointer to current offset (updated on success) + * @param max_size Maximum buffer size to prevent overruns + * @return Result containing the uint64_t value or error + */ +gm_result_uint64_t gm_cbor_read_uint(const uint8_t *buf, size_t *offset, size_t max_size); + +/** + * Read a CBOR byte string from buffer. + * @param buf Buffer to read from + * @param offset Pointer to current offset (updated on success) + * @param max_size Maximum buffer size to prevent overruns + * @param data Buffer to write bytes to + * @param expected_len Expected length of byte string + * @return Result indicating success or error + */ +gm_result_void_t gm_cbor_read_bytes(const uint8_t *buf, size_t *offset, size_t max_size, + uint8_t *data, size_t expected_len); + +/** + * Read a CBOR text string from buffer. + * @param buf Buffer to read from + * @param offset Pointer to current offset (updated on success) + * @param max_size Maximum buffer size to prevent overruns + * @param text Buffer to write text to (null-terminated) + * @param max_text_len Maximum text buffer size + * @return Result indicating success or error + */ +gm_result_void_t gm_cbor_read_text(const uint8_t *buf, size_t *offset, size_t max_size, + char *text, size_t max_text_len); + +/** + * CBOR write operations with Result types. + */ + +/** + * Write a CBOR unsigned integer to buffer. + * @param value Value to write + * @param buf Buffer to write to + * @param buf_size Size of output buffer + * @return Result containing number of bytes written or error + */ +gm_result_size_t gm_cbor_write_uint(uint64_t value, uint8_t *buf, size_t buf_size); + +/** + * Write a CBOR byte string to buffer. + * @param buf Buffer to write to + * @param buf_size Size of output buffer + * @param data Bytes to write + * @param data_len Length of bytes to write + * @return Result containing number of bytes written or error + */ +gm_result_size_t gm_cbor_write_bytes(uint8_t *buf, size_t buf_size, + const uint8_t *data, size_t data_len); + +/** + * Write a CBOR text string to buffer. + * @param buf Buffer to write to + * @param buf_size Size of output buffer + * @param text Null-terminated text to write + * @return Result containing number of bytes written or error + */ +gm_result_size_t gm_cbor_write_text(uint8_t *buf, size_t buf_size, const char *text); + +#endif /* GITMIND_CBOR_CBOR_H */ diff --git a/core/include/gitmind/cbor/constants_cbor.h b/core/include/gitmind/cbor/constants_cbor.h new file mode 100644 index 00000000..671bb8a4 --- /dev/null +++ b/core/include/gitmind/cbor/constants_cbor.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LicenseRef-MIND-UCAL-1.0 */ +/* © 2025 J. Kirby Ross / Neuroglyph Collective */ + +#ifndef GITMIND_CONSTANTS_CBOR_H +#define GITMIND_CONSTANTS_CBOR_H + +/* CBOR Major Types (RFC 7049) */ +#define CBOR_TYPE_UNSIGNED 0x00 +#define CBOR_TYPE_NEGATIVE 0x20 +#define CBOR_TYPE_BYTES 0x40 +#define CBOR_TYPE_TEXT 0x60 +#define CBOR_TYPE_ARRAY 0x80 +#define CBOR_TYPE_MAP 0xA0 +#define CBOR_TYPE_TAG 0xC0 +#define CBOR_TYPE_SIMPLE 0xE0 + +/* CBOR Type Masks */ +#define CBOR_TYPE_MASK 0xE0 +#define CBOR_ADDITIONAL_INFO_MASK 0x1F + +/* CBOR Additional Information Values */ +#define CBOR_MAX_IMMEDIATE 0x17 +#define CBOR_IMMEDIATE_THRESHOLD 24 +#define CBOR_UINT8_FOLLOWS 0x18 +#define CBOR_UINT16_FOLLOWS 0x19 +#define CBOR_UINT32_FOLLOWS 0x1A +#define CBOR_UINT64_FOLLOWS 0x1B +#define CBOR_RESERVED_28 0x1C +#define CBOR_RESERVED_29 0x1D +#define CBOR_RESERVED_30 0x1E +#define CBOR_INDEFINITE 0x1F + +/* CBOR Array Sizes for git-mind */ +#define CBOR_ARRAY_SIZE_ATTRIBUTED 13 /* Attributed edge format */ +#define CBOR_ARRAY_SIZE_LEGACY 8 /* Legacy edge format */ +#define CBOR_ARRAY_SIZE_EDGE 7 /* Basic edge format */ + +/* Field Sizes */ +#define CBOR_SHA_SIZE 20 /* Git SHA1 size in bytes */ +#define CBOR_ULID_SIZE 26 /* ULID size in bytes */ +#define CBOR_TIMESTAMP_SIZE 8 /* Unix timestamp size */ + +/* Field Limits */ +#define CBOR_MAX_STRING_LENGTH 65536 /* Maximum string field length */ +#define CBOR_MAX_PATH_LENGTH 4096 /* Maximum path length */ +#define CBOR_MAX_TYPE_LENGTH 64 /* Maximum relationship type length */ +#define CBOR_MAX_SOURCE_LENGTH 32 /* Maximum source identifier length */ +#define CBOR_MAX_AUTHOR_LENGTH 256 /* Maximum author string length */ +#define CBOR_MAX_SESSION_LENGTH 256 /* Maximum session ID length */ +#define CBOR_MAX_LANE_LENGTH 32 /* Maximum lane name length */ + +/* Bit Shift Constants */ +#define SHIFT_8 8 +#define SHIFT_16 16 +#define SHIFT_24 24 +#define SHIFT_32 32 +#define SHIFT_40 40 +#define SHIFT_48 48 +#define SHIFT_56 56 + +/* Byte Operations */ +#define BYTE_SIZE 8 +#define BYTE_MASK 0xFF + +/* Endianness Helpers */ +#define CBOR_ENCODE_UINT16(x) ((((x) >> 8) & 0xFF) | (((x) & 0xFF) << 8)) +#define CBOR_ENCODE_UINT32(x) \ + ((((x) >> 24) & 0xFF) | (((x) >> 8) & 0xFF00) | (((x) << 8) & 0xFF0000) | \ + (((x) << 24) & 0xFF000000)) + +#endif /* GITMIND_CONSTANTS_CBOR_H */ diff --git a/core/include/gitmind/time/time.h b/core/include/gitmind/time/time.h index db3da134..2de01219 100644 --- a/core/include/gitmind/time/time.h +++ b/core/include/gitmind/time/time.h @@ -7,6 +7,8 @@ #include #include #include +/* NOLINTNEXTLINE(misc-include-cleaner) - provides clockid_t on some systems */ +#include /* Result types for time operations */ GM_RESULT_DEF(gm_result_time, time_t); @@ -18,11 +20,12 @@ GM_RESULT_DEF(gm_result_tm_ptr, struct tm *); */ typedef struct gm_time_ops { gm_result_time_t (*time)(time_t *tloc); - gm_result_void_t (*clock_gettime)(clockid_t clk_id, struct timespec *tp); + /* NOLINTNEXTLINE(misc-include-cleaner) - clockid_t from time.h/sys/types.h */ + gm_result_void_t (*clock_gettime)(clockid_t clk_id, struct timespec *timespec_ptr); gm_result_tm_ptr_t (*localtime_r)(const time_t *timep, struct tm *result); gm_result_tm_ptr_t (*gmtime_r)(const time_t *timep, struct tm *result); - gm_result_size_t (*strftime)(char *s, size_t max, const char *format, - const struct tm *tm); + gm_result_size_t (*strftime)(char *str, size_t max, const char *format, + const struct tm *time_ptr); } gm_time_ops_t; /** diff --git a/core/src/cbor/cbor.c b/core/src/cbor/cbor.c new file mode 100644 index 00000000..b37ba785 --- /dev/null +++ b/core/src/cbor/cbor.c @@ -0,0 +1,478 @@ +/* SPDX-License-Identifier: LicenseRef-MIND-UCAL-1.0 */ +/* © 2025 J. Kirby Ross / Neuroglyph Collective */ + +#include +#include +#include +#include + +#include +#include + +/* Error code constants */ +static const int GmErrorCborTypeMismatch = 6001; +static const int GmErrorCborBufferTooSmall = 6002; +static const int GmErrorCborInvalidData = 6003; +static const int GmErrorCborOverflow = 6004; + +/* Size constants */ +static const size_t CborUint64Size = 8; +static const size_t CborUint32HeaderSize = 5; +static const size_t CborUint64HeaderSize = 9; + +/* Helper to check buffer bounds */ +static bool check_read_bounds(size_t offset, size_t read_size, size_t max_size) { + if (offset > max_size) { + return false; + } + if (read_size > max_size - offset) { + return false; + } + return true; +} + +/* Helper to read uint8 value */ +static gm_result_uint64_t read_uint8_value(const uint8_t *buf, size_t *offset, size_t max_size) { + if (!check_read_bounds(*offset, 1, max_size)) { + return (gm_result_uint64_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborBufferTooSmall, "Buffer underrun reading uint8") + }; + } + return (gm_result_uint64_t){.ok = true, .u.val = buf[(*offset)++]}; +} + +/* Helper to read uint16 value */ +static gm_result_uint64_t read_uint16_value(const uint8_t *buf, size_t *offset, size_t max_size) { + if (!check_read_bounds(*offset, 2, max_size)) { + return (gm_result_uint64_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborBufferTooSmall, "Buffer underrun reading uint16") + }; + } + uint64_t value = (uint64_t)(((uint16_t)buf[*offset] << SHIFT_8) | (uint16_t)buf[*offset + 1]); + *offset += 2; + return (gm_result_uint64_t){.ok = true, .u.val = value}; +} + +/* Helper to read uint32 value */ +static gm_result_uint64_t read_uint32_value(const uint8_t *buf, size_t *offset, size_t max_size) { + if (!check_read_bounds(*offset, 4, max_size)) { + return (gm_result_uint64_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborBufferTooSmall, "Buffer underrun reading uint32") + }; + } + uint64_t value = ((uint32_t)buf[*offset] << SHIFT_24) | + ((uint32_t)buf[*offset + 1] << SHIFT_16) | + ((uint32_t)buf[*offset + 2] << SHIFT_8) | + (uint32_t)buf[*offset + 3]; + *offset += 4; + return (gm_result_uint64_t){.ok = true, .u.val = value}; +} + +/* Helper to read uint64 value */ +static gm_result_uint64_t read_uint64_value(const uint8_t *buf, size_t *offset, size_t max_size) { + if (!check_read_bounds(*offset, CborUint64Size, max_size)) { + return (gm_result_uint64_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborBufferTooSmall, "Buffer underrun reading uint64") + }; + } + uint64_t value = 0; + for (int i = 0; i < BYTE_SIZE; i++) { + value = (value << SHIFT_8) | buf[(*offset)++]; + } + return (gm_result_uint64_t){.ok = true, .u.val = value}; +} + +/* Helper to read CBOR uint value based on info type */ +/* NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - parameter order is intentional for consistency */ +static gm_result_uint64_t read_uint_value(const uint8_t *buf, size_t *offset, + size_t max_size, uint8_t additional_info) { /* NOLINT(bugprone-easily-swappable-parameters) */ + if (additional_info < CBOR_IMMEDIATE_THRESHOLD) { + return (gm_result_uint64_t){.ok = true, .u.val = additional_info}; + } + + if (additional_info == CBOR_UINT8_FOLLOWS) { + return read_uint8_value(buf, offset, max_size); + } + + if (additional_info == CBOR_UINT16_FOLLOWS) { + return read_uint16_value(buf, offset, max_size); + } + + if (additional_info == CBOR_UINT32_FOLLOWS) { + return read_uint32_value(buf, offset, max_size); + } + + if (additional_info == CBOR_UINT64_FOLLOWS) { + return read_uint64_value(buf, offset, max_size); + } + + return (gm_result_uint64_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborInvalidData, + "Invalid additional info: 0x%02x", additional_info) + }; +} + +/* Read CBOR unsigned integer with bounds checking */ +gm_result_uint64_t gm_cbor_read_uint(const uint8_t *buf, size_t *offset, size_t max_size) { + if (!buf || !offset) { + return (gm_result_uint64_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborInvalidData, "NULL buffer or offset") + }; + } + + if (!check_read_bounds(*offset, 1, max_size)) { + return (gm_result_uint64_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborBufferTooSmall, "Buffer underrun reading type byte") + }; + } + + uint8_t initial = buf[(*offset)++]; + uint8_t type = initial & CBOR_TYPE_MASK; + uint8_t info = initial & CBOR_ADDITIONAL_INFO_MASK; + + if (type != CBOR_TYPE_UNSIGNED) { + return (gm_result_uint64_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborTypeMismatch, + "Expected unsigned integer, got type 0x%02x", type) + }; + } + + return read_uint_value(buf, offset, max_size, info); +} + +/* Helper to read CBOR length from additional info */ +/* NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - parameter order is intentional for consistency */ +static gm_result_size_t read_cbor_length(const uint8_t *buf, size_t *offset, + size_t max_size, uint8_t additional_info) { /* NOLINT(bugprone-easily-swappable-parameters) */ + if (additional_info < CBOR_IMMEDIATE_THRESHOLD) { + return (gm_result_size_t){.ok = true, .u.val = additional_info}; + } + + if (additional_info == CBOR_UINT8_FOLLOWS) { + if (!check_read_bounds(*offset, 1, max_size)) { + return (gm_result_size_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborBufferTooSmall, "Buffer underrun reading length") + }; + } + return (gm_result_size_t){.ok = true, .u.val = buf[(*offset)++]}; + } + + if (additional_info == CBOR_UINT16_FOLLOWS) { + if (!check_read_bounds(*offset, 2, max_size)) { + return (gm_result_size_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborBufferTooSmall, "Buffer underrun reading length") + }; + } + size_t len = ((size_t)buf[*offset] << SHIFT_8) | (size_t)buf[*offset + 1]; + *offset += 2; + return (gm_result_size_t){.ok = true, .u.val = len}; + } + + return (gm_result_size_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborInvalidData, + "Invalid additional info: 0x%02x", additional_info) + }; +} + +/* Read CBOR byte string with bounds checking */ +gm_result_void_t gm_cbor_read_bytes(const uint8_t *buf, size_t *offset, size_t max_size, + uint8_t *data, size_t expected_len) { + if (!buf || !offset || !data) { + return (gm_result_void_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborInvalidData, "NULL buffer, offset, or data") + }; + } + + if (!check_read_bounds(*offset, 1, max_size)) { + return (gm_result_void_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborBufferTooSmall, "Buffer underrun reading type byte") + }; + } + + uint8_t initial = buf[(*offset)++]; + uint8_t type = initial & CBOR_TYPE_MASK; + uint8_t info = initial & CBOR_ADDITIONAL_INFO_MASK; + + if (type != CBOR_TYPE_BYTES) { + return (gm_result_void_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborTypeMismatch, + "Expected byte string, got type 0x%02x", type) + }; + } + + gm_result_size_t len_result = read_cbor_length(buf, offset, max_size, info); + if (!len_result.ok) { + return (gm_result_void_t){.ok = false, .u.err = len_result.u.err}; + } + size_t len = len_result.u.val; + + if (len != expected_len) { + return (gm_result_void_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborInvalidData, + "Length mismatch: expected %zu, got %zu", expected_len, len) + }; + } + + if (!check_read_bounds(*offset, len, max_size)) { + return (gm_result_void_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborBufferTooSmall, + "Buffer underrun reading %zu bytes", len) + }; + } + + /* NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - bounds checked */ + memcpy(data, buf + *offset, len); + *offset += len; + + return (gm_result_void_t){.ok = true}; +} + +/* Read CBOR text string with bounds checking */ +gm_result_void_t gm_cbor_read_text(const uint8_t *buf, size_t *offset, size_t max_size, + char *text, size_t max_text_len) { + if (!buf || !offset || !text) { + return (gm_result_void_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborInvalidData, "NULL buffer, offset, or text") + }; + } + + if (max_text_len == 0) { + return (gm_result_void_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborInvalidData, "Text buffer size is zero") + }; + } + + if (!check_read_bounds(*offset, 1, max_size)) { + return (gm_result_void_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborBufferTooSmall, "Buffer underrun reading type byte") + }; + } + + uint8_t initial = buf[(*offset)++]; + uint8_t type = initial & CBOR_TYPE_MASK; + uint8_t info = initial & CBOR_ADDITIONAL_INFO_MASK; + + if (type != CBOR_TYPE_TEXT) { + return (gm_result_void_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborTypeMismatch, + "Expected text string, got type 0x%02x", type) + }; + } + + gm_result_size_t len_result = read_cbor_length(buf, offset, max_size, info); + if (!len_result.ok) { + return (gm_result_void_t){.ok = false, .u.err = len_result.u.err}; + } + size_t len = len_result.u.val; + + if (len >= max_text_len) { + return (gm_result_void_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborOverflow, + "Text too long: %zu bytes, buffer size %zu", len, max_text_len) + }; + } + + if (!check_read_bounds(*offset, len, max_size)) { + return (gm_result_void_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborBufferTooSmall, + "Buffer underrun reading %zu bytes", len) + }; + } + + /* NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - bounds checked */ + memcpy(text, buf + *offset, len); + text[len] = '\0'; + *offset += len; + + return (gm_result_void_t){.ok = true}; +} + +/* Write CBOR unsigned integer with bounds checking */ +gm_result_size_t gm_cbor_write_uint(uint64_t value, uint8_t *buf, size_t buf_size) { + if (!buf) { + return (gm_result_size_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborInvalidData, "NULL buffer") + }; + } + + size_t required_size = 0; + + if (value < CBOR_IMMEDIATE_THRESHOLD) { + required_size = 1; + } else if (value <= UINT8_MAX) { + required_size = 2; + } else if (value <= UINT16_MAX) { + required_size = 3; + } else if (value <= UINT32_MAX) { + required_size = CborUint32HeaderSize; + } else { + required_size = CborUint64HeaderSize; + } + + if (buf_size < required_size) { + return (gm_result_size_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborBufferTooSmall, + "Need %zu bytes, have %zu", required_size, buf_size) + }; + } + + if (value < CBOR_IMMEDIATE_THRESHOLD) { + buf[0] = CBOR_TYPE_UNSIGNED | (uint8_t)value; + return (gm_result_size_t){.ok = true, .u.val = 1}; + } + + if (value <= UINT8_MAX) { + buf[0] = CBOR_TYPE_UNSIGNED | CBOR_UINT8_FOLLOWS; + buf[1] = (uint8_t)value; + return (gm_result_size_t){.ok = true, .u.val = 2}; + } + + if (value <= UINT16_MAX) { + buf[0] = CBOR_TYPE_UNSIGNED | CBOR_UINT16_FOLLOWS; + buf[1] = (uint8_t)((value >> SHIFT_8) & BYTE_MASK); + buf[2] = (uint8_t)(value & BYTE_MASK); + return (gm_result_size_t){.ok = true, .u.val = 3}; + } + + if (value <= UINT32_MAX) { + buf[0] = CBOR_TYPE_UNSIGNED | CBOR_UINT32_FOLLOWS; + buf[1] = (uint8_t)((value >> SHIFT_24) & BYTE_MASK); + buf[2] = (uint8_t)((value >> SHIFT_16) & BYTE_MASK); + buf[3] = (uint8_t)((value >> SHIFT_8) & BYTE_MASK); + buf[4] = (uint8_t)(value & BYTE_MASK); + return (gm_result_size_t){.ok = true, .u.val = CborUint32HeaderSize}; + } + + buf[0] = CBOR_TYPE_UNSIGNED | CBOR_UINT64_FOLLOWS; + for (int i = 0; i < BYTE_SIZE; i++) { + buf[1 + i] = (uint8_t)((value >> (SHIFT_56 - i * BYTE_SIZE)) & BYTE_MASK); + } + return (gm_result_size_t){.ok = true, .u.val = CborUint64HeaderSize}; +} + +/* Write CBOR byte string with bounds checking */ +gm_result_size_t gm_cbor_write_bytes(uint8_t *buf, size_t buf_size, + const uint8_t *data, size_t data_len) { + if (!buf || !data) { + return (gm_result_size_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborInvalidData, "NULL buffer or data") + }; + } + + size_t header_size = 0; + + if (data_len < CBOR_IMMEDIATE_THRESHOLD) { + header_size = 1; + } else if (data_len <= UINT8_MAX) { + header_size = 2; + } else if (data_len <= UINT16_MAX) { + header_size = 3; + } else { + return (gm_result_size_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborOverflow, + "Data length %zu exceeds maximum", data_len) + }; + } + + size_t total_size = header_size + data_len; + if (buf_size < total_size) { + return (gm_result_size_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborBufferTooSmall, + "Need %zu bytes, have %zu", total_size, buf_size) + }; + } + + if (data_len < CBOR_IMMEDIATE_THRESHOLD) { + buf[0] = CBOR_TYPE_BYTES | (uint8_t)data_len; + } else if (data_len <= UINT8_MAX) { + buf[0] = CBOR_TYPE_BYTES | CBOR_UINT8_FOLLOWS; + buf[1] = (uint8_t)data_len; + } else { + buf[0] = CBOR_TYPE_BYTES | CBOR_UINT16_FOLLOWS; + buf[1] = (uint8_t)((data_len >> SHIFT_8) & BYTE_MASK); + buf[2] = (uint8_t)(data_len & BYTE_MASK); + } + + /* NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - bounds checked */ + memcpy(buf + header_size, data, data_len); + return (gm_result_size_t){.ok = true, .u.val = total_size}; +} + +/* Write CBOR text string with bounds checking */ +gm_result_size_t gm_cbor_write_text(uint8_t *buf, size_t buf_size, const char *text) { + if (!buf || !text) { + return (gm_result_size_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborInvalidData, "NULL buffer or text") + }; + } + + size_t text_len = strlen(text); + size_t header_size = 0; + + if (text_len < CBOR_IMMEDIATE_THRESHOLD) { + header_size = 1; + } else if (text_len <= UINT8_MAX) { + header_size = 2; + } else if (text_len <= UINT16_MAX) { + header_size = 3; + } else { + return (gm_result_size_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborOverflow, + "Text length %zu exceeds maximum", text_len) + }; + } + + size_t total_size = header_size + text_len; + if (buf_size < total_size) { + return (gm_result_size_t){ + .ok = false, + .u.err = GM_ERROR(GmErrorCborBufferTooSmall, + "Need %zu bytes, have %zu", total_size, buf_size) + }; + } + + if (text_len < CBOR_IMMEDIATE_THRESHOLD) { + buf[0] = CBOR_TYPE_TEXT | (uint8_t)text_len; + } else if (text_len <= UINT8_MAX) { + buf[0] = CBOR_TYPE_TEXT | CBOR_UINT8_FOLLOWS; + buf[1] = (uint8_t)text_len; + } else { + buf[0] = CBOR_TYPE_TEXT | CBOR_UINT16_FOLLOWS; + buf[1] = (uint8_t)((text_len >> SHIFT_8) & BYTE_MASK); + buf[2] = (uint8_t)(text_len & BYTE_MASK); + } + + /* CBOR text strings have explicit length and don't require null termination */ + /* NOLINTNEXTLINE(bugprone-not-null-terminated-result,clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - CBOR strings are length-prefixed, bounds checked above */ + memcpy(buf + header_size, text, text_len); + return (gm_result_size_t){.ok = true, .u.val = total_size}; +} diff --git a/core/src/io/io.c b/core/src/io/io.c index caa982ef..353efd20 100644 --- a/core/src/io/io.c +++ b/core/src/io/io.c @@ -23,10 +23,10 @@ #include /* Error code constants */ -static const int GM_ERROR_FILE_OPERATION = 1001; -static const int GM_ERROR_DIR_OPERATION = 2001; -static const int GM_ERROR_FS_OPERATION = 3001; -static const int GM_ERROR_PROCESS_OPERATION = 4001; +static const int GmErrorFileOperation = 1001; +static const int GmErrorDirOperation = 2001; +static const int GmErrorFsOperation = 3001; +static const int GmErrorProcessOperation = 4001; /* Wrapper functions for file operations with Result types */ @@ -35,7 +35,7 @@ static gm_result_file_ptr_t wrap_fopen(const char *path, const char *mode) { if (!file) { return (gm_result_file_ptr_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FILE_OPERATION, "Failed to open file: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFileOperation, "Failed to open file: %s", strerror(errno)) }; } return (gm_result_file_ptr_t){.ok = true, .u.val = file}; @@ -45,7 +45,7 @@ static gm_result_void_t wrap_fclose(FILE *stream) { if (fclose(stream) != 0) { return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FILE_OPERATION, "Failed to close file: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFileOperation, "Failed to close file: %s", strerror(errno)) }; } return (gm_result_void_t){.ok = true}; @@ -56,7 +56,7 @@ static gm_result_size_t wrap_fread(void *ptr, size_t size, size_t count, FILE *s if (result < count && ferror(stream)) { return (gm_result_size_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FILE_OPERATION, "Failed to read file: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFileOperation, "Failed to read file: %s", strerror(errno)) }; } return (gm_result_size_t){.ok = true, .u.val = result}; @@ -67,7 +67,7 @@ static gm_result_size_t wrap_fwrite(const void *ptr, size_t size, size_t count, if (result < count) { return (gm_result_size_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FILE_OPERATION, "Failed to write file: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFileOperation, "Failed to write file: %s", strerror(errno)) }; } return (gm_result_size_t){.ok = true, .u.val = result}; @@ -82,7 +82,7 @@ static gm_result_int_t wrap_fprintf(FILE *stream, const char *format, ...) { if (result < 0) { return (gm_result_int_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FILE_OPERATION, "Failed to write formatted output: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFileOperation, "Failed to write formatted output: %s", strerror(errno)) }; } return (gm_result_int_t){.ok = true, .u.val = result}; @@ -92,7 +92,7 @@ static gm_result_void_t wrap_fflush(FILE *stream) { if (fflush(stream) != 0) { return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FILE_OPERATION, "Failed to flush stream: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFileOperation, "Failed to flush stream: %s", strerror(errno)) }; } return (gm_result_void_t){.ok = true}; @@ -102,7 +102,7 @@ static gm_result_void_t wrap_remove(const char *path) { if (remove(path) != 0) { return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FILE_OPERATION, "Failed to remove file: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFileOperation, "Failed to remove file: %s", strerror(errno)) }; } return (gm_result_void_t){.ok = true}; @@ -112,7 +112,7 @@ static gm_result_void_t wrap_rename(const char *oldpath, const char *newpath) { if (rename(oldpath, newpath) != 0) { return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FILE_OPERATION, "Failed to rename file: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFileOperation, "Failed to rename file: %s", strerror(errno)) }; } return (gm_result_void_t){.ok = true}; @@ -124,7 +124,7 @@ static gm_result_void_t wrap_mkdir(const char *path, mode_t mode) { if (mkdir(path, mode) != 0) { return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_DIR_OPERATION, "Failed to create directory: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorDirOperation, "Failed to create directory: %s", strerror(errno)) }; } return (gm_result_void_t){.ok = true}; @@ -134,7 +134,7 @@ static gm_result_void_t wrap_rmdir(const char *path) { if (rmdir(path) != 0) { return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_DIR_OPERATION, "Failed to remove directory: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorDirOperation, "Failed to remove directory: %s", strerror(errno)) }; } return (gm_result_void_t){.ok = true}; @@ -144,7 +144,7 @@ static gm_result_void_t wrap_chdir(const char *path) { if (chdir(path) != 0) { return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_DIR_OPERATION, "Failed to change directory: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorDirOperation, "Failed to change directory: %s", strerror(errno)) }; } return (gm_result_void_t){.ok = true}; @@ -155,7 +155,7 @@ static gm_result_string_t wrap_getcwd(char *buf, size_t size) { if (!result) { return (gm_result_string_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_DIR_OPERATION, "Failed to get current directory: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorDirOperation, "Failed to get current directory: %s", strerror(errno)) }; } return (gm_result_string_t){.ok = true, .u.val = result}; @@ -167,7 +167,7 @@ static gm_result_void_t wrap_stat(const char *path, struct stat *buf) { if (stat(path, buf) != 0) { return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FS_OPERATION, "Failed to stat file: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFsOperation, "Failed to stat file: %s", strerror(errno)) }; } return (gm_result_void_t){.ok = true}; @@ -177,7 +177,7 @@ static gm_result_void_t wrap_lstat(const char *path, struct stat *buf) { if (lstat(path, buf) != 0) { return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FS_OPERATION, "Failed to lstat file: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFsOperation, "Failed to lstat file: %s", strerror(errno)) }; } return (gm_result_void_t){.ok = true}; @@ -187,7 +187,7 @@ static gm_result_void_t wrap_access(const char *path, int mode) { if (access(path, mode) != 0) { return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FS_OPERATION, "Access check failed: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFsOperation, "Access check failed: %s", strerror(errno)) }; } return (gm_result_void_t){.ok = true}; @@ -197,7 +197,7 @@ static gm_result_void_t wrap_chmod(const char *path, mode_t mode) { if (chmod(path, mode) != 0) { return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FS_OPERATION, "Failed to change file mode: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFsOperation, "Failed to change file mode: %s", strerror(errno)) }; } return (gm_result_void_t){.ok = true}; @@ -207,7 +207,7 @@ static gm_result_void_t wrap_unlink(const char *path) { if (unlink(path) != 0) { return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FS_OPERATION, "Failed to unlink file: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFsOperation, "Failed to unlink file: %s", strerror(errno)) }; } return (gm_result_void_t){.ok = true}; @@ -218,7 +218,7 @@ static gm_result_ssize_t wrap_readlink(const char *path, char *buf, size_t bufsi if (result < 0) { return (gm_result_ssize_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FS_OPERATION, "Failed to read link: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFsOperation, "Failed to read link: %s", strerror(errno)) }; } return (gm_result_ssize_t){.ok = true, .u.val = result}; @@ -228,7 +228,7 @@ static gm_result_void_t wrap_symlink(const char *oldpath, const char *newpath) { if (symlink(oldpath, newpath) != 0) { return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_FS_OPERATION, "Failed to create symlink: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorFsOperation, "Failed to create symlink: %s", strerror(errno)) }; } return (gm_result_void_t){.ok = true}; @@ -242,7 +242,7 @@ static gm_result_int_t wrap_system(const char *command) { if (result == -1) { return (gm_result_int_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_PROCESS_OPERATION, "Failed to execute command: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorProcessOperation, "Failed to execute command: %s", strerror(errno)) }; } return (gm_result_int_t){.ok = true, .u.val = result}; @@ -253,7 +253,7 @@ static gm_result_pid_t wrap_fork(void) { if (result < 0) { return (gm_result_pid_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_PROCESS_OPERATION, "Failed to fork process: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorProcessOperation, "Failed to fork process: %s", strerror(errno)) }; } return (gm_result_pid_t){.ok = true, .u.val = result}; @@ -264,7 +264,7 @@ static gm_result_void_t wrap_execvp(const char *file, char *const argv[]) { /* If we get here, execvp failed */ return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_PROCESS_OPERATION, "Failed to execute program: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorProcessOperation, "Failed to execute program: %s", strerror(errno)) }; } @@ -273,7 +273,7 @@ static gm_result_pid_t wrap_waitpid(pid_t pid, int *status, int options) { if (result < 0) { return (gm_result_pid_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_PROCESS_OPERATION, "Failed to wait for process: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorProcessOperation, "Failed to wait for process: %s", strerror(errno)) }; } return (gm_result_pid_t){.ok = true, .u.val = result}; diff --git a/core/src/time/time.c b/core/src/time/time.c index 77420a1c..ef2bd4f3 100644 --- a/core/src/time/time.c +++ b/core/src/time/time.c @@ -9,13 +9,14 @@ #include #include +#include #include #include #include /* Error code constants */ -static const int GM_ERROR_TIME_OPERATION = 5001; +static const int GmErrorTimeOperation = 5001; /* Wrapper functions for time operations with Result types */ @@ -24,17 +25,18 @@ static gm_result_time_t wrap_time(time_t *tloc) { if (result == (time_t)-1) { return (gm_result_time_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_TIME_OPERATION, "Failed to get time: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorTimeOperation, "Failed to get time: %s", strerror(errno)) }; } return (gm_result_time_t){.ok = true, .u.val = result}; } -static gm_result_void_t wrap_clock_gettime(clockid_t clk_id, struct timespec *tp) { - if (clock_gettime(clk_id, tp) != 0) { +/* NOLINTNEXTLINE(misc-include-cleaner) - clockid_t from time.h with _POSIX_C_SOURCE */ +static gm_result_void_t wrap_clock_gettime(clockid_t clk_id, struct timespec *timespec_ptr) { + if (clock_gettime(clk_id, timespec_ptr) != 0) { return (gm_result_void_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_TIME_OPERATION, "Failed to get clock time: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorTimeOperation, "Failed to get clock time: %s", strerror(errno)) }; } return (gm_result_void_t){.ok = true}; @@ -45,7 +47,7 @@ static gm_result_tm_ptr_t wrap_localtime_r(const time_t *timep, struct tm *resul if (!tm_result) { return (gm_result_tm_ptr_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_TIME_OPERATION, "Failed to convert to local time: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorTimeOperation, "Failed to convert to local time: %s", strerror(errno)) }; } return (gm_result_tm_ptr_t){.ok = true, .u.val = tm_result}; @@ -56,19 +58,19 @@ static gm_result_tm_ptr_t wrap_gmtime_r(const time_t *timep, struct tm *result) if (!tm_result) { return (gm_result_tm_ptr_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_TIME_OPERATION, "Failed to convert to GMT: %s", strerror(errno)) + .u.err = GM_ERROR(GmErrorTimeOperation, "Failed to convert to GMT: %s", strerror(errno)) }; } return (gm_result_tm_ptr_t){.ok = true, .u.val = tm_result}; } -static gm_result_size_t wrap_strftime(char *s, size_t max, const char *format, - const struct tm *tm) { - size_t result = strftime(s, max, format, tm); +static gm_result_size_t wrap_strftime(char *str, size_t max, const char *format, + const struct tm *time_struct) { + size_t result = strftime(str, max, format, time_struct); if (result == 0) { return (gm_result_size_t){ .ok = false, - .u.err = GM_ERROR(GM_ERROR_TIME_OPERATION, "Failed to format time: buffer too small") + .u.err = GM_ERROR(GmErrorTimeOperation, "Failed to format time: buffer too small") }; } return (gm_result_size_t){.ok = true, .u.val = result}; diff --git a/core/src/types/ulid.c b/core/src/types/ulid.c index c20335e4..b4b23e95 100644 --- a/core/src/types/ulid.c +++ b/core/src/types/ulid.c @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LicenseRef-MIND-UCAL-1.0 */ +/* NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming) - POSIX feature test macro */ +#define _POSIX_C_SOURCE 199309L #include "gitmind/types/ulid.h" #include @@ -8,6 +10,7 @@ #include "gitmind/crypto/backend.h" #include "gitmind/crypto/random.h" #include "gitmind/error.h" +#include "gitmind/result.h" /* Crockford's Base32 alphabet (excludes I, L, O, U to avoid confusion) */ static const char ENCODING[32] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; @@ -20,9 +23,12 @@ static const char ENCODING[32] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; #define BITS_PER_CHARACTER 5 #define MILLISECONDS_PER_SECOND 1000 #define NANOSECONDS_PER_MILLISECOND 1000000 +#define BITS_PER_BYTE 8 +#define RANDOM_BYTES_COUNT 10 +#define MAX_TIMESTAMP_VALUE 16 /* Decoding table for validation (-1 for invalid characters) */ -static const int8_t DECODING[256] = { +static const int DECODING[256] = { /* 0-31: Control characters */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -52,12 +58,13 @@ static const int8_t DECODING[256] = { /* Get current time in milliseconds */ static uint64_t get_current_time_ms(void) { struct timespec timestamp; + /* NOLINTNEXTLINE(misc-include-cleaner) - CLOCK_REALTIME from time.h with _POSIX_C_SOURCE */ if (clock_gettime(CLOCK_REALTIME, ×tamp) != 0) { /* Fallback to time() if clock_gettime fails */ return (uint64_t)time(NULL) * MILLISECONDS_PER_SECOND; } - return (uint64_t)timestamp.tv_sec * MILLISECONDS_PER_SECOND + - (uint64_t)timestamp.tv_nsec / NANOSECONDS_PER_MILLISECOND; + return ((uint64_t)timestamp.tv_sec * MILLISECONDS_PER_SECOND) + + ((uint64_t)timestamp.tv_nsec / NANOSECONDS_PER_MILLISECOND); } /* Encode time component (48 bits) into 10 base32 characters */ @@ -71,7 +78,7 @@ static void encode_time(uint64_t time_ms, char *output) { /* Encode random component (80 bits) into 16 base32 characters */ static gm_result_void_t encode_random(char *output) { - uint8_t random_bytes[10]; /* 80 bits = 10 bytes */ + uint8_t random_bytes[RANDOM_BYTES_COUNT]; /* 80 bits = 10 bytes */ /* Get random bytes using default backend */ const gm_crypto_backend_t *backend = gm_crypto_backend_libsodium(); @@ -94,8 +101,8 @@ static gm_result_void_t encode_random(char *output) { for (int char_index = 0; char_index < RANDOM_COMPONENT_LENGTH; char_index++) { /* Ensure we have at least 5 bits */ while (bits_available < BITS_PER_CHARACTER && byte_index < sizeof(random_bytes)) { - bit_buffer = (bit_buffer << 8) | random_bytes[byte_index++]; - bits_available += 8; + bit_buffer = (bit_buffer << BITS_PER_BYTE) | random_bytes[byte_index++]; + bits_available += BITS_PER_BYTE; } /* Extract 5 bits */ @@ -165,11 +172,8 @@ bool gm_ulid_is_valid(const char *ulid) { /* Check timestamp doesn't overflow (first 10 chars = 50 bits, max 48 bits) */ unsigned char first_char = (unsigned char)ulid[0]; int first_value = DECODING[first_char]; - if (first_value >= 16) { /* Top 2 bits must be 0 for 48-bit value */ - return false; - } - - return true; + /* Top 2 bits must be 0 for 48-bit value */ + return first_value < MAX_TIMESTAMP_VALUE; } gm_result_void_t gm_ulid_get_timestamp(const char *ulid, diff --git a/core/tests/unit/test_cbor.c b/core/tests/unit/test_cbor.c new file mode 100644 index 00000000..f10d2934 --- /dev/null +++ b/core/tests/unit/test_cbor.c @@ -0,0 +1,356 @@ +/* SPDX-License-Identifier: LicenseRef-MIND-UCAL-1.0 */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +/* Test constants */ +#define BUFFER_SIZE 1024 + +/* Test helper to verify error codes */ +static void assert_error_code(gm_error_t *err, int expected_code) { + assert(err != NULL); + assert(err->code == expected_code); + gm_error_free(err); +} + +/* Test unsigned integer encoding and decoding */ +static void test_cbor_uint(void) { + printf("test_cbor_uint... "); + + uint8_t buffer[BUFFER_SIZE]; + size_t offset; + + /* Test cases with expected encoding sizes */ + struct { + uint64_t value; + size_t expected_size; + } test_cases[] = { + {0, 1}, /* Immediate value 0 */ + {23, 1}, /* Immediate value 23 */ + {24, 2}, /* uint8 follows */ + {255, 2}, /* uint8 max */ + {256, 3}, /* uint16 follows */ + {65535, 3}, /* uint16 max */ + {65536, 5}, /* uint32 follows */ + {4294967295UL, 5}, /* uint32 max */ + {4294967296UL, 9}, /* uint64 follows */ + {UINT64_MAX, 9}, /* uint64 max */ + }; + + for (size_t i = 0; i < sizeof(test_cases) / sizeof(test_cases[0]); i++) { + uint64_t value = test_cases[i].value; + size_t expected_size = test_cases[i].expected_size; + + /* Write value */ + gm_result_size_t write_result = gm_cbor_write_uint(value, buffer, BUFFER_SIZE); + assert(write_result.ok); + assert(write_result.u.val == expected_size); + + /* Read value back */ + offset = 0; + gm_result_uint64_t read_result = gm_cbor_read_uint(buffer, &offset, BUFFER_SIZE); + assert(read_result.ok); + assert(read_result.u.val == value); + assert(offset == expected_size); + } + + /* Test buffer too small for write */ + gm_result_size_t write_result = gm_cbor_write_uint(42, buffer, 0); + assert(!write_result.ok); + assert_error_code(write_result.u.err, 6002); /* GM_ERROR_CBOR_BUFFER_TOO_SMALL */ + + /* Test NULL buffer */ + write_result = gm_cbor_write_uint(42, NULL, BUFFER_SIZE); + assert(!write_result.ok); + assert_error_code(write_result.u.err, 6003); /* GM_ERROR_CBOR_INVALID_DATA */ + + /* Test type mismatch on read */ + buffer[0] = CBOR_TYPE_TEXT | 5; /* Text string, not uint */ + offset = 0; + gm_result_uint64_t read_result = gm_cbor_read_uint(buffer, &offset, BUFFER_SIZE); + assert(!read_result.ok); + assert_error_code(read_result.u.err, 6001); /* GM_ERROR_CBOR_TYPE_MISMATCH */ + + printf("OK\n"); +} + +/* Test byte string encoding and decoding */ +static void test_cbor_bytes(void) { + printf("test_cbor_bytes... "); + + uint8_t buffer[BUFFER_SIZE]; + uint8_t data[256]; + uint8_t read_data[256]; + size_t offset; + + /* Initialize test data */ + for (size_t i = 0; i < sizeof(data); i++) { + data[i] = (uint8_t)i; + } + + /* Test various lengths */ + size_t test_lengths[] = {0, 1, 23, 24, 255, 256}; + + for (size_t i = 0; i < sizeof(test_lengths) / sizeof(test_lengths[0]); i++) { + size_t len = test_lengths[i]; + if (len > sizeof(data)) continue; + + /* Write bytes */ + gm_result_size_t write_result = gm_cbor_write_bytes(buffer, BUFFER_SIZE, data, len); + assert(write_result.ok); + + /* Read bytes back */ + memset(read_data, 0xFF, sizeof(read_data)); + offset = 0; + gm_result_void_t read_result = gm_cbor_read_bytes(buffer, &offset, BUFFER_SIZE, + read_data, len); + assert(read_result.ok); + assert(memcmp(data, read_data, len) == 0); + } + + /* Test length mismatch */ + gm_result_size_t write_result = gm_cbor_write_bytes(buffer, BUFFER_SIZE, data, 10); + assert(write_result.ok); + + offset = 0; + gm_result_void_t read_result = gm_cbor_read_bytes(buffer, &offset, BUFFER_SIZE, + read_data, 11); /* Wrong length */ + assert(!read_result.ok); + assert_error_code(read_result.u.err, 6003); /* GM_ERROR_CBOR_INVALID_DATA */ + + /* Test buffer overflow protection */ + /* Set up a byte string that would overflow when read */ + buffer[BUFFER_SIZE - 5] = CBOR_TYPE_BYTES | 10; /* 10 bytes needed, but only 4 available */ + offset = BUFFER_SIZE - 5; /* Not enough space */ + read_result = gm_cbor_read_bytes(buffer, &offset, BUFFER_SIZE, read_data, 10); + assert(!read_result.ok); + assert_error_code(read_result.u.err, 6002); /* GM_ERROR_CBOR_BUFFER_TOO_SMALL */ + + printf("OK\n"); +} + +/* Test text string encoding and decoding */ +static void test_cbor_text(void) { + printf("test_cbor_text... "); + + uint8_t buffer[BUFFER_SIZE]; + char text[256]; + size_t offset; + + /* Test strings */ + const char *test_strings[] = { + "", /* Empty string */ + "Hello", /* Short string */ + "This is a test string", /* Medium string */ + ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.") /* Long string */ + }; + + for (size_t i = 0; i < sizeof(test_strings) / sizeof(test_strings[0]); i++) { + const char *str = test_strings[i]; + + /* Write text */ + gm_result_size_t write_result = gm_cbor_write_text(buffer, BUFFER_SIZE, str); + assert(write_result.ok); + assert(write_result.u.val == strlen(str) + (strlen(str) < 24 ? 1 : + strlen(str) <= 255 ? 2 : 3)); + + /* Read text back */ + memset(text, 0xFF, sizeof(text)); + offset = 0; + gm_result_void_t read_result = gm_cbor_read_text(buffer, &offset, BUFFER_SIZE, + text, sizeof(text)); + assert(read_result.ok); + assert(strcmp(str, text) == 0); + assert(offset == write_result.u.val); + } + + /* Test text buffer too small */ + gm_result_size_t write_result = gm_cbor_write_text(buffer, BUFFER_SIZE, "Hello World"); + assert(write_result.ok); + + offset = 0; + gm_result_void_t read_result = gm_cbor_read_text(buffer, &offset, BUFFER_SIZE, + text, 5); /* Too small */ + assert(!read_result.ok); + assert_error_code(read_result.u.err, 6004); /* GM_ERROR_CBOR_OVERFLOW */ + + /* Test UTF-8 text */ + const char *utf8_text = "Hello 世界 🌍"; + write_result = gm_cbor_write_text(buffer, BUFFER_SIZE, utf8_text); + assert(write_result.ok); + + offset = 0; + read_result = gm_cbor_read_text(buffer, &offset, BUFFER_SIZE, text, sizeof(text)); + assert(read_result.ok); + assert(strcmp(utf8_text, text) == 0); + + printf("OK\n"); +} + +/* Test edge cases and error handling */ +static void test_cbor_edge_cases(void) { + printf("test_cbor_edge_cases... "); + + uint8_t buffer[BUFFER_SIZE]; + size_t offset; + + /* Test NULL pointer handling */ + gm_result_uint64_t uint_result = gm_cbor_read_uint(NULL, &offset, BUFFER_SIZE); + assert(!uint_result.ok); + assert_error_code(uint_result.u.err, 6003); /* GM_ERROR_CBOR_INVALID_DATA */ + + uint_result = gm_cbor_read_uint(buffer, NULL, BUFFER_SIZE); + assert(!uint_result.ok); + assert_error_code(uint_result.u.err, 6003); /* GM_ERROR_CBOR_INVALID_DATA */ + + /* Test invalid additional info */ + buffer[0] = CBOR_TYPE_UNSIGNED | 0x1C; /* Reserved value */ + offset = 0; + uint_result = gm_cbor_read_uint(buffer, &offset, BUFFER_SIZE); + assert(!uint_result.ok); + assert_error_code(uint_result.u.err, 6003); /* GM_ERROR_CBOR_INVALID_DATA */ + + /* Test reading past buffer end */ + buffer[BUFFER_SIZE - 2] = CBOR_TYPE_UNSIGNED | CBOR_UINT32_FOLLOWS; + offset = BUFFER_SIZE - 2; /* Not enough space for uint32 */ + uint_result = gm_cbor_read_uint(buffer, &offset, BUFFER_SIZE); + assert(!uint_result.ok); + assert_error_code(uint_result.u.err, 6002); /* GM_ERROR_CBOR_BUFFER_TOO_SMALL */ + + /* Test empty text buffer */ + char text[1]; + offset = 0; + gm_result_void_t text_result = gm_cbor_read_text(buffer, &offset, BUFFER_SIZE, + text, 0); + assert(!text_result.ok); + assert_error_code(text_result.u.err, 6003); /* GM_ERROR_CBOR_INVALID_DATA */ + + printf("OK\n"); +} + +/* Test real-world CBOR sequences */ +static void test_cbor_sequences(void) { + printf("test_cbor_sequences... "); + + uint8_t buffer[BUFFER_SIZE]; + size_t write_offset = 0; + size_t read_offset = 0; + + /* Write a sequence of values */ + uint64_t uint_val = 42; + const uint8_t bytes_val[] = {0xDE, 0xAD, 0xBE, 0xEF}; + const char *text_val = "Hello CBOR"; + + /* Write uint */ + gm_result_size_t write_result = gm_cbor_write_uint(uint_val, buffer + write_offset, + BUFFER_SIZE - write_offset); + assert(write_result.ok); + write_offset += write_result.u.val; + + /* Write bytes */ + write_result = gm_cbor_write_bytes(buffer + write_offset, + BUFFER_SIZE - write_offset, + bytes_val, sizeof(bytes_val)); + assert(write_result.ok); + write_offset += write_result.u.val; + + /* Write text */ + write_result = gm_cbor_write_text(buffer + write_offset, + BUFFER_SIZE - write_offset, text_val); + assert(write_result.ok); + write_offset += write_result.u.val; + + /* Read back in same order */ + gm_result_uint64_t uint_result = gm_cbor_read_uint(buffer, &read_offset, write_offset); + assert(uint_result.ok); + assert(uint_result.u.val == uint_val); + + uint8_t read_bytes[4]; + gm_result_void_t bytes_result = gm_cbor_read_bytes(buffer, &read_offset, write_offset, + read_bytes, sizeof(bytes_val)); + assert(bytes_result.ok); + assert(memcmp(bytes_val, read_bytes, sizeof(bytes_val)) == 0); + + char read_text[64]; + gm_result_void_t text_result = gm_cbor_read_text(buffer, &read_offset, write_offset, + read_text, sizeof(read_text)); + assert(text_result.ok); + assert(strcmp(text_val, read_text) == 0); + + /* Verify we consumed entire buffer */ + assert(read_offset == write_offset); + + printf("OK\n"); +} + +/* Test bounds checking thoroughly */ +static void test_cbor_bounds_checking(void) { + printf("test_cbor_bounds_checking... "); + + uint8_t small_buffer[10]; + size_t offset; + + /* Test writing to small buffers */ + struct { + uint64_t value; + size_t buf_size; + bool should_succeed; + } write_tests[] = { + {0, 1, true}, /* Exact fit */ + {0, 0, false}, /* Too small */ + {24, 2, true}, /* Exact fit for uint8 */ + {24, 1, false}, /* Too small for uint8 */ + {256, 3, true}, /* Exact fit for uint16 */ + {256, 2, false}, /* Too small for uint16 */ + }; + + for (size_t i = 0; i < sizeof(write_tests) / sizeof(write_tests[0]); i++) { + gm_result_size_t result = gm_cbor_write_uint(write_tests[i].value, small_buffer, + write_tests[i].buf_size); + assert(result.ok == write_tests[i].should_succeed); + if (!result.ok) { + assert(result.u.err->code == 6002); /* GM_ERROR_CBOR_BUFFER_TOO_SMALL */ + gm_error_free(result.u.err); + } + } + + /* Test reading from truncated buffers */ + small_buffer[0] = CBOR_TYPE_UNSIGNED | CBOR_UINT64_FOLLOWS; + for (size_t max_size = 1; max_size < 9; max_size++) { + offset = 0; + gm_result_uint64_t result = gm_cbor_read_uint(small_buffer, &offset, max_size); + assert(!result.ok); + assert(result.u.err->code == 6002); /* GM_ERROR_CBOR_BUFFER_TOO_SMALL */ + gm_error_free(result.u.err); + } + + /* Test text overflow */ + const char *long_text = "This is a very long string"; + gm_result_size_t write_result = gm_cbor_write_text(small_buffer, 5, long_text); + assert(!write_result.ok); + assert_error_code(write_result.u.err, 6002); /* GM_ERROR_CBOR_BUFFER_TOO_SMALL */ + + printf("OK\n"); +} + +int main(void) { + printf("Running CBOR tests...\n"); + + test_cbor_uint(); + test_cbor_bytes(); + test_cbor_text(); + test_cbor_edge_cases(); + test_cbor_sequences(); + test_cbor_bounds_checking(); + + printf("All tests passed!\n"); + return 0; +} diff --git a/meson.build b/meson.build index 5ba7afc6..d93fdf2b 100644 --- a/meson.build +++ b/meson.build @@ -36,6 +36,7 @@ thread_dep = dependency('threads') # Source files src = files( + 'core/src/cbor/cbor.c', 'core/src/crypto/backend.c', 'core/src/crypto/random.c', 'core/src/crypto/sha256.c', @@ -149,6 +150,12 @@ test_time = executable('test_time', link_with : libgitmind, dependencies : [libsodium_dep, thread_dep]) +test_cbor = executable('test_cbor', + 'core/tests/unit/test_cbor.c', + include_directories : inc, + link_with : libgitmind, + dependencies : [libsodium_dep, thread_dep]) + # Register tests test('error', test_error) test('id', test_id) @@ -162,3 +169,4 @@ test('crypto_backend', test_crypto_backend) test('ulid', test_ulid) test('io', test_io) test('time', test_time) +test('cbor', test_cbor) diff --git a/src/attribution/cbor.c b/src/attribution/cbor.c deleted file mode 100644 index 8a239c90..00000000 --- a/src/attribution/cbor.c +++ /dev/null @@ -1,253 +0,0 @@ -/* SPDX-License-Identifier: LicenseRef-MIND-UCAL-1.0 */ -/* © 2025 J. Kirby Ross / Neuroglyph Collective */ - -#include "gitmind.h" - -#include "gitmind/cbor_common.h" -#include "gitmind/constants_cbor.h" - -#include - -#include "../util/gm_mem.h" - -/* - * CBOR edge encoding/decoding with strict SRP, DI, and test-double-friendly - * design - */ - -/* ========== CBOR Writer Interface (for DI) ========== */ -typedef struct { - size_t (*write_header)(uint8_t *buf); - size_t (*write_sha)(uint8_t *buf, const uint8_t *sha); - size_t (*write_metadata)(uint8_t *buf, uint16_t type, uint16_t conf, - uint64_t tstamp); - size_t (*write_path)(uint8_t *buf, const char *path); -} gm_cbor_writer_t; - -/* ========== CBOR Reader Interface (for DI) ========== */ -typedef struct { - int (*read_header)(const uint8_t *buf, size_t len, size_t *offset); - int (*read_sha)(const uint8_t *buf, size_t *offset, uint8_t *sha); - int (*read_type)(const uint8_t *buf, size_t *offset, uint16_t *type); - int (*read_conf)(const uint8_t *buf, size_t *offset, uint16_t *conf); - int (*read_tstamp)(const uint8_t *buf, size_t *offset, uint64_t *tstamp); - int (*read_path)(const uint8_t *buf, size_t *offset, char *path); -} gm_cbor_reader_t; - -/* ========== Writer Implementation (SRP: Each writes ONE thing) ========== */ - -static size_t write_edge_header(uint8_t *buf) { - buf[0] = CBOR_TYPE_ARRAY | CBOR_ARRAY_SIZE_EDGE; - return 1; -} - -static size_t write_edge_sha(uint8_t *buf, const uint8_t *sha) { - return gm_cbor_write_bytes(buf, sha, GM_SHA1_SIZE); -} - -static size_t write_edge_metadata(uint8_t *buf, uint16_t type, uint16_t conf, - uint64_t tstamp) { - size_t offset = 0; - offset += gm_cbor_write_uint(buf + offset, type); - offset += gm_cbor_write_uint(buf + offset, conf); - offset += gm_cbor_write_uint(buf + offset, tstamp); - return offset; -} - -static size_t write_edge_path(uint8_t *buf, const char *path) { - return gm_cbor_write_text(buf, path); -} - -/* Default writer instance */ -static const gm_cbor_writer_t GM_DEFAULT_CBOR_WRITER = { - .write_header = write_edge_header, - .write_sha = write_edge_sha, - .write_metadata = write_edge_metadata, - .write_path = write_edge_path}; - -/* ========== Reader Implementation (SRP: Each reads ONE thing) ========== */ - -static int read_edge_header(const uint8_t *buf, size_t len, size_t *offset) { - if (len < 1) { - return GM_INVALID_ARG; - } - - if (buf[*offset] != (CBOR_TYPE_ARRAY | CBOR_ARRAY_SIZE_EDGE)) { - return GM_INVALID_ARG; - } - - (*offset)++; - return GM_OK; -} - -static int read_edge_sha(const uint8_t *buf, size_t *offset, uint8_t *sha) { - return gm_cbor_read_bytes(buf, offset, sha, GM_SHA1_SIZE); -} - -/* Helper to read type and confidence separately (avoid swappable params) */ -static int read_edge_type(const uint8_t *buf, size_t *offset, uint16_t *type) { - uint64_t temp; - if (gm_cbor_read_uint(buf, offset, &temp) != GM_OK) { - return GM_INVALID_ARG; - } - *type = (uint16_t)temp; - return GM_OK; -} - -static int read_edge_confidence(const uint8_t *buf, size_t *offset, - uint16_t *conf) { - uint64_t temp; - if (gm_cbor_read_uint(buf, offset, &temp) != GM_OK) { - return GM_INVALID_ARG; - } - *conf = (uint16_t)temp; - return GM_OK; -} - -static int read_edge_timestamp(const uint8_t *buf, size_t *offset, - uint64_t *tstamp) { - return gm_cbor_read_uint(buf, offset, tstamp); -} - -/* Removed - no longer needed with separated functions */ - -static int read_edge_path(const uint8_t *buf, size_t *offset, char *path) { - return gm_cbor_read_text(buf, offset, path, GM_PATH_MAX); -} - -/* Default reader instance */ -static const gm_cbor_reader_t GM_DEFAULT_CBOR_READER = { - .read_header = read_edge_header, - .read_sha = read_edge_sha, - .read_type = read_edge_type, - .read_conf = read_edge_confidence, - .read_tstamp = read_edge_timestamp, - .read_path = read_edge_path}; - -/* ========== Main Encode Function (Orchestrates writing) ========== */ - -static int encode_with_writer(const gm_edge_t *edge, uint8_t *buffer, - size_t *len, const gm_cbor_writer_t *writer) { - size_t offset = 0; - - /* Write header */ - offset += writer->write_header(buffer + offset); - - /* Write SHAs */ - offset += writer->write_sha(buffer + offset, edge->src_sha); - offset += writer->write_sha(buffer + offset, edge->tgt_sha); - - /* Write metadata */ - offset += writer->write_metadata(buffer + offset, edge->rel_type, - edge->confidence, edge->timestamp); - - /* Write paths */ - offset += writer->write_path(buffer + offset, edge->src_path); - offset += writer->write_path(buffer + offset, edge->tgt_path); - - *len = offset; - return GM_OK; -} - -/* Public API - uses default writer */ -int gm_edge_encode_cbor(const gm_edge_t *edge, uint8_t *buffer, size_t *len) { - if (!edge || !buffer || !len) { - return GM_INVALID_ARG; - } - - return encode_with_writer(edge, buffer, len, &GM_DEFAULT_CBOR_WRITER); -} - -/* ========== Decode Helpers (reduce complexity) ========== */ - -static int decode_edge_metadata(const uint8_t *buffer, size_t *offset, - const gm_cbor_reader_t *reader, - gm_edge_t *edge) { - if (reader->read_type(buffer, offset, &edge->rel_type) != GM_OK) { - return GM_INVALID_ARG; - } - - if (reader->read_conf(buffer, offset, &edge->confidence) != GM_OK) { - return GM_INVALID_ARG; - } - - return reader->read_tstamp(buffer, offset, &edge->timestamp); -} - -static int decode_edge_shas(const uint8_t *buffer, size_t *offset, - const gm_cbor_reader_t *reader, gm_edge_t *edge) { - if (reader->read_sha(buffer, offset, edge->src_sha) != GM_OK) { - return GM_INVALID_ARG; - } - - if (reader->read_sha(buffer, offset, edge->tgt_sha) != GM_OK) { - return GM_INVALID_ARG; - } - - return GM_OK; -} - -static int decode_edge_paths(const uint8_t *buffer, size_t *offset, - const gm_cbor_reader_t *reader, gm_edge_t *edge) { - if (reader->read_path(buffer, offset, edge->src_path) != GM_OK) { - return GM_INVALID_ARG; - } - - if (reader->read_path(buffer, offset, edge->tgt_path) != GM_OK) { - return GM_INVALID_ARG; - } - - return GM_OK; -} - -/* ========== Main Decode Function (Orchestrates reading) ========== */ - -static int decode_with_reader(const uint8_t *buffer, size_t len, - gm_edge_t *edge, const gm_cbor_reader_t *reader) { - size_t offset = 0; - gm_memset(edge, 0, sizeof(gm_edge_t)); - - /* Read all components */ - if (reader->read_header(buffer, len, &offset) != GM_OK) { - return GM_INVALID_ARG; - } - if (decode_edge_shas(buffer, &offset, reader, edge) != GM_OK) { - return GM_INVALID_ARG; - } - if (decode_edge_metadata(buffer, &offset, reader, edge) != GM_OK) { - return GM_INVALID_ARG; - } - if (decode_edge_paths(buffer, &offset, reader, edge) != GM_OK) { - return GM_INVALID_ARG; - } - - return GM_OK; -} - -/* Public API - uses default reader */ -int gm_edge_decode_cbor(const uint8_t *buffer, size_t len, gm_edge_t *edge) { - if (!buffer || !edge || len == 0) { - return GM_INVALID_ARG; - } - - return decode_with_reader(buffer, len, edge, &GM_DEFAULT_CBOR_READER); -} - -/* Test-double-friendly APIs (allows injection of custom reader/writer) */ -int gm_edge_encode_cbor_ex(const gm_edge_t *edge, uint8_t *buffer, size_t *len, - const gm_cbor_writer_t *writer) { - if (!edge || !buffer || !len || !writer) { - return GM_INVALID_ARG; - } - - return encode_with_writer(edge, buffer, len, writer); -} - -int gm_edge_decode_cbor_ex(const uint8_t *buffer, size_t len, gm_edge_t *edge, - const gm_cbor_reader_t *reader) { - if (!buffer || !edge || !reader || len == 0) { - return GM_INVALID_ARG; - } - - return decode_with_reader(buffer, len, edge, reader); -} \ No newline at end of file diff --git a/src/attribution/cbor_common.c b/src/attribution/cbor_common.c deleted file mode 100644 index b5b7e2c7..00000000 --- a/src/attribution/cbor_common.c +++ /dev/null @@ -1,243 +0,0 @@ -/* SPDX-License-Identifier: LicenseRef-MIND-UCAL-1.0 */ -/* © 2025 J. Kirby Ross / Neuroglyph Collective */ - -#include "gitmind/cbor_common.h" - -#include "gitmind.h" - -#include "gitmind/constants_cbor.h" - -#include - -#include "../util/gm_mem.h" - -/* - * CBOR common functions with strict SRP and reduced complexity - * Each function has ONE responsibility and CCN < 10 - */ - -/* ========== Type Checking (SRP: validate CBOR type) ========== */ - -/* NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - intentional design */ -static int validate_type(uint8_t initial_byte, uint8_t expected_type) { - uint8_t type = initial_byte & CBOR_TYPE_MASK; - return (type == expected_type) ? GM_OK : GM_INVALID_ARG; -} - -/* ========== Length Decoding (SRP: decode CBOR length) ========== */ - -static int decode_uint8_length(const uint8_t *buf, size_t *offset, - uint64_t *value) { - *value = buf[(*offset)++]; - return GM_OK; -} - -static int decode_uint16_length(const uint8_t *buf, size_t *offset, - uint64_t *value) { - *value = (buf[*offset] << SHIFT_8) | buf[*offset + 1]; - *offset += 2; - return GM_OK; -} - -static int decode_uint32_length(const uint8_t *buf, size_t *offset, - uint64_t *value) { - *value = ((uint32_t)buf[*offset] << SHIFT_24) | - ((uint32_t)buf[*offset + 1] << SHIFT_16) | - ((uint32_t)buf[*offset + 2] << SHIFT_8) | buf[*offset + 3]; - *offset += 4; - return GM_OK; -} - -static int decode_uint64_length(const uint8_t *buf, size_t *offset, - uint64_t *value) { - *value = 0; - for (int idx = 0; idx < BYTE_SIZE; idx++) { - *value = (*value << SHIFT_8) | buf[(*offset)++]; - } - return GM_OK; -} - -static int decode_length_by_info(uint8_t info, const uint8_t *buf, - size_t *offset, uint64_t *value) { - if (info < CBOR_IMMEDIATE_THRESHOLD) { - *value = info; - return GM_OK; - } - - switch (info) { - case CBOR_UINT8_FOLLOWS: - return decode_uint8_length(buf, offset, value); - case CBOR_UINT16_FOLLOWS: - return decode_uint16_length(buf, offset, value); - case CBOR_UINT32_FOLLOWS: - return decode_uint32_length(buf, offset, value); - case CBOR_UINT64_FOLLOWS: - return decode_uint64_length(buf, offset, value); - default: - return GM_INVALID_ARG; - } -} - -/* ========== Read Functions ========== */ - -int gm_cbor_read_uint(const uint8_t *buf, size_t *offset, uint64_t *value) { - uint8_t initial = buf[(*offset)++]; - uint8_t info = initial & CBOR_ADDITIONAL_INFO_MASK; - - if (validate_type(initial, CBOR_TYPE_UNSIGNED) != GM_OK) { - return GM_INVALID_ARG; - } - - return decode_length_by_info(info, buf, offset, value); -} - -/* Helper: Read length for bytes/text */ -static int read_string_length(const uint8_t *buf, size_t *offset, - uint8_t expected_type, size_t *len) { - uint8_t initial = buf[(*offset)++]; - uint8_t info = initial & CBOR_ADDITIONAL_INFO_MASK; - uint64_t len64; - - if (validate_type(initial, expected_type) != GM_OK) { - return GM_INVALID_ARG; - } - - /* Only support up to UINT16 lengths for strings */ - if (info >= CBOR_UINT32_FOLLOWS) { - return GM_INVALID_ARG; - } - - if (decode_length_by_info(info, buf, offset, &len64) != GM_OK) { - return GM_INVALID_ARG; - } - - *len = (size_t)len64; - return GM_OK; -} - -int gm_cbor_read_bytes(const uint8_t *buf, size_t *offset, uint8_t *data, - size_t expected_len) { - size_t len; - - if (read_string_length(buf, offset, CBOR_TYPE_BYTES, &len) != GM_OK) { - return GM_INVALID_ARG; - } - - if (len != expected_len) { - return GM_INVALID_ARG; - } - - gm_memcpy(data, buf + *offset, len); - *offset += len; - - return GM_OK; -} - -int gm_cbor_read_text(const uint8_t *buf, size_t *offset, char *text, - size_t max_len) { - size_t len; - - if (read_string_length(buf, offset, CBOR_TYPE_TEXT, &len) != GM_OK) { - return GM_INVALID_ARG; - } - - if (len >= max_len) { - return GM_INVALID_ARG; - } - - gm_memcpy(text, buf + *offset, len); - text[len] = '\0'; - *offset += len; - - return GM_OK; -} - -/* ========== Write Functions ========== */ - -/* Helper: Encode length header */ -/* NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - intentional design */ -static size_t encode_uint8_header(uint8_t *buf, uint8_t cbor_type, - uint8_t data_value) { - buf[0] = cbor_type | CBOR_UINT8_FOLLOWS; - buf[1] = data_value; - return 2; -} - -/* NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - intentional design */ -static size_t encode_uint16_header(uint8_t *buf, uint8_t cbor_type, - uint16_t data_value) { - buf[0] = cbor_type | CBOR_UINT16_FOLLOWS; - buf[1] = (data_value >> SHIFT_8) & BYTE_MASK; - buf[2] = data_value & BYTE_MASK; - return 3; -} - -static size_t encode_immediate(uint8_t *buf, uint8_t cbor_type, - uint8_t immediate_val) { - buf[0] = cbor_type | immediate_val; - return 1; -} - -static size_t encode_uint32(uint8_t *buf, uint32_t value) { - buf[0] = CBOR_TYPE_UNSIGNED | CBOR_UINT32_FOLLOWS; - buf[1] = (value >> SHIFT_24) & BYTE_MASK; - buf[2] = (value >> SHIFT_16) & BYTE_MASK; - buf[3] = (value >> SHIFT_8) & BYTE_MASK; - buf[4] = value & BYTE_MASK; - return 5; -} - -static size_t encode_uint64(uint8_t *buf, uint64_t value) { - buf[0] = CBOR_TYPE_UNSIGNED | CBOR_UINT64_FOLLOWS; - for (int idx = 0; idx < BYTE_SIZE; idx++) { - buf[1 + idx] = (value >> (SHIFT_56 - idx * BYTE_SIZE)) & BYTE_MASK; - } - return 9; -} - -size_t gm_cbor_write_uint(uint8_t *buf, uint64_t value) { - if (value < CBOR_IMMEDIATE_THRESHOLD) { - return encode_immediate(buf, CBOR_TYPE_UNSIGNED, (uint8_t)value); - } - - if (value <= UINT8_MAX) { - return encode_uint8_header(buf, CBOR_TYPE_UNSIGNED, (uint8_t)value); - } - - if (value <= UINT16_MAX) { - return encode_uint16_header(buf, CBOR_TYPE_UNSIGNED, (uint16_t)value); - } - - if (value <= UINT32_MAX) { - return encode_uint32(buf, (uint32_t)value); - } - - return encode_uint64(buf, value); -} - -/* Helper: Write string with length header */ -static size_t write_string_header(uint8_t *buf, uint8_t cbor_type, - size_t length) { - if (length < CBOR_IMMEDIATE_THRESHOLD) { - return encode_immediate(buf, cbor_type, (uint8_t)length); - } - - if (length <= UINT8_MAX) { - return encode_uint8_header(buf, cbor_type, (uint8_t)length); - } - - return encode_uint16_header(buf, cbor_type, (uint16_t)length); -} - -size_t gm_cbor_write_bytes(uint8_t *buf, const uint8_t *data, size_t len) { - size_t offset = write_string_header(buf, CBOR_TYPE_BYTES, len); - gm_memcpy(buf + offset, data, len); - return offset + len; -} - -size_t gm_cbor_write_text(uint8_t *buf, const char *text) { - size_t len = strlen(text); - size_t offset = write_string_header(buf, CBOR_TYPE_TEXT, len); - gm_memcpy(buf + offset, text, len); - return offset + len; -} \ No newline at end of file diff --git a/src/attribution/cbor_decode_ex.c b/src/attribution/cbor_decode_ex.c deleted file mode 100644 index fbc7b5e1..00000000 --- a/src/attribution/cbor_decode_ex.c +++ /dev/null @@ -1,141 +0,0 @@ -/* SPDX-License-Identifier: LicenseRef-MIND-UCAL-1.0 */ -/* © 2025 J. Kirby Ross / Neuroglyph Collective */ - -#include "gitmind.h" - -#include "gitmind/cbor_common.h" -#include "gitmind/constants_cbor.h" - -#include - -#include "../util/gm_mem.h" - -/* - * Extended CBOR decoder with consumed bytes tracking - * Follows strict SRP, DI principles with pedantic quality - */ - -/* Validate CBOR array header (SRP: validate header only) */ -/* NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - buffer_len vs - * expected_size */ -static int validate_array_header(const uint8_t *buffer, size_t buffer_len, - size_t expected_size) { - if (buffer_len < 1) { - return GM_INVALID_ARG; - } - - uint8_t header = buffer[0]; - if ((header & CBOR_TYPE_MASK) != CBOR_TYPE_ARRAY || - (header & CBOR_ADDITIONAL_INFO_MASK) != expected_size) { - return GM_INVALID_ARG; - } - - return GM_OK; -} - -/* Decode single SHA field (SRP: decode one SHA) */ -static int decode_sha_field(const uint8_t *buffer, size_t *offset, - uint8_t *sha) { - return gm_cbor_read_bytes(buffer, offset, sha, GM_SHA1_SIZE); -} - -/* Decode relationship type (SRP: decode one field) */ -static int decode_rel_type(const uint8_t *buffer, size_t *offset, - uint16_t *rel_type) { - uint64_t temp; - if (gm_cbor_read_uint(buffer, offset, &temp) != GM_OK) { - return GM_INVALID_ARG; - } - *rel_type = (uint16_t)temp; - return GM_OK; -} - -/* Decode confidence (SRP: decode one field) */ -static int decode_confidence(const uint8_t *buffer, size_t *offset, - uint16_t *confidence) { - uint64_t temp; - if (gm_cbor_read_uint(buffer, offset, &temp) != GM_OK) { - return GM_INVALID_ARG; - } - *confidence = (uint16_t)temp; - return GM_OK; -} - -/* Decode timestamp (SRP: decode one field) */ -static int decode_timestamp(const uint8_t *buffer, size_t *offset, - uint64_t *timestamp) { - return gm_cbor_read_uint(buffer, offset, timestamp); -} - -/* Decode single path (SRP: decode one path) */ -static int decode_path(const uint8_t *buffer, size_t *offset, char *path) { - return gm_cbor_read_text(buffer, offset, path, GM_PATH_MAX); -} - -/* Initialize edge structure (SRP: initialization only) */ -static void init_edge(gm_edge_t *edge) { - gm_memset(edge, 0, sizeof(gm_edge_t)); -} - -/* Helper: Decode all SHAs */ -static int decode_all_shas(const uint8_t *buffer, size_t *offset, - gm_edge_t *edge) { - int result = decode_sha_field(buffer, offset, edge->src_sha); - if (result != GM_OK) { - return result; - } - return decode_sha_field(buffer, offset, edge->tgt_sha); -} - -/* Helper: Decode all metadata */ -static int decode_all_metadata(const uint8_t *buffer, size_t *offset, - gm_edge_t *edge) { - int result = decode_rel_type(buffer, offset, &edge->rel_type); - if (result != GM_OK) { - return result; - } - - result = decode_confidence(buffer, offset, &edge->confidence); - if (result != GM_OK) { - return result; - } - - return decode_timestamp(buffer, offset, &edge->timestamp); -} - -/* Helper: Decode all paths */ -static int decode_all_paths(const uint8_t *buffer, size_t *offset, - gm_edge_t *edge) { - int result = decode_path(buffer, offset, edge->src_path); - if (result != GM_OK) { - return result; - } - return decode_path(buffer, offset, edge->tgt_path); -} - -/* Main decoder with consumed bytes tracking */ -int gm_edge_decode_cbor_ex(const uint8_t *buffer, size_t len, gm_edge_t *edge, - size_t *consumed) { - if (!buffer || !edge || !consumed || len == 0) { - return GM_INVALID_ARG; - } - - size_t offset = 0; - init_edge(edge); - - /* Validate and decode */ - int result = validate_array_header(buffer, len, CBOR_ARRAY_SIZE_EDGE); - if (result != GM_OK) { - return result; - } - offset++; - - if ((result = decode_all_shas(buffer, &offset, edge)) != GM_OK || - (result = decode_all_metadata(buffer, &offset, edge)) != GM_OK || - (result = decode_all_paths(buffer, &offset, edge)) != GM_OK) { - return result; - } - - *consumed = offset; - return GM_OK; -} \ No newline at end of file diff --git a/src/edge/cbor.c b/src/edge/cbor.c deleted file mode 100644 index f3147917..00000000 --- a/src/edge/cbor.c +++ /dev/null @@ -1,120 +0,0 @@ -/* SPDX-License-Identifier: LicenseRef-MIND-UCAL-1.0 */ -/* © 2025 J. Kirby Ross / Neuroglyph Collective */ - -#include "gitmind.h" - -#include "gitmind/cbor_common.h" -#include "gitmind/constants_cbor.h" - -#include - -/* Use common CBOR functions from cbor_common.h */ - -/* cbor_write_* functions moved to cbor_common.c */ - -/* Encode edge to CBOR */ -int gm_edge_encode_cbor(const gm_edge_t *edge, uint8_t *buffer, size_t *len) { - if (!edge || !buffer || !len) { - return GM_INVALID_ARG; - } - - size_t offset = 0; - - /* Array header with 7 elements */ - buffer[offset++] = CBOR_TYPE_ARRAY | CBOR_ARRAY_SIZE_EDGE; - - /* 1. Source SHA */ - offset += gm_cbor_write_bytes(buffer + offset, edge->src_sha, GM_SHA1_SIZE); - - /* 2. Target SHA */ - offset += gm_cbor_write_bytes(buffer + offset, edge->tgt_sha, GM_SHA1_SIZE); - - /* 3. Relationship type */ - offset += gm_cbor_write_uint(buffer + offset, edge->rel_type); - - /* 4. Confidence */ - offset += gm_cbor_write_uint(buffer + offset, edge->confidence); - - /* 5. Timestamp */ - offset += gm_cbor_write_uint(buffer + offset, edge->timestamp); - - /* 6. Source path */ - offset += gm_cbor_write_text(buffer + offset, edge->src_path); - - /* 7. Target path */ - offset += gm_cbor_write_text(buffer + offset, edge->tgt_path); - - *len = offset; - return GM_OK; -} - -/* cbor_read_* functions moved to cbor_common.c */ - -/* Decode CBOR to edge */ -int gm_edge_decode_cbor(const uint8_t *buffer, size_t len, gm_edge_t *edge) { - if (!buffer || !edge || len == 0) { - return GM_INVALID_ARG; - } - - size_t offset = 0; - - /* Initialize edge structure */ - memset(edge, 0, sizeof(gm_edge_t)); - - /* Need at least one byte for array header */ - if (len < 1) { - return GM_INVALID_ARG; - } - - /* Check array header */ - /* Check array header */ - if (buffer[offset] != (CBOR_TYPE_ARRAY | CBOR_ARRAY_SIZE_EDGE)) { - return GM_INVALID_ARG; - } - offset++; - - /* 1. Source SHA */ - if (gm_cbor_read_bytes(buffer, &offset, edge->src_sha, GM_SHA1_SIZE) != - GM_OK) { - return GM_INVALID_ARG; - } - - /* 2. Target SHA */ - if (gm_cbor_read_bytes(buffer, &offset, edge->tgt_sha, GM_SHA1_SIZE) != - GM_OK) { - return GM_INVALID_ARG; - } - - /* 3. Relationship type */ - uint64_t rel_type; - if (gm_cbor_read_uint(buffer, &offset, &rel_type) != GM_OK) { - return GM_INVALID_ARG; - } - edge->rel_type = (uint16_t)rel_type; - - /* 4. Confidence */ - uint64_t confidence; - if (gm_cbor_read_uint(buffer, &offset, &confidence) != GM_OK) { - return GM_INVALID_ARG; - } - edge->confidence = (uint16_t)confidence; - - /* 5. Timestamp */ - if (gm_cbor_read_uint(buffer, &offset, &edge->timestamp) != GM_OK) { - return GM_INVALID_ARG; - } - - /* 6. Source path */ - if (gm_cbor_read_text(buffer, &offset, edge->src_path, GM_PATH_MAX) != - GM_OK) { - return GM_INVALID_ARG; - } - - /* 7. Target path */ - if (gm_cbor_read_text(buffer, &offset, edge->tgt_path, GM_PATH_MAX) != - GM_OK) { - return GM_INVALID_ARG; - } - - return GM_OK; -} \ No newline at end of file diff --git a/src/edge/cbor.md b/src/edge/cbor.md deleted file mode 100644 index 43513601..00000000 --- a/src/edge/cbor.md +++ /dev/null @@ -1,104 +0,0 @@ -# CBOR Edge Encoder/Decoder - -## Purpose -Serialize and deserialize edge structures to/from CBOR (Concise Binary Object Representation) format for storage in Git commit messages. - -## Design Rationale - -### Why CBOR? -1. **Binary efficiency**: ~100-300 bytes per edge vs 500+ for JSON -2. **Self-describing**: Can decode without schema -3. **Streaming**: Can decode partial data -4. **Standard**: RFC 8949, widely supported -5. **Git-friendly**: Binary commit messages supported via `encoding=binary` - -### Why Not Protocol Buffers/MessagePack/etc? -- **Protobuf**: Requires schema files, versioning complexity -- **MessagePack**: Less standard, fewer tools -- **Raw binary**: No forward compatibility, brittle -- **JSON**: 3-5x larger, escaping issues in commit messages - -## Implementation Details - -### CBOR Structure -``` -Array[7] { - [0] bytes(20) - Source SHA-1 - [1] bytes(20) - Target SHA-1 - [2] uint - Relationship type (1-65535) - [3] uint - Confidence (IEEE-754 half as uint16) - [4] uint - Timestamp (Unix millis) - [5] text - Source path (for humans) - [6] text - Target path (for humans) -} -``` - -### Encoding Strategy -- Use definite-length encoding (not indefinite) -- Optimize for small numbers (< 24 = 1 byte) -- Text strings use UTF-8 -- No CBOR tags (keep it simple) - -## Edge Cases - -### Size Limits -- **Max edge size**: ~512 bytes (paths < 256 chars each) -- **Max commit message**: 8KB (can hold ~16 edges) -- **Batch overflow**: Writer creates new commit when approaching limit - -### Invalid Data -- **Truncated CBOR**: Decoder returns GM_INVALID_ARG -- **Wrong types**: Strict type checking, no coercion -- **Buffer overflow**: All bounds checked before memcpy - -### Unicode Paths -- Paths stored as UTF-8 text -- Git already handles Unicode paths -- No normalization (preserve exact bytes) - -## Security Considerations - -1. **Buffer overflows**: Every read checks bounds -2. **Integer overflow**: 64-bit arithmetic throughout -3. **Malformed input**: Fails safely with GM_INVALID_ARG -4. **DoS**: Max recursion depth = 1 (flat array only) - -## Performance - -- **Encode**: ~1-2 μs per edge -- **Decode**: ~2-3 μs per edge -- **Memory**: Zero allocations (stack buffers) -- **Cache-friendly**: Sequential access pattern - -## Future Considerations - -### SHA-256 Support -When Git transitions to SHA-256: -- Detect hash size from repository -- Encode as bytes(32) instead of bytes(20) -- Version flag in first commit? - -### Compression -If edge volume becomes massive: -- ZSTD compress commit messages -- Git already compresses objects -- Probably unnecessary complexity - -## Testing Notes - -Key test cases: -1. Round-trip encode/decode -2. Boundary values (0, MAX_UINT64, etc) -3. Truncated input at every byte position -4. Invalid type bytes -5. Path encoding edge cases (empty, max length, Unicode) - -## Why This Makes Linus Happy - -1. **No allocations**: Stack only, predictable memory -2. **No dependencies**: Pure C99, no external libs -3. **Fail fast**: Invalid input detected immediately -4. **Simple**: ~200 lines, obvious correctness -5. **Efficient**: Minimal CPU cycles, no waste - -As Linus says: "Show me the code." And this code shows exactly what it does, no more, no less. \ No newline at end of file diff --git a/src/edge/cbor_decode_ex.c b/src/edge/cbor_decode_ex.c deleted file mode 100644 index f33f9e60..00000000 --- a/src/edge/cbor_decode_ex.c +++ /dev/null @@ -1,118 +0,0 @@ -/* SPDX-License-Identifier: LicenseRef-MIND-UCAL-1.0 */ -/* © 2025 J. Kirby Ross / Neuroglyph Collective */ - -#include - -#include "../../include/gitmind.h" -#include "../../include/gitmind/cbor_common.h" -#include "../../include/gitmind/constants_cbor.h" - -/* Validate CBOR array header */ -static int validate_array_header(const uint8_t *buffer, size_t len, - size_t expected_size) { - if (len < 1) { - return GM_INVALID_ARG; - } - - uint8_t header = buffer[0]; - if ((header & CBOR_TYPE_MASK) != CBOR_TYPE_ARRAY || - (header & CBOR_ADDITIONAL_INFO_MASK) != expected_size) { - return GM_INVALID_ARG; - } - - return GM_OK; -} - -/* Decode SHA field */ -static int decode_sha_field(const uint8_t *buffer, size_t *offset, - uint8_t *sha) { - if ((buffer[*offset] & CBOR_TYPE_MASK) != CBOR_TYPE_BYTES) { - return GM_INVALID_ARG; - } - (*offset)++; /* Skip byte string header */ - - return gm_cbor_read_bytes(buffer, offset, sha, GM_SHA1_SIZE); -} - -/* Decode edge metadata fields */ -static int decode_edge_metadata(const uint8_t *buffer, size_t *offset, - gm_edge_t *edge) { - uint64_t temp; - - /* Relationship type */ - if (gm_cbor_read_uint(buffer, offset, &temp) != GM_OK) { - return GM_INVALID_ARG; - } - edge->rel_type = (uint16_t)temp; - - /* Confidence */ - if (gm_cbor_read_uint(buffer, offset, &temp) != GM_OK) { - return GM_INVALID_ARG; - } - edge->confidence = (uint16_t)temp; - - /* Timestamp */ - return gm_cbor_read_uint(buffer, offset, &edge->timestamp); -} - -/* Decode path fields */ -static int decode_path_fields(const uint8_t *buffer, size_t *offset, - gm_edge_t *edge) { - /* Source path */ - if (gm_cbor_read_text(buffer, offset, edge->src_path, GM_PATH_MAX) != - GM_OK) { - return GM_INVALID_ARG; - } - - /* Target path */ - return gm_cbor_read_text(buffer, offset, edge->tgt_path, GM_PATH_MAX); -} - -/* Decode CBOR to edge with consumed bytes */ -int gm_edge_decode_cbor_ex(const uint8_t *buffer, size_t len, gm_edge_t *edge, - size_t *consumed) { - if (!buffer || !edge || !consumed || len == 0) { - return GM_INVALID_ARG; - } - - size_t offset = 0; - int rc; - - /* Initialize edge structure */ - memset(edge, 0, sizeof(gm_edge_t)); - - /* Validate array header */ - rc = validate_array_header(buffer, len, CBOR_ARRAY_SIZE_EDGE); - if (rc != GM_OK) { - return rc; - } - offset++; - - /* Decode SHA fields */ - rc = decode_sha_field(buffer, &offset, edge->src_sha); - if (rc != GM_OK) { - return rc; - } - - rc = decode_sha_field(buffer, &offset, edge->tgt_sha); - if (rc != GM_OK) { - return rc; - } - - /* Decode metadata */ - rc = decode_edge_metadata(buffer, &offset, edge); - if (rc != GM_OK) { - return rc; - } - - /* Decode paths */ - rc = decode_path_fields(buffer, &offset, edge); - if (rc != GM_OK) { - return rc; - } - - /* Return consumed bytes */ - *consumed = offset; - - return GM_OK; -} \ No newline at end of file diff --git a/src/util/cbor_common.c b/src/util/cbor_common.c deleted file mode 100644 index b5396586..00000000 --- a/src/util/cbor_common.c +++ /dev/null @@ -1,186 +0,0 @@ -/* SPDX-License-Identifier: LicenseRef-MIND-UCAL-1.0 */ -/* © 2025 J. Kirby Ross / Neuroglyph Collective */ - -#include "gitmind/cbor_common.h" - -#include "gitmind.h" - -#include "gitmind/constants_cbor.h" - -#include - -/* Read CBOR unsigned integer */ -int gm_cbor_read_uint(const uint8_t *buf, size_t *offset, uint64_t *value) { - uint8_t initial = buf[(*offset)++]; - uint8_t type = initial & CBOR_TYPE_MASK; - uint8_t info = initial & CBOR_ADDITIONAL_INFO_MASK; - - if (type != CBOR_TYPE_UNSIGNED) { - return GM_INVALID_ARG; - } - - if (info < CBOR_IMMEDIATE_THRESHOLD) { - *value = info; - } else if (info == CBOR_UINT8_FOLLOWS) { - *value = buf[(*offset)++]; - } else if (info == CBOR_UINT16_FOLLOWS) { - *value = (buf[*offset] << SHIFT_8) | buf[*offset + 1]; - *offset += 2; - } else if (info == CBOR_UINT32_FOLLOWS) { - *value = ((uint32_t)buf[*offset] << SHIFT_24) | - ((uint32_t)buf[*offset + 1] << SHIFT_16) | - ((uint32_t)buf[*offset + 2] << SHIFT_8) | buf[*offset + 3]; - *offset += 4; - } else if (info == CBOR_UINT64_FOLLOWS) { - *value = 0; - for (int i = 0; i < BYTE_SIZE; i++) { - *value = (*value << SHIFT_8) | buf[(*offset)++]; - } - } else { - return GM_INVALID_ARG; - } - - return GM_OK; -} - -/* Read CBOR byte string */ -int gm_cbor_read_bytes(const uint8_t *buf, size_t *offset, uint8_t *data, - size_t expected_len) { - uint8_t initial = buf[(*offset)++]; - uint8_t type = initial & CBOR_TYPE_MASK; - uint8_t info = initial & CBOR_ADDITIONAL_INFO_MASK; - size_t len; - - if (type != CBOR_TYPE_BYTES) { - return GM_INVALID_ARG; - } - - if (info < CBOR_IMMEDIATE_THRESHOLD) { - len = info; - } else if (info == CBOR_UINT8_FOLLOWS) { - len = buf[(*offset)++]; - } else if (info == CBOR_UINT16_FOLLOWS) { - len = (buf[*offset] << SHIFT_8) | buf[*offset + 1]; - *offset += 2; - } else { - return GM_INVALID_ARG; - } - - if (len != expected_len) { - return GM_INVALID_ARG; - } - - memcpy(data, buf + *offset, len); - *offset += len; - - return GM_OK; -} - -/* Read CBOR text string */ -int gm_cbor_read_text(const uint8_t *buf, size_t *offset, char *text, - size_t max_len) { - uint8_t initial = buf[(*offset)++]; - uint8_t type = initial & CBOR_TYPE_MASK; - uint8_t info = initial & CBOR_ADDITIONAL_INFO_MASK; - size_t len; - - if (type != CBOR_TYPE_TEXT) { - return GM_INVALID_ARG; - } - - if (info < CBOR_IMMEDIATE_THRESHOLD) { - len = info; - } else if (info == CBOR_UINT8_FOLLOWS) { - len = buf[(*offset)++]; - } else if (info == CBOR_UINT16_FOLLOWS) { - len = (buf[*offset] << SHIFT_8) | buf[*offset + 1]; - *offset += 2; - } else { - return GM_INVALID_ARG; - } - - if (len >= max_len) { - return GM_INVALID_ARG; - } - - memcpy(text, buf + *offset, len); - text[len] = '\0'; - *offset += len; - - return GM_OK; -} - -/* Write CBOR unsigned integer */ -size_t gm_cbor_write_uint(uint8_t *buf, uint64_t value) { - if (value < CBOR_IMMEDIATE_THRESHOLD) { - buf[0] = CBOR_TYPE_UNSIGNED | (uint8_t)value; - return 1; - } else if (value <= UINT8_MAX) { - buf[0] = CBOR_TYPE_UNSIGNED | CBOR_UINT8_FOLLOWS; - buf[1] = (uint8_t)value; - return 2; - } else if (value <= UINT16_MAX) { - buf[0] = CBOR_TYPE_UNSIGNED | CBOR_UINT16_FOLLOWS; - buf[1] = (value >> SHIFT_8) & BYTE_MASK; - buf[2] = value & BYTE_MASK; - return 3; - } else if (value <= UINT32_MAX) { - buf[0] = CBOR_TYPE_UNSIGNED | CBOR_UINT32_FOLLOWS; - buf[1] = (value >> SHIFT_24) & BYTE_MASK; - buf[2] = (value >> SHIFT_16) & BYTE_MASK; - buf[3] = (value >> SHIFT_8) & BYTE_MASK; - buf[4] = value & BYTE_MASK; - return 5; - } else { - buf[0] = CBOR_TYPE_UNSIGNED | CBOR_UINT64_FOLLOWS; - for (int i = 0; i < BYTE_SIZE; i++) { - buf[1 + i] = (value >> (SHIFT_56 - i * BYTE_SIZE)) & BYTE_MASK; - } - return 9; - } -} - -/* Write CBOR byte string */ -size_t gm_cbor_write_bytes(uint8_t *buf, const uint8_t *data, size_t len) { - size_t offset = 0; - - if (len < CBOR_IMMEDIATE_THRESHOLD) { - buf[0] = CBOR_TYPE_BYTES | (uint8_t)len; - offset = 1; - } else if (len <= UINT8_MAX) { - buf[0] = CBOR_TYPE_BYTES | CBOR_UINT8_FOLLOWS; - buf[1] = (uint8_t)len; - offset = 2; - } else { - buf[0] = CBOR_TYPE_BYTES | CBOR_UINT16_FOLLOWS; - buf[1] = (len >> SHIFT_8) & BYTE_MASK; - buf[2] = len & BYTE_MASK; - offset = 3; - } - - memcpy(buf + offset, data, len); - return offset + len; -} - -/* Write CBOR text string */ -size_t gm_cbor_write_text(uint8_t *buf, const char *text) { - size_t len = strlen(text); - size_t offset = 0; - - if (len < CBOR_IMMEDIATE_THRESHOLD) { - buf[0] = CBOR_TYPE_TEXT | (uint8_t)len; - offset = 1; - } else if (len <= UINT8_MAX) { - buf[0] = CBOR_TYPE_TEXT | CBOR_UINT8_FOLLOWS; - buf[1] = (uint8_t)len; - offset = 2; - } else { - buf[0] = CBOR_TYPE_TEXT | CBOR_UINT16_FOLLOWS; - buf[1] = (len >> SHIFT_8) & BYTE_MASK; - buf[2] = len & BYTE_MASK; - offset = 3; - } - - memcpy(buf + offset, text, len); - return offset + len; -} \ No newline at end of file