diff --git a/Makefile b/Makefile index 613cf56..8aee0ff 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))) @@ -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/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/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 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 5060497..a7854d6 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. * @@ -106,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", diff --git a/test/units/base64.c b/test/units/base64.c new file mode 100644 index 0000000..fcf4010 --- /dev/null +++ b/test/units/base64.c @@ -0,0 +1,318 @@ +/* + * 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_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 = "😸", + .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, + }, + { + .name = "wrong_padding", + .enc = "S2l0dHk==", + .enc_len = 9, + .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/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); diff --git a/test/units/errs.c b/test/units/errs.c index bdbfeb7..75f2ec4 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). * @@ -2456,45 +2663,40 @@ static bool test_err_save_schema_bad_data_size_8( } /** - * Test copying with schema with bad sequence count size. + * 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_copy_schema_bad_data_size_1( +static bool test_err_save_schema_bad_data_size_9( ttest_report_ctx_t *report, const cyaml_config_t *config) { - unsigned arr[] = { 1, 2, 3, 4, 5 }; - struct target_struct { - unsigned *seq; - uint32_t seq_count; + static const struct target_struct { + uint8_t value[64]; + unsigned value_count; } data = { - .seq = arr, - .seq_count = CYAML_ARRAY_LEN(arr), - }; - struct target_struct *copy = NULL; - static const struct cyaml_schema_value entry_schema = { - CYAML_VALUE_UINT(CYAML_FLAG_DEFAULT, *(data.seq)), + .value = { 0x01, 0x02, 0x03, 0x04, 0x05 }, + .value_count = 5, }; static const struct cyaml_schema_field mapping_schema[] = { { - .key = "sequence", + .key = "key", .value = { - .type = CYAML_SEQUENCE, - .flags = CYAML_FLAG_POINTER, - .data_size = sizeof(*(data.seq)), - .sequence = { - .entry = &entry_schema, + .type = CYAML_BINARY, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 1, + .binary = { .min = 0, - .max = 10, - + .max = 64, }, }, - .data_offset = offsetof(struct target_struct, seq), - .count_offset = offsetof(struct target_struct, seq_count), + .data_offset = offsetof(struct target_struct, value), .count_size = 9, + .count_offset = offsetof( + struct target_struct, + value_count), }, CYAML_FIELD_END }; @@ -2505,7 +2707,6 @@ static bool test_err_copy_schema_bad_data_size_1( char *buffer = NULL; size_t len = 0; test_data_t td = { - .copy = (cyaml_data_t **) ©, .buffer = &buffer, .config = config, }; @@ -2516,9 +2717,8 @@ static bool test_err_copy_schema_bad_data_size_1( return true; } - err = cyaml_copy(config, &top_schema, - (const cyaml_data_t *) &data, 0, - (cyaml_data_t **) ©); + 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)); } @@ -2531,43 +2731,45 @@ static bool test_err_copy_schema_bad_data_size_1( } /** - * Test loading with schema with sequence fixed with unequal min and max. + * Test copying with schema with bad sequence 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_load_schema_sequence_min_max( +static bool test_err_copy_schema_bad_data_size_1( ttest_report_ctx_t *report, const cyaml_config_t *config) { - static const unsigned char yaml[] = - "sequence:\n" - " - \n"; + unsigned arr[] = { 1, 2, 3, 4, 5 }; struct target_struct { unsigned *seq; uint32_t seq_count; - } *data_tgt = NULL; + } data = { + .seq = arr, + .seq_count = CYAML_ARRAY_LEN(arr), + }; + struct target_struct *copy = NULL; static const struct cyaml_schema_value entry_schema = { - CYAML_VALUE_UINT(CYAML_FLAG_DEFAULT, *(data_tgt->seq)), + CYAML_VALUE_UINT(CYAML_FLAG_DEFAULT, *(data.seq)), }; static const struct cyaml_schema_field mapping_schema[] = { { .key = "sequence", .value = { - .type = CYAML_SEQUENCE_FIXED, + .type = CYAML_SEQUENCE, .flags = CYAML_FLAG_POINTER, - .data_size = sizeof(*(data_tgt->seq)), + .data_size = sizeof(*(data.seq)), .sequence = { .entry = &entry_schema, .min = 0, - .max = CYAML_UNLIMITED, + .max = 10, }, }, .data_offset = offsetof(struct target_struct, seq), .count_offset = offsetof(struct target_struct, seq_count), - .count_size = sizeof(data_tgt->seq_count), + .count_size = 9, }, CYAML_FIELD_END }; @@ -2575,10 +2777,12 @@ static bool test_err_load_schema_sequence_min_max( CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, struct target_struct, mapping_schema), }; + char *buffer = NULL; + size_t len = 0; test_data_t td = { - .data = (cyaml_data_t **) &data_tgt, + .copy = (cyaml_data_t **) ©, + .buffer = &buffer, .config = config, - .schema = &top_schema, }; cyaml_err_t err; ttest_ctx_t tc; @@ -2587,31 +2791,240 @@ static bool test_err_load_schema_sequence_min_max( return true; } - err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, - (cyaml_data_t **) &data_tgt, NULL); - if (err != CYAML_ERR_SEQUENCE_FIXED_COUNT) { + 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 (data_tgt != NULL) { - return ttest_fail(&tc, "Data non-NULL on error."); + if (buffer != NULL || len != 0) { + return ttest_fail(&tc, "Buffer/len not untouched."); } return ttest_pass(&tc); } /** - * Test saving with schema with sequence fixed with unequal min and max. + * 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_save_schema_sequence_min_max( +static bool test_err_copy_schema_bad_data_size_2( ttest_report_ctx_t *report, const cyaml_config_t *config) { - unsigned value = 5; + 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. + * + * \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_sequence_min_max( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "sequence:\n" + " - \n"; + struct target_struct { + unsigned *seq; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_UINT(CYAML_FLAG_DEFAULT, *(data_tgt->seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "sequence", + .value = { + .type = CYAML_SEQUENCE_FIXED, + .flags = CYAML_FLAG_POINTER, + .data_size = sizeof(*(data_tgt->seq)), + .sequence = { + .entry = &entry_schema, + .min = 0, + .max = CYAML_UNLIMITED, + + }, + }, + .data_offset = offsetof(struct target_struct, seq), + .count_offset = offsetof(struct target_struct, seq_count), + .count_size = sizeof(data_tgt->seq_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_SEQUENCE_FIXED_COUNT) { + 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 sequence fixed with unequal min and max. + * + * \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_sequence_min_max( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + unsigned value = 5; struct target_struct { unsigned *seq; } data = { @@ -3011,30 +3424,41 @@ static bool test_err_copy_schema_required_mapping_missing( } /** - * Test loading when schema expects uint, but value is invalid. + * 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_load_schema_invalid_value_uint( +static bool test_err_copy_schema_required_mapping_missing2( ttest_report_ctx_t *report, const cyaml_config_t *config) { - static const unsigned char yaml[] = - "a: scalar\n"; - struct target_struct { - unsigned a; - } *data_tgt = NULL; - static const struct cyaml_schema_field mapping_schema[] = { - CYAML_FIELD_UINT("a", CYAML_FLAG_DEFAULT, - struct target_struct, a), - CYAML_FIELD_END + 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_MAPPING(CYAML_FLAG_POINTER, - struct target_struct, mapping_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, @@ -3047,9 +3471,10 @@ static bool test_err_load_schema_invalid_value_uint( 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) { + 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)); } @@ -3061,24 +3486,44 @@ static bool test_err_load_schema_invalid_value_uint( } /** - * Test loading with schema with string top level type, with bad value. + * 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_load_schema_invalid_value_string( +static bool test_err_save_schema_required_mapping_missing( ttest_report_ctx_t *report, const cyaml_config_t *config) { - static const unsigned char yaml[] = - "{ Hello }\n"; - char *value = NULL; + 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_STRING(CYAML_FLAG_POINTER, int, 0, CYAML_UNLIMITED) + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, int, + &entry_schema, 0, CYAML_UNLIMITED), }; + char *buffer = NULL; + size_t len; test_data_t td = { - .data = (cyaml_data_t **) &value, + .buffer = &buffer, .config = config, .schema = &top_schema, }; @@ -3089,40 +3534,131 @@ static bool test_err_load_schema_invalid_value_string( return true; } - err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, - (cyaml_data_t **) &value, NULL); - if (err != CYAML_ERR_INVALID_VALUE) { + 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 flags expected, but numerical value has trailing junk. + * Test loading when schema expects uint, but value is invalid. * * \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_value_flags_junk( +static bool test_err_load_schema_invalid_value_uint( ttest_report_ctx_t *report, const cyaml_config_t *config) { - enum test_flags { - TEST_FLAGS_NONE = 0, - TEST_FLAGS_FIRST = (1 << 0), - TEST_FLAGS_SECOND = (1 << 1), - TEST_FLAGS_THIRD = (1 << 2), - TEST_FLAGS_FOURTH = (1 << 3), - TEST_FLAGS_FIFTH = (1 << 4), - TEST_FLAGS_SIXTH = (1 << 5), - }; - static const cyaml_strval_t strings[] = { - { "first", (1 << 0) }, - { "second", (1 << 1) }, - { "third", (1 << 2) }, - { "fourth", (1 << 3) }, + static const unsigned char yaml[] = + "a: scalar\n"; + struct target_struct { + unsigned a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("a", CYAML_FLAG_DEFAULT, + struct target_struct, a), + 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)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading with schema with string top level type, with bad 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_value_string( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "{ Hello }\n"; + char *value = NULL; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_POINTER, int, 0, CYAML_UNLIMITED) + }; + test_data_t td = { + .data = (cyaml_data_t **) &value, + .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 **) &value, NULL); + if (err != CYAML_ERR_INVALID_VALUE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading when flags expected, but numerical value has trailing junk. + * + * \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_value_flags_junk( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_flags { + TEST_FLAGS_NONE = 0, + TEST_FLAGS_FIRST = (1 << 0), + TEST_FLAGS_SECOND = (1 << 1), + TEST_FLAGS_THIRD = (1 << 2), + TEST_FLAGS_FOURTH = (1 << 3), + TEST_FLAGS_FIFTH = (1 << 4), + TEST_FLAGS_SIXTH = (1 << 5), + }; + static const cyaml_strval_t strings[] = { + { "first", (1 << 0) }, + { "second", (1 << 1) }, + { "third", (1 << 2) }, + { "fourth", (1 << 3) }, { "fifth", (1 << 4) }, { "sixth", (1 << 5) }, }; @@ -3864,25 +4400,676 @@ static bool test_err_save_schema_bad_bitfield( } /** - * Test loading when schema expects float but value is out of range. + * Test loading when schema expects float but value is out of range. + * + * \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_value_float_range1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 3.5e+38\n"; + struct target_struct { + float a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT("a", + CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT, + struct target_struct, a), + 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)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading when schema expects float but value is out of range. + * + * \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_value_float_range2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: -3.5e+38\n"; + struct target_struct { + float a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT("a", + CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT, + struct target_struct, a), + 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)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading when schema expects float but value is out of range. + * + * \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_value_float_range3( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 1.55331e-40f\n"; + struct target_struct { + float a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT("a", + CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT, + struct target_struct, a), + 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)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading when schema expects float but value is out of range. + * + * \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_value_float_range4( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: -1.55331e-40f\n"; + struct target_struct { + float a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT("a", + CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT, + struct target_struct, a), + 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)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading when schema expects float but value is invalid. + * + * \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_value_float_junk( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 0.452*00E003\n"; + struct target_struct { + float a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT("a", CYAML_FLAG_DEFAULT, + struct target_struct, a), + 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)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading when schema expects float but value is invalid. + * + * \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_value_float_invalid( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: Gasp\n"; + struct target_struct { + float a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT("a", CYAML_FLAG_DEFAULT, + struct target_struct, a), + 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)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading when schema expects double but value is out of range. + * + * \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_value_double_range1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 1.8e+4999\n"; + struct target_struct { + double a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT("a", + CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT, + struct target_struct, a), + 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)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading when schema expects double but value is out of range. + * + * \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_value_double_range2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: -1.8e+4999\n"; + struct target_struct { + double a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT("a", + CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT, + struct target_struct, a), + 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)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading when schema expects double but value is invalid. + * + * \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_value_double_junk( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 0.452*00E003\n"; + struct target_struct { + double a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT("a", CYAML_FLAG_DEFAULT, + struct target_struct, a), + 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)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading when schema expects double but value is invalid. + * + * \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_value_double_invalid( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: Gasp\n"; + struct target_struct { + double a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT("a", CYAML_FLAG_DEFAULT, + struct target_struct, a), + 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)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + 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_value_float_range1( +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[] = - "a: 3.5e+38\n"; + "test_data: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n"; struct target_struct { - float a; + char data[8]; + size_t data_len; } *data_tgt = NULL; static const struct cyaml_schema_field mapping_schema[] = { - CYAML_FIELD_FLOAT("a", - CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT, - struct target_struct, a), + 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 = { @@ -3903,42 +5090,32 @@ static bool test_err_load_schema_invalid_value_float_range1( err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, (cyaml_data_t **) &data_tgt, NULL); - if (err != CYAML_ERR_INVALID_VALUE) { + if (err != CYAML_ERR_BASE64_MAX_LEN) { 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 when schema expects float but value is out of range. + * 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_value_float_range2( +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[] = - "a: -3.5e+38\n"; + "test_data: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n"; struct target_struct { - float a; + char data[8]; + size_t data_len; } *data_tgt = NULL; - static const struct cyaml_schema_field mapping_schema[] = { - CYAML_FIELD_FLOAT("a", - CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT, - struct target_struct, a), - CYAML_FIELD_END - }; static const struct cyaml_schema_value top_schema = { - CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, - struct target_struct, mapping_schema), + CYAML_VALUE(BINARY, CYAML_FLAG_POINTER, uint8_t, { .max = 64}), }; test_data_t td = { .data = (cyaml_data_t **) &data_tgt, @@ -3954,42 +5131,31 @@ static bool test_err_load_schema_invalid_value_float_range2( err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, (cyaml_data_t **) &data_tgt, NULL); - if (err != CYAML_ERR_INVALID_VALUE) { + 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 loading when schema expects float but value is out of range. + * 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_load_schema_invalid_value_float_range3( +static bool test_err_copy_schema_invalid_base64_top_level( ttest_report_ctx_t *report, const cyaml_config_t *config) { - static const unsigned char yaml[] = - "a: 1.55331e-40f\n"; struct target_struct { - float a; + char data[8]; + size_t data_len; } *data_tgt = NULL; - static const struct cyaml_schema_field mapping_schema[] = { - CYAML_FIELD_FLOAT("a", - CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT, - struct target_struct, a), - CYAML_FIELD_END - }; + struct target_struct *data_cpy = NULL; static const struct cyaml_schema_value top_schema = { - CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, - struct target_struct, mapping_schema), + CYAML_VALUE(BINARY, CYAML_FLAG_POINTER, uint8_t, { .max = 64}), }; test_data_t td = { .data = (cyaml_data_t **) &data_tgt, @@ -4003,39 +5169,37 @@ static bool test_err_load_schema_invalid_value_float_range3( 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) { + 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)); } - if (data_tgt != NULL) { - return ttest_fail(&tc, "Data non-NULL on error."); - } - return ttest_pass(&tc); } /** - * Test loading when schema expects float but value is out of range. + * 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_value_float_range4( +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[] = - "a: -1.55331e-40f\n"; + "test_data: Cat=\n"; struct target_struct { - float a; + char *data; + size_t data_len; } *data_tgt = NULL; static const struct cyaml_schema_field mapping_schema[] = { - CYAML_FIELD_FLOAT("a", - CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT, - struct target_struct, a), + 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 = { @@ -4056,36 +5220,34 @@ static bool test_err_load_schema_invalid_value_float_range4( err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, (cyaml_data_t **) &data_tgt, NULL); - if (err != CYAML_ERR_INVALID_VALUE) { + if (err != CYAML_ERR_BASE64_MAX_LEN) { 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 when schema expects float but value is invalid. + * 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_value_float_junk( +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[] = - "a: 0.452*00E003\n"; + "test_data: d2FsdGhhemFyYm9iYWx0aGF6YXI=\n"; struct target_struct { - float a; + char *data; + size_t data_len; } *data_tgt = NULL; static const struct cyaml_schema_field mapping_schema[] = { - CYAML_FIELD_FLOAT("a", CYAML_FLAG_DEFAULT, - struct target_struct, a), + 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 = { @@ -4106,36 +5268,57 @@ static bool test_err_load_schema_invalid_value_float_junk( err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, (cyaml_data_t **) &data_tgt, NULL); - if (err != CYAML_ERR_INVALID_VALUE) { + if (err != CYAML_ERR_BASE64_MAX_LEN) { 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 when schema expects float but value is invalid. + * 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_value_float_invalid( +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[] = - "a: Gasp\n"; + "before: 1\n" + "after: 0xff\n"; struct target_struct { - float a; + 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_FLOAT("a", CYAML_FLAG_DEFAULT, - struct target_struct, a), + 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 = { @@ -4156,37 +5339,57 @@ static bool test_err_load_schema_invalid_value_float_invalid( err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, (cyaml_data_t **) &data_tgt, NULL); - if (err != CYAML_ERR_INVALID_VALUE) { + if (err != CYAML_ERR_BASE64_MAX_LEN) { 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 when schema expects double but value is out of range. + * 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_value_double_range1( +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[] = - "a: 1.8e+4999\n"; + "before: 1\n" + "after: 0xff\n"; struct target_struct { - double a; + 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_FLOAT("a", - CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT, - struct target_struct, a), + 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 = { @@ -4207,37 +5410,57 @@ static bool test_err_load_schema_invalid_value_double_range1( err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, (cyaml_data_t **) &data_tgt, NULL); - if (err != CYAML_ERR_INVALID_VALUE) { + if (err != CYAML_ERR_BASE64_MAX_LEN) { 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 when schema expects double but value is out of range. + * 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_value_double_range2( +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[] = - "a: -1.8e+4999\n"; + "before: 1\n" + "after: 0xff\n"; struct target_struct { - double a; + 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_FLOAT("a", - CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT, - struct target_struct, a), + 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 = { @@ -4258,36 +5481,57 @@ static bool test_err_load_schema_invalid_value_double_range2( err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, (cyaml_data_t **) &data_tgt, NULL); - if (err != CYAML_ERR_INVALID_VALUE) { + if (err != CYAML_ERR_BASE64_MAX_LEN) { 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 when schema expects double but value is invalid. + * 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_value_double_junk( +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[] = - "a: 0.452*00E003\n"; + "before: 1\n" + "after: 0xff\n"; struct target_struct { - double a; + 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_FLOAT("a", CYAML_FLAG_DEFAULT, - struct target_struct, a), + 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 = { @@ -4308,36 +5552,57 @@ static bool test_err_load_schema_invalid_value_double_junk( err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, (cyaml_data_t **) &data_tgt, NULL); - if (err != CYAML_ERR_INVALID_VALUE) { + if (err != CYAML_ERR_BASE64_MAX_LEN) { 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 when schema expects double but value is invalid. + * 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_value_double_invalid( +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[] = - "a: Gasp\n"; + "before: 1\n" + "after: 0xff\n"; struct target_struct { - double a; + 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_FLOAT("a", CYAML_FLAG_DEFAULT, - struct target_struct, a), + 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 = { @@ -4358,14 +5623,10 @@ static bool test_err_load_schema_invalid_value_double_invalid( err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, (cyaml_data_t **) &data_tgt, NULL); - if (err != CYAML_ERR_INVALID_VALUE) { + if (err != CYAML_ERR_BAD_MIN_MAX_SCHEMA) { return ttest_fail(&tc, cyaml_strerror(err)); } - if (data_tgt != NULL) { - return ttest_fail(&tc, "Data non-NULL on error."); - } - return ttest_pass(&tc); } @@ -6100,6 +7361,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. * @@ -7697,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" @@ -7707,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" @@ -7718,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"; @@ -7725,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; @@ -7744,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), @@ -7982,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" @@ -7990,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" @@ -8000,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; }; @@ -8018,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), @@ -8262,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" @@ -8270,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" @@ -8280,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; }; @@ -8299,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), @@ -9396,8 +10755,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 +10771,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"); @@ -9447,6 +10814,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); @@ -9474,6 +10857,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); 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); 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); 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,