diff --git a/include/quarry/runtime_c/generic_brf.h b/include/quarry/runtime_c/generic_brf.h index b577693..f194d9e 100644 --- a/include/quarry/runtime_c/generic_brf.h +++ b/include/quarry/runtime_c/generic_brf.h @@ -224,6 +224,23 @@ quarry_generic_status_t quarry_qbs_find_record_by_id(const quarry_qbs_view_t*, u const quarry_qbs_record_view_t**); quarry_generic_status_t quarry_qbs_find_record_by_name(const quarry_qbs_view_t*, const char*, size_t, const quarry_qbs_record_view_t**); +/* + * Returns a non-owning view into the optional reflective QBS string section. + * The view remains valid while the input QBS byte buffer remains unchanged and + * alive. QUARRY_GENERIC_FIELD_ABSENT means that reflective strings are not + * available or that the descriptor has no name. + */ +quarry_generic_status_t quarry_qbs_get_string(const quarry_qbs_view_t*, uint16_t, + quarry_string_view_t*); +quarry_generic_status_t quarry_qbs_record_name(const quarry_qbs_view_t*, + const quarry_qbs_record_view_t*, + quarry_string_view_t*); +quarry_generic_status_t quarry_qbs_field_name(const quarry_qbs_view_t*, + const quarry_qbs_record_view_t*, uint16_t, + quarry_string_view_t*); +quarry_generic_status_t quarry_qbs_enum_name(const quarry_qbs_view_t*, + const quarry_qbs_enum_view_t*, + quarry_string_view_t*); quarry_generic_status_t quarry_qbs_record_field(const quarry_qbs_view_t*, const quarry_qbs_record_view_t*, uint16_t, const quarry_qbs_field_view_t**); diff --git a/runtime_c/README.md b/runtime_c/README.md index 1af850f..b13a7ed 100644 --- a/runtime_c/README.md +++ b/runtime_c/README.md @@ -18,6 +18,27 @@ sibling implementations: neither depends on the other, and each is installed under its own canonical path (`include/quarry/runtime/` for C++, `include/quarry/runtime_c/` for C -- see "CMake Package" below). +## Reflective QBS names + +The generic QBS API exposes optional reflective names without exposing the +binary string-section representation. `quarry_qbs_get_string()` resolves a +string-table index, while `quarry_qbs_record_name()`, +`quarry_qbs_field_name()`, and `quarry_qbs_enum_name()` resolve descriptor +names through their existing `name_index` metadata. Each result is the public +`quarry_string_view_t` (`data` plus `size`), points into the caller-owned QBS +input buffer, is not NUL-terminated, and remains valid only while that buffer +is unchanged and alive. + +Minimal QBS images do not contain reflective strings. Name lookup then returns +`QUARRY_GENERIC_FIELD_ABSENT`; the image remains valid and all structural +metadata remains usable. An invalid string index returns +`QUARRY_GENERIC_FIELD_NOT_FOUND`, while an invalid argument or malformed +string-table state returns the corresponding generic status. + +QBS currently stores enum type names and numeric enum values, but not symbolic +names for individual enum values. These APIs therefore do not provide enum +value names; consumers should render enum values numerically. + **Status: scalar, enum, bounded string, bounded bytes, bounded array (of scalar, enum, bounded string, bounded bytes, or record elements), nested record fields, and compiler-resolved cross-namespace enum/record fields and diff --git a/runtime_c/generic_brf.c b/runtime_c/generic_brf.c index cc35838..a782e49 100644 --- a/runtime_c/generic_brf.c +++ b/runtime_c/generic_brf.c @@ -68,6 +68,31 @@ static bool utf8(const uint8_t* p, size_t n) { return true; } +static quarry_generic_status_t string_section_entry(const quarry_qbs_view_t* q, uint16_t index, + size_t* data, size_t* length) { + if (q->bytes == NULL || q->strings_offset == 0U || q->strings_size < 8U || + q->strings_offset > q->size || q->strings_size > q->size - q->strings_offset) + return QUARRY_GENERIC_MALFORMED_QBS; + const uint8_t* section = q->bytes + q->strings_offset; + const uint32_t count = u32(section); + if (count > UINT16_MAX) + return QUARRY_GENERIC_MALFORMED_QBS; + if (index >= count) + return QUARRY_GENERIC_FIELD_NOT_FOUND; + const size_t offsets = 4U + ((size_t)count + 1U) * 4U; + if (offsets > q->strings_size) + return QUARRY_GENERIC_MALFORMED_QBS; + const size_t payload = q->strings_size - offsets; + const uint32_t begin = u32(section + 4U + (size_t)index * 4U); + const uint32_t end = u32(section + 4U + ((size_t)index + 1U) * 4U); + if (u32(section + 4U) != 0U || + u32(section + 4U + (size_t)count * 4U) != payload || begin > end || end > payload) + return QUARRY_GENERIC_MALFORMED_QBS; + *data = q->strings_offset + offsets + begin; + *length = (size_t)(end - begin); + return QUARRY_GENERIC_OK; +} + static quarry_generic_status_t section(const uint8_t* b, size_t n, uint16_t kind, size_t* at, size_t* size) { if (n < 40U || u16(b + 28U) > 64U) @@ -226,6 +251,54 @@ quarry_generic_status_t quarry_qbs_find_record_by_name(const quarry_qbs_view_t* } return QUARRY_GENERIC_FIELD_NOT_FOUND; } + +quarry_generic_status_t quarry_qbs_get_string(const quarry_qbs_view_t* q, uint16_t index, + quarry_string_view_t* out) { + size_t data = 0U, length = 0U; + if (out != NULL) + *out = (quarry_string_view_t){NULL, 0U}; + if (q == NULL || out == NULL) + return QUARRY_GENERIC_INVALID_ARGUMENT; + if (index == UINT16_MAX) + return QUARRY_GENERIC_FIELD_ABSENT; + if (q->strings_offset == 0U) + return QUARRY_GENERIC_FIELD_ABSENT; + const quarry_generic_status_t status = string_section_entry(q, index, &data, &length); + if (status != QUARRY_GENERIC_OK) + return status; + *out = (quarry_string_view_t){(const char*)q->bytes + data, length}; + return QUARRY_GENERIC_OK; +} + +quarry_generic_status_t quarry_qbs_record_name(const quarry_qbs_view_t* q, + const quarry_qbs_record_view_t* record, + quarry_string_view_t* out) { + if (q == NULL || record == NULL || out == NULL) + return QUARRY_GENERIC_INVALID_ARGUMENT; + return quarry_qbs_get_string(q, record->name_index, out); +} + +quarry_generic_status_t quarry_qbs_field_name(const quarry_qbs_view_t* q, + const quarry_qbs_record_view_t* record, uint16_t index, + quarry_string_view_t* out) { + const quarry_qbs_field_view_t* field = NULL; + quarry_generic_status_t status; + if (q == NULL || record == NULL || out == NULL) + return QUARRY_GENERIC_INVALID_ARGUMENT; + status = quarry_qbs_record_field(q, record, index, &field); + if (status != QUARRY_GENERIC_OK) + return status; + return quarry_qbs_get_string(q, field->name_index, out); +} + +quarry_generic_status_t quarry_qbs_enum_name(const quarry_qbs_view_t* q, + const quarry_qbs_enum_view_t* enumeration, + quarry_string_view_t* out) { + if (q == NULL || enumeration == NULL || out == NULL) + return QUARRY_GENERIC_INVALID_ARGUMENT; + return quarry_qbs_get_string(q, enumeration->name_index, out); +} + quarry_generic_status_t quarry_qbs_record_field(const quarry_qbs_view_t* q, const quarry_qbs_record_view_t* r, uint16_t idx, const quarry_qbs_field_view_t** out) { diff --git a/tests/runtime_c/generic_runtime_test.c b/tests/runtime_c/generic_runtime_test.c index 9c3fbf6..f36a90c 100644 --- a/tests/runtime_c/generic_runtime_test.c +++ b/tests/runtime_c/generic_runtime_test.c @@ -551,6 +551,33 @@ int main(void) { } if (parent->field_count != 13U) return 1; + quarry_string_view_t name; + if (quarry_qbs_record_name(&schema, parent, &name) != QUARRY_GENERIC_OK || + name.size != 6U || memcmp(name.data, "Parent", name.size) != 0) + return 1; + if (quarry_qbs_field_name(&schema, parent, 0U, &name) != QUARRY_GENERIC_OK || + name.size != 8U || memcmp(name.data, "sequence", name.size) != 0) + return 1; + if (schema.enum_count == 0U || + quarry_qbs_enum_name(&schema, &schema.enums[0], &name) != QUARRY_GENERIC_OK || + name.size != 5U || memcmp(name.data, "State", name.size) != 0) + return 1; + if (quarry_qbs_get_string(&schema, UINT16_MAX, &name) != QUARRY_GENERIC_FIELD_ABSENT || + quarry_qbs_get_string(&schema, (uint16_t)schema.enum_count + 100U, &name) != + QUARRY_GENERIC_FIELD_NOT_FOUND || + quarry_qbs_get_string(NULL, 0U, &name) != QUARRY_GENERIC_INVALID_ARGUMENT || + quarry_qbs_get_string(&schema, 0U, NULL) != QUARRY_GENERIC_INVALID_ARGUMENT || + quarry_qbs_record_name(&schema, NULL, &name) != QUARRY_GENERIC_INVALID_ARGUMENT || + quarry_qbs_field_name(&schema, parent, parent->field_count, &name) != + QUARRY_GENERIC_FIELD_NOT_FOUND) + return 1; + quarry_qbs_view_t minimal_view = schema; + minimal_view.strings_offset = 0U; + minimal_view.strings_size = 0U; + if (quarry_qbs_get_string(&minimal_view, 0U, &name) != QUARRY_GENERIC_FIELD_ABSENT || + quarry_qbs_record_name(&minimal_view, parent, &name) != QUARRY_GENERIC_FIELD_ABSENT || + quarry_qbs_field_name(&minimal_view, parent, 0U, &name) != QUARRY_GENERIC_FIELD_ABSENT) + return 1; quarry_brf_value_provider_t provider = {encode_fixture_field, NULL}; quarry_brf_encoder_field_t encoded_fields[13]; quarry_brf_encoder_workspace_t encoder_workspace =