From 11785d597fdcfb964898658c0f6ae4f10f3d61ad Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sat, 20 Jul 2024 15:33:36 +0100 Subject: [PATCH 01/11] Util: Add helper to get field for a value --- src/util.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/util.h b/src/util.h index 5060497..9bd777f 100644 --- a/src/util.h +++ b/src/util.h @@ -26,6 +26,17 @@ /** Macro to squash unused variable compiler warnings. */ #define CYAML_UNUSED(_x) ((void)(_x)) +/** + * Macro to get a cyaml_schema_field_t container of a cyaml_schema_value_t + * + * \param[in] _value The value MUST be a member of a mapping field. + * \return The mapping field schema for the value. + */ +#define CYAML_FIELD_OF_VALUE(_value) \ + (const cyaml_schema_field_t *)( \ + (char *)_value - offsetof(cyaml_schema_field_t, value) \ + ) + /** * Check whether the host is little endian. * From 114d4a39ff519cf56f656da7105f9e15e0085c36 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sat, 20 Jul 2024 16:08:05 +0100 Subject: [PATCH 02/11] Base64: Add encoder and decoder --- src/base64.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/base64.h | 67 ++++++++++++++ 2 files changed, 310 insertions(+) create mode 100644 src/base64.c create mode 100644 src/base64.h diff --git a/src/base64.c b/src/base64.c new file mode 100644 index 0000000..7b49a7a --- /dev/null +++ b/src/base64.c @@ -0,0 +1,243 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2023 Michael Drake + */ + +#include +#include + +#include "base64.h" +#include "util.h" + +/** + * \file + * \brief CYAML functions for handling base64 encode and decode. + */ + +/** Base64 value to character mapping. */ +static const char enc[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/"; + +/* Exported function, documented in base64.h. */ +size_t cyaml_base64_calc_encoded_size( + size_t data_len) +{ + return (data_len + 2) / 3 * 4; +} + +/** + * Read up to 3 input bytes and write up to 4 output characters. + * + * \param[in] count Number of input bytes to encode (1, 2, or 3). + * \param[in] i Input: Array of at least `count` bytes. + * \param[out] o Output: Storage to write the encoded data. + * \return Number of output characters written. +*/ +static inline size_t cyaml_base64__encode( + size_t count, + const uint8_t *i, + char *o) +{ + uint32_t combined = 0; + + assert(count >= 1); + assert(count <= 3); + + switch (count) { + case 3: combined |= ((uint32_t)i[2] << 0); /* Fall through. */ + case 2: combined |= ((uint32_t)i[1] << 8); /* Fall through. */ + case 1: combined |= ((uint32_t)i[0] << 16); + } + + switch (count) { + case 3: o[3] = enc[(combined >> 0) & 0x3f]; /* Fall through. */ + case 2: o[2] = enc[(combined >> 6) & 0x3f]; /* Fall through. */ + case 1: o[1] = enc[(combined >> 12) & 0x3f]; + o[0] = enc[(combined >> 18) & 0x3f]; + } + + return count + 1; +} + +/* Exported function, documented in base64.h. */ +void cyaml_base64_encode( + const uint8_t *data, + size_t data_len, + char *str) +{ + size_t str_len = 0; + + while (data_len >= 3) { + str_len += cyaml_base64__encode(3, data, str + str_len); + data_len -= 3; + data += 3; + } + + if (data_len != 0) { + str_len += cyaml_base64__encode(data_len, data, str + str_len); + } + + assert((str_len & 0x3U) != 1); + switch (str_len & 0x3U) { + case 2: str[str_len++] = '='; /* Fall through. */ + case 3: str[str_len++] = '='; + } +} + +/** Non-base64 character mapping values. */ +enum { + BAD = 64, /**< Invalid character. */ + PAD = 65, /**< Padding character ('='). */ +}; + +/** Base64 character to value mapping. */ +static const uint8_t dec[256] = { + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, 62, BAD, BAD, BAD, 63, /* ...+ .../ */ + 52, 53, 54, 55, 56, 57, 58, 59, /* 0123 4567 */ + 60, 61, BAD, BAD, BAD, PAD, BAD, BAD, /* 89.. .=.. */ + BAD, 0, 1, 2, 3, 4, 5, 6, /* .ABC DEFG */ + 7, 8, 9, 10, 11, 12, 13, 14, /* HIJK LMNO */ + 15, 16, 17, 18, 19, 20, 21, 22, /* PQRS TUVW */ + 23, 24, 25, BAD, BAD, BAD, BAD, BAD, /* XYZ. .... */ + BAD, 26, 27, 28, 29, 30, 31, 32, /* .abc defg */ + 33, 34, 35, 36, 37, 38, 39, 40, /* hijk lmno */ + 41, 42, 43, 44, 45, 46, 47, 48, /* pqrs tuvw */ + 49, 50, 51, BAD, BAD, BAD, BAD, BAD, /* zyz. .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ + BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, /* .... .... */ +}; + +/* Exported function, documented in base64.h. */ +cyaml_err_t cyaml_base64_calc_decoded_size( + const char *str, + size_t str_len, + size_t *size) +{ + const uint8_t *raw = (const uint8_t *) str; + size_t padding = 0; + size_t extra = 0; + size_t len = 0; + + for (size_t i = 0; i < str_len; i++) { + switch (dec[raw[i]]) { + case PAD: + if (padding >= 2) { + return CYAML_ERR_INVALID_BASE64; + } + padding++; + break; + case BAD: + break; + default: + if (padding) { + return CYAML_ERR_INVALID_BASE64; + } + len++; + break; + } + } + + if (len < 2) { + return CYAML_ERR_INVALID_BASE64; + } + + switch (len & 0x3U) { + case 3: + if (padding && padding != 1) { + return CYAML_ERR_INVALID_BASE64; + } + extra = 2; + break; + case 2: + if (padding && padding != 2) { + return CYAML_ERR_INVALID_BASE64; + } + extra = 1; + break; + case 1: + return CYAML_ERR_INVALID_BASE64; + case 0: + if (padding != 0) { + return CYAML_ERR_INVALID_BASE64; + } + break; + } + + *size = len / 4 * 3 + extra; + return CYAML_OK; +} + +/** + * Read up to 4 input characters and write up to 3 output bytes. + * + * \param[in] count Number of input characters to decode (2, 3, or 4). + * \param[in] i Input: Array of at least `count` characters. + * \param[out] o Output: Storage to write the decoded data. + * \return Number of output bytes written. + */ +static inline size_t cyaml_base64__decode( + size_t count, + const uint8_t *i, + uint8_t *o) +{ + assert(count <= 4); + assert(count >= 2); + + switch (count) { + case 4: o[2] = (uint8_t)((dec[i[3]] ) | (dec[i[2]] << 6)); /* Fall through. */ + case 3: o[1] = (uint8_t)((dec[i[2]] >> 2) | (dec[i[1]] << 4)); /* Fall through. */ + case 2: o[0] = (uint8_t)((dec[i[1]] >> 4) | (dec[i[0]] << 2)); + } + + return count - 1; +} + +/* Exported function, documented in base64.h. */ +void cyaml_base64_decode( + const char *str, + size_t str_len, + uint8_t *data) +{ + const uint8_t *input = (const uint8_t *) str; + size_t buf_count = 0; + uint8_t buf[4]; + + while (str_len > 0) { + if (dec[*input] < 64) { + buf[buf_count++] = *(input); + if (buf_count == 4) { + data += cyaml_base64__decode(4, buf, data); + buf_count = 0; + } + } + str_len--; + input++; + } + + if (buf_count != 0) { + cyaml_base64__decode(buf_count, buf, data); + } +} diff --git a/src/base64.h b/src/base64.h new file mode 100644 index 0000000..95dca5b --- /dev/null +++ b/src/base64.h @@ -0,0 +1,67 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2023 Michael Drake + */ + +/** + * \file + * \brief CYAML functions for handling base64 encode and decode. + */ + +#ifndef CYAML_BASE64_H +#define CYAML_BASE64_H + +#include "cyaml/cyaml.h" + +/** + * Calculate the size of a base64 encoded string. + * + * \param[in] data_len The length of the data to encode. + * \return The size of the base64 encoded string in bytes. + * Excludes space for any trailing '\0' character. + */ +size_t cyaml_base64_calc_encoded_size( + size_t data_len); + +/** + * Encode data as base64. + * + * \param[in] data The data to encode. + * \param[in] data_len The length of the data to encode. + * \param[out] str Pointer to buffer to receive encoded string. + * Must be at least cyaml_base64_calc_encoded_size(). + * No trailing '\0' character is written. + */ +void cyaml_base64_encode( + const uint8_t *data, + size_t data_len, + char *str); + +/** + * Calculate the size of a decoded base64 string. + * + * \param[in] str The base64 string to decode. + * \param[in] str_len The length of the base64 string to decode. + * \param[out] size Pointer to variable to receive decoded size. + * \return CYAML_OK on success, or appropriate error code otherwise. + */ +cyaml_err_t cyaml_base64_calc_decoded_size( + const char *str, + size_t str_len, + size_t *size); + +/** + * Decode a base64 string. + * + * \param[in] str The base64 string to decode. + * \param[in] str_len The length of the base64 string to decode. + * \param[out] data Pointer to buffer to receive decoded data. + * Must be at least cyaml_base64_calc_decoded_size(). + */ +void cyaml_base64_decode( + const char *str, + size_t str_len, + uint8_t *data); + +#endif From 6862cab097ca62153ad783998847f0b2359ea107 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sat, 20 Jul 2024 16:07:07 +0100 Subject: [PATCH 03/11] API: Add CYAML_BINARY type This adds support for Base64-encoded binary values to the load, save, copy, and free code. --- Makefile | 2 +- include/cyaml/cyaml.h | 156 ++++++++++++++++++++++++++++++++++++++++ include/cyaml/private.h | 6 ++ src/copy.c | 58 +++++++++++++++ src/free.c | 6 ++ src/load.c | 139 +++++++++++++++++++++++++++++++++++ src/save.c | 56 +++++++++++++++ src/util.c | 3 + src/util.h | 1 + 9 files changed, 426 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 613cf56..e470ba4 100644 --- a/Makefile +++ b/Makefile @@ -96,7 +96,7 @@ endif BUILDDIR_SHARED = $(BUILDDIR)/shared BUILDDIR_STATIC = $(BUILDDIR)/static -LIB_SRC_FILES = mem.c free.c load.c save.c copy.c util.c utf8.c +LIB_SRC_FILES = base64.c mem.c free.c load.c save.c copy.c util.c utf8.c LIB_SRC := $(addprefix src/,$(LIB_SRC_FILES)) LIB_OBJ = $(patsubst %.c,%.o, $(addprefix $(BUILDDIR)/,$(LIB_SRC))) LIB_DEP = $(patsubst %.c,%.d, $(addprefix $(BUILDDIR)/,$(LIB_SRC))) diff --git a/include/cyaml/cyaml.h b/include/cyaml/cyaml.h index 188b473..bfe5e02 100644 --- a/include/cyaml/cyaml.h +++ b/include/cyaml/cyaml.h @@ -83,6 +83,18 @@ typedef enum cyaml_type { CYAML_FLAGS, CYAML_FLOAT, /**< Value is floating point. */ CYAML_STRING, /**< Value is a string. */ + /** + * Value is binary data, encoded in the YAML as a Base64 string. + * + * Values of this type must be the direct children of a mapping. + * They require: + * + * - Offset to the array entry count member in the mapping structure. + * - Size in bytes of the count member in the mapping structure. + * - The minimum and maximum number allowed size. + * Use \ref CYAML_UNLIMITED to have no maximum limit. + */ + CYAML_BINARY, /** * Value is a mapping. Values of this type require mapping schema * array in the schema entry. @@ -424,6 +436,25 @@ typedef bool (*cyaml_validate_string_fn_t)( const cyaml_schema_value_t *schema, const char *value); +/** + * Value validation callback function for \ref CYAML_BINARY. + * + * Clients may provide this for values of type \ref CYAML_BINARY. + * When called, return `false` to reject the value as invalid or `true` to + * accept it as valid. + * + * \param[in] ctx Client's private validation context. + * \param[in] schema The schema for the value. + * \param[in] value The binary data value to be validated. + * \param[in] len The length of the binary data. + * \return `true` if values is valid, `false` otherwise. + */ +typedef bool (*cyaml_validate_binary_fn_t)( + void *ctx, + const cyaml_schema_value_t *schema, + const uint8_t *value, + size_t len); + /** * Value validation callback function for \ref CYAML_MAPPING. * @@ -635,6 +666,44 @@ typedef struct cyaml_schema_value { */ const char *missing; } string; + /** \ref CYAML_BINARY type-specific schema data. */ + struct { + /** + * Minimum string length (bytes). + * + * \note Excludes trailing NUL. + */ + uint32_t min; + /** + * Maximum string length (bytes). + * + * \note Excludes trailing NULL, so for character array + * strings (rather than pointer strings), this + * must be no more than `data_size - 1`. + */ + uint32_t max; + /** + * Optional client value validation callback. + * + * May be NULL. + */ + cyaml_validate_binary_fn_t validation_cb; + /** + * Value to use for missing YAML field. + * + * This is only used when the value is used for a + * mapping field with the \ref CYAML_FLAG_OPTIONAL flag + * set. + * + * \note This is may be NULL, if no default sequence is + * wanted for a missing field in the YAML. + */ + const void *missing; + /** + * Number of entries in missing array. + */ + size_t missing_len; + } binary; /** \ref CYAML_MAPPING type-specific schema data. */ struct { /** @@ -893,6 +962,9 @@ typedef enum cyaml_err { CYAML_ERR_INVALID_VALUE, /**< Value rejected by schema. */ CYAML_ERR_INVALID_ALIAS, /**< No anchor found for alias. */ CYAML_ERR_INTERNAL_ERROR, /**< Internal error in LibCYAML. */ + CYAML_ERR_INVALID_BASE64, /**< Invalid Base64 string. */ + CYAML_ERR_BASE64_MAX_LEN, /**< Too much base64 data. */ + CYAML_ERR_MAPPING_REQUIRED, /**< Value requires parent mapping. */ CYAML_ERR_UNEXPECTED_EVENT, /**< YAML event rejected by schema. */ CYAML_ERR_STRING_LENGTH_MIN, /**< String length too short. */ CYAML_ERR_STRING_LENGTH_MAX, /**< String length too long. */ @@ -1499,6 +1571,90 @@ typedef enum cyaml_err { } \ ) +/** + * Mapping schema helper macro for keys with \ref CYAML_BINARY type. + * + * To use this, there must be a member in {_structure} called "{_member}_len", + * for storing the byte length of the data. + * + * For example, for the following structure: + * + * ``` + * struct my_structure { + * uint8_t *my_data; + * size_t my_data_len; + * }; + * ``` + * + * Pass the following as parameters: + * + * | Parameter | Value | + * | ---------- | --------------------- | + * | _structure | `struct my_structure` | + * | _member | `my_data` | + * + * If you want to call the structure member for storing the sequence entry + * count something else, then use \ref CYAML_FIELD_BINARY_LENGTH instead. + * + * \param[in] _key String defining the YAML mapping key for this value. + * \param[in] _flags Any behavioural flags relevant to this value. + * \param[in] _structure The structure containing the binary value. + * \param[in] _member The member in _structure for this binary value. + * \param[in] _min Minimum binary data length in bytes. + * \param[in] _max Maximum binary data length in bytes. + */ +#define CYAML_FIELD_BINARY( \ + _key, _flags, _structure, _member, _min, _max) \ + CYAML_FIELD_PTR(BINARY, _key, _flags, _structure, _member, \ + { \ + .min = _min, \ + .max = _max, \ + } \ + ) + +/** + * Mapping schema helper macro for keys with \ref CYAML_BINARY type. + * + * Compared to .\ref CYAML_FIELD_BINARY, this macro takes an extra `_length` + * parameter, allowing the structure member name for storing the byte length + * of the data to be provided explicitly. + * + * For example, for the following structure: + * + * ``` + * struct my_structure { + * uint8_t *my_data; + * size_t length; + * }; + * ``` + * + * Pass the following as parameters: + * + * | Parameter | Value | + * | ---------- | --------------------- | + * | _structure | `struct my_structure` | + * | _member | `my_data` | + * | _length | `length` | + * + * \param[in] _key String defining the YAML mapping key for this value. + * \param[in] _flags Any behavioural flags relevant to this value. + * \param[in] _structure The structure containing the binary value. + * \param[in] _member The member in _structure for this binary value. + * \param[in] _length The member in _structure for this data's byte length. + * \param[in] _min Minimum binary data length in bytes. + * \param[in] _max Maximum binary data length in bytes. + */ +#define CYAML_FIELD_BINARY_LENGTH( \ + _key, _flags, _structure, _member, _length, _min, _max) \ + CYAML_FIELD_PTR_COUNT(BINARY, \ + _key, _flags, _structure, _member, _length, \ + { \ + .min = _min, \ + .max = _max, \ + } \ + ) + + /** * Mapping schema helper macro for keys with \ref CYAML_MAPPING type. * diff --git a/include/cyaml/private.h b/include/cyaml/private.h index 526427b..58ab01c 100644 --- a/include/cyaml/private.h +++ b/include/cyaml/private.h @@ -52,6 +52,8 @@ extern "C" #define CYAML__UNION_MEMBER_FLOAT .floating_point /** Type to \ref cyaml_schema_value union member for STRING. */ #define CYAML__UNION_MEMBER_STRING .string +/** Type to \ref cyaml_schema_value union member for BINARY. */ +#define CYAML__UNION_MEMBER_BINARY .binary /** Type to \ref cyaml_schema_value union member for MAPPING. */ #define CYAML__UNION_MEMBER_MAPPING .mapping /** Type to \ref cyaml_schema_value union member for BITFIELD. */ @@ -75,6 +77,8 @@ extern "C" #define CYAML__SEQUENCE_COUNT_FLOAT(_member, _count) _member /** Fake a valid sequence count member for STRING. */ #define CYAML__SEQUENCE_COUNT_STRING(_member, _count) _member +/** Fake a valid sequence count member for BINARY. */ +#define CYAML__SEQUENCE_COUNT_BINARY(_member, _count) _member ## _len /** Fake a valid sequence count member for MAPPING. */ #define CYAML__SEQUENCE_COUNT_MAPPING(_member, _count) _member /** Fake a valid sequence count member for BITFIELD. */ @@ -98,6 +102,8 @@ extern "C" #define CYAML__HANDLE_PTR_FLOAT(_flags) CYAML__POINTER_ADD(_flags) /** Adapt flags for mapping schema building macro for STRING. */ #define CYAML__HANDLE_PTR_STRING(_flags) CYAML__POINTER_ADD(_flags) +/** Adapt flags for mapping schema building macro for BINARY. */ +#define CYAML__HANDLE_PTR_BINARY(_flags) _flags /** Adapt flags for mapping schema building macro for MAPPING. */ #define CYAML__HANDLE_PTR_MAPPING(_flags) CYAML__POINTER_ADD(_flags) /** Adapt flags for mapping schema building macro for BITFIELD. */ diff --git a/src/copy.c b/src/copy.c index 64176aa..d88f98d 100644 --- a/src/copy.c +++ b/src/copy.c @@ -243,8 +243,10 @@ static cyaml_err_t cyaml__data_handle_pointer( if (schema->flags & CYAML_FLAG_POINTER) { /* Need to create/extend an allocation. */ + const cyaml_schema_field_t *field; size_t delta = schema->data_size; uint8_t *value_copy = NULL; + cyaml_err_t err; if (*value_data_io == NULL) { return CYAML_ERR_BAD_PARAM_NULL_DATA; @@ -256,6 +258,15 @@ static cyaml_err_t cyaml__data_handle_pointer( * size from the event, plus trailing NULL. */ delta = strlen((const char *) *value_data_io) + 1; break; + case CYAML_BINARY: + field = CYAML_FIELD_OF_VALUE(schema); + delta = cyaml_data_read(field->count_size, + state->data + field->count_offset, + &err); + if (err != CYAML_OK) { + return err; + } + break; case CYAML_SEQUENCE: delta *= state->sequence.count; break; @@ -367,6 +378,44 @@ static cyaml_err_t cyaml__clone_string( return CYAML_OK; } +/** + * Read a value of type \ref CYAML_BINARY. + * + * \param[in] ctx The CYAML copying context. + * \param[in] schema The schema for the value to be copied. + * \param[in] data The place to read the value from in the client data. + * \param[in] copy The place to write the value to in the client data. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__clone_binary( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const uint8_t *data, + uint8_t *copy) +{ + const cyaml_state_t *state = ctx->state; + const cyaml_schema_field_t *field; + cyaml_err_t err; + size_t len; + + assert(schema->type == CYAML_BINARY); + assert(state->state == CYAML_STATE_IN_MAP_KEY); + + field = CYAML_FIELD_OF_VALUE(schema); + len = cyaml_data_read(field->count_size, + state->data + field->count_offset, &err); + if (err != CYAML_OK) { + return err; + } + + memcpy(copy, data, len); + + memcpy(state->copy + field->count_offset, + state->data + field->count_offset, field->count_size); + + return CYAML_OK; +} + /** * Write a sequence entry count to the client data structure. * @@ -421,6 +470,12 @@ static cyaml_err_t cyaml__clone_value( cyaml__type_to_str(schema->type), schema->flags & CYAML_FLAG_POINTER ? " (pointer)" : ""); + if (schema->type == CYAML_BINARY) { + if (ctx->state->state != CYAML_STATE_IN_MAP_KEY) { + return CYAML_ERR_MAPPING_REQUIRED; + } + } + if (!cyaml__is_sequence(schema)) { /* Since sequences extend their allocation for each entry, * they're handled in the sequence-specific code. @@ -444,6 +499,9 @@ static cyaml_err_t cyaml__clone_value( case CYAML_STRING: err = cyaml__clone_string(ctx, schema, data, copy); break; + case CYAML_BINARY: + err = cyaml__clone_binary(ctx, schema, data, copy); + break; case CYAML_MAPPING: err = cyaml__stack_push(ctx, CYAML_STATE_IN_MAP_KEY, schema, data, copy); diff --git a/src/free.c b/src/free.c index 266cd7b..f5d2248 100644 --- a/src/free.c +++ b/src/free.c @@ -62,6 +62,12 @@ static void cyaml__free_sequence( const cyaml_schema_value_t *schema = sequence_schema->sequence.entry; uint32_t data_size = schema->data_size; + if (schema->type == CYAML_BINARY) { + cyaml__log(cfg, CYAML_LOG_ERROR, + "Free: Invalid schema; binary outside mapping\n"); + return; + } + cyaml__log(cfg, CYAML_LOG_DEBUG, "Free: Freeing sequence with count: %u\n", count); diff --git a/src/load.c b/src/load.c index b0c8483..36f7570 100644 --- a/src/load.c +++ b/src/load.c @@ -26,6 +26,7 @@ #include "mem.h" #include "data.h" #include "util.h" +#include "base64.h" /** Identifies that no mapping schema entry was found for key. */ #define CYAML_FIELDS_IDX_NONE 0xffff @@ -496,6 +497,72 @@ static cyaml_err_t cyaml__store_string( return CYAML_OK; } +/** + * Store a binary value to client data structure according to schema. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be stored. + * \param[in] location The place to write the value in the output data. + * \param[in] decoded The value to store. + * \param[in] decoded_len The length of the value to store. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__store_binary( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + uint8_t *location, + const uint8_t *decoded, + size_t decoded_len, + const char *encoded, + size_t encoded_len) +{ + cyaml_validate_binary_fn_t validate_cb = schema->binary.validation_cb; + const cyaml_state_t *state = ctx->state; + const cyaml_schema_field_t *field; + cyaml_err_t err; + + assert(schema->type == CYAML_BINARY); + assert(state->state == CYAML_STATE_IN_MAP_KEY); + assert(decoded == NULL || encoded == NULL); + assert(decoded != NULL || encoded != NULL); + + if (schema->binary.min > schema->binary.max) { + return CYAML_ERR_BAD_MIN_MAX_SCHEMA; + } else if (decoded_len < schema->binary.min) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Load: BINARY length < %"PRIu32"\n", + schema->binary.min); + return CYAML_ERR_BASE64_MAX_LEN; + } else if (decoded_len > schema->binary.max) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Load: BINARY length > %"PRIu32"\n", + schema->binary.max); + return CYAML_ERR_BASE64_MAX_LEN; + } + + if (encoded != NULL) { + cyaml_base64_decode(encoded, encoded_len, location); + } else { + memcpy(location, decoded, decoded_len); + } + + if (validate_cb != NULL) { + if (!validate_cb(ctx->config->validation_ctx, schema, + location, decoded_len)) { + return CYAML_ERR_INVALID_VALUE; + } + } + + field = CYAML_FIELD_OF_VALUE(schema); + err = cyaml_data_write(decoded_len, field->count_size, + state->data + field->count_offset); + if (err != CYAML_OK) { + return err; + } + + return CYAML_OK; +} + /** * Get the CYAML event type from a `libyaml` event. * @@ -1348,6 +1415,9 @@ static cyaml_err_t cyaml__field_scalar_apply_default( case CYAML_STRING: size = strlen(schema->string.missing) + 1; break; + case CYAML_BINARY: + size = schema->binary.missing_len; + break; default: size = schema->data_size; break; @@ -1392,6 +1462,11 @@ static cyaml_err_t cyaml__field_scalar_apply_default( case CYAML_STRING: return cyaml__store_string(ctx, schema, data, schema->string.missing); + case CYAML_BINARY: + return cyaml__store_binary(ctx, schema, data, + schema->binary.missing, + schema->binary.missing_len, + NULL, 0); default: return CYAML_ERR_INTERNAL_ERROR; } @@ -1424,6 +1499,7 @@ static cyaml_err_t cyaml__field_apply_default( case CYAML_FLAGS: /* Fall through */ case CYAML_FLOAT: /* Fall through */ case CYAML_STRING: /* Fall through */ + case CYAML_BINARY: /* Fall through */ case CYAML_BITFIELD: return cyaml__field_scalar_apply_default(ctx, field, data); case CYAML_MAPPING: @@ -1676,6 +1752,7 @@ static cyaml_err_t cyaml__validate_event_type_for_schema( [CYAML_ENUM] = CYAML_EVT_SCALAR, [CYAML_FLOAT] = CYAML_EVT_SCALAR, [CYAML_STRING] = CYAML_EVT_SCALAR, + [CYAML_BINARY] = CYAML_EVT_SCALAR, [CYAML_FLAGS] = CYAML_EVT_SEQ_START, [CYAML_MAPPING] = CYAML_EVT_MAP_START, [CYAML_BITFIELD] = CYAML_EVT_MAP_START, @@ -1727,6 +1804,7 @@ static cyaml_err_t cyaml__data_handle_pointer( uint8_t **value_data_io) { cyaml_state_t *state = ctx->state; + cyaml_err_t err; if (schema->flags & CYAML_FLAG_POINTER) { /* Need to create/extend an allocation. */ @@ -1742,6 +1820,22 @@ static cyaml_err_t cyaml__data_handle_pointer( delta = strlen((const char *) event->data.scalar.value) + 1; break; + case CYAML_BINARY: + /* For binary data, we ask the decoder how big an + * allocation it will need for the data. */ + err = cyaml_base64_calc_decoded_size( + (const char *) event->data.scalar.value, + strlen((const char *) + event->data.scalar.value), + &delta); + if (err != CYAML_OK) { + return err; + } + if (delta < schema->binary.min || + delta > schema->binary.max) { + return CYAML_ERR_BASE64_MAX_LEN; + } + break; case CYAML_SEQUENCE: /* Sequence; could be extending allocation. */ offset = data_size * state->sequence.count; @@ -2115,6 +2209,42 @@ static cyaml_err_t cyaml__read_scalar_value( return fn[schema->type](ctx, schema, value, data); } +/** + * Read a value of type \ref CYAML_BINARY. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be read. + * \param[in] event The `libyaml` event providing the scalar value data. + * \param[in] data The place to write the value in the output data. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__read_binary_value( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const yaml_event_t *event, + cyaml_data_t *data) +{ + const char *value = (const char *)event->data.scalar.value; + size_t value_len = strlen(value); + size_t decoded_size; + cyaml_err_t err; + + assert(ctx->state->state == CYAML_STATE_IN_MAP_KEY); + + if (schema->data_size != 1) { + return CYAML_ERR_INVALID_DATA_SIZE; + } + + err = cyaml_base64_calc_decoded_size(value, value_len, &decoded_size); + if (err != CYAML_OK) { + return err; + } + + return cyaml__store_binary(ctx, schema, data, + NULL, decoded_size, + value, value_len); +} + /** * Set a flag in a \ref CYAML_FLAGS value. * @@ -2486,6 +2616,12 @@ static cyaml_err_t cyaml__read_value( cyaml__type_to_str(schema->type), schema->flags & CYAML_FLAG_POINTER ? " (pointer)" : ""); + if (schema->type == CYAML_BINARY) { + if (ctx->state->state != CYAML_STATE_IN_MAP_KEY) { + return CYAML_ERR_MAPPING_REQUIRED; + } + } + if (cyaml_event == CYAML_EVT_SCALAR) { if (cyaml__string_is_null_ptr(schema, (const char *)event->data.scalar.value)) { @@ -2534,6 +2670,9 @@ static cyaml_err_t cyaml__read_value( err = cyaml__stack_push(ctx, CYAML_STATE_IN_SEQUENCE, event, schema, data); break; + case CYAML_BINARY: + err = cyaml__read_binary_value(ctx, schema, event, data); + break; case CYAML_IGNORE: err = cyaml__consume_ignored_value(ctx, cyaml_event); break; diff --git a/src/save.c b/src/save.c index 6fbcfe3..a5c909c 100644 --- a/src/save.c +++ b/src/save.c @@ -22,6 +22,7 @@ #include "mem.h" #include "data.h" #include "util.h" +#include "base64.h" /** * A CYAML save state machine stack entry. @@ -798,6 +799,52 @@ static cyaml_err_t cyaml__write_scalar_value( return fn[schema->type](ctx, schema, data); } +/** + * Write a binary value. + * + * \param[in] ctx The CYAML saving context. + * \param[in] schema The schema for the value to be written. + * \param[in] data The place to read the value from in the client data. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__write_binary_value( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const cyaml_data_t *data) +{ + const cyaml_state_t *state = ctx->state; + const cyaml_schema_field_t *field; + cyaml_err_t err; + size_t data_len; + size_t str_len; + char *str; + + assert(schema->type == CYAML_BINARY); + assert(state->state == CYAML_STATE_IN_MAP_KEY); + + field = CYAML_FIELD_OF_VALUE(schema); + data_len = cyaml_data_read(field->count_size, + state->data + field->count_offset, &err); + if (err != CYAML_OK) { + return err; + } + + str_len = cyaml_base64_calc_encoded_size(data_len); + str = cyaml__alloc(ctx->config, str_len + 1, false); + if (str == NULL) { + return CYAML_ERR_OOM; + } + + cyaml_base64_encode(data, data_len, str); + str[str_len] = '\0'; + + err = cyaml__emit_scalar(ctx, schema, str, "tag:yaml.org,2002:binary"); + /* Whether err is an error or not, we have to free str and return err */ + + cyaml__free(ctx->config, str); + return err; +} + /** * Emit a sequence of flag values. * @@ -991,6 +1038,12 @@ static cyaml_err_t cyaml__write_value( cyaml__type_to_str(schema->type), schema->flags & CYAML_FLAG_POINTER ? " (pointer)" : ""); + if (schema->type == CYAML_BINARY) { + if (ctx->state->state != CYAML_STATE_IN_MAP_KEY) { + return CYAML_ERR_MAPPING_REQUIRED; + } + } + data = cyaml_data_save_handle_pointer(ctx->config, schema, data, "Save"); @@ -1017,6 +1070,9 @@ static cyaml_err_t cyaml__write_value( case CYAML_STRING: err = cyaml__write_scalar_value(ctx, schema, data); break; + case CYAML_BINARY: + err = cyaml__write_binary_value(ctx, schema, data); + break; case CYAML_FLAGS: err = cyaml__write_flags_value(ctx, schema, data); break; diff --git a/src/util.c b/src/util.c index 6ba6129..3a98f96 100644 --- a/src/util.c +++ b/src/util.c @@ -90,6 +90,9 @@ const char * cyaml_strerror( [CYAML_ERR_INVALID_VALUE] = "Invalid value", [CYAML_ERR_INVALID_ALIAS] = "No anchor found for alias", [CYAML_ERR_INTERNAL_ERROR] = "Internal error", + [CYAML_ERR_INVALID_BASE64] = "Invalid Base64 string", + [CYAML_ERR_BASE64_MAX_LEN] = "Too much base64 data", + [CYAML_ERR_MAPPING_REQUIRED] = "Value type requires parent mapping", [CYAML_ERR_UNEXPECTED_EVENT] = "Unexpected event", [CYAML_ERR_STRING_LENGTH_MIN] = "String length too short", [CYAML_ERR_STRING_LENGTH_MAX] = "String length too long", diff --git a/src/util.h b/src/util.h index 9bd777f..a7854d6 100644 --- a/src/util.h +++ b/src/util.h @@ -117,6 +117,7 @@ static inline const char * cyaml__type_to_str(cyaml_type_e type) [CYAML_FLAGS] = "FLAGS", [CYAML_FLOAT] = "FLOAT", [CYAML_STRING] = "STRING", + [CYAML_BINARY] = "BINARY", [CYAML_MAPPING] = "MAPPING", [CYAML_BITFIELD] = "BITFIELD", [CYAML_SEQUENCE] = "SEQUENCE", From 26ceab238890a4b4ef5c741b2a1cb45a5360a955 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sat, 20 Jul 2024 16:32:50 +0100 Subject: [PATCH 04/11] Test: Add Base64 tests --- Makefile | 2 +- test/units/base64.c | 304 ++++++++++++++++++++++++++++++++++++++++++++ test/units/test.c | 1 + test/units/test.h | 6 + 4 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 test/units/base64.c diff --git a/Makefile b/Makefile index e470ba4..8aee0ff 100644 --- a/Makefile +++ b/Makefile @@ -117,7 +117,7 @@ endif TEST_SRC_FILES = units/free.c units/load.c units/test.c units/util.c \ units/errs.c units/file.c units/save.c units/copy.c \ - units/utf8.c + units/utf8.c units/base64.c TEST_SRC := $(addprefix test/,$(TEST_SRC_FILES)) TEST_OBJ = $(patsubst %.c,%.o, $(addprefix $(BUILDDIR)/,$(TEST_SRC))) TEST_DEP = $(patsubst %.c,%.d, $(addprefix $(BUILDDIR)/,$(TEST_SRC))) diff --git a/test/units/base64.c b/test/units/base64.c new file mode 100644 index 0000000..296474c --- /dev/null +++ b/test/units/base64.c @@ -0,0 +1,304 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2018-2021 Michael Drake + */ + +#include +#include +#include + +#include + +#include "../../src/base64.h" + +#include "ttest.h" +#include "test.h" + +/** Helper macro to squash unused variable warnings. */ +#define UNUSED(_x) ((void)(_x)) + +/** Helper macro to get the length of string string literals. */ +#define SLEN(_s) (CYAML_ARRAY_LEN(_s) - 1) + +#define STRING_PAIRS(_dec, _enc) \ + { \ + .dec = _dec, \ + .dec_len = SLEN(_dec), \ + .enc = _enc, \ + .enc_len = SLEN(_enc), \ + } + +static const struct string_pairs { + const char *enc; + size_t enc_len; + const char *dec; + size_t dec_len; +} data[] = { + STRING_PAIRS("😸" , "8J+YuA==" ), + STRING_PAIRS("Cat" , "Q2F0" ), + STRING_PAIRS("Cats" , "Q2F0cw==" ), + STRING_PAIRS("Kitty" , "S2l0dHk=" ), + STRING_PAIRS("Kitten" , "S2l0dGVu" ), + STRING_PAIRS("Kitties" , "S2l0dGllcw==" ), + STRING_PAIRS("Kittens" , "S2l0dGVucw==" ), + STRING_PAIRS("Kitty cat" , "S2l0dHkgY2F0" ), + STRING_PAIRS("Kitty cats", "S2l0dHkgY2F0cw=="), +}; + +/** + * Test base64 encoding. + * + * \param[in] report The test report context. + * \return true if test passes, false otherwise. + */ +static bool test_base64_encode( + ttest_report_ctx_t *report) +{ + bool pass = true; + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(data); i++) { + size_t enc_len; + ttest_ctx_t tc; + char name[sizeof(__func__) + 64]; + char encoded[64]; + + sprintf(name, "%s_%u", __func__, i); + if (!ttest_start(report, name, NULL, NULL, &tc)) { + continue; + } + + enc_len = cyaml_base64_calc_encoded_size(data[i].dec_len); + if (enc_len != data[i].enc_len) { + pass &= ttest_fail(&tc, "Incorrect encoded size: " + "Got %zu, expected %zu", + enc_len, data[i].enc_len); + continue; + } + + cyaml_base64_encode((const uint8_t *)data[i].dec, + data[i].dec_len, encoded); + encoded[enc_len] = '\0'; + if (memcmp(encoded, data[i].enc, enc_len) != 0) { + pass &= ttest_fail(&tc, "Wrong encoded data:\n" + "\t Got: %s\n" + "\tExpected: %s", + encoded, data[i].enc); + continue; + } + + pass &= ttest_pass(&tc); + } + + return pass; +} + +/** + * Test base64 decoding. + * + * \param[in] report The test report context. + * \return true if test passes, false otherwise. + */ +static bool test_base64_decode( + ttest_report_ctx_t *report) +{ + bool pass = true; + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(data); i++) { + size_t dec_len; + ttest_ctx_t tc; + cyaml_err_t err; + char name[sizeof(__func__) + 64]; + char decoded[64]; + + sprintf(name, "%s_%u", __func__, i); + if (!ttest_start(report, name, NULL, NULL, &tc)) { + continue; + } + + err = cyaml_base64_calc_decoded_size(data[i].enc, + data[i].enc_len, &dec_len); + if (err != CYAML_OK) { + pass &= ttest_fail(&tc, "Failed to calc decoded size: " + "%s", cyaml_strerror(err)); + continue; + } + if (dec_len != data[i].dec_len) { + pass &= ttest_fail(&tc, "Incorrect decoded size: " + "Got %zu, expected %zu", + dec_len, data[i].dec_len); + continue; + } + + cyaml_base64_decode(data[i].enc, data[i].enc_len, + (uint8_t *)decoded); + if (memcmp(decoded, data[i].dec, dec_len) != 0) { + pass &= ttest_fail(&tc, "Wrong decoded data:\n" + "\t Got: %*.*s\n" + "\tExpected: %*.*s", + (int) dec_len, (int) dec_len, decoded, + (int) dec_len, (int) dec_len, data[i].dec); + continue; + } + + pass &= ttest_pass(&tc); + } + + return pass; +} + +/** + * Test base64 decoding oddities. + * + * \param[in] report The test report context. + * \return true if test passes, false otherwise. + */ +static bool test_base64_decode_odd( + ttest_report_ctx_t *report) +{ + bool pass = true; + static const struct odd_data { + const char *name; + const char *enc; + size_t enc_len; + const char *dec; + size_t dec_len; + cyaml_err_t err; + } odd_data[] = { + { + .name = "str_len", + .enc = "C", + .enc_len = 1, + .err = CYAML_ERR_INVALID_BASE64, + }, + { + .name = "bad_char", + .enc = "Q2F0!", + .enc_len = 5, + .dec = "Cat", + .dec_len = 3, + .err = CYAML_OK, + }, + { + .name = "no_padding", + .enc = "8J+YuA", + .enc_len = 6, + .dec = "😸", + .dec_len = 4, + .err = CYAML_OK, + }, + { + .name = "padding_count1", + .enc = "S2l0dHk==", + .enc_len = 9, + .err = CYAML_ERR_INVALID_BASE64, + }, + { + .name = "padding_count2", + .enc = "Q2F0cw=", + .enc_len = 7, + .err = CYAML_ERR_INVALID_BASE64, + }, + { + .name = "padding_count3", + .enc = "Q2F00", + .enc_len = 5, + .err = CYAML_ERR_INVALID_BASE64, + }, + { + .name = "excess_padding", + .enc = "C===", + .enc_len = 4, + .err = CYAML_ERR_INVALID_BASE64, + }, + { + .name = "unnecessary_padding", + .enc = "Q2F0=", + .enc_len = 5, + .err = CYAML_ERR_INVALID_BASE64, + }, + { + .name = "internal_padding", + .enc = "C=at", + .enc_len = 4, + .err = CYAML_ERR_INVALID_BASE64, + }, + }; + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(odd_data); i++) { + size_t dec_len; + ttest_ctx_t tc; + cyaml_err_t err; + char name[sizeof(__func__) + 64]; + char decoded[64]; + + sprintf(name, "%s_%s", __func__, odd_data[i].name); + if (!ttest_start(report, name, NULL, NULL, &tc)) { + continue; + } + + err = cyaml_base64_calc_decoded_size(odd_data[i].enc, + odd_data[i].enc_len, &dec_len); + if (err != odd_data[i].err) { + pass &= ttest_fail(&tc, "Unexpected return value: %s", + cyaml_strerror(err)); + continue; + } + if (err != CYAML_OK) { + pass &= ttest_pass(&tc); + continue; + } + + if (dec_len != odd_data[i].dec_len) { + pass &= ttest_fail(&tc, "Incorrect decoded size: " + "Got %zu, expected %zu", + dec_len, odd_data[i].dec_len); + continue; + } + + cyaml_base64_decode(odd_data[i].enc, odd_data[i].enc_len, + (uint8_t *)decoded); + if (memcmp(decoded, odd_data[i].dec, dec_len) != 0) { + pass &= ttest_fail(&tc, "Wrong decoded data:\n" + "\t Got: %*.*s\n" + "\tExpected: %*.*s", + (int) dec_len, (int) dec_len, decoded, + (int) dec_len, (int) dec_len, odd_data[i].dec); + continue; + } + + pass &= ttest_pass(&tc); + } + + return pass; +} + +/** + * Run the CYAML base64 unit tests. + * + * \param[in] rc The ttest report context. + * \param[in] log_level CYAML log level. + * \param[in] log_fn CYAML logging function, or NULL. + * \return true iff all unit tests pass, otherwise false. + */ +bool base64_tests( + ttest_report_ctx_t *rc, + cyaml_log_t log_level, + cyaml_log_fn_t log_fn) +{ + bool pass = true; + + UNUSED(log_level); + UNUSED(log_fn); + + ttest_heading(rc, "Base64 tests: Encode & decode"); + + pass &= test_base64_encode(rc); + pass &= test_base64_decode(rc); + + ttest_heading(rc, "Base64 tests: Decode errors"); + + pass &= test_base64_decode_odd(rc); + + return pass; +} diff --git a/test/units/test.c b/test/units/test.c index 542e6f8..0780256 100644 --- a/test/units/test.c +++ b/test/units/test.c @@ -71,6 +71,7 @@ int main(int argc, char *argv[]) rc = ttest_init(test_list, quiet); + pass &= base64_tests(&rc, log_level, log_fn); pass &= utf8_tests(&rc, log_level, log_fn); pass &= util_tests(&rc, log_level, log_fn); pass &= free_tests(&rc, log_level, log_fn); diff --git a/test/units/test.h b/test/units/test.h index 642f9cd..56d3c9a 100644 --- a/test/units/test.h +++ b/test/units/test.h @@ -31,6 +31,12 @@ extern bool utf8_tests( cyaml_log_t log_level, cyaml_log_fn_t log_fn); +/** In base64.c */ +extern bool base64_tests( + ttest_report_ctx_t *rc, + cyaml_log_t log_level, + cyaml_log_fn_t log_fn); + /** In util.c */ extern bool util_tests( ttest_report_ctx_t *rc, From 5cf6e6dd5cdb961ff46155002420b7db65db8f8c Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sat, 20 Jul 2024 16:53:40 +0100 Subject: [PATCH 05/11] Test: Load: Add binary type tests --- test/units/load.c | 423 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 423 insertions(+) diff --git a/test/units/load.c b/test/units/load.c index 272be16..b4082c1 100644 --- a/test/units/load.c +++ b/test/units/load.c @@ -689,6 +689,189 @@ static bool test_load_mapping_field_default_string( return ttest_pass(&tc); } +/** + * Test loading a string with a default value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_load_mapping_field_default_binary( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const uint8_t before = 1; + const char *value = "walthazarbobalthazar"; + size_t value_len = strlen(value); + const uint8_t after = 0xff; + static const unsigned char yaml[] = + "before: 1\n" + "after: 0xff\n"; + struct target_struct { + uint8_t before; + char data[64]; + size_t data_len; + uint8_t after; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("before", CYAML_FLAG_DEFAULT, + struct target_struct, before), + CYAML_FIELD_PTR(BINARY, "test_data", CYAML_FLAG_OPTIONAL, + struct target_struct, data, + { + .min = 0, + .max = sizeof(data_tgt->data), + .missing = "walthazarbobalthazar", + .missing_len = YAML_LEN("walthazarbobalthazar"), + }), + CYAML_FIELD_UINT("after", CYAML_FLAG_DEFAULT, + struct target_struct, after), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt->before != before) { + return ttest_fail(&tc, "Incorrect value before default"); + } + + if (value_len != data_tgt->data_len) { + return ttest_fail(&tc, "Incorrect length"); + } + + if (memcmp(data_tgt->data, value, value_len) != 0) { + fprintf(stderr, "expected:\n"); + for (unsigned i = 0; i < strlen(value) + 1; i++) { + fprintf(stderr, "%2.2x ", value[i]); + } + fprintf(stderr, "\n"); + fprintf(stderr, " got:\n"); + for (unsigned i = 0; i < sizeof(data_tgt->data); i++) { + fprintf(stderr, "%2.2x ", value[i]); + } + fprintf(stderr, "\n"); + return ttest_fail(&tc, "Incorrect value"); + } + + if (data_tgt->after != after) { + return ttest_fail(&tc, "Incorrect value after default"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a string with a default value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_load_mapping_field_default_binary_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const uint8_t before = 1; + const char *value = "walthazarbobalthazar"; + const size_t value_len = strlen(value); + const uint8_t after = 0xff; + static const unsigned char yaml[] = + "before: 1\n" + "after: 0xff\n"; + struct target_struct { + uint8_t before; + char *data; + size_t data_len; + uint8_t after; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("before", CYAML_FLAG_DEFAULT, + struct target_struct, before), + CYAML_FIELD_PTR(BINARY, "test_data", + CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL, + struct target_struct, data, + { + .min = 0, + .max = CYAML_UNLIMITED, + .missing = "walthazarbobalthazar", + .missing_len = YAML_LEN("walthazarbobalthazar"), + }), + CYAML_FIELD_UINT("after", CYAML_FLAG_DEFAULT, + struct target_struct, after), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt->before != before) { + return ttest_fail(&tc, "Incorrect value before default"); + } + + if (value_len != data_tgt->data_len) { + return ttest_fail(&tc, "Incorrect length: " + "expected %zu, got %zu", + value_len, data_tgt->data_len); + } + + if (memcmp(data_tgt->data, value, value_len) != 0) { + fprintf(stderr, "expected:\n"); + for (unsigned i = 0; i < strlen(value) + 1; i++) { + fprintf(stderr, "%2.2x ", value[i]); + } + fprintf(stderr, "\n"); + fprintf(stderr, " got:\n"); + for (unsigned i = 0; i < sizeof(data_tgt->data); i++) { + fprintf(stderr, "%2.2x ", value[i]); + } + fprintf(stderr, "\n"); + return ttest_fail(&tc, "Incorrect value"); + } + + if (data_tgt->after != after) { + return ttest_fail(&tc, "Incorrect value after default"); + } + + return ttest_pass(&tc); +} + /** * Test loading a bitfield with a default value. * @@ -4035,6 +4218,142 @@ static bool test_load_mapping_entry_string( return ttest_pass(&tc); } +/** + * Test loading a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_load_mapping_entry_binary( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *value = "walthazarbobalthazar"; + size_t value_len = strlen(value); + static const unsigned char yaml[] = + "test_data: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n"; + struct target_struct { + char data[64]; + size_t data_len; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BINARY("test_data", CYAML_FLAG_DEFAULT, + struct target_struct, data, + 0, sizeof(data_tgt->data)), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (value_len != data_tgt->data_len) { + return ttest_fail(&tc, "Incorrect length"); + } + + if (memcmp(data_tgt->data, value, value_len) != 0) { + fprintf(stderr, "expected:\n"); + for (unsigned i = 0; i < strlen(value) + 1; i++) { + fprintf(stderr, "%2.2x ", value[i]); + } + fprintf(stderr, "\n"); + fprintf(stderr, " got:\n"); + for (unsigned i = 0; i < sizeof(data_tgt->data); i++) { + fprintf(stderr, "%2.2x ", value[i]); + } + fprintf(stderr, "\n"); + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_load_mapping_entry_binary_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *value = "walthazarbobalthazar"; + size_t value_len = strlen(value); + static const unsigned char yaml[] = + "test_data: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n"; + struct target_struct { + uint8_t *data; + size_t data_len; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BINARY("test_data", CYAML_FLAG_POINTER, + struct target_struct, data, + 0, CYAML_UNLIMITED), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (value_len != data_tgt->data_len) { + return ttest_fail(&tc, "Incorrect length"); + } + + if (memcmp(data_tgt->data, value, value_len) != 0) { + fprintf(stderr, "expected:\n"); + for (unsigned i = 0; i < strlen(value) + 1; i++) { + fprintf(stderr, "%2.2x ", value[i]); + } + fprintf(stderr, "\n"); + fprintf(stderr, " got:\n"); + for (unsigned i = 0; i < sizeof(data_tgt->data); i++) { + fprintf(stderr, "%2.2x ", value[i]); + } + fprintf(stderr, "\n"); + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + /** * Test loading a string to an allocated char pointer. * @@ -11439,6 +11758,105 @@ static bool test_load_mapping_field_validate_string( return ttest_pass(&tc); } +/** + * Binary validation callback. + * + * \param[in] ctx Client's private validation context. + * \param[in] schema The schema for the value. + * \param[in] value The value to be validated. + * \param[in] len Number bytes in value. + * \return `true` if values is valid, `false` otherwise. + */ +static bool test__binary_is_valid( + void *ctx, + const cyaml_schema_value_t *schema, + const uint8_t *value, + size_t len) +{ + UNUSED(ctx); + UNUSED(schema); + + if (value != NULL && len > 3) { + if (memcmp(value, "wal", 3) == 0) { + return true; + } + } + + return false; +} + +/** + * Test loading a binary value with a validation callback. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_load_mapping_field_validate_binary( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *value = "walthazarbobalthazar"; + size_t value_len = strlen(value); + static const unsigned char yaml[] = + "test_data: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n"; + struct target_struct { + char data[64]; + size_t data_len; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_PTR(BINARY, "test_data", CYAML_FLAG_OPTIONAL, + struct target_struct, data, + { + .min = 0, + .max = sizeof(data_tgt->data), + .validation_cb = test__binary_is_valid, + }), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (value_len != data_tgt->data_len) { + return ttest_fail(&tc, "Incorrect length"); + } + + if (memcmp(data_tgt->data, value, value_len) != 0) { + fprintf(stderr, "expected:\n"); + for (unsigned i = 0; i < strlen(value) + 1; i++) { + fprintf(stderr, "%2.2x ", value[i]); + } + fprintf(stderr, "\n"); + fprintf(stderr, " got:\n"); + for (unsigned i = 0; i < sizeof(data_tgt->data); i++) { + fprintf(stderr, "%2.2x ", value[i]); + } + fprintf(stderr, "\n"); + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + /** * Test loading a mapping with a validation callback. * @@ -11687,6 +12105,7 @@ bool load_tests( pass &= test_load_mapping_entry_float(rc, &config); pass &= test_load_mapping_entry_double(rc, &config); pass &= test_load_mapping_entry_string(rc, &config); + pass &= test_load_mapping_entry_binary(rc, &config); pass &= test_load_mapping_entry_int_pos(rc, &config); pass &= test_load_mapping_entry_int_neg(rc, &config); pass &= test_load_mapping_entry_enum_ptr(rc, &config); @@ -11696,6 +12115,7 @@ bool load_tests( pass &= test_load_mapping_entry_bool_false(rc, &config); pass &= test_load_mapping_entry_double_ptr(rc, &config); pass &= test_load_mapping_entry_string_ptr(rc, &config); + pass &= test_load_mapping_entry_binary_ptr(rc, &config); pass &= test_load_mapping_entry_int_pos_ptr(rc, &config); pass &= test_load_mapping_entry_int_neg_ptr(rc, &config); pass &= test_load_mapping_entry_enum_sparse(rc, &config); @@ -11836,6 +12256,7 @@ bool load_tests( pass &= test_load_mapping_field_default_float(rc, &config); pass &= test_load_mapping_field_default_double(rc, &config); pass &= test_load_mapping_field_default_string(rc, &config); + pass &= test_load_mapping_field_default_binary(rc, &config); pass &= test_load_mapping_field_default_bitfield(rc, &config); pass &= test_load_mapping_field_default_mapping_large(rc, &config); pass &= test_load_mapping_field_default_mapping_small(rc, &config); @@ -11852,6 +12273,7 @@ bool load_tests( pass &= test_load_mapping_field_default_float_ptr(rc, &config); pass &= test_load_mapping_field_default_double_ptr(rc, &config); pass &= test_load_mapping_field_default_string_ptr(rc, &config); + pass &= test_load_mapping_field_default_binary_ptr(rc, &config); pass &= test_load_mapping_field_default_bitfield_ptr(rc, &config); pass &= test_load_mapping_field_default_mapping_large_ptr(rc, &config); pass &= test_load_mapping_field_default_mapping_small_ptr(rc, &config); @@ -11888,6 +12310,7 @@ bool load_tests( pass &= test_load_mapping_field_validate_float(rc, &config); pass &= test_load_mapping_field_validate_double(rc, &config); pass &= test_load_mapping_field_validate_string(rc, &config); + pass &= test_load_mapping_field_validate_binary(rc, &config); pass &= test_load_mapping_field_validate_mapping(rc, &config); pass &= test_load_mapping_field_validate_bitfield(rc, &config); pass &= test_load_mapping_field_validate_sequence(rc, &config); From 5b9db59c699383bfb3124587a45e8ecae78cd49d Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sat, 20 Jul 2024 16:53:57 +0100 Subject: [PATCH 06/11] Test: Save: Add binary type tests --- test/units/save.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/units/save.c b/test/units/save.c index 158deb9..a14cf55 100644 --- a/test/units/save.c +++ b/test/units/save.c @@ -279,6 +279,68 @@ static bool test_save_mapping_entry_string( return ttest_pass(&tc); } +/** + * Test saving a binary. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_save_mapping_entry_binary( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "test_data: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n" + "...\n"; + static const struct target_struct { + char data[64]; + size_t data_len; + } data = { + .data = "walthazarbobalthazar", + .data_len = 20, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BINARY("test_data", CYAML_FLAG_DEFAULT, + struct target_struct, data, + 0, sizeof(data.data)), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer; + size_t len; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_save_data(&buffer, &len, config, &top_schema, + &data, 0); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (len != YAML_LEN(ref) || memcmp(ref, buffer, len) != 0) { + return ttest_fail(&tc, "Bad data:\n" + "EXPECTED (%zu):\n\n%.*s\n\n" + "GOT (%zu):\n\n%.*s\n", + YAML_LEN(ref), YAML_LEN(ref), ref, + len, len, buffer); + } + + return ttest_pass(&tc); +} + /** * Test saving a positive signed integer. * @@ -4447,6 +4509,7 @@ bool save_tests( pass &= test_save_mapping_entry_float(rc, &config); pass &= test_save_mapping_entry_double(rc, &config); pass &= test_save_mapping_entry_string(rc, &config); + pass &= test_save_mapping_entry_binary(rc, &config); pass &= test_save_mapping_entry_int_64(rc, &config); pass &= test_save_mapping_entry_int_pos(rc, &config); pass &= test_save_mapping_entry_int_neg(rc, &config); From 7055af3636e47b82ba2402a58d2db0165459b472 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sat, 20 Jul 2024 16:54:13 +0100 Subject: [PATCH 07/11] Test: Copy: Add binary type tests --- test/units/copy.c | 288 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) diff --git a/test/units/copy.c b/test/units/copy.c index 0d86b53..9c73d42 100644 --- a/test/units/copy.c +++ b/test/units/copy.c @@ -993,6 +993,149 @@ static bool test_copy_mapping_entry_string( return ttest_pass(&tc); } +/** + * Test copying a binary to a character array. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_copy_mapping_entry_binary( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + uint8_t before = 1; + uint8_t after = 0xff; + const char *value = "walthazarbobalthazar"; + static const unsigned char yaml[] = + "before: 1\n" + "test_data: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n" + "after: 0xff\n"; + struct target_struct { + uint8_t before; + char data[64]; + size_t data_len; + uint8_t after; + } *data_tgt = NULL; + struct target_struct *data_cpy = NULL; + struct target_struct data_cpy2 = { 0 }; + struct target_struct *data_cpy2_ptr = &data_cpy2; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("before", CYAML_FLAG_DEFAULT, + struct target_struct, before), + CYAML_FIELD_BINARY("test_data", CYAML_FLAG_DEFAULT, + struct target_struct, data, + 0, sizeof(data_tgt->data)), + CYAML_FIELD_UINT("after", CYAML_FLAG_DEFAULT, + struct target_struct, after), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + static const struct cyaml_schema_value top_schema2 = { + CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .copy = (cyaml_data_t **) &data_cpy, + .copy2 = (cyaml_data_t *) &data_cpy2, + .config = config, + .schema = &top_schema, + .schema2 = &top_schema2, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt->before != before) { + return ttest_fail(&tc, "Incorrect before value from load"); + } + + if (data_tgt->after != after) { + return ttest_fail(&tc, "Incorrect after value from load"); + } + + err = cyaml_copy(config, &top_schema, + (cyaml_data_t *) data_tgt, 0, + (cyaml_data_t **) &data_cpy); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_cpy->before != before) { + return ttest_fail(&tc, "Incorrect before value from copy"); + } + + if (data_cpy->after != after) { + return ttest_fail(&tc, "Incorrect after value from copy"); + } + + if (strlen(value) != data_cpy->data_len) { + return ttest_fail(&tc, "Incorrect data length"); + } + + if (memcmp(data_cpy->data, value, data_cpy->data_len) != 0) { + fprintf(stderr, "expected: %s\n", value); + for (unsigned i = 0; i < strlen(value) + 1; i++) { + fprintf(stderr, "%2.2x ", value[i]); + } + fprintf(stderr, "\n"); + fprintf(stderr, " got: %s\n", data_cpy->data); + for (unsigned i = 0; i < sizeof(data_cpy->data); i++) { + fprintf(stderr, "%2.2x ", data_cpy->data[i]); + } + fprintf(stderr, "\n"); + return ttest_fail(&tc, "Incorrect value"); + } + + err = cyaml_copy(config, &top_schema2, + (cyaml_data_t *) data_tgt, 0, + (cyaml_data_t **) &data_cpy2_ptr); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_cpy2_ptr->before != before) { + return ttest_fail(&tc, "Incorrect before value from copy"); + } + + if (data_cpy2_ptr->after != after) { + return ttest_fail(&tc, "Incorrect after value from copy"); + } + + if (strlen(value) != data_cpy2_ptr->data_len) { + return ttest_fail(&tc, "Incorrect data length"); + } + + if (memcmp(data_cpy2_ptr->data, value, data_cpy2_ptr->data_len) != 0) { + fprintf(stderr, "expected: %s\n", value); + for (unsigned i = 0; i < strlen(value) + 1; i++) { + fprintf(stderr, "%2.2x ", value[i]); + } + fprintf(stderr, "\n"); + fprintf(stderr, " got: %s\n", data_cpy2_ptr->data); + for (unsigned i = 0; i < sizeof(data_cpy2_ptr->data); i++) { + fprintf(stderr, "%2.2x ", data_cpy2_ptr->data[i]); + } + fprintf(stderr, "\n"); + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + /** * Test copying a string to a allocated char pointer. * @@ -1073,6 +1216,149 @@ static bool test_copy_mapping_entry_string_ptr( return ttest_pass(&tc); } +/** + * Test copying a binary to a character array. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_copy_mapping_entry_binary_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + uint8_t before = 1; + uint8_t after = 0xff; + const char *value = "walthazarbobalthazar"; + static const unsigned char yaml[] = + "before: 1\n" + "test_data: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n" + "after: 0xff\n"; + struct target_struct { + uint8_t before; + char *data; + size_t data_len; + uint8_t after; + } *data_tgt = NULL; + struct target_struct *data_cpy = NULL; + struct target_struct data_cpy2 = { 0 }; + struct target_struct *data_cpy2_ptr = &data_cpy2; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("before", CYAML_FLAG_DEFAULT, + struct target_struct, before), + CYAML_FIELD_BINARY("test_data", CYAML_FLAG_POINTER, + struct target_struct, data, + 0, CYAML_UNLIMITED), + CYAML_FIELD_UINT("after", CYAML_FLAG_DEFAULT, + struct target_struct, after), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + static const struct cyaml_schema_value top_schema2 = { + CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .copy = (cyaml_data_t **) &data_cpy, + .copy2 = (cyaml_data_t *) &data_cpy2, + .config = config, + .schema = &top_schema, + .schema2 = &top_schema2, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt->before != before) { + return ttest_fail(&tc, "Incorrect before value from load"); + } + + if (data_tgt->after != after) { + return ttest_fail(&tc, "Incorrect after value from load"); + } + + err = cyaml_copy(config, &top_schema, + (cyaml_data_t *) data_tgt, 0, + (cyaml_data_t **) &data_cpy); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_cpy->before != before) { + return ttest_fail(&tc, "Incorrect before value from copy"); + } + + if (data_cpy->after != after) { + return ttest_fail(&tc, "Incorrect after value from copy"); + } + + if (strlen(value) != data_cpy->data_len) { + return ttest_fail(&tc, "Incorrect data length"); + } + + if (memcmp(data_cpy->data, value, data_cpy->data_len) != 0) { + fprintf(stderr, "expected: %s\n", value); + for (unsigned i = 0; i < strlen(value) + 1; i++) { + fprintf(stderr, "%2.2x ", value[i]); + } + fprintf(stderr, "\n"); + fprintf(stderr, " got: %s\n", data_cpy->data); + for (unsigned i = 0; i < sizeof(data_cpy->data); i++) { + fprintf(stderr, "%2.2x ", data_cpy->data[i]); + } + fprintf(stderr, "\n"); + return ttest_fail(&tc, "Incorrect value"); + } + + err = cyaml_copy(config, &top_schema2, + (cyaml_data_t *) data_tgt, 0, + (cyaml_data_t **) &data_cpy2_ptr); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_cpy2_ptr->before != before) { + return ttest_fail(&tc, "Incorrect before value from copy"); + } + + if (data_cpy2_ptr->after != after) { + return ttest_fail(&tc, "Incorrect after value from copy"); + } + + if (strlen(value) != data_cpy2_ptr->data_len) { + return ttest_fail(&tc, "Incorrect data length"); + } + + if (memcmp(data_cpy2_ptr->data, value, data_cpy2_ptr->data_len) != 0) { + fprintf(stderr, "expected: %s\n", value); + for (unsigned i = 0; i < strlen(value) + 1; i++) { + fprintf(stderr, "%2.2x ", value[i]); + } + fprintf(stderr, "\n"); + fprintf(stderr, " got: %s\n", data_cpy2_ptr->data); + for (unsigned i = 0; i < sizeof(data_cpy2_ptr->data); i++) { + fprintf(stderr, "%2.2x ", data_cpy2_ptr->data[i]); + } + fprintf(stderr, "\n"); + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + /** * Test copying an ignored value with descendants. * @@ -7987,11 +8273,13 @@ bool copy_tests( pass &= test_copy_mapping_entry_float(rc, &config); pass &= test_copy_mapping_entry_double(rc, &config); pass &= test_copy_mapping_entry_string(rc, &config); + pass &= test_copy_mapping_entry_binary(rc, &config); pass &= test_copy_mapping_entry_int_pos(rc, &config); pass &= test_copy_mapping_entry_int_neg(rc, &config); pass &= test_copy_mapping_entry_bool_true(rc, &config); pass &= test_copy_mapping_entry_bool_false(rc, &config); pass &= test_copy_mapping_entry_string_ptr(rc, &config); + pass &= test_copy_mapping_entry_binary_ptr(rc, &config); pass &= test_copy_mapping_entry_enum_sparse(rc, &config); pass &= test_copy_mapping_entry_enum_strict(rc, &config); pass &= test_copy_mapping_entry_ignore_deep(rc, &config); From 5847aead7316621109bce9ebf6f61e2e2774fde2 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sat, 20 Jul 2024 16:56:27 +0100 Subject: [PATCH 08/11] Test: Errors: Add bad schema tests for binary type --- test/units/errs.c | 624 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 624 insertions(+) diff --git a/test/units/errs.c b/test/units/errs.c index bdbfeb7..ae6ad44 100644 --- a/test/units/errs.c +++ b/test/units/errs.c @@ -1945,6 +1945,213 @@ static bool test_err_load_schema_bad_data_size_10( return ttest_pass(&tc); } +/** + * Test loading with schema with invalid data size for binary. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_bad_data_size_11( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "{key: Cat=}\n"; + struct target_struct { + uint8_t value[64]; + unsigned value_count; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_BINARY, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 4, + .binary = { + .min = 0, + .max = 64, + }, + }, + .data_offset = offsetof(struct target_struct, value), + .count_size = sizeof(unsigned), + .count_offset = offsetof( + struct target_struct, + value_count), + }, + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading with schema with invalid data size for binary. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_bad_data_size_12( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "{key: Cat=}\n"; + struct target_struct { + uint8_t value[64]; + unsigned value_count; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_BINARY, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 1, + .binary = { + .min = 0, + .max = 64, + }, + }, + .data_offset = offsetof(struct target_struct, value), + .count_size = 9, + .count_offset = offsetof( + struct target_struct, + value_count), + }, + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading with schema with invalid data size for binary. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_bad_data_size_13( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "before: 1\n" + "after: 0xff\n"; + struct target_struct { + uint8_t before; + uint8_t value[64]; + unsigned value_count; + uint8_t after; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("before", CYAML_FLAG_DEFAULT, + struct target_struct, before), + { + .key = "key", + .value = { + .type = CYAML_BINARY, + .flags = CYAML_FLAG_OPTIONAL, + .data_size = 1, + .binary = { + .min = 0, + .max = 64, + .missing = "walthazarbobalthazar", + .missing_len = YAML_LEN("walthazarbobalthazar"), + }, + }, + .data_offset = offsetof(struct target_struct, value), + .count_size = 9, + .count_offset = offsetof( + struct target_struct, + value_count), + }, + CYAML_FIELD_UINT("after", CYAML_FLAG_DEFAULT, + struct target_struct, after), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + /** * Test saving with schema with data size (0). * @@ -2455,6 +2662,74 @@ static bool test_err_save_schema_bad_data_size_8( return ttest_pass(&tc); } +/** + * Test saving with schema with data size (0). + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_save_schema_bad_data_size_9( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const struct target_struct { + uint8_t value[64]; + unsigned value_count; + } data = { + .value = { 0x01, 0x02, 0x03, 0x04, 0x05 }, + .value_count = 5, + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_BINARY, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 1, + .binary = { + .min = 0, + .max = 64, + }, + }, + .data_offset = offsetof(struct target_struct, value), + .count_size = 9, + .count_offset = offsetof( + struct target_struct, + value_count), + }, + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_save_data(&buffer, &len, config, &top_schema, + &data, 0); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (buffer != NULL || len != 0) { + return ttest_fail(&tc, "Buffer/len not untouched."); + } + + return ttest_pass(&tc); +} + /** * Test copying with schema with bad sequence count size. * @@ -2530,6 +2805,144 @@ static bool test_err_copy_schema_bad_data_size_1( return ttest_pass(&tc); } +/** + * Test copying with schema with bad binary count size. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_copy_schema_bad_data_size_2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + char data[64]; + uint32_t data_len; + } data = { + .data = "walthazarbobalthazar", + .data_len = strlen("walthazarbobalthazar"), + }; + struct target_struct *copy = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "data", + .value = { + .type = CYAML_BINARY, + .flags = CYAML_FLAG_DEFAULT, + .data_size = sizeof(*(data.data)), + .binary = { + .min = 0, + .max = sizeof(data.data), + }, + }, + .data_offset = offsetof(struct target_struct, data), + .count_offset = offsetof(struct target_struct, data_len), + .count_size = 9, + }, + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .copy = (cyaml_data_t **) ©, + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_copy(config, &top_schema, + (const cyaml_data_t *) &data, 0, + (cyaml_data_t **) ©); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (buffer != NULL || len != 0) { + return ttest_fail(&tc, "Buffer/len not untouched."); + } + + return ttest_pass(&tc); +} + +/** + * Test copying with schema with bad binary pointer count size. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_copy_schema_bad_data_size_3( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + char *data; + uint32_t data_len; + } data = { + .data = (char *)"walthazarbobalthazar", + .data_len = strlen("walthazarbobalthazar"), + }; + struct target_struct *copy = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "data", + .value = { + .type = CYAML_BINARY, + .flags = CYAML_FLAG_POINTER, + .data_size = sizeof(*(data.data)), + .binary = { + .min = 0, + .max = CYAML_UNLIMITED, + }, + }, + .data_offset = offsetof(struct target_struct, data), + .count_offset = offsetof(struct target_struct, data_len), + .count_size = 9, + }, + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .copy = (cyaml_data_t **) ©, + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_copy(config, &top_schema, + (const cyaml_data_t *) &data, 0, + (cyaml_data_t **) ©); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (buffer != NULL || len != 0) { + return ttest_fail(&tc, "Buffer/len not untouched."); + } + + return ttest_pass(&tc); +} + /** * Test loading with schema with sequence fixed with unequal min and max. * @@ -3010,6 +3423,129 @@ static bool test_err_copy_schema_required_mapping_missing( return ttest_pass(&tc); } +/** + * Test copying with schema with a data type without mapping parent. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_copy_schema_required_mapping_missing2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static int values[] = { 10, 20 }; + static const struct target_struct { + int *value; + unsigned value_count; + } val = { + .value = values, + .value_count = CYAML_ARRAY_LEN(values), + }; + static const struct cyaml_schema_value entry_schema = { + .type = CYAML_BINARY, + .flags = CYAML_FLAG_POINTER, + .data_size = 8, + .binary = { + .min = 0, + .max = CYAML_UNLIMITED, + .validation_cb = NULL, + .missing = NULL, + .missing_len = 0, + }, + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, int, + &entry_schema, 0, CYAML_UNLIMITED), + }; + struct target_struct *data_tgt = NULL; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_copy(config, &top_schema, + (const cyaml_data_t *) &val, 2, + (cyaml_data_t **) &data_tgt); + if (err != CYAML_ERR_MAPPING_REQUIRED) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test saving with schema with a data type without mapping parent. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_save_schema_required_mapping_missing( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static int values[] = { 10, 20 }; + static const struct target_struct { + int *value; + unsigned value_count; + } data = { + .value = values, + .value_count = CYAML_ARRAY_LEN(values), + }; + static const struct cyaml_schema_value entry_schema = { + .type = CYAML_BINARY, + .flags = CYAML_FLAG_POINTER, + .data_size = 8, + .binary = { + .min = 0, + .max = CYAML_UNLIMITED, + .validation_cb = NULL, + .missing = NULL, + .missing_len = 0, + }, + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, int, + &entry_schema, 0, CYAML_UNLIMITED), + }; + char *buffer = NULL; + size_t len; + test_data_t td = { + .buffer = &buffer, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_save_data(&buffer, &len, config, &top_schema, &data, 2); + if (err != CYAML_ERR_MAPPING_REQUIRED) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (buffer != NULL) { + return ttest_fail(&tc, "Output non-NULL on error."); + } + + return ttest_pass(&tc); +} + /** * Test loading when schema expects uint, but value is invalid. * @@ -6100,6 +6636,85 @@ static bool test_err_load_schema_validation_cb_string( return ttest_pass(&tc); } +/** + * Binary validation callback. + * + * \param[in] ctx Client's private validation context. + * \param[in] schema The schema for the value. + * \param[in] value The value to be validated. + * \param[in] len Number bytes in value. + * \return `true` if values is valid, `false` otherwise. + */ +static bool test__binary_is_valid( + void *ctx, + const cyaml_schema_value_t *schema, + const uint8_t *value, + size_t len) +{ + UNUSED(ctx); + UNUSED(schema); + + if (value != NULL && len > 3) { + if (memcmp(value, "wal", 3) == 0) { + return true; + } + } + + return false; +} + +/** + * Test loading a binary value with a validation callback. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_validation_cb_binary( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "test_data: S2l0dHk=\n"; + struct target_struct { + char data[64]; + size_t data_len; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_PTR(BINARY, "test_data", CYAML_FLAG_OPTIONAL, + struct target_struct, data, + { + .min = 0, + .max = sizeof(data_tgt->data), + .validation_cb = test__binary_is_valid, + }), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_INVALID_VALUE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + /** * Test loading a mapping with a validation callback. * @@ -9396,8 +10011,14 @@ bool errs_tests( pass &= test_err_save_schema_bad_data_size_6(rc, &config); pass &= test_err_save_schema_bad_data_size_7(rc, &config); pass &= test_err_save_schema_bad_data_size_8(rc, &config); + pass &= test_err_save_schema_bad_data_size_9(rc, &config); pass &= test_err_copy_schema_bad_data_size_1(rc, &config); + pass &= test_err_copy_schema_bad_data_size_2(rc, &config); + pass &= test_err_copy_schema_bad_data_size_3(rc, &config); pass &= test_err_load_schema_bad_data_size_10(rc, &config); + pass &= test_err_load_schema_bad_data_size_11(rc, &config); + pass &= test_err_load_schema_bad_data_size_12(rc, &config); + pass &= test_err_load_schema_bad_data_size_13(rc, &config); pass &= test_err_load_schema_sequence_min_max(rc, &config); pass &= test_err_save_schema_sequence_min_max(rc, &config); pass &= test_err_copy_schema_sequence_min_max(rc, &config); @@ -9406,7 +10027,9 @@ bool errs_tests( pass &= test_err_load_schema_sequence_in_sequence(rc, &config); pass &= test_err_save_schema_sequence_in_sequence(rc, &config); pass &= test_err_copy_schema_sequence_in_sequence(rc, &config); + pass &= test_err_save_schema_required_mapping_missing(rc, &config); pass &= test_err_copy_schema_required_mapping_missing(rc, &config); + pass &= test_err_copy_schema_required_mapping_missing2(rc, &config); ttest_heading(rc, "YAML / schema mismatch: bad values"); @@ -9474,6 +10097,7 @@ bool errs_tests( pass &= test_err_load_schema_validation_cb_float(rc, &config); pass &= test_err_load_schema_validation_cb_double(rc, &config); pass &= test_err_load_schema_validation_cb_string(rc, &config); + pass &= test_err_load_schema_validation_cb_binary(rc, &config); pass &= test_err_load_schema_validation_cb_mapping(rc, &config); pass &= test_err_load_schema_validation_cb_bitfield(rc, &config); pass &= test_err_load_schema_validation_cb_sequence(rc, &config); From 0fdd13428d9adfc870511635093fc986a017e33d Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sat, 20 Jul 2024 16:57:15 +0100 Subject: [PATCH 09/11] Test: Errors: Add invalid Base64 tests --- test/units/errs.c | 741 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 741 insertions(+) diff --git a/test/units/errs.c b/test/units/errs.c index ae6ad44..31494d8 100644 --- a/test/units/errs.c +++ b/test/units/errs.c @@ -4905,6 +4905,731 @@ static bool test_err_load_schema_invalid_value_double_invalid( return ttest_pass(&tc); } +/** + * Test loading a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_invalid_base64_length( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "test_data: C\n"; + struct target_struct { + char data[8]; + size_t data_len; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BINARY("test_data", CYAML_FLAG_DEFAULT, + struct target_struct, data, + 0, sizeof(data_tgt->data)), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_INVALID_BASE64) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_invalid_base64_padding( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "test_data: Cat==\n"; + struct target_struct { + char *data; + size_t data_len; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BINARY("test_data", CYAML_FLAG_POINTER, + struct target_struct, data, + 0, sizeof(data_tgt->data)), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_INVALID_BASE64) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_invalid_base64_size_min( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "test_data: Cat=\n"; + struct target_struct { + char data[8]; + size_t data_len; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BINARY("test_data", CYAML_FLAG_DEFAULT, + struct target_struct, data, + 4, sizeof(data_tgt->data)), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_BASE64_MAX_LEN) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_invalid_base64_size_max( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "test_data: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n"; + struct target_struct { + char data[8]; + size_t data_len; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BINARY("test_data", CYAML_FLAG_DEFAULT, + struct target_struct, data, + 0, sizeof(data_tgt->data)), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_BASE64_MAX_LEN) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_invalid_base64_top_level( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "test_data: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n"; + struct target_struct { + char data[8]; + size_t data_len; + } *data_tgt = NULL; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE(BINARY, CYAML_FLAG_POINTER, uint8_t, { .max = 64}), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_MAPPING_REQUIRED) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test copying a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_copy_schema_invalid_base64_top_level( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + char data[8]; + size_t data_len; + } *data_tgt = NULL; + struct target_struct *data_cpy = NULL; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE(BINARY, CYAML_FLAG_POINTER, uint8_t, { .max = 64}), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_copy(config, &top_schema, + (const cyaml_data_t *) &data_tgt, 0, + (cyaml_data_t **) &data_cpy); + if (err != CYAML_ERR_MAPPING_REQUIRED) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_invalid_base64_ptr_size_min( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "test_data: Cat=\n"; + struct target_struct { + char *data; + size_t data_len; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BINARY("test_data", CYAML_FLAG_POINTER, + struct target_struct, data, + 4, 64), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_BASE64_MAX_LEN) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_invalid_base64_ptr_size_max( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "test_data: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n"; + struct target_struct { + char *data; + size_t data_len; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BINARY("test_data", CYAML_FLAG_POINTER, + struct target_struct, data, + 0, 8), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_BASE64_MAX_LEN) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_invalid_base64_default_size_min( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "before: 1\n" + "after: 0xff\n"; + struct target_struct { + uint8_t before; + char data[8]; + size_t data_len; + uint8_t after; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("before", CYAML_FLAG_DEFAULT, + struct target_struct, before), + { + .key = "key", + .value = { + .type = CYAML_BINARY, + .flags = CYAML_FLAG_OPTIONAL, + .data_size = 1, + .binary = { + .min = 32, + .max = 64, + .missing = "walthazarbobalthazar", + .missing_len = YAML_LEN("walthazarbobalthazar"), + }, + }, + .data_offset = offsetof(struct target_struct, data), + .count_size = 9, + .count_offset = offsetof( + struct target_struct, + data_len), + }, + CYAML_FIELD_UINT("after", CYAML_FLAG_DEFAULT, + struct target_struct, after), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_BASE64_MAX_LEN) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_invalid_base64_default_size_max( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "before: 1\n" + "after: 0xff\n"; + struct target_struct { + uint8_t before; + char data[8]; + size_t data_len; + uint8_t after; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("before", CYAML_FLAG_DEFAULT, + struct target_struct, before), + { + .key = "key", + .value = { + .type = CYAML_BINARY, + .flags = CYAML_FLAG_OPTIONAL, + .data_size = 1, + .binary = { + .min = 0, + .max = 16, + .missing = "walthazarbobalthazar", + .missing_len = YAML_LEN("walthazarbobalthazar"), + }, + }, + .data_offset = offsetof(struct target_struct, data), + .count_size = 9, + .count_offset = offsetof( + struct target_struct, + data_len), + }, + CYAML_FIELD_UINT("after", CYAML_FLAG_DEFAULT, + struct target_struct, after), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_BASE64_MAX_LEN) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_invalid_base64_default_ptr_size_min( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "before: 1\n" + "after: 0xff\n"; + struct target_struct { + uint8_t before; + char *data; + size_t data_len; + uint8_t after; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("before", CYAML_FLAG_DEFAULT, + struct target_struct, before), + { + .key = "key", + .value = { + .type = CYAML_BINARY, + .flags = CYAML_FLAG_OPTIONAL | CYAML_FLAG_POINTER, + .data_size = 1, + .binary = { + .min = 32, + .max = 64, + .missing = "walthazarbobalthazar", + .missing_len = YAML_LEN("walthazarbobalthazar"), + }, + }, + .data_offset = offsetof(struct target_struct, data), + .count_size = 9, + .count_offset = offsetof( + struct target_struct, + data_len), + }, + CYAML_FIELD_UINT("after", CYAML_FLAG_DEFAULT, + struct target_struct, after), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_BASE64_MAX_LEN) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_invalid_base64_default_ptr_size_max( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "before: 1\n" + "after: 0xff\n"; + struct target_struct { + uint8_t before; + char *data; + size_t data_len; + uint8_t after; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("before", CYAML_FLAG_DEFAULT, + struct target_struct, before), + { + .key = "key", + .value = { + .type = CYAML_BINARY, + .flags = CYAML_FLAG_OPTIONAL | CYAML_FLAG_POINTER, + .data_size = 1, + .binary = { + .min = 0, + .max = 16, + .missing = "walthazarbobalthazar", + .missing_len = YAML_LEN("walthazarbobalthazar"), + }, + }, + .data_offset = offsetof(struct target_struct, data), + .count_size = 9, + .count_offset = offsetof( + struct target_struct, + data_len), + }, + CYAML_FIELD_UINT("after", CYAML_FLAG_DEFAULT, + struct target_struct, after), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_BASE64_MAX_LEN) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a binary value. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_load_schema_invalid_base64_default_min_max( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "before: 1\n" + "after: 0xff\n"; + struct target_struct { + uint8_t before; + char *data; + size_t data_len; + uint8_t after; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("before", CYAML_FLAG_DEFAULT, + struct target_struct, before), + { + .key = "key", + .value = { + .type = CYAML_BINARY, + .flags = CYAML_FLAG_OPTIONAL | CYAML_FLAG_POINTER, + .data_size = 1, + .binary = { + .min = 32, + .max = 16, + .missing = "walthazarbobalthazar", + .missing_len = YAML_LEN("walthazarbobalthazar"), + }, + }, + .data_offset = offsetof(struct target_struct, data), + .count_size = 9, + .count_offset = offsetof( + struct target_struct, + data_len), + }, + CYAML_FIELD_UINT("after", CYAML_FLAG_DEFAULT, + struct target_struct, after), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + ttest_ctx_t tc; + + if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) { + return true; + } + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_BAD_MIN_MAX_SCHEMA) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + /** * Test loading when schema expects int but value has trailing junk. * @@ -10070,6 +10795,22 @@ bool errs_tests( pass &= test_err_load_schema_invalid_value_float_invalid(rc, &config); pass &= test_err_load_schema_invalid_value_double_invalid(rc, &config); + ttest_heading(rc, "YAML / schema mismatch: bad base64"); + + pass &= test_err_load_schema_invalid_base64_length(rc, &config); + pass &= test_err_load_schema_invalid_base64_padding(rc, &config); + pass &= test_err_load_schema_invalid_base64_size_min(rc, &config); + pass &= test_err_load_schema_invalid_base64_size_max(rc, &config); + pass &= test_err_load_schema_invalid_base64_top_level(rc, &config); + pass &= test_err_copy_schema_invalid_base64_top_level(rc, &config); + pass &= test_err_load_schema_invalid_base64_ptr_size_min(rc, &config); + pass &= test_err_load_schema_invalid_base64_ptr_size_max(rc, &config); + pass &= test_err_load_schema_invalid_base64_default_min_max(rc, &config); + pass &= test_err_load_schema_invalid_base64_default_size_min(rc, &config); + pass &= test_err_load_schema_invalid_base64_default_size_max(rc, &config); + pass &= test_err_load_schema_invalid_base64_default_ptr_size_min(rc, &config); + pass &= test_err_load_schema_invalid_base64_default_ptr_size_max(rc, &config); + ttest_heading(rc, "YAML / schema mismatch: Test integer limits"); pass &= test_err_load_schema_invalid_value_int8_limit_neg(rc, &config); From 63ed319dd494845c005cbd3e74afcac15d79b3d9 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sat, 20 Jul 2024 20:38:43 +0100 Subject: [PATCH 10/11] Test: Errors: Test OOM paths for binary type --- test/units/errs.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/units/errs.c b/test/units/errs.c index 31494d8..75f2ec4 100644 --- a/test/units/errs.c +++ b/test/units/errs.c @@ -9037,6 +9037,7 @@ static bool test_err_load_alloc_oom_2( " - &a1 {" " kind: cat,\n" " sound: meow,\n" + " bin: d2FsdGhhemFyYm9iYWx0aGF6YXI=,\n" " position: &a2 [ 1, &my_value 2, 1],\n" " flags: &a3 [\n" " first,\n" @@ -9047,6 +9048,7 @@ static bool test_err_load_alloc_oom_2( " }\n" " - kind: snake\n" " sound: &a5 hiss\n" + " bin: &a8 d2FsdGhhemFyYm9iYWx0aGF6YXI=\n" " position: &a6 [ 3, 1, 0]\n" " flags: &a7 [\n" " first,\n" @@ -9058,6 +9060,7 @@ static bool test_err_load_alloc_oom_2( " - *a1\n" " - kind: snake\n" " sound: *a5\n" + " bin: *a8\n" " position: *a6\n" " flags: *a7\n" " value: *my_value\n"; @@ -9065,6 +9068,8 @@ static bool test_err_load_alloc_oom_2( char *kind; char *sound; int **position; + uint8_t *bin; + size_t bin_len; unsigned position_count; enum test_f *flags; int value; @@ -9084,6 +9089,8 @@ static bool test_err_load_alloc_oom_2( CYAML_FIELD_SEQUENCE("position", CYAML_FLAG_POINTER, struct animal_s, position, &position_entry_schema, 0, CYAML_UNLIMITED), + CYAML_FIELD_BINARY("bin", CYAML_FLAG_POINTER, + struct animal_s, bin, 0, CYAML_UNLIMITED), CYAML_FIELD_FLAGS("flags", CYAML_FLAG_STRICT | CYAML_FLAG_POINTER, struct animal_s, flags, strings, 4), @@ -9322,6 +9329,7 @@ static bool test_err_save_alloc_oom_2( "animals:\n" " - kind: cat\n" " sound: meow\n" + " bin: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n" " position: [ 1, 2, 1]\n" " flags:\n" " - first\n" @@ -9330,6 +9338,7 @@ static bool test_err_save_alloc_oom_2( " - fourth\n" " - kind: snake\n" " sound: hiss\n" + " bin: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n" " position: [ 3, 1, 0]\n" " flags:\n" " - first\n" @@ -9340,6 +9349,8 @@ static bool test_err_save_alloc_oom_2( char *kind; char *sound; int **position; + uint8_t *bin; + size_t bin_len; unsigned position_count; enum test_f *flags; }; @@ -9358,6 +9369,8 @@ static bool test_err_save_alloc_oom_2( CYAML_FIELD_SEQUENCE("position", CYAML_FLAG_POINTER, struct animal_s, position, &position_entry_schema, 0, CYAML_UNLIMITED), + CYAML_FIELD_BINARY("bin", CYAML_FLAG_POINTER, + struct animal_s, bin, 0, CYAML_UNLIMITED), CYAML_FIELD_FLAGS("flags", CYAML_FLAG_STRICT | CYAML_FLAG_POINTER, struct animal_s, flags, strings, 4), @@ -9602,6 +9615,7 @@ static bool test_err_copy_alloc_oom_2( "animals:\n" " - kind: cat\n" " sound: meow\n" + " bin: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n" " position: [ 1, 2, 1]\n" " flags:\n" " - first\n" @@ -9610,6 +9624,7 @@ static bool test_err_copy_alloc_oom_2( " - fourth\n" " - kind: snake\n" " sound: hiss\n" + " bin: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n" " position: [ 3, 1, 0]\n" " flags:\n" " - first\n" @@ -9620,6 +9635,8 @@ static bool test_err_copy_alloc_oom_2( char *kind; char *sound; int **position; + uint8_t *bin; + size_t bin_len; unsigned position_count; enum test_f *flags; }; @@ -9639,6 +9656,8 @@ static bool test_err_copy_alloc_oom_2( CYAML_FIELD_SEQUENCE("position", CYAML_FLAG_POINTER, struct animal_s, position, &position_entry_schema, 0, CYAML_UNLIMITED), + CYAML_FIELD_BINARY("bin", CYAML_FLAG_POINTER, + struct animal_s, bin, 0, CYAML_UNLIMITED), CYAML_FIELD_FLAGS("flags", CYAML_FLAG_STRICT | CYAML_FLAG_POINTER, struct animal_s, flags, strings, 4), From 908a3f43cc9f7b5a8ba100115ebd34f1ceaa68f7 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sun, 21 Jul 2024 10:33:15 +0100 Subject: [PATCH 11/11] Test: Base64: Add a couple more tests for padding edge cases --- test/units/base64.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/units/base64.c b/test/units/base64.c index 296474c..fcf4010 100644 --- a/test/units/base64.c +++ b/test/units/base64.c @@ -180,7 +180,15 @@ static bool test_base64_decode_odd( .err = CYAML_OK, }, { - .name = "no_padding", + .name = "no_padding_1", + .enc = "S2l0dHk", + .enc_len = 7, + .dec = "Kitty", + .dec_len = 5, + .err = CYAML_OK, + }, + { + .name = "no_padding_2", .enc = "8J+YuA", .enc_len = 6, .dec = "😸", @@ -223,6 +231,12 @@ static bool test_base64_decode_odd( .enc_len = 4, .err = CYAML_ERR_INVALID_BASE64, }, + { + .name = "wrong_padding", + .enc = "S2l0dHk==", + .enc_len = 9, + .err = CYAML_ERR_INVALID_BASE64, + }, }; for (unsigned i = 0; i < CYAML_ARRAY_LEN(odd_data); i++) {