From f8eb024c1a8c493602e1e359fcc7f48a5b1c919d Mon Sep 17 00:00:00 2001 From: Alexandros Theodotou Date: Sun, 9 Feb 2020 18:08:00 +0000 Subject: [PATCH] initial commit --- .gitignore | 85 + LICENSE | 14 + Makefile | 195 + README.md | 106 + docs/api.doxygen.conf | 15 + docs/devel.doxygen.conf | 15 + docs/guide.md | 155 + examples/numerical/README.md | 7 + examples/numerical/fibonacci.yaml | 8 + examples/numerical/main.c | 107 + examples/planner/README.md | 8 + examples/planner/main.c | 468 +++ examples/planner/project.yaml | 58 + include/cyaml/cyaml.h | 1616 +++++++ libcyaml.pc.in | 10 + meson.build | 59 + src/data.h | 142 + src/free.c | 161 + src/load.c | 2508 +++++++++++ src/mem.c | 35 + src/mem.h | 103 + src/save.c | 1470 +++++++ src/utf8.c | 259 ++ src/utf8.h | 46 + src/util.c | 122 + src/util.h | 184 + test/data/basic.yaml | 19 + test/units/errs.c | 5918 ++++++++++++++++++++++++++ test/units/file.c | 429 ++ test/units/free.c | 152 + test/units/load.c | 6530 +++++++++++++++++++++++++++++ test/units/save.c | 4028 ++++++++++++++++++ test/units/test.c | 120 + test/units/ttest.h | 232 + test/units/utf8.c | 271 ++ test/units/util.c | 246 ++ 36 files changed, 25901 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 docs/api.doxygen.conf create mode 100644 docs/devel.doxygen.conf create mode 100644 docs/guide.md create mode 100644 examples/numerical/README.md create mode 100644 examples/numerical/fibonacci.yaml create mode 100644 examples/numerical/main.c create mode 100644 examples/planner/README.md create mode 100644 examples/planner/main.c create mode 100644 examples/planner/project.yaml create mode 100644 include/cyaml/cyaml.h create mode 100644 libcyaml.pc.in create mode 100644 meson.build create mode 100644 src/data.h create mode 100644 src/free.c create mode 100644 src/load.c create mode 100644 src/mem.c create mode 100644 src/mem.h create mode 100644 src/save.c create mode 100644 src/utf8.c create mode 100644 src/utf8.h create mode 100644 src/util.c create mode 100644 src/util.h create mode 100644 test/data/basic.yaml create mode 100644 test/units/errs.c create mode 100644 test/units/file.c create mode 100644 test/units/free.c create mode 100644 test/units/load.c create mode 100644 test/units/save.c create mode 100644 test/units/test.c create mode 100644 test/units/ttest.h create mode 100644 test/units/utf8.c create mode 100644 test/units/util.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..225d8d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,85 @@ +# Copyright (C) 2019 Alexandros Theodotou +# +# This file is part of Zrythm +# +# Zrythm is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Zrythm is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Zrythm. If not, see . + +# Build files +build +builddir +_build + +# -- C++ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj +**/*/*.so* + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +build/*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# -- Vim +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] +.vimrc + +# Session +Session.vim + +# Temporary +.netrwhist +*~ +*.ui# +**/.deps + +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +# Backup files +*.bak + +# Valgrind +vgcore.* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7a987d9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2017-2018, Michael Drake + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2978815 --- /dev/null +++ b/Makefile @@ -0,0 +1,195 @@ +# SPDX-License-Identifier: ISC +# +# Copyright (C) 2017-2020 Michael Drake + +# CYAML's versioning is ..[-DEVEL] +# Master branch will always be DEVEL. The release process will be to make +# the release branch, set VESION_DEVEL to 0, and tag the release. +VERSION_MAJOR = 1 +VERSION_MINOR = 0 +VERSION_PATCH = 0 +VERSION_DEVEL = 0 +VERSION_STR = $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH) + +# Default variant depends on whether it's a development build. +ifeq ($(VERSION_DEVEL), 1) + VARIANT = debug +else + VARIANT = release +endif + +# Unfortunately ASan is incompatible with valgrind, so we have a special +# variant for running with sanitisers. +VALID_VARIANTS := release debug san +ifneq ($(filter $(VARIANT),$(VALID_VARIANTS)),) +else +$(error VARIANT must be 'debug' (default), 'san', or 'release') +endif + +LIB_NAME = libcyaml +LIB_PKGCON = $(LIB_NAME).pc +LIB_STATIC = $(LIB_NAME).a +LIB_SHARED = $(LIB_NAME).so +LIB_SH_MAJ = $(LIB_SHARED).$(VERSION_MAJOR) +LIB_SH_VER = $(LIB_SHARED).$(VERSION_STR) + +.IMPLICIT = + +PREFIX ?= /usr/local +LIBDIR ?= lib +INCLUDEDIR ?= include + +CC ?= gcc +AR ?= ar +MKDIR = mkdir -p +INSTALL ?= install -c -D +VALGRIND = valgrind --leak-check=full --track-origins=yes + +VERSION_FLAGS = -DVERSION_MAJOR=$(VERSION_MAJOR) \ + -DVERSION_MINOR=$(VERSION_MINOR) \ + -DVERSION_PATCH=$(VERSION_PATCH) \ + -DVERSION_DEVEL=$(VERSION_DEVEL) + +LIBYAML = yaml-0.1 +LIBYAML_CFLAGS := $(if $(PKG_CONFIG),$(shell $(PKG_CONFIG) --cflags $(LIBYAML)),) +LIBYAML_LIBS := $(if $(PKG_CONFIG),$(shell $(PKG_CONFIG) --libs $(LIBYAML)),-lyaml) + +INCLUDE = -I include +CFLAGS += $(INCLUDE) $(VERSION_FLAGS) $(LIBYAML_CFLAGS) +CFLAGS += -std=c11 -Wall -Wextra -pedantic -Wconversion +LDFLAGS += $(LIBYAML_LIBS) +LDFLAGS_SHARED += -Wl,-soname=$(LIB_SH_MAJ) -shared + +ifeq ($(VARIANT), debug) + CFLAGS += -O0 -g +else ifeq ($(VARIANT), san) + CFLAGS += -O0 -g -fsanitize=address -fsanitize=undefined -fno-sanitize-recover + LDFLAGS += -fsanitize=address -fsanitize=undefined -fno-sanitize-recover +else + CFLAGS += -O2 -DNDEBUG +endif + +ifneq ($(filter coverage,$(MAKECMDGOALS)),) + BUILDDIR = build/coverage/$(VARIANT) + CFLAGS_COV = --coverage -DNDEBUG + LDFLAGS_COV = --coverage +else + BUILDDIR = build/$(VARIANT) + CFLAGS_COV = + LDFLAGS_COV = +endif + +BUILDDIR_SHARED = $(BUILDDIR)/shared +BUILDDIR_STATIC = $(BUILDDIR)/static + +LIB_SRC_FILES = mem.c free.c load.c save.c util.c utf8.c +LIB_SRC := $(addprefix src/,$(LIB_SRC_FILES)) +LIB_OBJ = $(patsubst %.c,%.o, $(addprefix $(BUILDDIR)/,$(LIB_SRC))) +LIB_OBJ_SHARED = $(patsubst $(BUILDDIR)%,$(BUILDDIR_SHARED)%,$(LIB_OBJ)) +LIB_OBJ_STATIC = $(patsubst $(BUILDDIR)%,$(BUILDDIR_STATIC)%,$(LIB_OBJ)) + +LIB_PATH = LD_LIBRARY_PATH=$(BUILDDIR) + +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/utf8.c +TEST_SRC := $(addprefix test/,$(TEST_SRC_FILES)) +TEST_OBJ = $(patsubst %.c,%.o, $(addprefix $(BUILDDIR)/,$(TEST_SRC))) + +TEST_BINS = \ + $(BUILDDIR)/test/units/cyaml-shared \ + $(BUILDDIR)/test/units/cyaml-static + +all: $(BUILDDIR)/$(LIB_SH_MAJ) $(BUILDDIR)/$(LIB_STATIC) examples + +coverage: test-verbose + @$(MKDIR) $(BUILDDIR) + @gcovr -e 'test/.*' -r . + @gcovr -e 'test/.*' -x -o build/coverage.xml -r . + @gcovr -e 'test/.*' --html --html-details -o build/coverage.html -r . + +test: $(TEST_BINS) + @for i in $(^); do $(LIB_PATH) $$i || exit; done + +test-quiet: $(TEST_BINS) + @for i in $(^); do $(LIB_PATH) $$i -q || exit; done + +test-verbose: $(TEST_BINS) + @for i in $(^); do $(LIB_PATH) $$i -v || exit; done + +test-debug: $(TEST_BINS) + @for i in $(^); do $(LIB_PATH) $$i -d || exit; done + +valgrind: $(TEST_BINS) + @for i in $(^); do $(LIB_PATH) $(VALGRIND) $$i || exit; done + +valgrind-quiet: $(TEST_BINS) + @for i in $(^); do $(LIB_PATH) $(VALGRIND) $$i -q || exit; done + +valgrind-verbose: $(TEST_BINS) + @for i in $(^); do $(LIB_PATH) $(VALGRIND) $$i -v || exit; done + +valgrind-debug: $(TEST_BINS) + @for i in $(^); do $(LIB_PATH) $(VALGRIND) $$i -d || exit; done + +$(BUILDDIR)/$(LIB_PKGCON): $(LIB_PKGCON).in + sed \ + -e 's#PREFIX#$(PREFIX)#' \ + -e 's#LIBDIR#$(LIBDIR)#' \ + -e 's#INCLUDEDIR#$(INCLUDEDIR)#' \ + -e 's#VERSION#$(VERSION_STR)#' \ + $(LIB_PKGCON).in >$(BUILDDIR)/$(LIB_PKGCON) + +$(BUILDDIR)/$(LIB_STATIC): $(LIB_OBJ_STATIC) + $(AR) -rcs $@ $^ + +$(BUILDDIR)/$(LIB_SH_MAJ): $(LIB_OBJ_SHARED) + $(CC) $(LDFLAGS) $(LDFLAGS_COV) $(LDFLAGS_SHARED) -o $@ $^ + +$(LIB_OBJ_STATIC): $(BUILDDIR_STATIC)/%.o : %.c + @$(MKDIR) $(BUILDDIR_STATIC)/src + $(CC) $(CFLAGS) $(CFLAGS_COV) -c -o $@ $< + +$(LIB_OBJ_SHARED): $(BUILDDIR_SHARED)/%.o : %.c + @$(MKDIR) $(BUILDDIR_SHARED)/src + $(CC) $(CFLAGS) -fPIC $(CFLAGS_COV) -c -o $@ $< + +docs: + $(MKDIR) build/docs/api + $(MKDIR) build/docs/devel + doxygen docs/api.doxygen.conf + doxygen docs/devel.doxygen.conf + +clean: + rm -rf build/ + +install: $(BUILDDIR)/$(LIB_SH_MAJ) $(BUILDDIR)/$(LIB_STATIC) $(BUILDDIR)/$(LIB_PKGCON) + $(INSTALL) $(BUILDDIR)/$(LIB_SH_MAJ) $(DESTDIR)$(PREFIX)/$(LIBDIR)/$(LIB_SH_VER) + (cd $(DESTDIR)$(PREFIX)/$(LIBDIR) && { ln -s -f $(LIB_SH_VER) $(LIB_SH_MAJ) || { rm -f $(LIB_SH_MAJ) && ln -s $(LIB_SH_VER) $(LIB_SH_MAJ); }; }) + (cd $(DESTDIR)$(PREFIX)/$(LIBDIR) && { ln -s -f $(LIB_SH_VER) $(LIB_SHARED) || { rm -f $(LIB_SHARED) && ln -s $(LIB_SH_VER) $(LIB_SHARED); }; }) + $(INSTALL) $(BUILDDIR)/$(LIB_STATIC) $(DESTDIR)$(PREFIX)/$(LIBDIR)/$(LIB_STATIC) + chmod 644 $(DESTDIR)$(PREFIX)/$(LIBDIR)/$(LIB_STATIC) + $(INSTALL) -d $(DESTDIR)$(PREFIX)/$(INCLUDEDIR)/cyaml + $(INSTALL) -m 644 include/cyaml/* $(DESTDIR)$(PREFIX)/$(INCLUDEDIR)/cyaml + $(INSTALL) -m 644 $(BUILDDIR)/$(LIB_PKGCON) $(DESTDIR)$(PREFIX)/$(LIBDIR)/pkgconfig/$(LIB_PKGCON) + +examples: $(BUILDDIR)/planner $(BUILDDIR)/numerical + +$(BUILDDIR)/planner: examples/planner/main.c $(BUILDDIR)/$(LIB_STATIC) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +$(BUILDDIR)/numerical: examples/numerical/main.c $(BUILDDIR)/$(LIB_STATIC) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +.PHONY: all test test-quiet test-verbose test-debug \ + valgrind valgrind-quiet valgrind-verbose valgrind-debug \ + clean coverage docs install examples + +$(BUILDDIR)/test/units/cyaml-static: $(TEST_OBJ) $(BUILDDIR)/$(LIB_STATIC) + $(CC) $(LDFLAGS_COV) -o $@ $^ $(LDFLAGS) + +$(BUILDDIR)/test/units/cyaml-shared: $(TEST_OBJ) $(BUILDDIR)/$(LIB_SH_MAJ) + $(CC) $(LDFLAGS_COV) -o $@ $^ $(LDFLAGS) + +$(TEST_OBJ): $(BUILDDIR)/%.o : %.c + @$(MKDIR) $(BUILDDIR)/test/units + $(CC) $(CFLAGS) $(CFLAGS_COV) -c -o $@ $< diff --git a/README.md b/README.md new file mode 100644 index 0000000..6db7677 --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ +LibCYAML: Schema-based YAML parsing and serialisation +===================================================== + +[![Build Status](https://github.com/tlsa/libcyaml/workflows/CI/badge.svg)](https://github.com/tlsa/libcyaml/actions) [![Code Coverage](https://codecov.io/gh/tlsa/libcyaml/branch/master/graph/badge.svg)](https://codecov.io/gh/tlsa/libcyaml) [![Code Quality](https://img.shields.io/lgtm/grade/cpp/g/tlsa/libcyaml.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/tlsa/libcyaml/alerts/) + +LibCYAML is a C library for reading and writing structured YAML documents. +It is written in ISO C11 and licensed under the ISC licence. + +Overview +-------- + +The fundamental idea behind CYAML is to allow applications to construct +schemas which describe both the permissible structure of the YAML documents +to read/write, and the C data structure(s) in which the loaded data is +arranged in memory. + +### Goals + +* Make it easy to load YAML into client's custom C data structures. +* Good compromise between flexibility and simplicity. + +### Features + +* Load, Save and Free functions. +* Reads and writes arbitrary client data structures. +* Schema-driven, allowing control over permitted YAML, for example: + - Required / optional mapping fields. + - Allowed / disallowed values. + - Minimum / maximum sequence entry count. + - etc... +* Enumerations and flag words. +* YAML backtraces make it simple for users to fix their YAML to + conform to your schema. +* Uses standard [`libyaml`](https://github.com/yaml/libyaml) library for + low-level YAML read / write. +* Support for YAML aliases and anchors. + +Building +-------- + +To build the library, run: + + make + +You can control the optimisation and building of asserts by setting +the build variant: + + make VARIANT=debug + make VARIANT=release + +Another debug build variant which is built with address sanitiser (incompatible +with valgrind) can be built with: + + make VARIANT=san + +Installation +------------ + +To install a release version of the library, run: + + make install VARIANT=release + +It will install to the PREFIX `/usr/local` by default, and it will use +DESTDIR and PREFIX from the environment if set. + +Testing +------- + +To run the tests, run any of the following, which generate various +levels of output verbosity (optionally setting `VARIANT=release`, or +`VARIANT=san`): + + make test + make test-quiet + make test-verbose + make test-debug + +To run the tests under `valgrind`, a similar set of targets is available: + + make valgrind + make valgrind-quiet + make valgrind-verbose + make valgrind-debug + +To generate a test coverage report, `gcovr` is required: + + make coverage + +Documentation +------------- + +To generate both public API documentation, and documentation of CYAML's +internals, `doxygen` is required: + + make docs + +Alternatively, the read the API documentation directly from the +[cyaml.h](https://github.com/tlsa/libcyaml/blob/master/include/cyaml/cyaml.h) +header file. + +There is also a [tutorial](docs/guide.md). + +Examples +-------- + +In addition to the documentation, you can study the [examples](examples/). diff --git a/docs/api.doxygen.conf b/docs/api.doxygen.conf new file mode 100644 index 0000000..1f41f43 --- /dev/null +++ b/docs/api.doxygen.conf @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: ISC + +# This generates documentation for developers using libcyaml. + +PROJECT_NAME = "CYAML" +OUTPUT_DIRECTORY = "build/docs/api" +OPTIMIZE_OUTPUT_FOR_C = YES +EXTRACT_STATIC = NO +MARKDOWN_SUPPORT = YES +INPUT = include README.md docs/guide.md +RECURSIVE = YES +USE_MDFILE_AS_MAINPAGE = README.md +GENERATE_HTML = YES +GENERATE_LATEX = NO +QUIET = YES diff --git a/docs/devel.doxygen.conf b/docs/devel.doxygen.conf new file mode 100644 index 0000000..e6d80e6 --- /dev/null +++ b/docs/devel.doxygen.conf @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: ISC + +# This generates documentation for developers developing libcyaml. + +PROJECT_NAME = "CYAML Internals" +OUTPUT_DIRECTORY = "build/docs/devel" +OPTIMIZE_OUTPUT_FOR_C = YES +EXTRACT_STATIC = YES +MARKDOWN_SUPPORT = YES +INPUT = include src README.md docs/guide.md +RECURSIVE = YES +USE_MDFILE_AS_MAINPAGE = README.md +GENERATE_HTML = YES +GENERATE_LATEX = NO +QUIET = YES diff --git a/docs/guide.md b/docs/guide.md new file mode 100644 index 0000000..4420a74 --- /dev/null +++ b/docs/guide.md @@ -0,0 +1,155 @@ +LibCYAML: Tutorial +================== + +This document is intended for C developers wishing to make use of +[LibCYAML](https://github.com/tlsa/libcyaml). + +Overview +-------- + +If you want to use LibCYAML you'll need to have two things: + +1. A consistent structure to the sort of YAML you want to load/save. +2. Some C data structure to load/save to/from. + +LibCYAML's aim is to make this as simple as possible for the programmer. +However, LibCYAML knows nothing about either your data structure or the +"shape" of the YAML you want to load. You provide this information by +defining "schemas", and passing them to LibCYAML. + +> **Note**: If you need to handle arbitrary "free-form" YAML (e.g. for a tool +> to convert between YAML and JSON), then LibCYAML would not be much help. +> In such a case, I'd recommend using [libyaml](https://github.com/yaml/libyaml) +> directly. + +A simple example: loading YAML +------------------------------ + +Let's say you want to load the following YAML document: + +```yaml +name: Fibonacci +data: + - 1 + - 1 + - 2 + - 3 + - 5 + - 8 +``` + +And you want to load it into the following C data structure: + +```c +struct numbers { + char *name; + int *data; + unsigned data_count; +}; +``` + +Then we need to define a CYAML schema to describe these to LibCYAML. + +> **Note**: Use the doxygen API documentation, or else the documentation in +> [cyaml.h](https://github.com/tlsa/libcyaml/blob/master/include/cyaml/cyaml.h) +> in conjunction with this guide. + +At the top level of the YAML is a mapping with two fields, "name" and +"data". The the first field is just a simple scalar value (it's neither +a mapping nor a sequence). The second field has a sequence value. + +We'll start by defining the CYAML schema for the "data" sequence, +since since that's the "deepest" non-scalar type. The reason for +starting here will become clear later. + +```c +/* CYAML value schema for entries of the data sequence. */ +static const cyaml_schema_value_t data_entry = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), +}; +``` + +Here we're making a `cyaml_schema_value_t` for the entries in the +sequence. There are various `CYAML_VALUE_{TYPE}` macros to assist with +this. Here we're using `CYAML_VALUE_INT`, because the value is a signed +integer. The parameters passed to the macro are `enum cyaml_flag`, and +the C data type of the value. + +Now we can write the schema for the mapping. First we'll construct +an array of `cyaml_schema_field_t` entries that describe each +field in the mapping. + +```c +/* CYAML mapping schema fields array for the top level mapping. */ +static const cyaml_schema_field_t top_mapping_schema[] = { + CYAML_FIELD_STRING_PTR("name", CYAML_FLAG_POINTER, + struct numbers, name, + 0, CYAML_UNLIMITED), + CYAML_FIELD_SEQUENCE("data", CYAML_FLAG_POINTER, + struct numbers, data, &data_entry, + 0, CYAML_UNLIMITED), + CYAML_FIELD_END +}; +``` + +There are `CYAML_FIELD_{TYPE}` helper macros to construct the mapping field +entries. The array must be terminated by a `CYAML_FIELD_END` entry. +The helper macro parameters are specific to each `CYAML_FIELD_{TYPE}` macro. + +The entry for the name field is of type string pointer. You can consult the +documentation for the `CYAML_FIELD_{TYPE}` macros to see what the parameters +mean. + +> **Note**: The field for the sequence takes a pointer to the sequence entry +> data type that we defined earlier as `data_entry`. + +Finally we can define the schema for the top level value that gets passed to +the LibCYAML. + +```c +/* CYAML value schema for the top level mapping. */ +static const cyaml_schema_value_t top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct numbers, top_mapping_schema), +}; +``` + +In this case our top level value is a mapping type. One of the parameters +needed for mappings is the array of field definitions. In this case we're +passing the `top_mapping_schema` that we defined above. + +```c +/* Create our CYAML configuration. */ +static const cyaml_config_t config = { + .log_level = CYAML_LOG_WARNING, /* Logging errors and warnings only. */ + .log_fn = cyaml_log, /* Use the default logging function. */ + .mem_fn = cyaml_mem, /* Use the default memory allocator. */ +}; + +/* Where to store the loaded data */ +struct numbers *n; + +/* Load the file into p */ +cyaml_err_t err = cyaml_load_file(argv[ARG_PATH_IN], &config, + &top_schema, (cyaml_data_t **)&n, NULL); +if (err != CYAML_OK) { + /* Handle error */ +} + +/* Use the data. */ +printf("%s:\n", n->name); +for (unsigned i = 0; i < n->data_count; i++) { + printf(" - %i\n", n->data[i]); +} + +/* Free the data */ +err = cyaml_free(&config, &top_schema, n, 0); +if (err != CYAML_OK) { + /* Handle error */ +} +``` + +And that's it, the YAML is loaded into the custom C data structure. + +You can find the code for this in the "numerical" example in the +[examples](../examples) directory. diff --git a/examples/numerical/README.md b/examples/numerical/README.md new file mode 100644 index 0000000..9e9fac7 --- /dev/null +++ b/examples/numerical/README.md @@ -0,0 +1,7 @@ +Simple LibCYAML example +======================= + +This has a simple YAML document for the Fibonacci sequence, and an example +LibCYAML binding to load/free the data from C. + +The [tutorial](../../docs/guide.md) explains how this works. diff --git a/examples/numerical/fibonacci.yaml b/examples/numerical/fibonacci.yaml new file mode 100644 index 0000000..2d07c8d --- /dev/null +++ b/examples/numerical/fibonacci.yaml @@ -0,0 +1,8 @@ +name: Fibonacci +data: + - 1 + - 1 + - 2 + - 3 + - 5 + - 8 diff --git a/examples/numerical/main.c b/examples/numerical/main.c new file mode 100644 index 0000000..b7be0dc --- /dev/null +++ b/examples/numerical/main.c @@ -0,0 +1,107 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2018 Michael Drake + */ + +#include +#include + +#include + +/****************************************************************************** + * C data structure for storing a project plan. + * + * This is what we want to load the YAML into. + ******************************************************************************/ + +/* Structure for storing numerical sequence */ +struct numbers { + char *name; + int *data; + unsigned data_count; +}; + + +/****************************************************************************** + * CYAML schema to tell libcyaml about both expected YAML and data structure. + * + * (Our CYAML schema is just a bunch of static const data.) + ******************************************************************************/ + +/* CYAML value schema for entries of the data sequence. */ +static const cyaml_schema_value_t data_entry = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), +}; + +/* CYAML mapping schema fields array for the top level mapping. */ +static const cyaml_schema_field_t top_mapping_schema[] = { + CYAML_FIELD_STRING_PTR("name", CYAML_FLAG_POINTER, + struct numbers, name, + 0, CYAML_UNLIMITED), + CYAML_FIELD_SEQUENCE("data", CYAML_FLAG_POINTER, + struct numbers, data, &data_entry, + 0, CYAML_UNLIMITED), + CYAML_FIELD_END +}; + +/* CYAML value schema for the top level mapping. */ +static const cyaml_schema_value_t top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct numbers, top_mapping_schema), +}; + + +/****************************************************************************** + * Actual code to load and save YAML doc using libcyaml. + ******************************************************************************/ + +/* Our CYAML config. + * + * If you want to change it between calls, don't make it const. + * + * Here we have a very basic config. + */ +static const cyaml_config_t config = { + .log_level = CYAML_LOG_WARNING, /* Logging errors and warnings only. */ + .log_fn = cyaml_log, /* Use the default logging function. */ + .mem_fn = cyaml_mem, /* Use the default memory allocator. */ +}; + +/* Main entry point from OS. */ +int main(int argc, char *argv[]) +{ + cyaml_err_t err; + struct numbers *n; + enum { + ARG_PROG_NAME, + ARG_PATH_IN, + ARG__COUNT, + }; + + /* Handle args */ + if (argc != ARG__COUNT) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s \n", argv[ARG_PROG_NAME]); + return EXIT_FAILURE; + } + + /* Load input file. */ + err = cyaml_load_file(argv[ARG_PATH_IN], &config, + &top_schema, (cyaml_data_t **)&n, NULL); + if (err != CYAML_OK) { + fprintf(stderr, "ERROR: %s\n", cyaml_strerror(err)); + return EXIT_FAILURE; + } + + /* Use the data. */ + printf("%s:\n", n->name); + for (unsigned i = 0; i < n->data_count; i++) { + printf(" - %i\n", n->data[i]); + } + + /* Free the data */ + cyaml_free(&config, &top_schema, n, 0); + + return EXIT_SUCCESS; +} diff --git a/examples/planner/README.md b/examples/planner/README.md new file mode 100644 index 0000000..3ca5f20 --- /dev/null +++ b/examples/planner/README.md @@ -0,0 +1,8 @@ +Simple LibCYAML example +======================= + +This has a simple YAML document for project planning, and an example +LibCYAML binding to load/modify/save/free the data from C. + +The C source code is heavliy documented to help explain how to +write a CYAML schema data structure. diff --git a/examples/planner/main.c b/examples/planner/main.c new file mode 100644 index 0000000..64456ba --- /dev/null +++ b/examples/planner/main.c @@ -0,0 +1,468 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2018 Michael Drake + */ + +#include +#include + +#include + +/****************************************************************************** + * C data structure for storing a project plan. + * + * This is what we want to load the YAML into. + ******************************************************************************/ + +/* Enumeration of months of the year */ +enum months { + MONTH_JAN = 1, + MONTH_FEB, + MONTH_MAR, + MONTH_APR, + MONTH_MAY, + MONTH_JUN, + MONTH_JUL, + MONTH_AUG, + MONTH_SEP, + MONTH_OCT, + MONTH_NOV, + MONTH_DEC +}; + +/* Structure for storing dates */ +struct date { + uint8_t day; + enum months month; + uint16_t year; +}; + +/* Structure for storing durations */ +struct duration { + uint8_t hours; + unsigned days; + unsigned weeks; + unsigned years; +}; + +/* Enumeration of task flags */ +enum task_flags { + FLAGS_NONE = 0, + FLAGS_IMPORTANT = (1 << 0), + FLAGS_ENGINEERING = (1 << 1), + FLAGS_DOCUMENTATION = (1 << 2), + FLAGS_MANAGEMENT = (1 << 3), +}; + +/* Structure for storing a task */ +struct task { + const char *name; + enum task_flags flags; + struct duration estimate; + + const char **depends; + unsigned depends_count; + + const char **people; + unsigned n_people; +}; + +/* Top-level structure for storing a plan */ +struct plan { + const char *name; + struct date *start; + + const char **people; + unsigned n_people; + + struct task *tasks; + uint64_t tasks_count; +}; + + +/****************************************************************************** + * CYAML schema to tell libcyaml about both expected YAML and data structure. + * + * (Our CYAML schema is just a bunch of static const data.) + ******************************************************************************/ + +/* Mapping from "task_flags" strings to enum values for schema. */ +static const cyaml_strval_t task_flags_strings[] = { + { "None", FLAGS_NONE }, + { "Important", FLAGS_IMPORTANT }, + { "Engineering", FLAGS_ENGINEERING }, + { "Documentation", FLAGS_DOCUMENTATION }, + { "Management", FLAGS_MANAGEMENT }, +}; + +/* Mapping from "month" strings to flag values for schema. */ +static const cyaml_strval_t month_strings[] = { + { "January", MONTH_JAN }, + { "February", MONTH_FEB }, + { "March", MONTH_MAR }, + { "April", MONTH_APR }, + { "May", MONTH_MAY }, + { "June", MONTH_JUN }, + { "July", MONTH_JUL }, + { "August", MONTH_AUG }, + { "September", MONTH_SEP }, + { "October", MONTH_OCT }, + { "November", MONTH_NOV }, + { "December", MONTH_DEC }, +}; + +/* Schema for string pointer values (used in sequences of strings). */ +static const cyaml_schema_value_t string_ptr_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char, 0, CYAML_UNLIMITED), +}; + +/* The duration mapping's field definitions schema is an array. + * + * All the field entries will refer to their parent mapping's structure, + * in this case, `struct duration`. + */ +static const cyaml_schema_field_t duration_fields_schema[] = { + /* The fields here are all optional unsigned integers. + * + * Note that if an optional field is unset in the YAML, its value + * will be zero in the C data structure. + * + * In all cases here, the YAML mapping key name (first parameter to + * the macros) matches the name of the associated member of the + * `duration` structure (fourth parameter). + */ + CYAML_FIELD_UINT( + "hours", CYAML_FLAG_OPTIONAL, + struct duration, hours), + CYAML_FIELD_UINT( + "days", CYAML_FLAG_OPTIONAL, + struct duration, days), + CYAML_FIELD_UINT( + "weeks", CYAML_FLAG_OPTIONAL, + struct duration, weeks), + CYAML_FIELD_UINT( + "years", CYAML_FLAG_OPTIONAL, + struct duration, years), + + /* The field array must be terminated by an entry with a NULL key. + * Here we use the CYAML_FIELD_END macro for that. */ + CYAML_FIELD_END +}; + +/* The date mapping's field definitions schema is an array. + * + * All the field entries will refer to their parent mapping's structure, + * in this case, `struct date`. + */ +static const cyaml_schema_field_t date_fields_schema[] = { + /* The "day" and "year" fields are just normal UNIT CYAML types. + * See the comments for duration_fields_schema for details. + * The only difference is neither of these are optional. + * Note: The order of the fields in this array doesn't matter. + */ + CYAML_FIELD_UINT( + "day", CYAML_FLAG_DEFAULT, + struct date, day), + + CYAML_FIELD_UINT( + "year", CYAML_FLAG_DEFAULT, + struct date, year), + + /* The month field is an enum. + * + * YAML key: "month". + * C structure member for this key: "month". + * Array mapping strings to values: month_strings + * + * Its CYAML type is ENUM, so an array of cyaml_strval_t must be + * provided to map from string to values. + * Note that we're not setting the strict flag here so both strings and + * numbers are accepted in the YAML. (For example, both "4" and "April" + * would be accepted.) + */ + CYAML_FIELD_ENUM( + "month", CYAML_FLAG_DEFAULT, + struct date, month, month_strings, + CYAML_ARRAY_LEN(month_strings)), + + /* The field array must be terminated by an entry with a NULL key. + * Here we use the CYAML_FIELD_END macro for that. */ + CYAML_FIELD_END +}; + +/* The task mapping's field definitions schema is an array. + * + * All the field entries will refer to their parent mapping's structure, + * in this case, `struct task`. + */ +static const cyaml_schema_field_t task_fields_schema[] = { + /* The first field in the mapping is a task name. + * + * YAML key: "name". + * C structure member for this key: "name". + * + * Its CYAML type is string pointer, and we have no minimum or maximum + * string length constraints. + */ + CYAML_FIELD_STRING_PTR( + "name", CYAML_FLAG_POINTER, + struct task, name, 0, CYAML_UNLIMITED), + + /* The flags field is a flags value. + * + * YAML key: "flags". + * C structure member for this key: "flags". + * Array mapping strings to values: task_flags_strings + * + * In the YAML a CYAML flags value should be a sequence of scalars. + * The values of each set scalar is looked up the in array of + * string/value mappings, and the values are bitwise ORed together. + * + * Note that we're setting the strict flag here so only strings + * present in task_flags_strings are allowed, and numbers are not. + * + * We make the field optional so when there are no flags set, the field + * can be omitted from the YAML. + */ + CYAML_FIELD_FLAGS( + "flags", CYAML_FLAG_OPTIONAL | CYAML_FLAG_STRICT, + struct task, flags, task_flags_strings, + CYAML_ARRAY_LEN(task_flags_strings)), + + /* The next field is the task estimate. + * + * YAML key: "estimate". + * C structure member for this key: "estimate". + * + * Its CYAML type is a mapping. + * + * Since it's a mapping type, the schema for its mapping's fields must + * be provided too. In this case, it's `duration_fields_schema`. + */ + CYAML_FIELD_MAPPING( + "estimate", CYAML_FLAG_DEFAULT, + struct task, estimate, duration_fields_schema), + + /* The next field is the tasks that this task depends on. + * + * YAML key: "depends". + * C structure member for this key: "depends". + * + * Its CYAML type is a sequence. + * + * Since it's a sequence type, the value schema for its entries must + * be provided too. In this case, it's string_ptr_schema. + * + * Since it's not a sequence of a fixed-length, we must tell CYAML + * where the sequence entry count is to be stored. In this case, it + * goes in the "depends_count" C structure member in `struct task`. + * Since this is "depends" with the "_count" postfix, we can use + * the following macro, which assumes a postfix of "_count" in the + * struct member name. + */ + CYAML_FIELD_SEQUENCE( + "depends", CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL, + struct task, depends, + &string_ptr_schema, 0, CYAML_UNLIMITED), + + /* The next field is the task people. + * + * YAML key: "people". + * C structure member for this key: "people". + * + * Its CYAML type is a sequence. + * + * Since it's a sequence type, the value schema for its entries must + * be provided too. In this case, it's string_ptr_schema. + * + * Since it's not a sequence of a fixed-length, we must tell CYAML + * where the sequence entry count is to be stored. In this case, it + * goes in the "n_people" C structure member in `struct plan`. + */ + CYAML_FIELD_SEQUENCE_COUNT( + "people", CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL, + struct task, people, n_people, + &string_ptr_schema, 0, CYAML_UNLIMITED), + + /* The field array must be terminated by an entry with a NULL key. + * Here we use the CYAML_FIELD_END macro for that. */ + CYAML_FIELD_END +}; + +/* The value for a task is a mapping. + * + * Its fields are defined in task_fields_schema. + */ +static const cyaml_schema_value_t task_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT, + struct task, task_fields_schema), +}; + +/* The plan mapping's field definitions schema is an array. + * + * All the field entries will refer to their parent mapping's structure, + * in this case, `struct plan`. + */ +static const cyaml_schema_field_t plan_fields_schema[] = { + /* The first field in the mapping is a project name. + * + * YAML key: "project". + * C structure member for this key: "name". + * + * Its CYAML type is string pointer, and we have no minimum or maximum + * string length constraints. + */ + CYAML_FIELD_STRING_PTR( + "project", CYAML_FLAG_POINTER, + struct plan, name, 0, CYAML_UNLIMITED), + + /* The next field is the project start date. + * + * YAML key: "start". + * C structure member for this key: "start". + * + * Its CYAML type is a mapping pointer. + * + * Since it's a mapping type, the schema for its mapping's fields must + * be provided too. In this case, it's `date_fields_schema`. + */ + CYAML_FIELD_MAPPING_PTR( + "start", CYAML_FLAG_POINTER, + struct plan, start, date_fields_schema), + + /* The next field is the project people. + * + * YAML key: "people". + * C structure member for this key: "people". + * + * Its CYAML type is a sequence. + * + * Since it's a sequence type, the value schema for its entries must + * be provided too. In this case, it's string_ptr_schema. + * + * Since it's not a sequence of a fixed-length, we must tell CYAML + * where the sequence entry count is to be stored. In this case, it + * goes in the "n_people" C structure member in `struct plan`. + */ + CYAML_FIELD_SEQUENCE_COUNT( + "people", CYAML_FLAG_POINTER, + struct plan, people, n_people, + &string_ptr_schema, 0, CYAML_UNLIMITED), + + /* The next field is the project tasks. + * + * YAML key: "tasks". + * C structure member for this key: "tasks". + * + * Its CYAML type is a sequence. + * + * Since it's a sequence type, the value schema for its entries must + * be provided too. In this case, it's task_schema. + * + * Since it's not a sequence of a fixed-length, we must tell CYAML + * where the sequence entry count is to be stored. In this case, it + * goes in the "tasks_count" C structure member in `struct plan`. + * Since this is "tasks" with the "_count" postfix, we can use + * the following macro, which assumes a postfix of "_count" in the + * struct member name. + */ + CYAML_FIELD_SEQUENCE( + "tasks", CYAML_FLAG_POINTER, + struct plan, tasks, + &task_schema, 0, CYAML_UNLIMITED), + + /* If the YAML contains a field that our program is not interested in + * we can ignore it, so the value of the field will not be loaded. + * + * Note that unless the OPTIONAL flag is set, the ignored field must + * be present. + * + * Another way of handling this would be to use the config flag + * to ignore unknown keys. This config is passed to libcyaml + * separately from the schema. + */ + CYAML_FIELD_IGNORE("irrelevant", CYAML_FLAG_OPTIONAL), + + /* The field array must be terminated by an entry with a NULL key. + * Here we use the CYAML_FIELD_END macro for that. */ + CYAML_FIELD_END +}; + +/* Top-level schema. The top level value for the plan is a mapping. + * + * Its fields are defined in plan_fields_schema. + */ +static const cyaml_schema_value_t plan_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct plan, plan_fields_schema), +}; + + +/****************************************************************************** + * Actual code to load and save YAML doc using libcyaml. + ******************************************************************************/ + +/* Our CYAML config. + * + * If you want to change it between calls, don't make it const. + * + * Here we have a very basic config. + */ +static const cyaml_config_t config = { + .log_level = CYAML_LOG_WARNING, /* Logging errors and warnings only. */ + .log_fn = cyaml_log, /* Use the default logging function. */ + .mem_fn = cyaml_mem, /* Use the default memory allocator. */ +}; + +/* Main entry point from OS. */ +int main(int argc, char *argv[]) +{ + cyaml_err_t err; + struct plan *plan; + enum { + ARG_PROG_NAME, + ARG_PATH_IN, + ARG_PATH_OUT, + ARG__COUNT, + }; + + /* Handle args */ + if (argc != ARG__COUNT) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s \n", argv[ARG_PROG_NAME]); + return EXIT_FAILURE; + } + + /* Load input file. */ + err = cyaml_load_file(argv[ARG_PATH_IN], &config, + &plan_schema, (void **) &plan, NULL); + if (err != CYAML_OK) { + fprintf(stderr, "ERROR: %s\n", cyaml_strerror(err)); + return EXIT_FAILURE; + } + + /* Use the data. */ + printf("Project: %s\n", plan->name); + for (unsigned i = 0; i < plan->tasks_count; i++) { + printf("%u. %s\n", i + 1, plan->tasks[i].name); + } + + /* Modify the data */ + plan->tasks[0].estimate.days += 3; + plan->tasks[0].estimate.weeks += 1; + + /* Save data to new YAML file. */ + err = cyaml_save_file(argv[ARG_PATH_OUT], &config, + &plan_schema, plan, 0); + if (err != CYAML_OK) { + fprintf(stderr, "ERROR: %s\n", cyaml_strerror(err)); + cyaml_free(&config, &plan_schema, plan, 0); + return EXIT_FAILURE; + } + + /* Free the data */ + cyaml_free(&config, &plan_schema, plan, 0); + + return EXIT_SUCCESS; +} diff --git a/examples/planner/project.yaml b/examples/planner/project.yaml new file mode 100644 index 0000000..40aeb6c --- /dev/null +++ b/examples/planner/project.yaml @@ -0,0 +1,58 @@ +project: Write new browser layout engine. +start: + day: 1 + month: June + year: 2018 +people: + - Stephen + - Neil + - Alex +irrelevant: + - details: + - the app doesn't need this stuff + - so it should be able to ignore it + +tasks: + - name: Read the HTML and CSS specs. + flags: + - Important + estimate: + weeks: 2 + people: + - Stephen + - Neil + + - name: Think of name for library. + estimate: + hours: 1 + + - name: Create project repo. + estimate: + hours: 1 + depends: + - Think of name for library. + people: + - Alex + + - name: Initial design of library API. + flags: + - Important + - Engineering + depends: + - Read the HTML and CSS specs. + - Create project repo. + estimate: + days: 1 + + - name: Plan the initial implementation + flags: + - Engineering + - Documentation + - Management + depends: + - Initial design of library API. + estimate: + days: 6 + people: + - Stephen + - Alex diff --git a/include/cyaml/cyaml.h b/include/cyaml/cyaml.h new file mode 100644 index 0000000..59d9019 --- /dev/null +++ b/include/cyaml/cyaml.h @@ -0,0 +1,1616 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2017-2019 Michael Drake + */ + +/** + * \file + * \brief CYAML library public header. + * + * CYAML is a C library for parsing and serialising structured YAML documents. + */ + +#ifndef CYAML_H +#define CYAML_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include +#include + +/** + * CYAML library version string. + */ +extern const char *cyaml_version_str; + +/** + * CYAML library version number suitable for comparisons. + * + * Version number binary composition is `0bRUUUUUUUJJJJJJJJNNNNNNNNPPPPPPPP`. + * + * | Character | Meaning | + * | --------- | -------------------------------------- | + * | `R` | Release flag. If set, it's a release. | + * | `U` | Unused / reserved. | + * | `J` | Major component of version. | + * | `N` | Minor component of version. | + * | `P` | Patch component of version. | + */ +extern const uint32_t cyaml_version; + +/** + * CYAML value types. + * + * These are the fundamental data types that apply to "values" in CYAML. + * + * CYAML "values" are represented in by \ref cyaml_schema_value. + */ +typedef enum cyaml_type { + CYAML_INT, /**< Value is a signed integer. */ + CYAML_UINT, /**< Value is an unsigned signed integer. */ + CYAML_BOOL, /**< Value is a boolean. */ + /** + * Value is an enum. Values of this type require a string / value + * mapping array in the schema entry, to define the list of valid + * enum values. + */ + CYAML_ENUM, + /** + * Value is a flags bit field. Values of this type require a string / + * value list in the schema entry, to define the list of valid flag + * values. Each bit is a boolean flag. To store values of various + * bit sizes, use a \ref CYAML_BITFIELD instead. + * + * In the YAML, a \ref CYAML_FLAGS value must be presented as a + * sequence of strings. + */ + CYAML_FLAGS, + CYAML_FLOAT, /**< Value is floating point. */ + CYAML_STRING, /**< Value is a string. */ + /** + * Value is a mapping. Values of this type require mapping schema + * array in the schema entry. + */ + CYAML_MAPPING, + /** + * Value is a bit field. Values of this type require an array of value + * definitions in the schema entry. If the bitfield is used to store + * only single-bit flags, it may be better to use \ref CYAML_FLAGS + * instead. + * + * In the YAML, a \ref CYAML_FLAGS value must be presented as a + * mapping of bitfield entry names to their numerical values. + */ + CYAML_BITFIELD, + /** + * Value is a sequence. Values of this type must be the direct + * children of a mapping. They require: + * + * - A schema describing the type of the sequence entries. + * - 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 of sequence count entries. + * Set `max` to \ref CYAML_UNLIMITED to make array count + * unconstrained. + */ + CYAML_SEQUENCE, + /** + * Value is a **fixed length** sequence. It is similar to \ref + * CYAML_SEQUENCE, however: + * + * - Values of this type do not need to be direct children of a mapping. + * - The minimum and maximum entry count must be the same. If not + * \ref CYAML_ERR_SEQUENCE_FIXED_COUNT will be returned. + * - Thee offset and size of the count structure member is unused. + * Because the count is a schema-defined constant, it does not need + * to be recorded. + */ + CYAML_SEQUENCE_FIXED, + /** + * Value of this type is completely ignored. This is most useful for + * ignoring particular keys in a mapping, when CYAML client sets + * configuration of \ref CYAML_CFG_IGNORE_UNKNOWN_KEYS. + */ + CYAML_IGNORE, + /** + * Count of the valid CYAML types. This value is **not a valid type** + * itself. + */ + CYAML__TYPE_COUNT, +} cyaml_type_e; + +/** + * CYAML value flags. + * + * These may be bitwise-ORed together. + */ +typedef enum cyaml_flag { + CYAML_FLAG_DEFAULT = 0, /**< Default value flags (none set). */ + CYAML_FLAG_OPTIONAL = (1 << 0), /**< Mapping field is optional. */ + /** + * Value is a pointer to its type. + * + * With this there must be a non-NULL value. Consider using + * \ref CYAML_FLAG_POINTER_NULL or \ref CYAML_FLAG_POINTER_NULL_STR + * if you want to allow NULL values. + */ + CYAML_FLAG_POINTER = (1 << 1), + /** + * Permit `NULL` values for \ref CYAML_FLAG_POINTER types. + * + * An empty value in the YAML is loaded as a NULL pointer, and NULL + * pointers are saved in YAML as empty values. + * + * Note, when you set \ref CYAML_FLAG_POINTER_NULL, then + * \ref CYAML_FLAG_POINTER is set automatically. + */ + CYAML_FLAG_POINTER_NULL = (1 << 2) | CYAML_FLAG_POINTER, + /** + * Permit storage of `NULL` values as special NULL strings in YAML. + * + * This extends \ref CYAML_FLAG_POINTER_NULL, but in addition to + * treating empty values as NULL, any of the following are also treated + * as NULL: + * + * * `null`, + * * `Null`, + * * `NULL`, + * * `~`, + * + * Note that as a side effect, loading a \ref CYAML_STRING field with + * one of these values will not store the literal string, it will store + * NULL. + * + * When saving, a NULL value will be recorded in the YAML as `null`. + * + * Note, when you set \ref CYAML_FLAG_POINTER_NULL_STR, then both + * \ref CYAML_FLAG_POINTER and \ref CYAML_FLAG_POINTER_NULL are set + * automatically. + */ + CYAML_FLAG_POINTER_NULL_STR = (1 << 3) | CYAML_FLAG_POINTER_NULL, + /** + * Make value handling strict. + * + * For \ref CYAML_ENUM and \ref CYAML_FLAGS types, in strict mode + * the YAML must contain a matching string. Without strict, numerical + * values are also permitted. + * + * * For \ref CYAML_ENUM, the value becomes the value of the enum. + * The numerical value is treated as signed. + * * For \ref CYAML_FLAGS, the values are bitwise ORed together. + * The numerical values are treated as unsigned, + */ + CYAML_FLAG_STRICT = (1 << 4), + /** + * When saving, emit mapping / sequence value in block style. + * + * This can be used to override, for this value, any default style set + * in the \ref cyaml_cfg_flags CYAML behavioural configuration flags. + * + * \note This is ignored unless the value's type is \ref CYAML_MAPPING, + * \ref CYAML_SEQUENCE, or \ref CYAML_SEQUENCE_FIXED. + * + * \note If both \ref CYAML_FLAG_BLOCK and \ref CYAML_FLAG_FLOW are set, + * then block style takes precedence. + * + * \note If neither block nor flow style set either here, or in the + * \ref cyaml_cfg_flags CYAML behavioural configuration flags, + * then libyaml's default behaviour is used. + */ + CYAML_FLAG_BLOCK = (1 << 5), + /** + * When saving, emit mapping / sequence value in flow style. + * + * This can be used to override, for this value, any default style set + * in the \ref cyaml_cfg_flags CYAML behavioural configuration flags. + * + * \note This is ignored unless the value's type is \ref CYAML_MAPPING, + * \ref CYAML_SEQUENCE, or \ref CYAML_SEQUENCE_FIXED. + * + * \note If both \ref CYAML_FLAG_BLOCK and \ref CYAML_FLAG_FLOW are set, + * then block style takes precedence. + * + * \note If neither block nor flow style set either here, or in the + * \ref cyaml_cfg_flags CYAML behavioural configuration flags, + * then libyaml's default behaviour is used. + */ + CYAML_FLAG_FLOW = (1 << 6), + /** + * When comparing strings for this value, compare with case sensitivity. + * + * By default, strings are compared with case sensitivity. + * If \ref CYAML_CFG_CASE_INSENSITIVE is set, this can override + * the configured behaviour for this specific value. + * + * \note If both \ref CYAML_FLAG_CASE_SENSITIVE and + * \ref CYAML_FLAG_CASE_INSENSITIVE are set, + * then case insensitive takes precedence. + * + * \note This applies to values of types \ref CYAML_MAPPING, + * \ref CYAML_ENUM, and \ref CYAML_FLAGS. For mappings, + * it applies to matching of the mappings' keys. For + * enums and flags it applies to the comparison of + * \ref cyaml_strval strings. + */ + CYAML_FLAG_CASE_SENSITIVE = (1 << 7), + /** + * When comparing strings for this value, compare with case sensitivity. + * + * By default, strings are compared with case sensitivity. + * If \ref CYAML_CFG_CASE_INSENSITIVE is set, this can override + * the configured behaviour for this specific value. + * + * \note If both \ref CYAML_FLAG_CASE_SENSITIVE and + * \ref CYAML_FLAG_CASE_INSENSITIVE are set, + * then case insensitive takes precedence. + * + * \note This applies to values of types \ref CYAML_MAPPING, + * \ref CYAML_ENUM, and \ref CYAML_FLAGS. For mappings, + * it applies to matching of the mappings' keys. For + * enums and flags it applies to the comparison of + * \ref cyaml_strval strings. + */ + CYAML_FLAG_CASE_INSENSITIVE = (1 << 8), +} cyaml_flag_e; + +/** + * Mapping between a string and a signed value. + * + * Used for \ref CYAML_ENUM and \ref CYAML_FLAGS types. + */ +typedef struct cyaml_strval { + const char *str; /**< String representing enum or flag value. */ + int64_t val; /**< Value of given string. */ +} cyaml_strval_t; + +/** + * Bitfield value info. + * + * Used for \ref CYAML_BITFIELD type. + */ +typedef struct cyaml_bitdef { + const char *name; /**< String representing the value's name. */ + uint8_t offset; /**< Bit offset to value in bitfield. */ + uint8_t bits; /**< Maximum bits available for value. */ +} cyaml_bitdef_t; + +/** + * Schema definition for a value. + * + * \note There are convenience macros for each of the types to assist in + * building a CYAML schema data structure for your YAML documents. + * + * This is the fundamental building block of CYAML schemas. The load, save and + * free functions take parameters of this type to explain what the top-level + * type of the YAML document should be. + * + * Values of type \ref CYAML_SEQUENCE and \ref CYAML_SEQUENCE_FIXED + * contain a reference to another \ref cyaml_schema_value representing + * the type of the entries of the sequence. For example, if you want + * a sequence of integers, you'd have a \ref cyaml_schema_value for the + * for the sequence value type, and another for the integer value type. + * + * Values of type \ref CYAML_MAPPING contain an array of \ref cyaml_schema_field + * entries, defining the YAML keys allowed by the mapping. Each field contains + * a \ref cyaml_schema_value representing the schema for the value. + */ +typedef struct cyaml_schema_value { + /** + * The type of the value defined by this schema entry. + */ + enum cyaml_type type; + /** Flags indicating value's characteristics. */ + enum cyaml_flag flags; + /** + * Size of the value's client data type in bytes. + * + * For example, `short` `int`, `long`, `int8_t`, etc are all signed + * integer types, so they would have the type \ref CYAML_INT, + * however, they have different sizes. + */ + uint32_t data_size; + /** Anonymous union containing type-specific attributes. */ + union { + /** \ref CYAML_STRING 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; + } string; + /** \ref CYAML_MAPPING type-specific schema data. */ + struct { + /** + * Array of cyaml mapping field schema definitions. + * + * The array must be terminated by an entry with a + * NULL key. See \ref cyaml_schema_field_t + * and \ref CYAML_FIELD_END for more info. + */ + const struct cyaml_schema_field *fields; + } mapping; + /** \ref CYAML_BITFIELD type-specific schema data. */ + struct { + /** Array of bit definitions for the bitfield. */ + const struct cyaml_bitdef *bitdefs; + /** Entry count for bitdefs array. */ + uint32_t count; + } bitfield; + /** + * \ref CYAML_SEQUENCE and \ref CYAML_SEQUENCE_FIXED + * type-specific schema data. + */ + struct { + /** + * Schema definition for the type of the entries in the + * sequence. + * + * All of a sequence's entries must be of the same + * type, and a sequence can not have an entry type of + * type \ref CYAML_SEQUENCE (although \ref + * CYAML_SEQUENCE_FIXED is allowed). That is, you + * can't have a sequence of variable-length sequences. + */ + const struct cyaml_schema_value *entry; + /** + * Minimum number of sequence entries. + * + * \note min and max must be the same for \ref + * CYAML_SEQUENCE_FIXED. + */ + uint32_t min; + /** + * Maximum number of sequence entries. + * + * \note min and max must be the same for \ref + * CYAML_SEQUENCE_FIXED. + */ + uint32_t max; + } sequence; + /** + * \ref CYAML_ENUM and \ref CYAML_FLAGS type-specific schema + * data. + */ + struct { + /** Array of string / value mappings defining enum. */ + const cyaml_strval_t *strings; + /** Entry count for strings array. */ + uint32_t count; + } enumeration; + }; +} cyaml_schema_value_t; + +/** + * Schema definition entry for mapping fields. + * + * YAML mappings are key:value pairs. CYAML only supports scalar mapping keys, + * i.e. the keys must be strings. Each mapping field schema contains a + * \ref cyaml_schema_value to define field's value. + * + * The schema for mappings is composed of an array of entries of this + * data structure. It specifies the name of the key, and the type of + * the value. It also specifies the offset into the data at which value + * data should be placed. The array is terminated by an entry with a NULL key. + */ +typedef struct cyaml_schema_field { + /** + * String for YAML mapping key that his schema entry describes, + * or NULL to indicated the end of an array of \ref cyaml_schema_field + * entries. + */ + const char *key; + /** + * Offset in data structure at which the value for this key should + * be placed / read from. + */ + uint32_t data_offset; + /** + * \ref CYAML_SEQUENCE only: Offset to sequence + * entry count member in mapping's data structure. + */ + uint32_t count_offset; + /** + * \ref CYAML_SEQUENCE only: Size in bytes of sequence + * entry count member in mapping's data structure. + */ + uint8_t count_size; + /** + * Defines the schema for the mapping field's value. + */ + struct cyaml_schema_value value; +} cyaml_schema_field_t; + +/** + * CYAML behavioural configuration flags for clients + * + * These may be bitwise-ORed together. + */ +typedef enum cyaml_cfg_flags { + /** + * This indicates CYAML's default behaviour. + */ + CYAML_CFG_DEFAULT = 0, + /** + * When set, unknown mapping keys are ignored when loading YAML. + * Without this flag set, CYAML's default behaviour is to return + * with the error \ref CYAML_ERR_INVALID_KEY. + */ + CYAML_CFG_IGNORE_UNKNOWN_KEYS = (1 << 0), + /** + * When saving, emit mapping / sequence values in block style. + * + * This setting can be overridden for specific values using schema + * value flags (\ref cyaml_flag). + * + * \note This only applies to values of type \ref CYAML_MAPPING, + * \ref CYAML_SEQUENCE, or \ref CYAML_SEQUENCE_FIXED. + * + * \note If both \ref CYAML_CFG_STYLE_BLOCK and + * \ref CYAML_CFG_STYLE_FLOW are set, then block style takes + * precedence. + */ + CYAML_CFG_STYLE_BLOCK = (1 << 1), + /** + * When saving, emit mapping / sequence values in flow style. + * + * This setting can be overridden for specific values using schema + * value flags (\ref cyaml_flag). + * + * \note This only applies to values of type \ref CYAML_MAPPING, + * \ref CYAML_SEQUENCE, or \ref CYAML_SEQUENCE_FIXED. + * + * \note If both \ref CYAML_CFG_STYLE_BLOCK and + * \ref CYAML_CFG_STYLE_FLOW are set, then block style takes + * precedence. + */ + CYAML_CFG_STYLE_FLOW = (1 << 2), + /** + * When saving, emit "---" at document start and "..." at document end. + * + * If this flag isn't set, these document delimiting marks will not + * be emitted. + */ + CYAML_CFG_DOCUMENT_DELIM = (1 << 3), + /** + * When comparing strings, compare without case sensitivity. + * + * By default, strings are compared with case sensitivity. + */ + CYAML_CFG_CASE_INSENSITIVE = (1 << 4), + /** + * When loading, don't allow YAML aliases in the document. + * + * If this option is enabled, anchors will be ignored, and the + * error code \ref CYAML_ERR_ALIAS will be returned if an alias + * is encountered. + * + * Setting this removes the overhead of recording anchors, so + * it may be worth setting if aliases are not required, and + * memory is constrained. + */ + CYAML_CFG_NO_ALIAS = (1 << 5), +} cyaml_cfg_flags_t; + +/** + * CYAML function return codes indicating success or reason for failure. + * + * Use \ref cyaml_strerror() to convert an error code to a human-readable + * string. + */ +typedef enum cyaml_err { + CYAML_OK, /**< Success. */ + CYAML_ERR_OOM, /**< Memory allocation failed. */ + CYAML_ERR_ALIAS, /**< See \ref CYAML_CFG_NO_ALIAS. */ + CYAML_ERR_FILE_OPEN, /**< Failed to open file. */ + CYAML_ERR_INVALID_KEY, /**< Mapping key rejected by schema. */ + 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_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. */ + CYAML_ERR_INVALID_DATA_SIZE, /**< Value's data size unsupported. */ + CYAML_ERR_TOP_LEVEL_NON_PTR, /**< Top level type must be pointer. */ + CYAML_ERR_BAD_TYPE_IN_SCHEMA, /**< Schema contains invalid type. */ + CYAML_ERR_BAD_MIN_MAX_SCHEMA, /**< Schema minimum exceeds maximum. */ + CYAML_ERR_BAD_PARAM_SEQ_COUNT, /**< Bad seq_count param for schema. */ + CYAML_ERR_BAD_PARAM_NULL_DATA, /**< Client gave NULL data argument. */ + CYAML_ERR_BAD_BITVAL_IN_SCHEMA, /**< Bit value beyond bitfield size. */ + CYAML_ERR_SEQUENCE_ENTRIES_MIN, /**< Too few sequence entries. */ + CYAML_ERR_SEQUENCE_ENTRIES_MAX, /**< Too many sequence entries. */ + CYAML_ERR_SEQUENCE_FIXED_COUNT, /**< Mismatch between min and max. */ + CYAML_ERR_SEQUENCE_IN_SEQUENCE, /**< Non-fixed sequence in sequence. */ + CYAML_ERR_MAPPING_FIELD_MISSING, /**< Required mapping field missing. */ + CYAML_ERR_BAD_CONFIG_NULL_MEMFN, /**< Client gave NULL mem function. */ + CYAML_ERR_BAD_PARAM_NULL_CONFIG, /**< Client gave NULL config arg. */ + CYAML_ERR_BAD_PARAM_NULL_SCHEMA, /**< Client gave NULL schema arg. */ + CYAML_ERR_LIBYAML_EMITTER_INIT, /**< Failed to initialise libyaml. */ + CYAML_ERR_LIBYAML_PARSER_INIT, /**< Failed to initialise libyaml. */ + CYAML_ERR_LIBYAML_EVENT_INIT, /**< Failed to initialise libyaml. */ + CYAML_ERR_LIBYAML_EMITTER, /**< Error inside libyaml emitter. */ + CYAML_ERR_LIBYAML_PARSER, /**< Error inside libyaml parser. */ + CYAML_ERR__COUNT, /**< Count of CYAML return codes. + * This is **not a valid return + * code** itself. + */ +} cyaml_err_t; + +/** + * Value schema helper macro for values with \ref CYAML_INT type. + * + * \param[in] _flags Any behavioural flags relevant to this value. + * \param[in] _type The C type for this value. + */ +#define CYAML_VALUE_INT( \ + _flags, _type) \ + .type = CYAML_INT, \ + .flags = (_flags), \ + .data_size = sizeof(_type) + +/** + * Mapping schema helper macro for keys with \ref CYAML_INT type. + * + * Use this for integers contained in structs. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + */ +#define CYAML_FIELD_INT( \ + _key, _flags, _structure, _member) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_INT(((_flags) & (~CYAML_FLAG_POINTER)), \ + (((_structure *)NULL)->_member)), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Mapping schema helper macro for keys with \ref CYAML_INT type. + * + * Use this for pointers to integers. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + */ +#define CYAML_FIELD_INT_PTR( \ + _key, _flags, _structure, _member) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_INT(((_flags) | CYAML_FLAG_POINTER), \ + (*(((_structure *)NULL)->_member))), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Value schema helper macro for values with \ref CYAML_UINT type. + * + * \param[in] _flags Any behavioural flags relevant to this value. + * \param[in] _type The C type for this value. + */ +#define CYAML_VALUE_UINT( \ + _flags, _type) \ + .type = CYAML_UINT, \ + .flags = (_flags), \ + .data_size = sizeof(_type) + +/** + * Mapping schema helper macro for keys with \ref CYAML_UINT type. + * + * Use this for unsigned integers contained in structs. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + */ +#define CYAML_FIELD_UINT( \ + _key, _flags, _structure, _member) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_UINT(((_flags) & (~CYAML_FLAG_POINTER)), \ + (((_structure *)NULL)->_member)), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Mapping schema helper macro for keys with \ref CYAML_UINT type. + * + * Use this for pointers to unsigned integers. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + */ +#define CYAML_FIELD_UINT_PTR( \ + _key, _flags, _structure, _member) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_UINT(((_flags) | CYAML_FLAG_POINTER), \ + (*(((_structure *)NULL)->_member))), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Value schema helper macro for values with \ref CYAML_BOOL type. + * + * \param[in] _flags Any behavioural flags relevant to this value. + * \param[in] _type The C type for this value. + */ +#define CYAML_VALUE_BOOL( \ + _flags, _type) \ + .type = CYAML_BOOL, \ + .flags = (_flags), \ + .data_size = sizeof(_type) + +/** + * Mapping schema helper macro for keys with \ref CYAML_BOOL type. + * + * Use this for boolean types contained in structs. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + */ +#define CYAML_FIELD_BOOL( \ + _key, _flags, _structure, _member) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_BOOL(((_flags) & (~CYAML_FLAG_POINTER)), \ + (((_structure *)NULL)->_member)), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Mapping schema helper macro for keys with \ref CYAML_BOOL type. + * + * Use this for pointers to boolean types. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + */ +#define CYAML_FIELD_BOOL_PTR( \ + _key, _flags, _structure, _member) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_BOOL(((_flags) | CYAML_FLAG_POINTER), \ + (*(((_structure *)NULL)->_member))), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Value schema helper macro for values with \ref CYAML_ENUM type. + * + * \param[in] _flags Any behavioural flags relevant to this value. + * \param[in] _type The C type for this value. + * \param[in] _strings Array of string data for enumeration values. + * \param[in] _strings_count Number of entries in _strings. + */ +#define CYAML_VALUE_ENUM( \ + _flags, _type, _strings, _strings_count) \ + .type = CYAML_ENUM, \ + .flags = (_flags), \ + .data_size = sizeof(_type), \ + .enumeration = { \ + .strings = _strings, \ + .count = _strings_count, \ + } + +/** + * Mapping schema helper macro for keys with \ref CYAML_ENUM type. + * + * Use this for enumerated types contained in structs. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + * \param[in] _strings Array of string data for enumeration values. + * \param[in] _strings_count Number of entries in _strings. + */ +#define CYAML_FIELD_ENUM( \ + _key, _flags, _structure, _member, _strings, _strings_count) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_ENUM(((_flags) & (~CYAML_FLAG_POINTER)), \ + (((_structure *)NULL)->_member), \ + _strings, _strings_count), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Mapping schema helper macro for keys with \ref CYAML_ENUM type. + * + * Use this for pointers to enumerated types. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + * \param[in] _strings Array of string data for enumeration values. + * \param[in] _strings_count Number of entries in _strings. + */ +#define CYAML_FIELD_ENUM_PTR( \ + _key, _flags, _structure, _member, _strings, _strings_count) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_ENUM(((_flags) | CYAML_FLAG_POINTER), \ + (*(((_structure *)NULL)->_member)), \ + _strings, _strings_count), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Value schema helper macro for values with \ref CYAML_FLAGS type. + * + * \param[in] _flags Any behavioural flags relevant to this value. + * \param[in] _type The C type for this value. + * \param[in] _strings Array of string data for flag values. + * \param[in] _strings_count Number of entries in _strings. + */ +#define CYAML_VALUE_FLAGS( \ + _flags, _type, _strings, _strings_count) \ + .type = CYAML_FLAGS, \ + .flags = (_flags), \ + .data_size = sizeof(_type), \ + .enumeration = { \ + .strings = _strings, \ + .count = _strings_count, \ + } + +/** + * Mapping schema helper macro for keys with \ref CYAML_FLAGS type. + * + * Use this for flag types contained in structs. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + * \param[in] _strings Array of string data for flag values. + * \param[in] _strings_count Number of entries in _strings. + */ +#define CYAML_FIELD_FLAGS( \ + _key, _flags, _structure, _member, _strings, _strings_count) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_FLAGS(((_flags) & (~CYAML_FLAG_POINTER)), \ + (((_structure *)NULL)->_member), \ + _strings, _strings_count), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Mapping schema helper macro for keys with \ref CYAML_FLAGS type. + * + * Use this for pointers to flag types. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + * \param[in] _strings Array of string data for flag values. + * \param[in] _strings_count Number of entries in _strings. + */ +#define CYAML_FIELD_FLAGS_PTR( \ + _key, _flags, _structure, _member, _strings, _strings_count) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_FLAGS(((_flags) | CYAML_FLAG_POINTER), \ + (*(((_structure *)NULL)->_member)), \ + _strings, _strings_count), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Value schema helper macro for values with \ref CYAML_BITFIELD type. + * + * \param[in] _flags Any behavioural flags relevant to this value. + * \param[in] _type The C type for this value. + * \param[in] _bitvals Array of bitfield value data for the bitfield. + * \param[in] _bitvals_count Number of entries in _bitvals. + */ +#define CYAML_VALUE_BITFIELD( \ + _flags, _type, _bitvals, _bitvals_count) \ + .type = CYAML_BITFIELD, \ + .flags = (_flags), \ + .data_size = sizeof(_type), \ + .bitfield = { \ + .bitdefs = _bitvals, \ + .count = _bitvals_count, \ + } + +/** + * Mapping schema helper macro for keys with \ref CYAML_BITFIELD type. + * + * Use this for bitfield types contained in structs. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + * \param[in] _bitvals Array of bitfield value data for the bitfield. + * \param[in] _bitvals_count Number of entries in _bitvals. + */ +#define CYAML_FIELD_BITFIELD( \ + _key, _flags, _structure, _member, _bitvals, _bitvals_count) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_BITFIELD(((_flags) & (~CYAML_FLAG_POINTER)), \ + (((_structure *)NULL)->_member), \ + _bitvals, _bitvals_count), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Mapping schema helper macro for keys with \ref CYAML_BITFIELD type. + * + * Use this for pointers to bitfied types. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + * \param[in] _bitvals Array of bitfield value data for the bitfield. + * \param[in] _bitvals_count Number of entries in _bitvals. + */ +#define CYAML_FIELD_BITFIELD_PTR( \ + _key, _flags, _structure, _member, _bitvals, _bitvals_count) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_BITFIELD(((_flags) | CYAML_FLAG_POINTER), \ + (*(((_structure *)NULL)->_member)), \ + _bitvals, _bitvals_count), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Value schema helper macro for values with \ref CYAML_FLOAT type. + * + * \param[in] _flags Any behavioural flags relevant to this value. + * \param[in] _type The C type for this value. + */ +#define CYAML_VALUE_FLOAT( \ + _flags, _type) \ + .type = CYAML_FLOAT, \ + .flags = (_flags), \ + .data_size = sizeof(_type) + +/** + * Mapping schema helper macro for keys with \ref CYAML_FLOAT type. + * + * Use this for floating point types contained in structs. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + */ +#define CYAML_FIELD_FLOAT( \ + _key, _flags, _structure, _member) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_FLOAT(((_flags) & (~CYAML_FLAG_POINTER)), \ + (((_structure *)NULL)->_member)), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Mapping schema helper macro for keys with \ref CYAML_FLOAT type. + * + * Use this for pointers to floating point types. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + */ +#define CYAML_FIELD_FLOAT_PTR( \ + _key, _flags, _structure, _member) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_FLOAT(((_flags) | CYAML_FLAG_POINTER), \ + (*(((_structure *)NULL)->_member))), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Value schema helper macro for values with \ref CYAML_STRING type. + * + * \note If the string is an array (`char str[N];`) then the \ref + * CYAML_FLAG_POINTER flag must **not** be set, and the max + * length must be no more than `N-1`. + * + * If the string is a pointer (`char *str;`), then the \ref + * CYAML_FLAG_POINTER flag **must be set**. + * + * \param[in] _flags Any behavioural flags relevant to this value. + * \param[in] _type The C type for this value. + * \param[in] _min Minimum string length in bytes. + * Excludes trailing '\0'. + * \param[in] _max The C type for this value. + * Excludes trailing '\0'. + */ +#define CYAML_VALUE_STRING( \ + _flags, _type, _min, _max) \ + .type = CYAML_STRING, \ + .flags = (_flags), \ + .data_size = sizeof(_type), \ + .string = { \ + .min = _min, \ + .max = _max, \ + } + +/** + * Mapping schema helper macro for keys with \ref CYAML_STRING type. + * + * Use this for fields with C array type, e.g. `char str[N];`. This fills the + * maximum string length (`N-1`) out automatically. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + * \param[in] _min Minimum string length in bytes. Excludes '\0'. + */ +#define CYAML_FIELD_STRING( \ + _key, _flags, _structure, _member, _min) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_STRING(((_flags) & (~CYAML_FLAG_POINTER)), \ + (((_structure *)NULL)->_member), _min, \ + sizeof(((_structure *)NULL)->_member) - 1), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Mapping schema helper macro for keys with \ref CYAML_STRING type. + * + * Use this for fields with C pointer type, e.g. `char *str;`. This creates + * a separate allocation for the string data, and fills in the pointer. + * + * Use `0` for _min and \ref CYAML_UNLIMITED for _max for unconstrained string + * lengths. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + * \param[in] _min Minimum string length in bytes. Excludes '\0'. + * \param[in] _max Maximum string length in bytes. Excludes '\0'. + */ +#define CYAML_FIELD_STRING_PTR( \ + _key, _flags, _structure, _member, _min, _max) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_STRING(((_flags) | CYAML_FLAG_POINTER), \ + (((_structure *)NULL)->_member), \ + _min, _max), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Value schema helper macro for values with \ref CYAML_MAPPING type. + * + * \param[in] _flags Any behavioural flags relevant to this value. + * \param[in] _type The C type of structure corresponding to mapping. + * \param[in] _fields Pointer to mapping fields schema array. + */ +#define CYAML_VALUE_MAPPING( \ + _flags, _type, _fields) \ + .type = CYAML_MAPPING, \ + .flags = (_flags), \ + .data_size = sizeof(_type), \ + .mapping = { \ + .fields = _fields, \ + } + +/** + * Mapping schema helper macro for keys with \ref CYAML_MAPPING type. + * + * Use this for structures contained within other structures. + * + * \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 corresponding to the containing mapping. + * \param[in] _member The member in _structure for this mapping value. + * \param[in] _fields Pointer to mapping fields schema array. + */ +#define CYAML_FIELD_MAPPING( \ + _key, _flags, _structure, _member, _fields) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_MAPPING(((_flags) & (~CYAML_FLAG_POINTER)), \ + (((_structure *)NULL)->_member), _fields), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Mapping schema helper macro for keys with \ref CYAML_MAPPING type. + * + * Use this for pointers to structures. + * + * \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 corresponding to the containing mapping. + * \param[in] _member The member in _structure for this mapping value. + * \param[in] _fields Pointer to mapping fields schema array. + */ +#define CYAML_FIELD_MAPPING_PTR( \ + _key, _flags, _structure, _member, _fields) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_MAPPING(((_flags) | CYAML_FLAG_POINTER), \ + (*(((_structure *)NULL)->_member)), _fields), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Value schema helper macro for values with \ref CYAML_SEQUENCE type. + * + * \param[in] _flags Any behavioural flags relevant to this value. + * \param[in] _type The C type of sequence **entries**. + * \param[in] _entry Pointer to schema for the **entries** in sequence. + * \param[in] _min Minimum number of sequence entries required. + * \param[in] _max Maximum number of sequence entries required. + */ +#define CYAML_VALUE_SEQUENCE( \ + _flags, _type, _entry, _min, _max) \ + .type = CYAML_SEQUENCE, \ + .flags = (_flags), \ + .data_size = sizeof(_type), \ + .sequence = { \ + .entry = _entry, \ + .min = _min, \ + .max = _max, \ + } + +/** + * Mapping schema helper macro for keys with \ref CYAML_SEQUENCE type. + * + * To use this, there must be a member in {_structure} called "{_member}_count", + * for storing the number of entries in the sequence. + * + * For example, for the following structure: + * + * ``` + * struct my_structure { + * unsigned *my_sequence; + * unsigned my_sequence_count; + * }; + * ``` + * + * Pass the following as parameters: + * + * | Parameter | Value | + * | ---------- | --------------------- | + * | _structure | `struct my_structure` | + * | _member | `my_sequence` | + * + * If you want to call the structure member for storing the sequence entry + * count something else, then use \ref CYAML_FIELD_SEQUENCE_COUNT 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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + * \param[in] _entry Pointer to schema for the **entries** in sequence. + * \param[in] _min Minimum number of sequence entries required. + * \param[in] _max Maximum number of sequence entries required. + */ +#define CYAML_FIELD_SEQUENCE( \ + _key, _flags, _structure, _member, _entry, _min, _max) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_SEQUENCE((_flags), \ + (*(((_structure *)NULL)->_member)), \ + _entry, _min, _max), \ + }, \ + .data_offset = offsetof(_structure, _member), \ + .count_size = sizeof(((_structure *)NULL)->_member ## _count), \ + .count_offset = offsetof(_structure, _member ## _count), \ +} + +/** + * Mapping schema helper macro for keys with \ref CYAML_SEQUENCE type. + * + * Compared to .\ref CYAML_FIELD_SEQUENCE, this macro takes an extra `_count` + * parameter, allowing the structure member name for the sequence entry count + * to be provided explicitly. + * + * For example, for the following structure: + * + * ``` + * struct my_structure { + * unsigned *things; + * unsigned n_things; + * }; + * ``` + * + * Pass the following as parameters: + * + * | Parameter | Value | + * | ---------- | --------------------- | + * | _structure | `struct my_structure` | + * | _member | `things` | + * | _count | `n_things` | + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + * \param[in] _count The member in _structure for this sequence's + * entry count. + * \param[in] _entry Pointer to schema for the **entries** in sequence. + * \param[in] _min Minimum number of sequence entries required. + * \param[in] _max Maximum number of sequence entries required. + */ +#define CYAML_FIELD_SEQUENCE_COUNT( \ + _key, _flags, _structure, _member, _count, _entry, _min, _max) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_SEQUENCE((_flags), \ + (*(((_structure *)NULL)->_member)), \ + _entry, _min, _max), \ + }, \ + .data_offset = offsetof(_structure, _member), \ + .count_size = sizeof(((_structure *)NULL)->_count), \ + .count_offset = offsetof(_structure, _count), \ +} + +/** + * Value schema helper macro for values with \ref CYAML_SEQUENCE_FIXED type. + * + * \param[in] _flags Any behavioural flags relevant to this value. + * \param[in] _type The C type of sequence **entries**. + * \param[in] _entry Pointer to schema for the **entries** in sequence. + * \param[in] _count Number of sequence entries required. + */ +#define CYAML_VALUE_SEQUENCE_FIXED( \ + _flags, _type, _entry, _count) \ + .type = CYAML_SEQUENCE_FIXED, \ + .flags = (_flags), \ + .data_size = sizeof(_type), \ + .sequence = { \ + .entry = _entry, \ + .min = _count, \ + .max = _count, \ + } + +/** + * Mapping schema helper macro for keys with \ref CYAML_SEQUENCE_FIXED type. + * + * \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 corresponding to the mapping. + * \param[in] _member The member in _structure for this mapping value. + * \param[in] _entry Pointer to schema for the **entries** in sequence. + * \param[in] _count Number of sequence entries required. + */ +#define CYAML_FIELD_SEQUENCE_FIXED( \ + _key, _flags, _structure, _member, _entry, _count) \ +{ \ + .key = _key, \ + .value = { \ + CYAML_VALUE_SEQUENCE_FIXED((_flags), \ + (*(((_structure *)NULL)->_member)), \ + _entry, _count), \ + }, \ + .data_offset = offsetof(_structure, _member) \ +} + +/** + * Mapping schema helper macro for keys with \ref CYAML_IGNORE type. + * + * \param[in] _key String defining the YAML mapping key to ignore. + * \param[in] _flags Any behavioural flags relevant to this key. + */ +#define CYAML_FIELD_IGNORE( \ + _key, _flags) \ +{ \ + .key = _key, \ + .value = { \ + .type = CYAML_IGNORE, \ + .flags = (_flags), \ + }, \ +} + +/** + * Mapping schema helper macro for terminating an array of mapping fields. + * + * CYAML mapping schemas are formed from an array of \ref cyaml_schema_field + * entries, and an entry with a NULL key indicates the end of the array. + */ +#define CYAML_FIELD_END { .key = NULL } + +/** + * Identifies that a \ref CYAML_SEQUENCE has unconstrained maximum entry + * count. + */ +#define CYAML_UNLIMITED 0xffffffff + +/** + * Helper macro for counting array elements. + * + * \note Don't use this macro on pointers. + * + * \param[in] _a A C array. + * \return Array element count. + */ +#define CYAML_ARRAY_LEN(_a) ((sizeof(_a)) / (sizeof(_a[0]))) + +/** + * Data loaded or saved by CYAML has this type. CYAML schemas are used + * to describe the data contained. + */ +typedef void cyaml_data_t; + +/** CYAML logging levels. */ +typedef enum cyaml_log_e { + CYAML_LOG_DEBUG, /**< Debug level logging. */ + CYAML_LOG_INFO, /**< Info level logging. */ + CYAML_LOG_NOTICE, /**< Notice level logging. */ + CYAML_LOG_WARNING, /**< Warning level logging. */ + CYAML_LOG_ERROR, /**< Error level logging. */ +} cyaml_log_t; + +/** + * CYAML logging function prototype. + * + * Clients may implement this to manage logging from CYAML themselves. + * Otherwise, consider using the standard logging function, \ref cyaml_log. + * + * \param[in] level Log level of message to log. + * \param[in] ctx Client's private logging context. + * \param[in] fmt Format string for message to log. + * \param[in] args Additional arguments used by fmt. + */ +typedef void (*cyaml_log_fn_t)( + cyaml_log_t level, + void *ctx, + const char *fmt, + va_list args); + +/** + * CYAML memory allocation / freeing function. + * + * Clients may implement this to handle memory allocation / freeing. + * + * \param[in] ctx Client's private allocation context. + * \param[in] ptr Existing allocation to resize, or NULL. + * \param[in] size The new size for the allocation. \note setting 0 must + * be treated as free(). + * \return If `size == 0`, returns NULL. If `size > 0`, returns NULL on failure, + * and any existing allocation is left untouched, or return non-NULL as + * the new allocation on success, and the original pointer becomes + * invalid. + */ +typedef void * (*cyaml_mem_fn_t)( + void *ctx, + void *ptr, + size_t size); + +/** + * Client CYAML configuration data. + * + * \todo Should provide facility for client to provide its own custom + * allocation functions. + */ +typedef struct cyaml_config { + /** + * Client function to use for logging. + * + * Clients can implement their own logging function and set it here. + * Otherwise, set `log_fn` to \ref cyaml_log if CYAML's default + * logging to `stderr` is suitable (see its documentation for more + * details), or set to `NULL` to suppress all logging. + * + * \note Useful backtraces are issued through the `log_fn` at + * \ref CYAML_LOG_ERROR level. If your application needs + * to load user YAML data, these backtraces can help users + * figure out what's wrong with their YAML, causing it to + * be rejected by your schema. + */ + cyaml_log_fn_t log_fn; + /** + * Client logging function context pointer. + * + * Clients using their own custom logging function can pass their + * context here, which will be passed through to their log_fn. + * + * The default logging function, \ref cyaml_log doesn't require a + * logging context, so pass NULL for the log_ctx if using that. + */ + void *log_ctx; + /** + * Client function to use for memory allocation handling. + * + * Clients can implement their own, or pass \ref cyaml_mem to use + * CYAML's default allocator. + * + * \note Depending on platform, when using CYAML's default allocator, + * clients may need to take care to ensure any allocated memory + * is freed using \ref cyaml_mem too. + */ + cyaml_mem_fn_t mem_fn; + /** + * Client memory function context pointer. + * + * Clients using their own custom allocation function can pass their + * context here, which will be passed through to their mem_fn. + * + * The default allocation function, \ref cyaml_mem doesn't require an + * allocation context, so pass NULL for the mem_ctx if using that. + */ + void *mem_ctx; + /** + * Minimum logging priority level to be issued. + * + * Specifying e.g. \ref CYAML_LOG_WARNING will cause only warnings and + * errors to emerge. + */ + cyaml_log_t log_level; + /** CYAML behaviour flags. */ + cyaml_cfg_flags_t flags; +} cyaml_config_t; + +/** + * Standard CYAML logging function. + * + * This logs to `stderr`. It clients want to log elsewhere they must + * implement their own logging function, and pass it to CYAML in the + * \ref cyaml_config_t structure. + * + * \note This default logging function composes single log messages from + * multiple separate fprintfs to `stderr`. If the client application + * writes to `stderr` from multiple threads, individual \ref cyaml_log + * messages may get broken up by the client applications logging. To + * avoid this, clients should implement their own \ref cyaml_log_fn_t and + * pass it in via \ref cyaml_config_t. + * + * \param[in] level Log level of message to log. + * \param[in] ctx Logging context, unused. + * \param[in] fmt Format string for message to log. + * \param[in] args Additional arguments used by fmt. + */ +extern void cyaml_log( + cyaml_log_t level, + void *ctx, + const char *fmt, + va_list args); + +/** + * CYAML default memory allocation / freeing function. + * + * This is used when clients don't supply their own. It is exposed to + * enable clients to use the same allocator as libcyaml used internally + * to allocate/free memory when they have not provided their own allocation + * function. + * + * \param[in] ctx Allocation context, unused. + * \param[in] ptr Existing allocation to resize, or NULL. + * \param[in] size The new size for the allocation. \note When `size == 0` + * this frees `ptr`. + * \return If `size == 0`, returns NULL. If `size > 0`, returns NULL on failure, + * and any existing allocation is left untouched, or return non-NULL as + * the new allocation on success, and the original pointer becomes + * invalid. + */ +extern void * cyaml_mem( + void *ctx, + void *ptr, + size_t size); + +/** + * Load a YAML document from a file at the given path. + * + * \note In the event of the top-level mapping having only optional fields, + * and the YAML not setting any of them, this function can return \ref + * CYAML_OK, and `NULL` in the `data_out` parameter. + * + * \param[in] path Path to YAML file to load. + * \param[in] config Client's CYAML configuration structure. + * \param[in] schema CYAML schema for the YAML to be loaded. + * \param[out] data_out Returns the caller-owned loaded data on success. + * Untouched on failure. + * \param[out] seq_count_out On success, returns the sequence entry count. + * Untouched on failure. + * Must be non-NULL if top-level schema type is + * \ref CYAML_SEQUENCE, otherwise, must be NULL. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +extern cyaml_err_t cyaml_load_file( + const char *path, + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + cyaml_data_t **data_out, + unsigned *seq_count_out); + +/** + * Load a YAML document from a data buffer. + * + * \note In the event of the top-level mapping having only optional fields, + * and the YAML not setting any of them, this function can return \ref + * CYAML_OK, and `NULL` in the `data_out` parameter. + * + * \param[in] input Buffer to load YAML data from. + * \param[in] input_len Length of input in bytes. + * \param[in] config Client's CYAML configuration structure. + * \param[in] schema CYAML schema for the YAML to be loaded. + * \param[out] data_out Returns the caller-owned loaded data on success. + * Untouched on failure. + * \param[out] seq_count_out On success, returns the sequence entry count. + * Untouched on failure. + * Must be non-NULL if top-level schema type is + * \ref CYAML_SEQUENCE, otherwise, must be NULL. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +extern cyaml_err_t cyaml_load_data( + const uint8_t *input, + size_t input_len, + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + cyaml_data_t **data_out, + unsigned *seq_count_out); + +/** + * Save a YAML document to a file at the given path. + * + * \param[in] path Path to YAML file to write. + * \param[in] config Client's CYAML configuration structure. + * \param[in] schema CYAML schema for the YAML to be saved. + * \param[in] data The caller-owned data to be saved. + * \param[in] seq_count If top level type is sequence, this should be the + * entry count, otherwise it is ignored. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +extern cyaml_err_t cyaml_save_file( + const char *path, + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + const cyaml_data_t *data, + unsigned seq_count); + +/** + * Save a YAML document into a string in memory. + * + * This allocates a buffer containing the serialised YAML data. + * + * To free the returned YAML string, clients should use the \ref cyaml_mem_fn_t + * function set in the \ref cyaml_config_t passed to this function. + * For example: + * + * ``` + * char *yaml; + * size_t len; + * err = cyaml_save_file(&yaml, &len, &config, &client_schema, client_data, 0); + * if (err == CYAML_OK) { + * // Use `yaml`: + * printf("%*s\n", len, yaml); + * // Free `yaml`: + * config.mem_fn(config.mem_ctx, yaml, 0); + * } + * ``` + * + * \note The returned YAML string does not have a trailing '\0'. + * + * \param[out] output Returns the caller-owned serialised YAML data on + * success, untouched on failure. Clients should use + * the \ref cyaml_mem_fn_t function set in the \ref + * cyaml_config_t to free the data. + * \param[out] len Returns the length of the data in output on success, + * untouched on failure. + * \param[in] config Client's CYAML configuration structure. + * \param[in] schema CYAML schema for the YAML to be saved. + * \param[in] data The caller-owned data to be saved. + * \param[in] seq_count If top level type is sequence, this should be the + * entry count, otherwise it is ignored. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +extern cyaml_err_t cyaml_save_data( + char **output, + size_t *len, + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + const cyaml_data_t *data, + unsigned seq_count); + +/** + * Free data returned by a CYAML load function. + * + * This is a convenience function, which is here purely to minimise the + * amount of code required in clients. Clients would be better off writing + * their own free function for the specific data once loaded. + * + * \note This is a recursive operation, freeing all nested data. + * + * \param[in] config The client's CYAML library config. + * \param[in] schema The schema describing the content of data. Must match + * the schema given to the CYAML load function used to + * load the data. + * \param[in] data The data structure to free. + * \param[in] seq_count If top level type is sequence, this should be the + * entry count, otherwise it is ignored. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +extern cyaml_err_t cyaml_free( + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + cyaml_data_t *data, + unsigned seq_count); + +/** + * Convert a cyaml error code to a human-readable string. + * + * \param[in] err Error code code to convert. + * \return String representing err. The string is '\0' terminated, and owned + * by libcyaml. + */ +extern const char * cyaml_strerror( + cyaml_err_t err); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libcyaml.pc.in b/libcyaml.pc.in new file mode 100644 index 0000000..0e38a1c --- /dev/null +++ b/libcyaml.pc.in @@ -0,0 +1,10 @@ +prefix=PREFIX +exec_prefix=${prefix} +libdir=${exec_prefix}/LIBDIR +includedir=${prefix}/INCLUDEDIR + +Name: libcyaml +Description: Schema-based YAML parsing and serialisation +Version: VERSION +Libs: -L${libdir} -lcyaml -lyaml +Cflags: -I${includedir} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..c71ca57 --- /dev/null +++ b/meson.build @@ -0,0 +1,59 @@ +# Copyright (C) 2019-2020 Alexandros Theodotou +# +# This file is part of Zrythm +# +# Zrythm is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Zrythm is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Zrythm. If not, see . + +project ( + 'zrythm-cyaml', ['c'], + version: '1.0.0', + license: 'ISC', + default_options: [ + 'warning_level=1', + 'buildtype=debug', + 'c_std=gnu11', + ], + ) + +cc = meson.get_compiler ('c') + +cargs = [ + '-DVERSION_MAJOR=1', + '-DVERSION_MINOR=0', + '-DVERSION_PATCH=0', + ] + +yaml_dep = dependency('yaml-0.1') + +zrythm_cyaml_inc = [ + include_directories ('include'), + ] +zrythm_cyaml = static_library ( + 'cyaml', + sources: [ + join_paths ('src', 'mem.c'), + join_paths ('src', 'save.c'), + join_paths ('src', 'free.c'), + join_paths ('src', 'load.c'), + join_paths ('src', 'utf8.c'), + join_paths ('src', 'util.c'), + ], + c_args: cargs, + dependencies: yaml_dep, + include_directories: zrythm_cyaml_inc, +) + +zrythm_cyaml_dep = declare_dependency ( + include_directories: 'include', + link_with: zrythm_cyaml) diff --git a/src/data.h b/src/data.h new file mode 100644 index 0000000..f85d08e --- /dev/null +++ b/src/data.h @@ -0,0 +1,142 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2017 Michael Drake + */ + +/** + * \file + * \brief CYAML functions for manipulating client data structures. + */ + +#ifndef CYAML_DATA_H +#define CYAML_DATA_H + +#include "cyaml/cyaml.h" +#include "util.h" + +/** + * Write a value of up to eight bytes to data_target. + * + * \param[in] value The value to write. + * \param[in] entry_size The number of bytes of value to write. + * \param[in] data_tgt The address to write to. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static inline cyaml_err_t cyaml_data_write( + uint64_t value, + uint64_t entry_size, + uint8_t *data_tgt) +{ + if (entry_size == 0) { + return CYAML_ERR_INVALID_DATA_SIZE; + } + + data_tgt += entry_size - 1; + + switch (entry_size) { + case 8: *data_tgt-- = (uint8_t)(value >> 56) & 0xff; /* Fall through. */ + case 7: *data_tgt-- = (uint8_t)(value >> 48) & 0xff; /* Fall through. */ + case 6: *data_tgt-- = (uint8_t)(value >> 40) & 0xff; /* Fall through. */ + case 5: *data_tgt-- = (uint8_t)(value >> 32) & 0xff; /* Fall through. */ + case 4: *data_tgt-- = (uint8_t)(value >> 24) & 0xff; /* Fall through. */ + case 3: *data_tgt-- = (uint8_t)(value >> 16) & 0xff; /* Fall through. */ + case 2: *data_tgt-- = (uint8_t)(value >> 8) & 0xff; /* Fall through. */ + case 1: *data_tgt-- = (uint8_t)(value >> 0) & 0xff; + break; + default: + return CYAML_ERR_INVALID_DATA_SIZE; + } + + return CYAML_OK; +} + +/** + * Write a pointer to data. + * + * This is a wrapper for \ref cyaml_data_write that does a compile time + * assertion on the pointer size, so it can never return a runtime error. + * + * \param[in] ptr The pointer address to write. + * \param[in] data_target The address to write to. + */ +static inline void cyaml_data_write_pointer( + const void *ptr, + uint8_t *data_target) +{ + /* Refuse to build on platforms where sizeof pointer would + * lead to \ref CYAML_ERR_INVALID_DATA_SIZE. */ + cyaml_static_assert(sizeof(char *) > 0); + cyaml_static_assert(sizeof(char *) <= sizeof(uint64_t)); + + CYAML_UNUSED(cyaml_data_write((uint64_t)ptr, sizeof(ptr), data_target)); + + return; +} + +/** + * Read a value of up to eight bytes from data. + * + * \param[in] entry_size The number of bytes to read. + * \param[in] data The address to read from. + * \param[out] error_out Returns the error code. \ref CYAML_OK on success, + * or appropriate error otherwise. + * \return On success, returns the value read from data. + * On failure, returns 0. + */ +static inline uint64_t cyaml_data_read( + uint64_t entry_size, + const uint8_t *data, + cyaml_err_t *error_out) +{ + uint64_t ret = 0; + + if (entry_size == 0) { + *error_out = CYAML_ERR_INVALID_DATA_SIZE; + return ret; + } + + data += entry_size - 1; + + switch (entry_size) { + case 8: ret |= ((uint64_t)(*data-- & 0xff)) << 56; /* Fall through. */ + case 7: ret |= ((uint64_t)(*data-- & 0xff)) << 48; /* Fall through. */ + case 6: ret |= ((uint64_t)(*data-- & 0xff)) << 40; /* Fall through. */ + case 5: ret |= ((uint64_t)(*data-- & 0xff)) << 32; /* Fall through. */ + case 4: ret |= ((uint64_t)(*data-- & 0xff)) << 24; /* Fall through. */ + case 3: ret |= ((uint64_t)(*data-- & 0xff)) << 16; /* Fall through. */ + case 2: ret |= ((uint64_t)(*data-- & 0xff)) << 8; /* Fall through. */ + case 1: ret |= ((uint64_t)(*data-- & 0xff)) << 0; + break; + default: + *error_out = CYAML_ERR_INVALID_DATA_SIZE; + return ret; + } + + *error_out = CYAML_OK; + return ret; +} + +/** + * Read a pointer from data. + * + * This is a wrapper for \ref cyaml_data_read that does a compile time + * assertion on the pointer size, so it can never return a runtime error. + * + * \param[in] data The address to read from. + * \return Returns the value read from data. + */ +static inline uint8_t * cyaml_data_read_pointer( + const uint8_t *data) +{ + cyaml_err_t err; + + /* Refuse to build on platforms where sizeof pointer would + * lead to \ref CYAML_ERR_INVALID_DATA_SIZE. */ + cyaml_static_assert(sizeof(char *) > 0); + cyaml_static_assert(sizeof(char *) <= sizeof(uint64_t)); + + return (void *)cyaml_data_read(sizeof(char *), data, &err); +} + +#endif diff --git a/src/free.c b/src/free.c new file mode 100644 index 0000000..3e03482 --- /dev/null +++ b/src/free.c @@ -0,0 +1,161 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2017-2018 Michael Drake + */ + +/** + * \file + * \brief Free data structures created by the CYAML load functions. + * + * As described in the public API for \ref cyaml_free(), it is preferable for + * clients to write their own free routines, tailored for their data structure. + * + * Recursion and stack usage + * ------------------------- + * + * This generic CYAML free routine is implemented using recursion, rather + * than iteration with a heap-allocated stack. This is because recursion + * seems less bad than allocating within the free code, and the stack-cost + * of these functions isn't huge. The maximum recursion depth is of course + * bound by the schema, however schemas for recursively nesting data structures + * are unbound, e.g. for a data tree structure. + */ + +#include +#include +#include + +#include "data.h" +#include "util.h" +#include "mem.h" + +/** + * Internal function for freeing a CYAML-parsed data structure. + * + * \param[in] cfg The client's CYAML library config. + * \param[in] schema The schema describing how to free `data`. + * \param[in] data The data structure to be freed. + * \param[in] count If data is of type \ref CYAML_SEQUENCE, this is the + * number of entries in the sequence. + */ +static void cyaml__free_value( + const cyaml_config_t *cfg, + const cyaml_schema_value_t *schema, + uint8_t * data, + uint64_t count); + +/** + * Internal function for freeing a CYAML-parsed sequence. + * + * \param[in] cfg The client's CYAML library config. + * \param[in] sequence_schema The schema describing how to free `data`. + * \param[in] data The data structure to be freed. + * \param[in] count The sequence's entry count. + */ +static void cyaml__free_sequence( + const cyaml_config_t *cfg, + const cyaml_schema_value_t *sequence_schema, + uint8_t * const data, + uint64_t count) +{ + const cyaml_schema_value_t *schema = sequence_schema->sequence.entry; + uint32_t data_size = schema->data_size; + + cyaml__log(cfg, CYAML_LOG_DEBUG, + "Free: Freeing sequence with count: %u\n", count); + + if (schema->flags & CYAML_FLAG_POINTER) { + data_size = sizeof(data); + } + + for (unsigned i = 0; i < count; i++) { + cyaml__log(cfg, CYAML_LOG_DEBUG, + "Free: Freeing sequence entry: %u\n", i); + cyaml__free_value(cfg, schema, data + data_size * i, 0); + } +} + +/** + * Internal function for freeing a CYAML-parsed mapping. + * + * \param[in] cfg The client's CYAML library config. + * \param[in] mapping_schema The schema describing how to free `data`. + * \param[in] data The data structure to be freed. + */ +static void cyaml__free_mapping( + const cyaml_config_t *cfg, + const cyaml_schema_value_t *mapping_schema, + uint8_t * const data) +{ + const cyaml_schema_field_t *schema = mapping_schema->mapping.fields; + + while (schema->key != NULL) { + uint64_t count = 0; + cyaml__log(cfg, CYAML_LOG_DEBUG, + "Free: Freeing key: %s (at offset: %u)\n", + schema->key, (unsigned)schema->data_offset); + if (schema->value.type == CYAML_SEQUENCE) { + cyaml_err_t err; + count = cyaml_data_read(schema->count_size, + data + schema->count_offset, &err); + if (err != CYAML_OK) { + return; + } + } + cyaml__free_value(cfg, &schema->value, + data + schema->data_offset, count); + schema++; + } +} + +/* This function is documented at the forward declaration above. */ +static void cyaml__free_value( + const cyaml_config_t *cfg, + const cyaml_schema_value_t *schema, + uint8_t * data, + uint64_t count) +{ + if (schema->flags & CYAML_FLAG_POINTER) { + data = cyaml_data_read_pointer(data); + if (data == NULL) { + return; + } + } + + if (schema->type == CYAML_MAPPING) { + cyaml__free_mapping(cfg, schema, data); + } else if (schema->type == CYAML_SEQUENCE || + schema->type == CYAML_SEQUENCE_FIXED) { + if (schema->type == CYAML_SEQUENCE_FIXED) { + count = schema->sequence.max; + } + cyaml__free_sequence(cfg, schema, data, count); + } + + if (schema->flags & CYAML_FLAG_POINTER) { + cyaml__log(cfg, CYAML_LOG_DEBUG, "Free: Freeing: %p\n", data); + cyaml__free(cfg, data); + } +} + +/* Exported function, documented in include/cyaml/cyaml.h */ +cyaml_err_t cyaml_free( + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + cyaml_data_t *data, + unsigned seq_count) +{ + if (config == NULL) { + return CYAML_ERR_BAD_PARAM_NULL_CONFIG; + } + if (config->mem_fn == NULL) { + return CYAML_ERR_BAD_CONFIG_NULL_MEMFN; + } + if (schema == NULL) { + return CYAML_ERR_BAD_PARAM_NULL_SCHEMA; + } + cyaml__log(config, CYAML_LOG_DEBUG, "Free: Top level data: %p\n", data); + cyaml__free_value(config, schema, (void *)&data, seq_count); + return CYAML_OK; +} diff --git a/src/load.c b/src/load.c new file mode 100644 index 0000000..2c55b57 --- /dev/null +++ b/src/load.c @@ -0,0 +1,2508 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2017-2020 Michael Drake + */ + +/** + * \file + * \brief Load YAML data into client's data structure. + * + * This uses `libyaml` to parse YAML documents, it validates the documents + * against the client provided schema, and uses the schema to place the data + * in the client's data structure. + */ + +#include +#include +#include +#include +#include + +#include + +#include "mem.h" +#include "data.h" +#include "util.h" + +/** Identifies that no mapping schema entry was found for key. */ +#define CYAML_SCHEMA_IDX_NONE 0xffff + +/** + * CYAML events. These correspond to `libyaml` events. + */ +typedef enum cyaml_event { + CYAML_EVT_NO_EVENT = YAML_NO_EVENT, + CYAML_EVT_STRM_START = YAML_STREAM_START_EVENT, + CYAML_EVT_STRM_END = YAML_STREAM_END_EVENT, + CYAML_EVT_DOC_START = YAML_DOCUMENT_START_EVENT, + CYAML_EVT_DOC_END = YAML_DOCUMENT_END_EVENT, + CYAML_EVT_ALIAS = YAML_ALIAS_EVENT, + CYAML_EVT_SCALAR = YAML_SCALAR_EVENT, + CYAML_EVT_SEQ_START = YAML_SEQUENCE_START_EVENT, + CYAML_EVT_SEQ_END = YAML_SEQUENCE_END_EVENT, + CYAML_EVT_MAP_START = YAML_MAPPING_START_EVENT, + CYAML_EVT_MAP_END = YAML_MAPPING_END_EVENT, + CYAML_EVT__COUNT, +} cyaml_event_t; + +/** + * A CYAML load state machine stack entry. + */ +typedef struct cyaml_state { + /** Current load state machine state. */ + enum cyaml_state_e state; + /** Schema for the expected value in this state. */ + const cyaml_schema_value_t *schema; + /** Anonymous union for schema type specific state. */ + union { + /** Additional state for \ref CYAML_STATE_IN_STREAM state. */ + struct { + /** Number of documents read in stream. */ + uint32_t doc_count; + } stream; + /** + * Additional state for \ref CYAML_STATE_IN_MAP_KEY and + * \ref CYAML_STATE_IN_MAP_VALUE states. */ + struct { + const cyaml_schema_field_t *schema; + /** Bit field of mapping fields found. */ + cyaml_bitfield_t *fields; + uint16_t schema_idx; + uint16_t entries_count; + } mapping; + /** Additional state for \ref CYAML_STATE_IN_SEQUENCE state. */ + struct { + uint8_t *data; + uint8_t *count_data; + uint32_t count; + uint8_t count_size; + } sequence; + }; + uint8_t *data; /**< Pointer to output client data for this state. */ +} cyaml_state_t; + +/** + * Anchor data. + * + * This is used to track the progress of recording the anchor's events, and then + * the data is referenced during replay. + */ +typedef struct cyaml_anchor { + char *name; /**< Anchor name. */ + uint32_t start; /**< Index into \ref cyaml_event_ctx_t events array. */ + uint32_t end; /**< Index into \ref cyaml_event_ctx_t events array. */ + bool recording; /**< Whether the anchor is being actively recorded. */ +} cyaml_anchor_t; + +/** + * Event recording context. + * + * This records anchor details, and any anchored events. A stack of + * events is maintained to keep track of matching start/end events, in + * order to end anchor recordings with the correct end event. + */ +typedef struct cyaml_event_record { + cyaml_anchor_t *anchors; /**< Array of anchor details or NULL. */ + yaml_event_t *data; /**< Array of anchor-referenced events. */ + uint32_t *events; /**< Array of event data indices. */ + uint32_t *stack; /**< Stack of start event array indices. */ + uint32_t anchors_count; /**< Number of anchor details in `anchors`. */ + uint32_t events_count; /**< Number of events in events array. */ + uint32_t stack_count; /**< Number of entries in the event stack. */ + uint32_t data_count; /**< Number of recorded libyaml events. */ + /** + * Number of anchors we're actively recording events for. + */ + uint32_t recording_count; +} cyaml_event_record_t; + +/** + * Event replay context. + */ +typedef struct cyaml_event_replay { + bool active; /**< Whether there's an active replay. */ + uint32_t anchor_idx; /**< The recorded anchor data to replay. */ + uint32_t event_idx; /**< Current recorded event index during replay. */ +} cyaml_event_replay_t; + +/** + * CYAML's LibYAML event context. + * + * Only \ref cyaml_get_next_event and friends should poke around inside here. + * Everything else should use \ref cyaml__current_event to access the current + * event data, and call \ref cyaml_get_next_event to pump the event stream. + */ +typedef struct cyaml_event_ctx { + bool have_event; /**< Whether event is currently populated. */ + yaml_event_t event; /**< Current event. */ + cyaml_event_record_t record; /**< Event recording context. */ + cyaml_event_replay_t replay; /**< Event replaying context. */ +} cyaml_event_ctx_t; + +/** + * Internal YAML loading context. + */ +typedef struct cyaml_ctx { + const cyaml_config_t *config; /**< Settings provided by client. */ + cyaml_event_ctx_t event_ctx; /**< Our LibYAML event context. */ + cyaml_state_t *state; /**< Current entry in state stack, or NULL. */ + cyaml_state_t *stack; /**< State stack */ + uint32_t stack_idx; /**< Next (empty) state stack slot */ + uint32_t stack_max; /**< Current stack allocation limit. */ + unsigned seq_count; /**< Top-level sequence count. */ + yaml_parser_t *parser; /**< Internal libyaml parser object. */ +} cyaml_ctx_t; + +/** + * Get the CYAML event type from a `libyaml` event. + * + * \param[in] event The `libyaml` event. + * \return corresponding CYAML event. + */ +static inline cyaml_event_t cyaml__get_event_type(const yaml_event_t *event) +{ + return (cyaml_event_t)event->type; +} + +/** + * Convert a `libyaml` event to a human readable string. + * + * \param[in] event The `libyaml` event. + * \return String representing event. + */ +static const char * cyaml__libyaml_event_type_str(const yaml_event_t *event) +{ + static const char * const strings[] = { + "NO_EVENT", + "STREAM_START", + "STREAM_END", + "DOC_START", + "DOC_END", + "ALIAS", + "SCALAR", + "SEQUENCE_START", + "SEQUENCE_END", + "MAPPING_START", + "MAPPING_END", + }; + return strings[event->type]; +} + +/** + * Get the anchor name for an event, or NULL if the event isn't an anchor. + * + * \param[in] event The `libyaml` event. + * \return String representing event's anchor, or NULL. + */ +static const char * cyaml__get_yaml_event_anchor(const yaml_event_t *event) +{ + switch (cyaml__get_event_type(event)) { + case CYAML_EVT_SCALAR: + return (const char *)event->data.scalar.anchor; + case CYAML_EVT_MAP_START: + return (const char *)event->data.mapping_start.anchor; + case CYAML_EVT_SEQ_START: + return (const char *)event->data.sequence_start.anchor; + default: + return NULL; + } +} + +/** + * Get the anchor name for an alias event. + * + * \param[in] event The `libyaml` event. + * \return String representing event's aliased anchor name. + */ +static const char * cyaml__get_yaml_event_alias(const yaml_event_t *event) +{ + assert(cyaml__get_event_type(event) == CYAML_EVT_ALIAS); + assert(event->data.alias.anchor != NULL); + + return (const char *)event->data.alias.anchor; +} + +/** + * Handle an alias event. + * + * This searches the know anchors for a match. If a matching anchor is found, + * it sets the replay context up to play back the recorded events associated + * with the anchor, setting the replay state to active. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event The `libyaml` event. + * \return \ref CYAML_OK on success, or appropriate error otherwise. + */ +static cyaml_err_t cyaml__handle_alias( + cyaml_ctx_t *ctx, + const yaml_event_t *event) +{ + uint32_t anchor_idx; + cyaml_event_ctx_t *e_ctx = &ctx->event_ctx; + cyaml_event_replay_t *replay = &e_ctx->replay; + const cyaml_event_record_t *record = &e_ctx->record; + const char *alias = cyaml__get_yaml_event_alias(event); + + anchor_idx = record->anchors_count; + for (uint32_t i = 0; i < record->anchors_count; i++) { + if (record->anchors[i].recording == false && + strcmp(record->anchors[i].name, alias) == 0) { + anchor_idx = i; + } + } + + if (anchor_idx >= record->anchors_count) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Load: No anchor found for alias: '%s'\n", + alias); + return CYAML_ERR_INVALID_ALIAS; + } + + cyaml__log(ctx->config, CYAML_LOG_INFO, + "Load: Found alias for anchor: '%s'\n", alias); + + replay->active = true; + replay->anchor_idx = anchor_idx; + replay->event_idx = record->anchors[anchor_idx].start; + + return CYAML_OK; +} + +/** + * Handle an event that establishes an anchor. + * + * This should not be called while replaying recorded events, or it will + * try to rebuild anchors we already have. + * + * This is a no-op if the event doesn't establish an anchor. If the event does + * create an anchor, an anchor entry is added to the recording context. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event The `libyaml` event to handle any anchor for. + * \param[out] is_anchor_out Returns whether the event creates an anchor. + * \return \ref CYAML_OK on success, or appropriate error otherwise. + */ +static cyaml_err_t cyaml__handle_anchor( + cyaml_ctx_t *ctx, + const yaml_event_t *event, + bool *is_anchor_out) +{ + bool recording; + size_t anchors_size; + uint32_t anchors_count; + const char *anchor_name; + cyaml_anchor_t *anchors; + cyaml_event_ctx_t *e_ctx = &ctx->event_ctx; + cyaml_event_record_t *record = &e_ctx->record; + + anchor_name = cyaml__get_yaml_event_anchor(event); + if (anchor_name == NULL) { + *is_anchor_out = false; + return CYAML_OK; + } + + cyaml__log(ctx->config, CYAML_LOG_INFO, + "Load: Found anchor: '%s'\n", anchor_name); + + anchors_count = record->anchors_count; + anchors_size = anchors_count * sizeof(*anchors); + anchors = cyaml__realloc(ctx->config, record->anchors, + anchors_size, anchors_size + sizeof(*anchors), + true); + if (anchors == NULL) { + return CYAML_ERR_OOM; + } + + recording = cyaml__get_event_type(event) == CYAML_EVT_SEQ_START || + cyaml__get_event_type(event) == CYAML_EVT_MAP_START; + + record->anchors_count++; + record->anchors = anchors; + record->recording_count += recording; + + anchors[anchors_count].recording = recording; + anchors[anchors_count].start = record->events_count; + anchors[anchors_count].end = record->events_count; + anchors[anchors_count].name = cyaml__strdup(ctx->config, anchor_name); + if (anchors[anchors_count].name == NULL) { + return CYAML_ERR_OOM; + } + + *is_anchor_out = true; + return CYAML_OK; +} + +/** + * Push a recording stack context entry. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event_index The current event's index in the recording. + * \return \ref CYAML_OK on success, or appropriate error otherwise. + */ +static cyaml_err_t cyaml__record_stack_push( + cyaml_ctx_t *ctx, + uint32_t event_index) +{ + uint32_t *stack; + size_t stack_size; + uint32_t stack_count; + cyaml_event_ctx_t *e_ctx = &ctx->event_ctx; + cyaml_event_record_t *record = &e_ctx->record; + + stack_count = record->stack_count; + stack_size = stack_count * sizeof(*stack); + stack = cyaml__realloc(ctx->config, record->stack, + stack_size, stack_size + sizeof(*stack), true); + if (stack == NULL) { + return CYAML_ERR_OOM; + } + record->stack = stack; + record->stack_count++; + + stack[stack_count] = event_index; + + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: Push recording stack entry for %s\n", + cyaml__libyaml_event_type_str( + &record->data[record->events[event_index]])); + + return CYAML_OK; +} + +/** + * Pop a recording stack context entry. + * + * Any actively recording anchors are checked, and if this event + * ends the anchor, the anchor recording is terminated. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event_index The current event's index in the recording. + */ +static void cyaml__record_stack_pop( + cyaml_ctx_t *ctx, + uint32_t event_index) +{ + uint32_t stack; + cyaml_event_ctx_t *e_ctx = &ctx->event_ctx; + cyaml_event_record_t *record = &e_ctx->record; + + assert(record->stack_count > 0); + + stack = record->stack[record->stack_count - 1]; + for (uint32_t i = 0; i < record->anchors_count; i++) { + if (!record->anchors[i].recording) { + continue; + } + + if (record->anchors[i].start != stack) { + continue; + } + + record->anchors[i].recording = false; + record->recording_count--; + + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: Finish recording events for '%s'\n", + record->anchors[i].name); + } + + record->stack_count--; + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: Pop recording stack entry for %s\n", + cyaml__libyaml_event_type_str( + &record->data[record->events[event_index]])); +} + +/** + * Update the anchor data in the recording context. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event_index The current event's index in the recording. + * \param[in] event The `libyaml` event to fill from the recording. + * \return \ref CYAML_OK on success, or appropriate error otherwise. + */ +static cyaml_err_t cyaml__update_anchor_recordings( + cyaml_ctx_t *ctx, + uint32_t event_index, + const yaml_event_t *event) +{ + cyaml_err_t err; + cyaml_event_ctx_t *e_ctx = &ctx->event_ctx; + cyaml_event_record_t *record = &e_ctx->record; + + for (uint32_t i = 0; i < record->anchors_count; i++) { + if (!record->anchors[i].recording) { + continue; + } + if (record->anchors[i].start == event_index) { + continue; + } + + record->anchors[i].end++; + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: Update '%s' end to index %"PRIu32"\n", + record->anchors[i].name, + record->anchors[i].end); + } + + switch (cyaml__get_event_type(event)) { + case CYAML_EVT_SEQ_START: /* Fall through. */ + case CYAML_EVT_MAP_START: + err = cyaml__record_stack_push(ctx, event_index); + if (err != CYAML_OK) { + return err; + } + break; + case CYAML_EVT_SEQ_END: /* Fall through. */ + case CYAML_EVT_MAP_END: + cyaml__record_stack_pop(ctx, event_index); + break; + default: + break; + } + + return CYAML_OK; +} + +/** + * Handle the recording of the current event. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event The `libyaml` to record. + * \param[in] replayed_event Whether this event is a replay. + * \param[in] replay_event_index Index in events array of replayed event. + * \return \ref CYAML_OK on success, or appropriate error otherwise. + */ +static cyaml_err_t cyaml__handle_record( + cyaml_ctx_t *ctx, + const yaml_event_t *event, + bool replayed_event, + uint32_t replay_event_index) +{ + uint32_t *events; + size_t events_size; + uint32_t event_index; + uint32_t events_count; + cyaml_event_ctx_t *e_ctx = &ctx->event_ctx; + cyaml_event_record_t *record = &e_ctx->record; + + if (replayed_event) { + if (e_ctx->record.recording_count == 0) { + return CYAML_OK; + } + + event_index = record->events[replay_event_index]; + } else { + size_t data_size; + yaml_event_t *data; + uint32_t data_count; + bool event_has_anchor = false; + + /* We've not already seen this event, so if it creates an + * anchor then we need to record it. */ + cyaml_err_t err = cyaml__handle_anchor(ctx, event, + &event_has_anchor); + if (err != CYAML_OK) { + return err; + } + + if (e_ctx->record.recording_count == 0 && + event_has_anchor == false) { + return CYAML_OK; + } + + /* Record event data. */ + data_count = record->data_count; + data_size = data_count * sizeof(*data); + data = cyaml__realloc(ctx->config, record->data, + data_size, data_size + sizeof(*data), + true); + if (data == NULL) { + return CYAML_ERR_OOM; + } + record->data = data; + record->data_count++; + + memcpy(data + data_count, event, sizeof(*data)); + e_ctx->have_event = false; + event_index = data_count; + } + + /* Record event data index. Multiple event data indexes can + * reference the same event data, due to replaying of events. */ + events_count = record->events_count; + events_size = events_count * sizeof(*events); + events = cyaml__realloc(ctx->config, record->events, + events_size, events_size + sizeof(*events), + true); + if (events == NULL) { + return CYAML_ERR_OOM; + } + record->events = events; + record->events_count++; + + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: Record event data %"PRIu32" at index %"PRIu32"\n", + event_index, events_count); + + events[events_count] = event_index; + + return cyaml__update_anchor_recordings(ctx, events_count, event); +} + +/** + * Handle the current event replay. + * + * There must be an active replay before this is called. If this call + * yields the final event of the anchor, the replay state is disabled. + * + * \param[in] ctx The CYAML loading context. + * \param[out] event_out The `libyaml` event to fill from the recording. + * \param[out] event_index_out The index in events array of the replayed event. + */ +static void cyaml__handle_replay( + cyaml_ctx_t *ctx, + yaml_event_t *event_out, + uint32_t *event_index_out) +{ + uint32_t event_index; + cyaml_event_ctx_t *e_ctx = &ctx->event_ctx; + cyaml_event_replay_t *replay = &e_ctx->replay; + const cyaml_event_record_t *record = &e_ctx->record; + const yaml_event_t *replay_event = record->data + + record->events[replay->event_idx]; + const cyaml_anchor_t *replay_anchor = record->anchors + + replay->anchor_idx; + + assert(replay->active); + + event_index = replay->event_idx; + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: Replaying event idx %"PRIu32": " + "event data: %"PRIu32"\n", + event_index, record->events[event_index]); + + if (event_index == replay_anchor->end) { + replay->active = false; + + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: Finishing handling of alias: '%s'\n", + replay_anchor->name); + } else { + replay->event_idx++; + } + + memcpy(event_out, replay_event, sizeof(*event_out)); + *event_index_out = event_index; +} + +/** + * Delete any current event from the load context. + * + * This gets the next event from the CYAML load context's `libyaml` parser + * object. + * + * \param[in] ctx The CYAML loading context. + * \return \ref CYAML_OK on success, or appropriate error otherwise. + */ +static void cyaml__delete_yaml_event( + cyaml_ctx_t *ctx) +{ + if (ctx->event_ctx.have_event) { + yaml_event_delete(&ctx->event_ctx.event); + ctx->event_ctx.have_event = false; + } +} + +/** + * Free any recordings from the CYAML loading context. + * + * \param[in] ctx The CYAML loading context. + */ +static void cyaml__free_recording( + cyaml_ctx_t *ctx) +{ + cyaml_event_ctx_t *e_ctx = &ctx->event_ctx; + cyaml_event_record_t *record = &e_ctx->record; + + for (uint32_t i = 0; i < record->anchors_count; i++) { + cyaml__free(ctx->config, record->anchors[i].name); + } + cyaml__free(ctx->config, record->anchors); + + for (uint32_t i = 0; i < record->data_count; i++) { + yaml_event_delete(&record->data[i]); + } + cyaml__free(ctx->config, record->events); + cyaml__free(ctx->config, record->stack); + cyaml__free(ctx->config, record->data); +} + +/** + * Helper function to read the next YAML input event into the context. + * + * This handles recording the events associated with anchors, and replaying + * them when an alias event references a valid anchor. If we are not replaying + * anchored events, this gets the next event from the CYAML load context's + * `libyaml` parser object. + * + * Any existing event in the load context is deleted first. + * + * Callers do not always need to delete the previous event from the context + * before calling this function. However, after the final call, when cleaning + * up the context, any event must be deleted with a single call to \ref + * cyaml__delete_yaml_event. + * + * \param[in] ctx The CYAML loading context. + * \return \ref CYAML_OK on success, or appropriate error otherwise. + */ +static cyaml_err_t cyaml_get_next_event( + cyaml_ctx_t *ctx) +{ + cyaml_event_ctx_t *e_ctx = &ctx->event_ctx; + yaml_event_t *event = &e_ctx->event; + uint32_t replay_event_index = 0; + bool replayed_event = false; + cyaml_err_t err; + + cyaml__delete_yaml_event(ctx); + + if (!e_ctx->replay.active) { + if (!yaml_parser_parse(ctx->parser, event)) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Load: libyaml: %s\n", + ctx->parser->problem); + return CYAML_ERR_LIBYAML_PARSER; + } + e_ctx->have_event = true; + + if (event->type == YAML_ALIAS_EVENT) { + if (ctx->config->flags & CYAML_CFG_NO_ALIAS) { + return CYAML_ERR_ALIAS; + } else { + err = cyaml__handle_alias(ctx, event); + if (err != CYAML_OK) { + return err; + } + } + + cyaml__delete_yaml_event(ctx); + } + } + + if (e_ctx->replay.active) { + cyaml__handle_replay(ctx, event, &replay_event_index); + replayed_event = true; + } + + cyaml__log(ctx->config, CYAML_LOG_DEBUG, "Load: Event: %s\n", + cyaml__libyaml_event_type_str(event)); + + if (ctx->config->flags & CYAML_CFG_NO_ALIAS) { + return CYAML_OK; + } + + return cyaml__handle_record(ctx, event, replayed_event, + replay_event_index); +} + +/** + * Get a pointer to the load context's current event. + * + * The outside world should use this to get the address of the event data, + * which will not change for subsequent events. The event should only be used + * after \ref cyaml_get_next_event() has returned \ref CYAML_OK. + * + * \param[in] ctx The CYAML loading context. + * \return \ref CYAML_OK on success, or appropriate error otherwise. + */ +static inline const yaml_event_t * cyaml__current_event( + const cyaml_ctx_t *ctx) +{ + return &ctx->event_ctx.event; +} + +/** + * Get the offset to a mapping field by key in a mapping schema array. + * + * \param[in] ctx The CYAML loading context. + * \param[in] key Key to search for in mapping schema. + * \return index the mapping schema's mapping fields array for key, or + * \ref CYAML_SCHEMA_IDX_NONE if key is not present in schema. + */ +static inline uint16_t cyaml__get_entry_from_mapping_schema( + const cyaml_ctx_t *ctx, + const char *key) +{ + const cyaml_schema_field_t *fields = ctx->state->mapping.schema; + const cyaml_schema_value_t *schema = ctx->state->schema; + uint16_t index = 0; + + /* Step through each entry in the schema */ + for (; fields->key != NULL; fields++) { + if (cyaml__strcmp(ctx->config, schema, fields->key, key) == 0) { + return index; + } + index++; + } + + return CYAML_SCHEMA_IDX_NONE; +} + +/** + *Helper to get the current mapping field. + * + * \note The current load state must be \ref CYAML_STATE_IN_MAP_KEY, or + * \ref CYAML_STATE_IN_MAP_VALUE, and there must be a current field + * index in the state. + * + * \param[in] ctx The CYAML loading context. + * \return Current mapping field's schema entry. + */ +static inline const cyaml_schema_field_t * cyaml_mapping_schema_field( + const cyaml_ctx_t *ctx) +{ + const cyaml_state_t *state = ctx->state; + + assert(state != NULL); + assert(state->state == CYAML_STATE_IN_MAP_KEY || + state->state == CYAML_STATE_IN_MAP_VALUE); + assert(state->mapping.schema_idx != CYAML_SCHEMA_IDX_NONE); + + return state->mapping.schema + state->mapping.schema_idx; +} + +/** + * Ensure that the CYAML load context has space for a new stack entry. + * + * \param[in] ctx The CYAML loading context. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__stack_ensure( + cyaml_ctx_t *ctx) +{ + cyaml_state_t *temp; + uint32_t max = ctx->stack_max + 16; + + if (ctx->stack_idx < ctx->stack_max) { + return CYAML_OK; + } + + temp = cyaml__realloc(ctx->config, ctx->stack, 0, + sizeof(*ctx->stack) * max, false); + if (temp == NULL) { + return CYAML_ERR_OOM; + } + + ctx->stack = temp; + ctx->stack_max = max; + ctx->state = ctx->stack + ctx->stack_idx - 1; + + return CYAML_OK; +} + +/** + * Count the entries in a mapping schema array. + * + * The mapping schema array must be terminated by an entry with a NULL key. + * + * \param[in] mapping_schema Array of mapping schema fields. + * \return Number of entries in mapping_schema array. + */ +static unsigned cyaml__get_entry_count_from_mapping_schema( + const cyaml_schema_field_t *mapping_schema) +{ + const cyaml_schema_field_t *entry = mapping_schema; + + while (entry->key != NULL) { + entry++; + } + + return (unsigned)(entry - mapping_schema); +} + +/** + * Create \ref CYAML_STATE_IN_MAP_KEY state's bitfield array allocation. + * + * The bitfield is used to record whether the mapping as all the required + * fields by mapping schema array index. + * + * \param[in] ctx The CYAML loading context. + * \param[in] state CYAML load state for a \ref CYAML_STATE_IN_MAP_KEY state. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__mapping_bitfieid_create( + cyaml_ctx_t *ctx, + cyaml_state_t *state) +{ + cyaml_bitfield_t *bitfield; + unsigned count = cyaml__get_entry_count_from_mapping_schema( + state->mapping.schema); + size_t size = ((count + CYAML_BITFIELD_BITS - 1) / CYAML_BITFIELD_BITS) + * sizeof(*bitfield); + + bitfield = cyaml__alloc(ctx->config, size, true); + if (bitfield == NULL) { + return CYAML_ERR_OOM; + } + + state->mapping.fields = bitfield; + + return CYAML_OK; +} + +/** + * Destroy a \ref CYAML_STATE_IN_MAP_KEY state's bitfield array allocation. + * + * \param[in] ctx The CYAML loading context. + * \param[in] state CYAML load state for a \ref CYAML_STATE_IN_MAP_KEY state. + */ +static void cyaml__mapping_bitfieid_destroy( + cyaml_ctx_t *ctx, + cyaml_state_t *state) +{ + cyaml__free(ctx->config, state->mapping.fields); +} + +/** + * Set the bit for the current mapping's current field, to indicate it exists. + * + * Current CYAML load state must be \ref CYAML_STATE_IN_MAP_KEY. + * + * \param[in] ctx The CYAML loading context. + */ +static void cyaml__mapping_bitfieid_set( + cyaml_ctx_t *ctx) +{ + cyaml_state_t *state = ctx->state; + unsigned idx = state->mapping.schema_idx; + + state->mapping.fields[idx / CYAML_BITFIELD_BITS] |= + 1u << (idx % CYAML_BITFIELD_BITS); +} + +/** + * Check a mapping had all the required fields. + * + * Checks all the bits are set in the bitfield, which correspond to + * entries in the mapping schema array which are not marked with the + * \ref CYAML_FLAG_OPTIONAL flag. + * + * Current CYAML load state must be \ref CYAML_STATE_IN_MAP_KEY. + * + * \param[in] ctx The CYAML loading context. + * \return \ref CYAML_OK if all required fields are present, or + * \ref CYAML_ERR_MAPPING_FIELD_MISSING any are missing. + */ +static cyaml_err_t cyaml__mapping_bitfieid_validate( + cyaml_ctx_t *ctx) +{ + cyaml_state_t *state = ctx->state; + unsigned count = cyaml__get_entry_count_from_mapping_schema( + state->mapping.schema); + + for (unsigned i = 0; i < count; i++) { + if (state->mapping.schema[i].value.flags & CYAML_FLAG_OPTIONAL) { + continue; + } + if (state->mapping.fields[i / CYAML_BITFIELD_BITS] & + (1u << (i % CYAML_BITFIELD_BITS))) { + continue; + } + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Load: Missing required mapping field: %s\n", + state->mapping.schema[i].key); + return CYAML_ERR_MAPPING_FIELD_MISSING; + } + + return CYAML_OK; +} + +/** + * Helper to check if schema is for a \ref CYAML_SEQUENCE type. + * + * \param[in] schema The schema entry for a type. + * \return true iff schema is for a \ref CYAML_SEQUENCE type, + * false otherwise. + */ +static inline bool cyaml__is_sequence(const cyaml_schema_value_t *schema) +{ + return ((schema->type == CYAML_SEQUENCE) || + (schema->type == CYAML_SEQUENCE_FIXED)); +} + +/** + * Push a new entry onto the CYAML load context's stack. + * + * \param[in] ctx The CYAML loading context. + * \param[in] state The CYAML load state we're pushing a stack entry for. + * \param[in] schema The CYAML schema for the value expected in state. + * \param[in] data Pointer to where value's data should be written. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__stack_push( + cyaml_ctx_t *ctx, + enum cyaml_state_e state, + const cyaml_schema_value_t *schema, + cyaml_data_t *data) +{ + cyaml_err_t err; + cyaml_state_t s = { + .data = data, + .state = state, + .schema = schema, + }; + + err = cyaml__stack_ensure(ctx); + if (err != CYAML_OK) { + return err; + } + + switch (state) { + case CYAML_STATE_IN_MAP_KEY: + assert(schema->type == CYAML_MAPPING); + s.mapping.schema = schema->mapping.fields; + err = cyaml__mapping_bitfieid_create(ctx, &s); + if (err != CYAML_OK) { + return err; + } + break; + case CYAML_STATE_IN_SEQUENCE: + assert(cyaml__is_sequence(schema)); + if (schema->type == CYAML_SEQUENCE_FIXED) { + if (schema->sequence.min != schema->sequence.max) { + return CYAML_ERR_SEQUENCE_FIXED_COUNT; + } + } else { + if (ctx->state->state == CYAML_STATE_IN_SEQUENCE) { + return CYAML_ERR_SEQUENCE_IN_SEQUENCE; + + } else if (ctx->state->state == + CYAML_STATE_IN_MAP_KEY) { + const cyaml_schema_field_t *field = + cyaml_mapping_schema_field(ctx); + s.sequence.count_data = ctx->state->data + + field->count_offset; + s.sequence.count_size = field->count_size; + } else { + assert(ctx->state->state == CYAML_STATE_IN_DOC); + s.sequence.count_data = (void *)&ctx->seq_count; + s.sequence.count_size = sizeof(ctx->seq_count); + } + } + break; + default: + break; + } + + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: PUSH[%u]: %s\n", ctx->stack_idx, + cyaml__state_to_str(state)); + + ctx->stack[ctx->stack_idx] = s; + ctx->state = ctx->stack + ctx->stack_idx; + ctx->stack_idx++; + + return CYAML_OK; +} + +/** + * Pop the current entry on the CYAML load context's stack. + * + * This frees any resources owned by the stack entry. + * + * \param[in] ctx The CYAML loading context. + */ +static void cyaml__stack_pop( + cyaml_ctx_t *ctx) +{ + uint32_t idx = ctx->stack_idx; + + assert(idx != 0); + + switch (ctx->state->state) { + case CYAML_STATE_IN_MAP_KEY: /* Fall through. */ + case CYAML_STATE_IN_MAP_VALUE: + cyaml__mapping_bitfieid_destroy(ctx, ctx->state); + break; + default: + break; + } + + idx--; + + cyaml__log(ctx->config, CYAML_LOG_DEBUG, "Load: POP[%u]: %s\n", idx, + cyaml__state_to_str(ctx->state->state)); + + ctx->state = (idx == 0) ? NULL : &ctx->stack[idx - 1]; + ctx->stack_idx = idx; +} + +/** + * Validate the current event for what's expected by the schema. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for value that the event belongs to. + * \param[in] event The event to be checked. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__validate_event_type_for_schema( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const yaml_event_t *event) +{ + cyaml_event_t cyaml_event = cyaml__get_event_type(event); + static const cyaml_event_t valid_event[CYAML__TYPE_COUNT] = { + [CYAML_INT] = CYAML_EVT_SCALAR, + [CYAML_UINT] = CYAML_EVT_SCALAR, + [CYAML_BOOL] = CYAML_EVT_SCALAR, + [CYAML_ENUM] = CYAML_EVT_SCALAR, + [CYAML_FLOAT] = CYAML_EVT_SCALAR, + [CYAML_STRING] = CYAML_EVT_SCALAR, + [CYAML_FLAGS] = CYAML_EVT_SEQ_START, + [CYAML_MAPPING] = CYAML_EVT_MAP_START, + [CYAML_BITFIELD] = CYAML_EVT_MAP_START, + [CYAML_SEQUENCE] = CYAML_EVT_SEQ_START, + [CYAML_SEQUENCE_FIXED] = CYAML_EVT_SEQ_START, + [CYAML_IGNORE] = CYAML_EVT__COUNT, + }; + + if (schema->type >= CYAML__TYPE_COUNT) { + return CYAML_ERR_BAD_TYPE_IN_SCHEMA; + } + + if (schema->type == CYAML_IGNORE) { + return CYAML_OK; + } + + if (cyaml_event != valid_event[schema->type]) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Load: Expecting %s, got event: %s\n", + cyaml__type_to_str(schema->type), + cyaml__libyaml_event_type_str(event)); + return CYAML_ERR_INVALID_VALUE; + } + + return CYAML_OK; +} + +/** + * Helper to make allocations for loaded YAML values. + * + * If the current state is sequence, this extends any existing allocation + * for the sequence. + * + * The current CYAML loading context's state is updated with new allocation + * address, where necessary. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for value to get data pointer for. + * \param[in] event The YAML event value to get data pointer for. + * \param[in,out] value_data_io Current address of value's data. Updated to + * new address if value is allocation requiring + * an allocation. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__data_handle_pointer( + cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const yaml_event_t *event, + uint8_t **value_data_io) +{ + cyaml_state_t *state = ctx->state; + + if (schema->flags & CYAML_FLAG_POINTER) { + /* Need to create/extend an allocation. */ + size_t data_size = schema->data_size; + uint8_t *value_data = NULL; + size_t offset = 0; + size_t delta; + + switch (schema->type) { + case CYAML_STRING: + /* For a string the allocation size is the string + * size from the event, plus trailing NULL. */ + delta = strlen((const char *) + event->data.scalar.value) + 1; + break; + case CYAML_SEQUENCE: + /* Sequence; could be extending allocation. */ + offset = data_size * state->sequence.count; + value_data = state->sequence.data; + delta = data_size; + break; + case CYAML_SEQUENCE_FIXED: + /* Allocation is only made for full fixed size + * of sequence, on creation, and not extended. */ + if (state->sequence.count > 0) { + *value_data_io = state->sequence.data; + return CYAML_OK; + } + delta = data_size * schema->sequence.max; + break; + default: + delta = data_size; + break; + } + + value_data = cyaml__realloc(ctx->config, value_data, + offset, offset + delta, true); + if (value_data == NULL) { + return CYAML_ERR_OOM; + } + + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: Allocation: %p (%zu + %zu bytes)\n", + value_data, offset, delta); + + if (cyaml__is_sequence(schema)) { + /* Updated the in sequence state so it knows the new + * allocation address. */ + state->sequence.data = value_data; + } + + /* Write the allocation pointer into the data structure. */ + cyaml_data_write_pointer(value_data, *value_data_io); + + /* Update the caller's pointer so it can write the value to + * the right place. */ + *value_data_io = value_data; + } + + return CYAML_OK; +} + +/** + * Dump a backtrace to the log. + * + * \param[in] ctx The CYAML loading context. + */ +static void cyaml__backtrace( + cyaml_ctx_t *ctx) +{ + if (ctx->stack_idx > 1) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, "Load: Backtrace:\n"); + } else { + return; + } + + for (uint32_t idx = ctx->stack_idx - 1; idx != 0; idx--) { + cyaml_state_t *state = ctx->stack + idx; + switch (state->state) { + case CYAML_STATE_IN_MAP_KEY: /* Fall through. */ + case CYAML_STATE_IN_MAP_VALUE: + if (state->mapping.schema_idx != + CYAML_SCHEMA_IDX_NONE) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + " in mapping field: %s\n", + state->mapping.schema[ + state->mapping.schema_idx].key); + } else { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + " in mapping:\n"); + } + break; + case CYAML_STATE_IN_SEQUENCE: + cyaml__log(ctx->config, CYAML_LOG_ERROR, + " in sequence entry: %"PRIu32"\n", + state->sequence.count); + break; + default: + /** \todo \ref CYAML_STATE_IN_DOC handling for multi + * document streams. + */ + break; + } + } +} + +/** + * Read a value of type \ref CYAML_INT. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be read. + * \param[in] value String containing scaler value. + * \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_int( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const char *value, + uint8_t *data) +{ + long long temp; + char *end = NULL; + int64_t max; + int64_t min; + + CYAML_UNUSED(ctx); + + if (schema->data_size == 0 || schema->data_size > sizeof(uint64_t)) { + return CYAML_ERR_INVALID_DATA_SIZE; + } + + max = (INT64_MAX >> ((8 - schema->data_size) * 8)) / 2; + min = (-max) - 1; + + errno = 0; + temp = strtoll(value, &end, 0); + + if (end == value || errno == ERANGE || + temp < min || temp > max) { + return CYAML_ERR_INVALID_VALUE; + } + + return cyaml_data_write((uint64_t)temp, schema->data_size, data); +} + +/** + * Helper to read a number into a uint64_t. + * + * \param[in] value String containing scaler value. + * \param[in] out The place to write the value in the output data. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static inline cyaml_err_t cyaml__read_uint64_t( + const char *value, + uint64_t *out) +{ + unsigned long long temp; + char *end = NULL; + + errno = 0; + temp = strtoull(value, &end, 0); + + if (end == value || errno == ERANGE) { + return CYAML_ERR_INVALID_VALUE; + } + + *out = (uint64_t)temp; + return CYAML_OK; +} + +/** + * Read a value of type \ref CYAML_UINT. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be read. + * \param[in] value String containing scaler value. + * \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_uint( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const char *value, + uint8_t *data) +{ + cyaml_err_t err; + uint64_t temp; + uint64_t max; + + CYAML_UNUSED(ctx); + + if (schema->data_size == 0) { + return CYAML_ERR_INVALID_DATA_SIZE; + } + + err = cyaml__read_uint64_t(value, &temp); + if (err != CYAML_OK) { + return err; + } + + max = (~(uint64_t)0) >> ((8 - schema->data_size) * 8); + if (temp > max) { + return CYAML_ERR_INVALID_VALUE; + } + + return cyaml_data_write(temp, schema->data_size, data); +} + +/** + * Read a value of type \ref CYAML_BOOL. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be read. + * \param[in] value String containing scaler value. + * \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_bool( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const char *value, + uint8_t *data) +{ + bool temp = true; + static const char * const false_strings[] = { + "false", "no", "disable", "0", + }; + + CYAML_UNUSED(ctx); + + for (uint32_t i = 0; i < CYAML_ARRAY_LEN(false_strings); i++) { + if (cyaml_utf8_casecmp(value, false_strings[i]) == 0) { + temp = false; + break; + } + } + + return cyaml_data_write(temp, schema->data_size, data); +} + +/** + * Read a value of type \ref CYAML_ENUM. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be read. + * \param[in] value String containing scaler value. + * \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_enum( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const char *value, + uint8_t *data) +{ + const cyaml_strval_t *strings = schema->enumeration.strings; + + for (uint32_t i = 0; i < schema->enumeration.count; i++) { + if (cyaml__strcmp(ctx->config, schema, + value, strings[i].str) == 0) { + return cyaml_data_write((uint32_t)strings[i].val, + schema->data_size, data); + } + } + + if (schema->flags & CYAML_FLAG_STRICT) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Load: Invalid enumeration value: %s\n", value); + return CYAML_ERR_INVALID_VALUE; + + } + + return cyaml__read_int(ctx, schema, value, data); +} + +/** + * Helper to read \ref CYAML_FLOAT of size `sizeof(float)`. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be read. + * \param[in] value String containing scaler value. + * \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_float_f( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const char *value, + uint8_t *data) +{ + float temp; + char *end = NULL; + + assert(schema->data_size == sizeof(temp)); + + CYAML_UNUSED(ctx); + CYAML_UNUSED(schema); + + errno = 0; + temp = strtof(value, &end); + + if (end == value || errno == ERANGE) { + return CYAML_ERR_INVALID_VALUE; + } + + memcpy(data, &temp, sizeof(temp)); + + return CYAML_OK; +} + +/** + * Helper to read \ref CYAML_FLOAT of size `sizeof(double)`. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be read. + * \param[in] value String containing scaler value. + * \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_float_d( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const char *value, + uint8_t *data) +{ + double temp; + char *end = NULL; + + assert(schema->data_size == sizeof(temp)); + + CYAML_UNUSED(ctx); + CYAML_UNUSED(schema); + + errno = 0; + temp = strtod(value, &end); + + if (end == value || errno == ERANGE) { + return CYAML_ERR_INVALID_VALUE; + } + + memcpy(data, &temp, sizeof(temp)); + + return CYAML_OK; +} + +/** + * Read a value of type \ref CYAML_FLOAT. + * + * The `data_size` of the schema entry must be the size of a known + * floating point C type. + * + * \note The `long double` type was causing problems, so it isn't currently + * supported. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be read. + * \param[in] value String containing scaler value. + * \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_float( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const char *value, + uint8_t *data) +{ + typedef cyaml_err_t (*cyaml_float_fn)( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const char *value, + uint8_t *data_target); + struct float_fns { + size_t size; + const cyaml_float_fn func; + }; + static const struct float_fns fns[] = { + { + .size = sizeof(float), + .func = cyaml__read_float_f, + }, + { + .size = sizeof(double), + .func = cyaml__read_float_d, + }, + }; + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(fns); i++) { + if (fns[i].size == schema->data_size) { + assert(fns[i].func != NULL); + return fns[i].func(ctx, schema, value, data); + } + } + + return CYAML_ERR_INVALID_DATA_SIZE; +} + +/** + * Read a value of type \ref CYAML_STRING. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be read. + * \param[in] value String containing scaler value. + * \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_string( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const char *value, + uint8_t *data) +{ + size_t str_len = strlen(value); + + CYAML_UNUSED(ctx); + + if (schema->string.min > schema->string.max) { + return CYAML_ERR_BAD_MIN_MAX_SCHEMA; + } else if (str_len < schema->string.min) { + return CYAML_ERR_STRING_LENGTH_MIN; + } else if (str_len > schema->string.max) { + return CYAML_ERR_STRING_LENGTH_MAX; + } + + memcpy(data, value, str_len + 1); + + return CYAML_OK; +} + +/** + * Read a scalar value. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be read. + * \param[in] data The place to write the value in the output data. + * \param[in] event The `libyaml` event providing the scalar value data. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__read_scalar_value( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + cyaml_data_t *data, + const yaml_event_t *event) +{ + const char *value = (const char *)event->data.scalar.value; + typedef cyaml_err_t (*cyaml_read_scalar_fn)( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const char *value, + uint8_t *data_target); + static const cyaml_read_scalar_fn fn[CYAML__TYPE_COUNT] = { + [CYAML_INT] = cyaml__read_int, + [CYAML_UINT] = cyaml__read_uint, + [CYAML_BOOL] = cyaml__read_bool, + [CYAML_ENUM] = cyaml__read_enum, + [CYAML_FLOAT] = cyaml__read_float, + [CYAML_STRING] = cyaml__read_string, + }; + + cyaml__log(ctx->config, CYAML_LOG_INFO, "Load: <%s>\n", value); + + assert(fn[schema->type] != NULL); + + return fn[schema->type](ctx, schema, value, data); +} + +/** + * Set a flag in a \ref CYAML_FLAGS value. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be read. + * \param[in] value String containing scaler value. + * \param[in,out] flags_out Current flags, updated on success. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__set_flag( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const char *value, + uint64_t *flags_out) +{ + const cyaml_strval_t *strings = schema->enumeration.strings; + + for (uint32_t i = 0; i < schema->enumeration.count; i++) { + if (cyaml__strcmp(ctx->config, schema, + value, strings[i].str) == 0) { + *flags_out |= ((uint64_t)strings[i].val); + return CYAML_OK; + } + } + + if (!(schema->flags & CYAML_FLAG_STRICT)) { + long long temp; + char *end = NULL; + uint64_t max = (~(uint64_t)0) >> ((8 - schema->data_size) * 8); + + errno = 0; + temp = strtoll(value, &end, 0); + + if (!(end == value || errno == ERANGE || + temp < 0 || (uint64_t)temp > max)) { + *flags_out |= ((uint64_t)temp); + return CYAML_OK; + } + } + + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Load: Unknown flag: %s\n", value); + + return CYAML_ERR_INVALID_VALUE; +} + +/** + * Read a value of type \ref CYAML_FLAGS. + * + * Since \ref CYAML_FLAGS is a composite value (a sequence of scalars), rather + * than a simple scaler, this consumes events from the YAML input stream. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be read. + * \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_flags_value( + cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + cyaml_data_t *data) +{ + bool exit = false; + uint64_t value = 0; + cyaml_err_t err = CYAML_OK; + const yaml_event_t *const event = cyaml__current_event(ctx); + + if (schema->data_size == 0) { + return CYAML_ERR_INVALID_DATA_SIZE; + } + + while (!exit) { + cyaml_event_t cyaml_event; + err = cyaml_get_next_event(ctx); + if (err != CYAML_OK) { + return err; + } + cyaml_event = cyaml__get_event_type(event); + + switch (cyaml_event) { + case CYAML_EVT_SCALAR: + err = cyaml__set_flag(ctx, schema, + (const char *)event->data.scalar.value, + &value); + if (err != CYAML_OK) { + return err; + } + break; + case CYAML_EVT_SEQ_END: + exit = true; + break; + default: + return CYAML_ERR_UNEXPECTED_EVENT; + } + } + + err = cyaml_data_write(value, schema->data_size, data); + if (err != CYAML_OK) { + return err; + } + + cyaml__log(ctx->config, CYAML_LOG_INFO, + "Load: \n", value); + + return err; +} + +/** + * Set some bits in a \ref CYAML_BITFIELD value. + * + * If the given bit value name is one expected by the schema, then this + * function consumes an event from the YAML input stream. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be read. + * \param[in] name String containing scaler bit value name. + * \param[in,out] bits_out Current bits, updated on success. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__set_bitval( + cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const char *name, + uint64_t *bits_out) +{ + const yaml_event_t *const event = cyaml__current_event(ctx); + const cyaml_bitdef_t *bitdef = schema->bitfield.bitdefs; + cyaml_err_t err; + uint64_t value; + uint64_t mask; + uint32_t i; + + for (i = 0; i < schema->bitfield.count; i++) { + if (bitdef[i].bits + bitdef[i].offset > schema->data_size * 8) { + return CYAML_ERR_BAD_BITVAL_IN_SCHEMA; + } + if (cyaml__strcmp(ctx->config, schema, + name, bitdef[i].name) == 0) { + break; + } + } + + if (i == schema->bitfield.count) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Load: Unknown bit value: %s\n", name); + return CYAML_ERR_INVALID_VALUE; + } + + err = cyaml_get_next_event(ctx); + if (err != CYAML_OK) { + return err; + } + + switch (cyaml__get_event_type(event)) { + case CYAML_EVT_SCALAR: + err = cyaml__read_uint64_t( + (const char *)event->data.scalar.value, &value); + if (err != CYAML_OK) { + return err; + } + break; + default: + return CYAML_ERR_UNEXPECTED_EVENT; + } + + mask = (~(uint64_t)0) >> ((8 * sizeof(uint64_t)) - bitdef[i].bits); + if (value > mask) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Load: Value too big for bits: %s\n", name); + return CYAML_ERR_INVALID_VALUE; + } + + *bits_out |= value << bitdef[i].offset; + return CYAML_OK; +} + +/** + * Read a value of type \ref CYAML_BITFIELD. + * + * Since \ref CYAML_FLAGS is a composite value (a mapping), rather + * than a simple scaler, this consumes events from the YAML input stream. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema The schema for the value to be read. + * \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_bitfield_value( + cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + cyaml_data_t *data) +{ + bool exit = false; + uint64_t value = 0; + cyaml_err_t err = CYAML_OK; + const yaml_event_t *const event = cyaml__current_event(ctx); + + while (!exit) { + cyaml_event_t cyaml_event; + err = cyaml_get_next_event(ctx); + if (err != CYAML_OK) { + return err; + } + cyaml_event = cyaml__get_event_type(event); + switch (cyaml_event) { + case CYAML_EVT_SCALAR: + err = cyaml__set_bitval(ctx, schema, + (const char *)event->data.scalar.value, + &value); + if (err != CYAML_OK) { + return err; + } + break; + case CYAML_EVT_MAP_END: + exit = true; + break; + default: + return CYAML_ERR_UNEXPECTED_EVENT; + } + } + + err = cyaml_data_write(value, schema->data_size, data); + if (err != CYAML_OK) { + return err; + } + + cyaml__log(ctx->config, CYAML_LOG_INFO, + "Load: \n", value); + + return err; +} + +/** + * Entirely consume an ignored value. + * + * This ignores all the descendants of the value, e.g. if the `ignored` key's + * value is of type \ref CYAML_IGNORE, all of the following is ignored: + * + * ``` + * ignored: + * - foo: 7 + * bar: 9 + * - foo: 1 + * bar: 2 + * ``` + * + * \param[in] ctx The CYAML loading context. + * \param[in] cyaml_event The event for the value to ignore. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__consume_ignored_value( + cyaml_ctx_t *ctx, + cyaml_event_t cyaml_event) +{ + if (cyaml_event != CYAML_EVT_SCALAR) { + unsigned level = 1; + + assert(cyaml_event == CYAML_EVT_SEQ_START || + cyaml_event == CYAML_EVT_MAP_START); + + while (level > 0) { + cyaml_err_t err; + const yaml_event_t *const event = + cyaml__current_event(ctx); + + err = cyaml_get_next_event(ctx); + if (err != CYAML_OK) { + return err; + } + switch (cyaml__get_event_type(event)) { + case CYAML_EVT_SEQ_START: /* Fall through */ + case CYAML_EVT_MAP_START: + level++; + break; + + case CYAML_EVT_SEQ_END: /* Fall through */ + case CYAML_EVT_MAP_END: + level--; + break; + + default: + break; + } + } + } + + return CYAML_OK; +} + +/** + * Check whether a string represents a NULL value. + * + * \param[in] schema CYAML schema for the value to test. + * \param[in] value String value to test. + * \return true if string represents a NULL, false otherwise. + */ +static bool cyaml__string_is_null_ptr( + const cyaml_schema_value_t *schema, + const char *value) +{ + assert(value != NULL); + + if (cyaml__flag_check_all(schema->flags, CYAML_FLAG_POINTER_NULL) && + value[0] == '\0') { + return true; + } + + if (!cyaml__flag_check_all(schema->flags, + CYAML_FLAG_POINTER_NULL_STR)) { + return false; + } + + switch (strlen(value)) { + case 1: + return (strcmp(value, "~") == 0); + case 4: + return (strcmp(value, "null") == 0 || + strcmp(value, "Null") == 0 || + strcmp(value, "NULL") == 0); + } + + return false; +} + +/** + * Handle a YAML event corresponding to a YAML data value. + * + * \param[in] ctx The CYAML loading context. + * \param[in] schema CYAML schema for the expected value. + * \param[in] data Pointer to where value's data should be written. + * \param[in] event The YAML event to handle. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__read_value( + cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + uint8_t *data, + const yaml_event_t *event) +{ + cyaml_event_t cyaml_event = cyaml__get_event_type(event); + cyaml_err_t err; + + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: Reading value of type '%s'%s\n", + cyaml__type_to_str(schema->type), + schema->flags & CYAML_FLAG_POINTER ? " (pointer)" : ""); + + err = cyaml__validate_event_type_for_schema(ctx, schema, event); + if (err != CYAML_OK) { + return err; + } + + if (cyaml_event == CYAML_EVT_SCALAR) { + if (cyaml__string_is_null_ptr(schema, + (const char *)event->data.scalar.value)) { + cyaml__log(ctx->config, CYAML_LOG_INFO, + "Load: \n"); + return CYAML_OK; + } + } + + if (cyaml__is_sequence(schema) == false) { + /* Since sequences extend their allocation for each entry, + * they're handled in the sequence-specific code. + */ + err = cyaml__data_handle_pointer(ctx, schema, event, &data); + if (err != CYAML_OK) { + return err; + } + } + + switch (schema->type) { + case CYAML_INT: /* Fall through. */ + case CYAML_UINT: /* Fall through. */ + case CYAML_BOOL: /* Fall through. */ + case CYAML_ENUM: /* Fall through. */ + case CYAML_FLOAT: /* Fall through. */ + case CYAML_STRING: + err = cyaml__read_scalar_value(ctx, schema, data, event); + break; + case CYAML_FLAGS: + err = cyaml__read_flags_value(ctx, schema, data); + break; + case CYAML_MAPPING: + err = cyaml__stack_push(ctx, CYAML_STATE_IN_MAP_KEY, + schema, data); + break; + case CYAML_BITFIELD: + err = cyaml__read_bitfield_value(ctx, schema, data); + break; + case CYAML_SEQUENCE: /* Fall through. */ + case CYAML_SEQUENCE_FIXED: + err = cyaml__stack_push(ctx, CYAML_STATE_IN_SEQUENCE, + schema, data); + break; + case CYAML_IGNORE: + err = cyaml__consume_ignored_value(ctx, cyaml_event); + break; + default: + return CYAML_ERR_INTERNAL_ERROR; + } + + return err; +} + +/** + * YAML loading handler for start of stream in the \ref CYAML_STATE_START state. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event The YAML event to handle. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__stream_start( + cyaml_ctx_t *ctx, + const yaml_event_t *event) +{ + CYAML_UNUSED(event); + return cyaml__stack_push(ctx, CYAML_STATE_IN_STREAM, + ctx->state->schema, ctx->state->data); +} + +/** + * YAML loading handler for documents in the \ref CYAML_STATE_IN_STREAM state. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event The YAML event to handle. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__doc_start( + cyaml_ctx_t *ctx, + const yaml_event_t *event) +{ + CYAML_UNUSED(event); + if (ctx->state->stream.doc_count == 1) { + cyaml__log(ctx->config, CYAML_LOG_WARNING, + "Ignoring documents after first in stream\n"); + cyaml__stack_pop(ctx); + return CYAML_OK; + } + ctx->state->stream.doc_count++; + return cyaml__stack_push(ctx, CYAML_STATE_IN_DOC, + ctx->state->schema, ctx->state->data); +} + +/** + * YAML loading handler for finalising the \ref CYAML_STATE_IN_STREAM state. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event The YAML event to handle. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__stream_end( + cyaml_ctx_t *ctx, + const yaml_event_t *event) +{ + CYAML_UNUSED(event); + cyaml__stack_pop(ctx); + return CYAML_OK; +} + +/** + * YAML loading handler for the root value in the \ref CYAML_STATE_IN_DOC state. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event The YAML event to handle. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__doc_root_value( + cyaml_ctx_t *ctx, + const yaml_event_t *event) +{ + return cyaml__read_value(ctx, ctx->state->schema, + ctx->state->data, event); +} + +/** + * YAML loading handler for finalising the \ref CYAML_STATE_IN_DOC state. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event The YAML event to handle. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__doc_end( + cyaml_ctx_t *ctx, + const yaml_event_t *event) +{ + CYAML_UNUSED(event); + cyaml__stack_pop(ctx); + return CYAML_OK; +} + +/** + * YAML loading handler for new mapping fields in the + * \ref CYAML_STATE_IN_MAP_KEY state. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event The YAML event to handle. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__map_key( + cyaml_ctx_t *ctx, + const yaml_event_t *event) +{ + const char *key; + cyaml_err_t err = CYAML_OK; + + key = (const char *)event->data.scalar.value; + ctx->state->mapping.schema_idx = + cyaml__get_entry_from_mapping_schema(ctx, key); + cyaml__log(ctx->config, CYAML_LOG_INFO, "Load: [%s]\n", key); + + if (ctx->state->mapping.schema_idx == CYAML_SCHEMA_IDX_NONE) { + const yaml_event_t *const ignore_event = + cyaml__current_event(ctx); + cyaml_event_t cyaml_event; + if (!(ctx->config->flags & + CYAML_CFG_IGNORE_UNKNOWN_KEYS)) { + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: Unexpected key: %s\n", key); + return CYAML_ERR_INVALID_KEY; + } + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: Ignoring key: %s\n", key); + err = cyaml_get_next_event(ctx); + if (err != CYAML_OK) { + return err; + } + cyaml_event = cyaml__get_event_type(ignore_event); + return cyaml__consume_ignored_value(ctx, cyaml_event); + } + cyaml__mapping_bitfieid_set(ctx); + + /* Toggle mapping sub-state to value */ + ctx->state->state = CYAML_STATE_IN_MAP_VALUE; + + return err; +} + +/** + * YAML loading handler for finalising the \ref CYAML_STATE_IN_MAP_KEY state. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event The YAML event to handle. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__map_end( + cyaml_ctx_t *ctx, + const yaml_event_t *event) +{ + cyaml_err_t err; + + CYAML_UNUSED(event); + + err = cyaml__mapping_bitfieid_validate(ctx); + if (err != CYAML_OK) { + return err; + } + + cyaml__stack_pop(ctx); + return CYAML_OK; +} + +/** + * YAML loading handler for the \ref CYAML_STATE_IN_MAP_VALUE state. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event The YAML event to handle. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__map_value( + cyaml_ctx_t *ctx, + const yaml_event_t *event) +{ + cyaml_state_t *state = ctx->state; + const cyaml_schema_field_t *entry = cyaml_mapping_schema_field(ctx); + cyaml_data_t *data = state->data + entry->data_offset; + + /* Toggle mapping sub-state back to key. Do this before + * reading value, because reading value might increase the + * CYAML context stack allocation, causing the state entry + * to move. */ + state->state = CYAML_STATE_IN_MAP_KEY; + + return cyaml__read_value(ctx, &entry->value, data, event); +} + +/** + * YAML loading handler for new sequence entries in the + * \ref CYAML_STATE_IN_SEQUENCE state. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event The YAML event to handle. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__seq_entry( + cyaml_ctx_t *ctx, + const yaml_event_t *event) +{ + cyaml_err_t err; + cyaml_state_t *state = ctx->state; + uint8_t *value_data = state->data; + const cyaml_schema_value_t *schema = state->schema; + + if (state->sequence.count + 1 > state->schema->sequence.max) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Load: Excessive entries (%"PRIu32" max) " + "in sequence.\n", + state->schema->sequence.max); + return CYAML_ERR_SEQUENCE_ENTRIES_MAX; + } + + err = cyaml__data_handle_pointer(ctx, schema, event, &value_data); + if (err != CYAML_OK) { + return err; + } + + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: Sequence entry: %u (%"PRIu32" bytes)\n", + state->sequence.count, schema->data_size); + value_data += schema->data_size * state->sequence.count; + state->sequence.count++; + + if (schema->type != CYAML_SEQUENCE_FIXED) { + err = cyaml_data_write(state->sequence.count, + state->sequence.count_size, + state->sequence.count_data); + if (err != CYAML_OK) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Load: Failed writing sequence count\n"); + if (schema->flags & CYAML_FLAG_POINTER) { + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: Freeing %p\n", + state->sequence.data); + cyaml__free(ctx->config, state->sequence.data); + } + return err; + } + } + + /* Read the actual value */ + err = cyaml__read_value(ctx, schema->sequence.entry, + value_data, event); + if (err != CYAML_OK) { + return err; + } + + return CYAML_OK; +} + +/** + * YAML loading handler for finalising the \ref CYAML_STATE_IN_SEQUENCE state. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event The YAML event to handle. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__seq_end( + cyaml_ctx_t *ctx, + const yaml_event_t *event) +{ + cyaml_state_t *state = ctx->state; + + CYAML_UNUSED(event); + + if (state->sequence.count < state->schema->sequence.min) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Load: Insufficient entries " + "(%"PRIu32" of %"PRIu32" min) in sequence.\n", + state->sequence.count, + state->schema->sequence.min); + return CYAML_ERR_SEQUENCE_ENTRIES_MIN; + } + cyaml__log(ctx->config, CYAML_LOG_DEBUG, "Load: Sequence count: %u\n", + state->sequence.count); + + cyaml__stack_pop(ctx); + return CYAML_OK; +} + +/** + * Check that common load parameters from client are valid. + * + * \param[in] config The client's CYAML library config. + * \param[in] schema The schema describing the content of data. + * \param[in] data_tgt Points to client's address to write data to. + * \param[in] seq_count_tgt Points to client's address to write sequence count. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static inline cyaml_err_t cyaml__validate_load_params( + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + cyaml_data_t * const *data_tgt, + const unsigned *seq_count_tgt) +{ + if (config == NULL) { + return CYAML_ERR_BAD_PARAM_NULL_CONFIG; + } + if (config->mem_fn == NULL) { + return CYAML_ERR_BAD_CONFIG_NULL_MEMFN; + } + if (schema == NULL) { + return CYAML_ERR_BAD_PARAM_NULL_SCHEMA; + } + if ((schema->type == CYAML_SEQUENCE) != (seq_count_tgt != NULL)) { + return CYAML_ERR_BAD_PARAM_SEQ_COUNT; + } + if (!(schema->flags & CYAML_FLAG_POINTER)) { + return CYAML_ERR_TOP_LEVEL_NON_PTR; + } + if (data_tgt == NULL) { + return CYAML_ERR_BAD_PARAM_NULL_DATA; + } + return CYAML_OK; +} + +/** + * YAML loading helper dispatch function. + * + * Dispatches events to the appropriate event handler function for the + * current combination of load state machine state (from the load context) + * and event type. + * + * \param[in] ctx The CYAML loading context. + * \param[in] event The YAML event to handle. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static inline cyaml_err_t cyaml__load_event( + cyaml_ctx_t *ctx, + const yaml_event_t *event) +{ + cyaml_state_t *state = ctx->state; + typedef cyaml_err_t (* const cyaml_read_fn)( + cyaml_ctx_t *ctx, + const yaml_event_t *event); + static const cyaml_read_fn fns[CYAML_STATE__COUNT][CYAML_EVT__COUNT] = { + [CYAML_STATE_START] = { + [CYAML_EVT_STRM_START] = cyaml__stream_start, + }, + [CYAML_STATE_IN_STREAM] = { + [CYAML_EVT_DOC_START] = cyaml__doc_start, + [CYAML_EVT_STRM_END] = cyaml__stream_end, + }, + [CYAML_STATE_IN_DOC] = { + [CYAML_EVT_SCALAR] = cyaml__doc_root_value, + [CYAML_EVT_SEQ_START] = cyaml__doc_root_value, + [CYAML_EVT_MAP_START] = cyaml__doc_root_value, + [CYAML_EVT_DOC_END] = cyaml__doc_end, + }, + [CYAML_STATE_IN_MAP_KEY] = { + [CYAML_EVT_SCALAR] = cyaml__map_key, + [CYAML_EVT_MAP_END] = cyaml__map_end, + }, + [CYAML_STATE_IN_MAP_VALUE] = { + [CYAML_EVT_SCALAR] = cyaml__map_value, + [CYAML_EVT_SEQ_START] = cyaml__map_value, + [CYAML_EVT_MAP_START] = cyaml__map_value, + }, + [CYAML_STATE_IN_SEQUENCE] = { + [CYAML_EVT_SCALAR] = cyaml__seq_entry, + [CYAML_EVT_SEQ_START] = cyaml__seq_entry, + [CYAML_EVT_MAP_START] = cyaml__seq_entry, + [CYAML_EVT_SEQ_END] = cyaml__seq_end, + }, + }; + const cyaml_read_fn fn = fns[state->state][event->type]; + cyaml_err_t err = CYAML_ERR_INTERNAL_ERROR; + + if (fn != NULL) { + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Load: Handle state %s\n", + cyaml__state_to_str(state->state)); + err = fn(ctx, event); + } + + return err; +} + +/** + * The main YAML loading function. + * + * The public interfaces are wrappers around this. + * + * \param[in] config Client's CYAML configuration structure. + * \param[in] schema CYAML schema for the YAML to be loaded. + * \param[out] data_out Returns the caller-owned loaded data on success. + * Untouched on failure. + * \param[out] seq_count_out On success, returns the sequence entry count. + * Untouched on failure. + * Must be non-NULL if top-level schema type is + * \ref CYAML_SEQUENCE, otherwise, must be NULL. + * \param[in] parser An initialised `libyaml` parser object + * with its input set. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__load( + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + cyaml_data_t **data_out, + unsigned *seq_count_out, + yaml_parser_t *parser) +{ + cyaml_data_t *data = NULL; + cyaml_ctx_t ctx = { + .config = config, + .parser = parser, + }; + cyaml_err_t err = CYAML_OK; + + err = cyaml__validate_load_params(config, schema, + data_out, seq_count_out); + if (err != CYAML_OK) { + return err; + } + + err = cyaml__stack_push(&ctx, CYAML_STATE_START, schema, &data); + if (err != CYAML_OK) { + goto out; + } + + do { + const yaml_event_t *const event = cyaml__current_event(&ctx); + + err = cyaml_get_next_event(&ctx); + if (err != CYAML_OK) { + goto out; + } + + err = cyaml__load_event(&ctx, event); + if (err != CYAML_OK) { + goto out; + } + } while (ctx.state->state > CYAML_STATE_START); + + cyaml__stack_pop(&ctx); + + assert(ctx.stack_idx == 0); + + *data_out = data; + if (seq_count_out != NULL) { + *seq_count_out = ctx.seq_count; + } +out: + if (err != CYAML_OK) { + cyaml_free(config, schema, data, ctx.seq_count); + cyaml__backtrace(&ctx); + } + while (ctx.stack_idx > 0) { + cyaml__stack_pop(&ctx); + } + cyaml__free(config, ctx.stack); + cyaml__delete_yaml_event(&ctx); + cyaml__free_recording(&ctx); + return err; +} + +/* Exported function, documented in include/cyaml/cyaml.h */ +cyaml_err_t cyaml_load_file( + const char *path, + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + cyaml_data_t **data_out, + unsigned *seq_count_out) +{ + FILE *file; + cyaml_err_t err; + yaml_parser_t parser; + + /* Initialize parser */ + if (!yaml_parser_initialize(&parser)) { + return CYAML_ERR_LIBYAML_PARSER_INIT; + } + + /* Open input file. */ + file = fopen(path, "r"); + if (file == NULL) { + yaml_parser_delete(&parser); + return CYAML_ERR_FILE_OPEN; + } + + /* Set input file */ + yaml_parser_set_input_file(&parser, file); + + /* Parse the input */ + err = cyaml__load(config, schema, data_out, seq_count_out, &parser); + if (err != CYAML_OK) { + yaml_parser_delete(&parser); + fclose(file); + return err; + } + + /* Cleanup */ + yaml_parser_delete(&parser); + fclose(file); + + return CYAML_OK; +} + +/* Exported function, documented in include/cyaml/cyaml.h */ +cyaml_err_t cyaml_load_data( + const uint8_t *input, + size_t input_len, + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + cyaml_data_t **data_out, + unsigned *seq_count_out) +{ + cyaml_err_t err; + yaml_parser_t parser; + + /* Initialize parser */ + if (!yaml_parser_initialize(&parser)) { + return CYAML_ERR_LIBYAML_PARSER_INIT; + } + + /* Set input data */ + yaml_parser_set_input_string(&parser, input, input_len); + + /* Parse the input */ + err = cyaml__load(config, schema, data_out, seq_count_out, &parser); + if (err != CYAML_OK) { + yaml_parser_delete(&parser); + return err; + } + + /* Cleanup */ + yaml_parser_delete(&parser); + + return CYAML_OK; +} diff --git a/src/mem.c b/src/mem.c new file mode 100644 index 0000000..d0adedf --- /dev/null +++ b/src/mem.c @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2018 Michael Drake + */ + +/** + * \file + * \brief CYAML memory allocation handling. + */ + +#include +#include +#include + +#include "mem.h" + +/** Macro to squash unused variable compiler warnings. */ +#define CYAML_UNUSED(_x) ((void)(_x)) + +/* Exported function, documented in include/cyaml/cyaml.h */ +void * cyaml_mem( + void *ctx, + void *ptr, + size_t size) +{ + CYAML_UNUSED(ctx); + + if (size == 0) { + free(ptr); + return NULL; + } + + return realloc(ptr, size); +} diff --git a/src/mem.h b/src/mem.h new file mode 100644 index 0000000..a5dfdaa --- /dev/null +++ b/src/mem.h @@ -0,0 +1,103 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2018 Michael Drake + */ + +/** + * \file + * \brief CYAML memory allocation handling. + */ + +#ifndef CYAML_MEM_H +#define CYAML_MEM_H + +#include "cyaml/cyaml.h" + +/** + * Helper for freeing using the client's choice of allocator routine. + * + * \param[in] config The CYAML client config. + * \param[in] ptr Pointer to allocation to free. + */ +static inline void cyaml__free( + const cyaml_config_t *config, + void *ptr) +{ + config->mem_fn(config->mem_ctx, ptr, 0); +} + +/** + * Helper for new allocations using the client's choice of allocator routine. + * + * \note On failure, any existing allocation is still owned by the caller, and + * they are responsible for freeing it. + * + * \param[in] config The CYAML client config. + * \param[in] ptr The existing allocation or NULL. + * \param[in] current_size Size of the current allocation. (Only needed if + * `clean != false`). + * \param[in] new_size The number of bytes to resize allocation to. + * \param[in] clean Only applies if `new_size > current_size`. + * If `false`, the new memory is uninitialised, + * if `true`, the new memory is initialised to zero. + * \return Pointer to allocation on success, or `NULL` on failure. + */ +static inline void * cyaml__realloc( + const cyaml_config_t *config, + void *ptr, + size_t current_size, + size_t new_size, + bool clean) +{ + uint8_t *temp = config->mem_fn(config->mem_ctx, ptr, new_size); + if (temp == NULL) { + return NULL; + } + + if (clean && (new_size > current_size)) { + memset(temp + current_size, 0, new_size - current_size); + } + + return temp; +} + +/** + * Helper for new allocations using the client's choice of allocator routine. + * + * \param[in] config The CYAML client config. + * \param[in] size The number of bytes to allocate. + * \param[in] clean If `false`, the memory is uninitialised, if `true`, + * the memory is initialised to zero. + * \return Pointer to allocation on success, or `NULL` on failure. + */ +static inline void * cyaml__alloc( + const cyaml_config_t *config, + size_t size, + bool clean) +{ + return cyaml__realloc(config, NULL, 0, size, clean); +} + +/** + * Helper for string duplication using the client's choice of allocator routine. + * + * \param[in] config The CYAML client config. + * \param[in] str The string to duplicate. + * \return Pointer to new string on success, or `NULL` on failure. + */ +static inline char * cyaml__strdup( + const cyaml_config_t *config, + const char *str) +{ + size_t len = strlen(str) + 1; + char *dup = cyaml__alloc(config, len, false); + if (dup == NULL) { + return NULL; + } + + memcpy(dup, str, len); + return dup; +} + +#endif diff --git a/src/save.c b/src/save.c new file mode 100644 index 0000000..a82e0ad --- /dev/null +++ b/src/save.c @@ -0,0 +1,1470 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2018 Michael Drake + */ + +/** + * \file + * \brief Save client's data structure to YAML, using schema. + * + * This uses `libyaml` to emit YAML documents, it uses the client-provided + * schema to access the client data, and validates it before emitting the YAML. + */ + +#include +#include +#include +#include + +#include + +#include "mem.h" +#include "data.h" +#include "util.h" + +/** + * A CYAML save state machine stack entry. + */ +typedef struct cyaml_state { + /** Current save state machine state. */ + enum cyaml_state_e state; + /** Schema for the expected value in this state. */ + const cyaml_schema_value_t *schema; + /** Anonymous union for schema type specific state. */ + union { + /** + * Additional state for \ref CYAML_STATE_IN_MAP_KEY and + * \ref CYAML_STATE_IN_MAP_VALUE states. + */ + struct { + const cyaml_schema_field_t *field; + } mapping; + /** Additional state for \ref CYAML_STATE_IN_SEQUENCE state. */ + struct { + uint64_t entry; + uint64_t count; + } sequence; + }; + const uint8_t *data; /**< Start of client value data for this state. */ + bool done; /**< Whether the state has been handled. */ +} cyaml_state_t; + +/** + * Internal YAML saving context. + */ +typedef struct cyaml_ctx { + const cyaml_config_t *config; /**< Settings provided by client. */ + cyaml_state_t *state; /**< Current entry in state stack, or NULL. */ + cyaml_state_t *stack; /**< State stack */ + uint32_t stack_idx; /**< Next (empty) state stack slot */ + uint32_t stack_max; /**< Current stack allocation limit. */ + unsigned seq_count; /**< Top-level sequence count. */ + yaml_emitter_t *emitter; /**< Internal libyaml parser object. */ +} cyaml_ctx_t; + +/** + * Ensure that the CYAML save context has space for a new stack entry. + * + * \param[in] ctx The CYAML saving context. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__stack_ensure( + cyaml_ctx_t *ctx) +{ + cyaml_state_t *temp; + uint32_t max = ctx->stack_max + 16; + + if (ctx->stack_idx < ctx->stack_max) { + return CYAML_OK; + } + + temp = cyaml__realloc(ctx->config, ctx->stack, 0, + sizeof(*ctx->stack) * max, false); + if (temp == NULL) { + return CYAML_ERR_OOM; + } + + ctx->stack = temp; + ctx->stack_max = max; + ctx->state = ctx->stack + ctx->stack_idx - 1; + + return CYAML_OK; +} + +/** + * Helper to simplify emitting libyaml events. + * + * This is a slightly peculiar function, but it is intended to reduce the + * boilerplate required to emit events. + * + * Intended usage is something like: + * + * ``` + * cyaml_err_t err; + * yaml_event_t event; + * int ret = yaml_mapping_end_event_initialize(&event); + * err = cyaml__emit_event_helper(ctx, ret, &event); + * ``` + * + * \param[in] ctx The CYAML saving context. + * \param[in] valid Whether the event pointer is valid. Typically this + * will be the value returned by the libyaml call that + * initialised the event. As such, if `valid` is non-zero, + * the helper will try to emit the event, otherwise, it + * will return \ref CYAML_ERR_LIBYAML_EVENT_INIT. + * \param[in] event The event to try to emit, if valid. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__emit_event_helper( + const cyaml_ctx_t *ctx, + int valid, + yaml_event_t *event) +{ + if (valid == 0) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Save: LibYAML: Failed to initialise event\n"); + return CYAML_ERR_LIBYAML_EVENT_INIT; + } + + /* Emit event and update save state stack. */ + valid = yaml_emitter_emit(ctx->emitter, event); + if (valid == 0) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, + "Save: LibYAML: Failed to emit event: %s\n", + ctx->emitter->problem); + return CYAML_ERR_LIBYAML_EMITTER; + } + + return CYAML_OK; +} + +/** The style to use when emitting mappings and sequences. */ +enum cyaml_emit_style { + CYAML_EMIT_STYLE_DEFAULT, + CYAML_EMIT_STYLE_BLOCK, + CYAML_EMIT_STYLE_FLOW, +}; + +/** + * Get the style to use for mappings/sequences from value flags and config. + * + * As described in the API, schema flags take priority over config flags, and + * block has precedence over flow, if both flags are set at the same level. + * + * \param[in] ctx The CYAML saving context. + * \param[in] schema The CYAML schema for the value expected in state. + * \return The generic style to emit the value with. + */ +static inline enum cyaml_emit_style cyaml__get_emit_style( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema) +{ + if (schema->flags & CYAML_FLAG_BLOCK) { + return CYAML_EMIT_STYLE_BLOCK; + + } else if (schema->flags & CYAML_FLAG_FLOW) { + return CYAML_EMIT_STYLE_FLOW; + + } else if (ctx->config->flags & CYAML_CFG_STYLE_BLOCK) { + return CYAML_EMIT_STYLE_BLOCK; + + } else if (ctx->config->flags & CYAML_CFG_STYLE_FLOW) { + return CYAML_EMIT_STYLE_FLOW; + } + + return CYAML_EMIT_STYLE_DEFAULT; +} + +/** + * Get the style to use for sequences from value flags and config. + * + * \param[in] ctx The CYAML saving context. + * \param[in] schema The CYAML schema for the value expected in state. + * \return The libyaml sequence style to emit the value with. + */ +static inline yaml_sequence_style_t cyaml__get_emit_style_seq( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema) +{ + switch (cyaml__get_emit_style(ctx, schema)) { + case CYAML_EMIT_STYLE_BLOCK: return YAML_BLOCK_SEQUENCE_STYLE; + case CYAML_EMIT_STYLE_FLOW: return YAML_FLOW_SEQUENCE_STYLE; + default: break; + } + + return YAML_ANY_SEQUENCE_STYLE; +} + +/** + * Get the style to use for mappings from value flags and config. + * + * \param[in] ctx The CYAML saving context. + * \param[in] schema The CYAML schema for the value expected in state. + * \return The libyaml mapping style to emit the value with. + */ +static inline yaml_mapping_style_t cyaml__get_emit_style_map( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema) +{ + switch (cyaml__get_emit_style(ctx, schema)) { + case CYAML_EMIT_STYLE_BLOCK: return YAML_BLOCK_MAPPING_STYLE; + case CYAML_EMIT_STYLE_FLOW: return YAML_FLOW_MAPPING_STYLE; + default: break; + } + + return YAML_ANY_MAPPING_STYLE; +} + +/** + * Helper to discern whether to emit document delimiting marks. + * + * These are "---" for document start, and "..." for document end. + * + * \param[in] ctx The CYAML saving context. + * \return true if delimiters should be emitted, false otherwise. + */ +static inline bool cyaml__emit_doc_delim( + const cyaml_ctx_t *ctx) +{ + return ctx->config->flags & CYAML_CFG_DOCUMENT_DELIM; +} + +/** + * Emit a YAML start event for the state being pushed to the stack. + * + * \param[in] ctx The CYAML saving context. + * \param[in] state The CYAML save state we're pushing a stack entry for. + * \param[in] schema The CYAML schema for the value expected in state. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__stack_push_write_event( + const cyaml_ctx_t *ctx, + enum cyaml_state_e state, + const cyaml_schema_value_t *schema) +{ + yaml_event_t event; + int ret; + + /* Create any appropriate event for the new state. */ + switch (state) { + case CYAML_STATE_START: + ret = yaml_stream_start_event_initialize(&event, + YAML_UTF8_ENCODING); + break; + case CYAML_STATE_IN_STREAM: + ret = yaml_document_start_event_initialize(&event, + NULL, NULL, NULL, !cyaml__emit_doc_delim(ctx)); + break; + case CYAML_STATE_IN_DOC: + return CYAML_OK; + case CYAML_STATE_IN_MAP_KEY: + ret = yaml_mapping_start_event_initialize(&event, NULL, + (yaml_char_t *)YAML_MAP_TAG, 1, + cyaml__get_emit_style_map(ctx, schema)); + break; + case CYAML_STATE_IN_SEQUENCE: + ret = yaml_sequence_start_event_initialize(&event, NULL, + (yaml_char_t *)YAML_SEQ_TAG, 1, + cyaml__get_emit_style_seq(ctx, schema)); + break; + default: + return CYAML_ERR_INTERNAL_ERROR; + } + + return cyaml__emit_event_helper(ctx, ret, &event); +} + +/** + * Push a new entry onto the CYAML save context's stack. + * + * \param[in] ctx The CYAML saving context. + * \param[in] state The CYAML save state we're pushing a stack entry for. + * \param[in] schema The CYAML schema for the value expected in state. + * \param[in] data Pointer to where value's data should be read from. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__stack_push( + cyaml_ctx_t *ctx, + enum cyaml_state_e state, + const cyaml_schema_value_t *schema, + const cyaml_data_t *data) +{ + cyaml_err_t err; + cyaml_state_t s = { + .data = data, + .state = state, + .schema = schema, + }; + + err = cyaml__stack_push_write_event(ctx, state, schema); + if (err != CYAML_OK) { + return err; + } + + err = cyaml__stack_ensure(ctx); + if (err != CYAML_OK) { + return err; + } + + switch (state) { + case CYAML_STATE_IN_MAP_KEY: + assert(schema->type == CYAML_MAPPING); + s.mapping.field = schema->mapping.fields; + break; + default: + break; + } + + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Save: PUSH[%u]: %s\n", ctx->stack_idx, + cyaml__state_to_str(state)); + + ctx->stack[ctx->stack_idx] = s; + ctx->state = ctx->stack + ctx->stack_idx; + ctx->stack_idx++; + + return CYAML_OK; +} + +/** + * Emit a YAML end event for the state being popped from the stack. + * + * This frees any resources owned by the stack entry. + * + * \param[in] ctx The CYAML saving context. + * \param[in] state The CYAML save state we're popping from the stack. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__stack_pop_write_event( + const cyaml_ctx_t *ctx, + enum cyaml_state_e state) +{ + yaml_event_t event; + int ret; + + /* Create any appropriate event for the new state. */ + switch (state) { + case CYAML_STATE_START: + return CYAML_OK; + case CYAML_STATE_IN_STREAM: + ret = yaml_stream_end_event_initialize(&event); + break; + case CYAML_STATE_IN_DOC: + ret = yaml_document_end_event_initialize(&event, + !cyaml__emit_doc_delim(ctx)); + break; + case CYAML_STATE_IN_MAP_KEY: + ret = yaml_mapping_end_event_initialize(&event); + break; + case CYAML_STATE_IN_SEQUENCE: + ret = yaml_sequence_end_event_initialize(&event); + break; + default: + return CYAML_ERR_INTERNAL_ERROR; + } + + return cyaml__emit_event_helper(ctx, ret, &event); +} + +/** + * Pop the current entry on the CYAML save context's stack. + * + * This frees any resources owned by the stack entry. + * + * \param[in] ctx The CYAML saving context. + * \param[in] emit Whether end events should be emitted. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__stack_pop( + cyaml_ctx_t *ctx, + bool emit) +{ + uint32_t idx = ctx->stack_idx; + + assert(idx != 0); + + if (emit) { + cyaml_err_t err; + err = cyaml__stack_pop_write_event(ctx, ctx->state->state); + if (err != CYAML_OK) { + return err; + } + } + + idx--; + + cyaml__log(ctx->config, CYAML_LOG_DEBUG, "Save: POP[%u]: %s\n", idx, + cyaml__state_to_str(ctx->state->state)); + + ctx->state = (idx == 0) ? NULL : &ctx->stack[idx - 1]; + ctx->stack_idx = idx; + + return CYAML_OK; +} + +/** + * Find address of actual value. + * + * If the value has the pointer flag, the pointer is read, otherwise the + * address is returned unchanged. + * + * \param[in] config The CYAML client configuration object. + * \param[in] schema CYAML schema for the expected value. + * \param[in] data_in The address to read from. + * \return New address or for \ref CYAML_FLAG_POINTER, or data_in. + */ +static const uint8_t * cyaml__data_handle_pointer( + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + const uint8_t *data_in) +{ + if (schema->flags & CYAML_FLAG_POINTER) { + const uint8_t *data = cyaml_data_read_pointer(data_in); + + cyaml__log(config, CYAML_LOG_DEBUG, + "Save: Handle pointer: %p --> %p\n", + data_in, data); + + return data; + } + + return data_in; +} + +/** + * Dump a backtrace to the log. + * + * \param[in] ctx The CYAML saving context. + */ +static void cyaml__backtrace( + const cyaml_ctx_t *ctx) +{ + if (ctx->stack_idx > 1) { + cyaml__log(ctx->config, CYAML_LOG_ERROR, "Save: Backtrace:\n"); + } else { + return; + } + + for (uint32_t idx = ctx->stack_idx - 1; idx != 0; idx--) { + cyaml_state_t *state = ctx->stack + idx; + switch (state->state) { + case CYAML_STATE_IN_MAP_KEY: /* Fall through. */ + case CYAML_STATE_IN_MAP_VALUE: + assert(state->mapping.field != NULL); + cyaml__log(ctx->config, CYAML_LOG_ERROR, + " in mapping field: %s\n", + state->mapping.field->key); + break; + case CYAML_STATE_IN_SEQUENCE: + cyaml__log(ctx->config, CYAML_LOG_ERROR, + " in sequence entry: %"PRIu32"\n", + state->sequence.count); + break; + default: + /** \todo \ref CYAML_STATE_IN_DOC handling for multi + * document streams. + */ + break; + } + } +} + +/** + * Helper to emit YAML scalar events, using libyaml. + * + * \param[in] ctx The CYAML saving context. + * \param[in] schema The schema for the value to emit. + * \param[in] value The value to emit as a null-terminated C string. + * \param[in] tag YAML tag to use for output. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__emit_scalar( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const char *value, + const char *tag) +{ + int ret; + yaml_event_t event; + + if (schema == NULL) { + cyaml__log(ctx->config, CYAML_LOG_INFO, "Save: [%s]\n", value); + } else { + cyaml__log(ctx->config, CYAML_LOG_INFO, + "Save: <%s>\n", value); + } + + ret = yaml_scalar_event_initialize(&event, NULL, + (yaml_char_t *)tag, + (yaml_char_t *)value, + (int)strlen(value), + 1, 0, YAML_PLAIN_SCALAR_STYLE); + + return cyaml__emit_event_helper(ctx, ret, &event); +} + +/** + * Convert signed integer to string. + * + * \param[in] value The integer to convert. + * \return String conversion of the value. + */ +static const char * cyaml__get_int( + int64_t value) +{ + static char string[32]; + + sprintf(string, "%"PRIi64, value); + + return string; +} + +/** + * Convert unsigned integer to string. + * + * \param[in] value The integer to convert. + * \param[in] hex Whether to render the number as hexadecimal. + * \return String conversion of the value. + */ +static const char * cyaml__get_uint( + uint64_t value, bool hex) +{ + static char string[32]; + + if (hex) { + sprintf(string, "0x%"PRIx64, value); + } else { + sprintf(string, "%"PRIu64, value); + } + + return string; +} + +/** + * Convert double precision floating point value to string. + * + * \param[in] value The value to convert. + * \return String conversion of the value. + */ +static const char * cyaml__get_double( + double value) +{ + static char string[64]; + + sprintf(string, "%g", value); + + return string; +} + +/** + * Pad a signed value that's smaller than 64-bit to an int64_t. + * + * This sets all the bits in the padded region. + * + * \param[in] raw Contains a signed value of size bytes. + * \param[in] size Number of bytes used in raw. + * \return Value padded to 64-bit signed. + */ +static int64_t cyaml_sign_pad(uint64_t raw, size_t size) +{ + uint64_t sign_bit = (size == 0) ? + UINT64_MAX : ((uint64_t)1) << (size * CHAR_BIT - 1); + unsigned padding = ((unsigned)(sizeof(raw) - size)) * CHAR_BIT; + + if ((sign_bit & raw) && (padding != 0)) { + raw |= (((uint64_t)1 << padding) - 1) << (size * CHAR_BIT); + } + + return (int64_t)raw; +} + +/** + * Write a value of type \ref CYAML_INT. + * + * \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_int( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const uint8_t *data) +{ + cyaml_err_t err; + int64_t number; + + number = cyaml_sign_pad( + cyaml_data_read(schema->data_size, data, &err), + schema->data_size); + if (err == CYAML_OK) { + const char *string = cyaml__get_int(number); + err = cyaml__emit_scalar(ctx, schema, string, YAML_INT_TAG); + } + + return err; +} + +/** + * Write a value of type \ref CYAML_UINT. + * + * \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_uint( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const uint8_t *data) +{ + uint64_t number; + cyaml_err_t err; + + number = cyaml_data_read(schema->data_size, data, &err); + if (err == CYAML_OK) { + const char *string = cyaml__get_uint(number, false); + err = cyaml__emit_scalar(ctx, schema, string, YAML_INT_TAG); + } + + return err; +} + +/** + * Write a value of type \ref CYAML_BOOL. + * + * \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_bool( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const uint8_t *data) +{ + uint64_t number; + cyaml_err_t err; + + number = cyaml_data_read(schema->data_size, data, &err); + if (err == CYAML_OK) { + err = cyaml__emit_scalar(ctx, schema, + number ? "true" : "false", YAML_BOOL_TAG); + } + + return err; +} + +/** + * Write a value of type \ref CYAML_ENUM. + * + * \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_enum( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const uint8_t *data) +{ + int64_t number; + cyaml_err_t err; + + number = (int64_t)cyaml_data_read(schema->data_size, data, &err); + if (err == CYAML_OK) { + const cyaml_strval_t *strings = schema->enumeration.strings; + const char *string = NULL; + for (uint32_t i = 0; i < schema->enumeration.count; i++) { + if (number == strings[i].val) { + string = strings[i].str; + break; + } + } + if (string == NULL) { + if (schema->flags & CYAML_FLAG_STRICT) { + return CYAML_ERR_INVALID_VALUE; + } else { + return cyaml__write_int(ctx, schema, data); + } + } + err = cyaml__emit_scalar(ctx, schema, string, YAML_STR_TAG); + } + + return err; +} + +/** + * Write a value of type \ref CYAML_FLOAT. + * + * The `data_size` of the schema entry must be the size of a known + * floating point C type. + * + * \note The `long double` type was causing problems, so it isn't currently + * supported. + * + * \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_float( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const uint8_t *data) +{ + const char *string = NULL; + + if (schema->data_size == sizeof(float)) { + float number; + memcpy(&number, data, schema->data_size); + string = cyaml__get_double(number); + + } else if (schema->data_size == sizeof(double)) { + double number; + memcpy(&number, data, schema->data_size); + string = cyaml__get_double(number); + } else { + return CYAML_ERR_INVALID_DATA_SIZE; + } + + return cyaml__emit_scalar(ctx, schema, string, YAML_FLOAT_TAG); +} + +/** + * Write a value of type \ref CYAML_STRING. + * + * \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_string( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const uint8_t *data) +{ + return cyaml__emit_scalar(ctx, schema, (const char *)data, + YAML_STR_TAG); +} + +/** + * Write a scalar 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_scalar_value( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const cyaml_data_t *data) +{ + typedef cyaml_err_t (*cyaml_read_scalar_fn)( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const uint8_t *data_target); + static const cyaml_read_scalar_fn fn[CYAML__TYPE_COUNT] = { + [CYAML_INT] = cyaml__write_int, + [CYAML_UINT] = cyaml__write_uint, + [CYAML_BOOL] = cyaml__write_bool, + [CYAML_ENUM] = cyaml__write_enum, + [CYAML_FLOAT] = cyaml__write_float, + [CYAML_STRING] = cyaml__write_string, + }; + + assert(fn[schema->type] != NULL); + + return fn[schema->type](ctx, schema, data); +} + +/** + * Emit a sequence of flag values. + * + * \param[in] ctx The CYAML saving context. + * \param[in] schema The schema for the value to be written. + * \param[in] number The value of the flag data. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__emit_flags_sequence( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + uint64_t number) +{ + yaml_event_t event; + cyaml_err_t err; + int ret; + + ret = yaml_sequence_start_event_initialize(&event, NULL, + (yaml_char_t *)YAML_SEQ_TAG, 1, + YAML_ANY_SEQUENCE_STYLE); + + err = cyaml__emit_event_helper(ctx, ret, &event); + if (err != CYAML_OK) { + return err; + } + + for (uint32_t i = 0; i < schema->enumeration.count; i++) { + const cyaml_strval_t *strval = &schema->enumeration.strings[i]; + uint64_t flag = (uint64_t)strval->val; + if (number & flag) { + err = cyaml__emit_scalar(ctx, schema, strval->str, + YAML_STR_TAG); + if (err != CYAML_OK) { + return err; + } + number &= ~flag; + } + } + if (number != 0) { + if (schema->flags & CYAML_FLAG_STRICT) { + return CYAML_ERR_INVALID_VALUE; + } else { + const char *string = cyaml__get_uint(number, false); + err = cyaml__emit_scalar(ctx, schema, string, + YAML_STR_TAG); + if (err != CYAML_OK) { + return err; + } + } + } + + ret = yaml_sequence_end_event_initialize(&event); + + return cyaml__emit_event_helper(ctx, ret, &event); +} + +/** + * Write a value of type \ref CYAML_FLAGS. + * + * \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_flags_value( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const cyaml_data_t *data) +{ + uint64_t number; + cyaml_err_t err; + + number = cyaml_data_read(schema->data_size, data, &err); + if (err == CYAML_OK) { + err = cyaml__emit_flags_sequence(ctx, schema, number); + } + + return err; +} + +/** + * Emit a mapping of bitfield values. + * + * \param[in] ctx The CYAML saving context. + * \param[in] schema The schema for the value to be written. + * \param[in] number The value of the bitfield data. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__emit_bitfield_mapping( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + uint64_t number) +{ + const cyaml_bitdef_t *bitdef = schema->bitfield.bitdefs; + yaml_event_t event; + cyaml_err_t err; + int ret; + + ret = yaml_mapping_start_event_initialize(&event, NULL, + (yaml_char_t *)YAML_MAP_TAG, 1, + cyaml__get_emit_style_map(ctx, schema)); + + err = cyaml__emit_event_helper(ctx, ret, &event); + if (err != CYAML_OK) { + return err; + } + + for (uint32_t i = 0; i < schema->bitfield.count; i++) { + const char *value_str; + uint64_t value; + uint64_t mask; + + if (bitdef[i].bits + bitdef[i].offset > schema->data_size * 8) { + return CYAML_ERR_BAD_BITVAL_IN_SCHEMA; + } + + mask = ((~(uint64_t)0) >> + ((8 * sizeof(uint64_t)) - bitdef[i].bits) + ) << bitdef[i].offset; + + if ((number & mask) == 0) { + continue; + } + + value = (number & mask) >> bitdef[i].offset; + + /* Emit bitfield value's name */ + err = cyaml__emit_scalar(ctx, schema, bitdef[i].name, + YAML_STR_TAG); + if (err != CYAML_OK) { + return err; + } + + /* Emit bitfield value's value */ + value_str = cyaml__get_uint(value, true); + err = cyaml__emit_scalar(ctx, schema, value_str, YAML_INT_TAG); + if (err != CYAML_OK) { + return err; + } + } + + ret = yaml_mapping_end_event_initialize(&event); + + return cyaml__emit_event_helper(ctx, ret, &event); +} + +/** + * Write a value of type \ref CYAML_BITFIELD. + * + * \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_bitfield_value( + const cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const cyaml_data_t *data) +{ + uint64_t number; + cyaml_err_t err; + + number = cyaml_data_read(schema->data_size, data, &err); + if (err == CYAML_OK) { + err = cyaml__emit_bitfield_mapping(ctx, schema, number); + } + + return err; +} + +/** + * Emit the YAML events required for a CYAML value. + * + * \param[in] ctx The CYAML saving context. + * \param[in] schema CYAML schema for the expected value. + * \param[in] data The place to read the value from in the client data. + * \param[in] seq_count Entry count for sequence values. Unused for + * non-sequence values. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__write_value( + cyaml_ctx_t *ctx, + const cyaml_schema_value_t *schema, + const uint8_t *data, + uint64_t seq_count) +{ + cyaml_err_t err; + + cyaml__log(ctx->config, CYAML_LOG_DEBUG, + "Save: Writing value of type '%s'%s\n", + cyaml__type_to_str(schema->type), + schema->flags & CYAML_FLAG_POINTER ? " (pointer)" : ""); + + data = cyaml__data_handle_pointer(ctx->config, schema, data); + + if (data == NULL) { + if (cyaml__flag_check_all(schema->flags, + CYAML_FLAG_POINTER_NULL_STR)) { + return cyaml__emit_scalar(ctx, schema, "null", + YAML_STR_TAG); + } else if (cyaml__flag_check_all(schema->flags, + CYAML_FLAG_POINTER_NULL)) { + return cyaml__emit_scalar(ctx, schema, "", + YAML_STR_TAG); + } else { + return CYAML_ERR_INVALID_VALUE; + } + } + + switch (schema->type) { + case CYAML_INT: /* Fall through. */ + case CYAML_UINT: /* Fall through. */ + case CYAML_BOOL: /* Fall through. */ + case CYAML_ENUM: /* Fall through. */ + case CYAML_FLOAT: /* Fall through. */ + case CYAML_STRING: + err = cyaml__write_scalar_value(ctx, schema, data); + break; + case CYAML_FLAGS: + err = cyaml__write_flags_value(ctx, schema, data); + break; + case CYAML_MAPPING: + err = cyaml__stack_push(ctx, CYAML_STATE_IN_MAP_KEY, + schema, data); + break; + case CYAML_BITFIELD: + err = cyaml__write_bitfield_value(ctx, schema, data); + break; + case CYAML_SEQUENCE_FIXED: + if (schema->sequence.min != schema->sequence.max) { + return CYAML_ERR_SEQUENCE_FIXED_COUNT; + } + /* Fall through. */ + case CYAML_SEQUENCE: + err = cyaml__stack_push(ctx, CYAML_STATE_IN_SEQUENCE, + schema, data); + if (err == CYAML_OK) { + ctx->state->sequence.count = seq_count; + } + break; + default: + err = CYAML_ERR_BAD_TYPE_IN_SCHEMA; + break; + } + + return err; +} + +/** + * YAML saving handler for the \ref CYAML_STATE_START state. + * + * \param[in] ctx The CYAML saving context. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__write_start( + cyaml_ctx_t *ctx) +{ + return cyaml__stack_push(ctx, CYAML_STATE_IN_STREAM, + ctx->state->schema, ctx->state->data); +} + +/** + * YAML saving handler for the \ref CYAML_STATE_IN_STREAM state. + * + * \param[in] ctx The CYAML saving context. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__write_stream( + cyaml_ctx_t *ctx) +{ + cyaml_err_t err; + + if (ctx->state->done) { + err = cyaml__stack_pop(ctx, true); + } else { + ctx->stack[CYAML_STATE_IN_STREAM].done = true; + err = cyaml__stack_push(ctx, CYAML_STATE_IN_DOC, + ctx->state->schema, ctx->state->data); + } + return err; +} + +/** + * YAML saving handler for the \ref CYAML_STATE_IN_DOC state. + * + * \param[in] ctx The CYAML saving context. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__write_doc( + cyaml_ctx_t *ctx) +{ + cyaml_err_t err; + + if (ctx->state->done) { + err = cyaml__stack_pop(ctx, true); + } else { + unsigned seq_count = ctx->seq_count; + if (ctx->state->schema->type == CYAML_SEQUENCE_FIXED) { + seq_count = ctx->state->schema->sequence.max; + } + ctx->stack[CYAML_STATE_IN_DOC].done = true; + err = cyaml__write_value(ctx, + ctx->state->schema, + ctx->state->data, + seq_count); + } + return err; +} + +/** + * YAML saving handler for the \ref CYAML_STATE_IN_MAP_KEY and \ref + * CYAML_STATE_IN_MAP_VALUE states. + * + * \param[in] ctx The CYAML saving context. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__write_mapping( + cyaml_ctx_t *ctx) +{ + const cyaml_schema_field_t *field = ctx->state->mapping.field; + cyaml_err_t err = CYAML_OK; + + if (field != NULL && field->key != NULL) { + uint64_t seq_count = 0; + + if (field->value.type == CYAML_IGNORE) { + ctx->state->mapping.field++; + return CYAML_OK; + } + + if ((field->value.flags & CYAML_FLAG_OPTIONAL) && + (field->value.flags & CYAML_FLAG_POINTER)) { + const void *ptr = cyaml_data_read_pointer( + ctx->state->data + field->data_offset); + if (ptr == NULL) { + ctx->state->mapping.field++; + return CYAML_OK; + } + } + + err = cyaml__emit_scalar(ctx, NULL, field->key, YAML_STR_TAG); + if (err != CYAML_OK) { + return err; + } + + /* Advance the field before writing value, since writing the + * value can put a new state entry on the stack. */ + ctx->state->mapping.field++; + + if (field->value.type == CYAML_SEQUENCE) { + seq_count = cyaml_data_read(field->count_size, + ctx->state->data + field->count_offset, + &err); + if (err != CYAML_OK) { + return err; + } + cyaml__log(ctx->config, CYAML_LOG_INFO, + "Save: Sequence entry count: %u\n", + seq_count); + + } else if (field->value.type == CYAML_SEQUENCE_FIXED) { + seq_count = field->value.sequence.min; + } + + err = cyaml__write_value(ctx, + &field->value, + ctx->state->data + field->data_offset, + seq_count); + } else { + err = cyaml__stack_pop(ctx, true); + } + + return err; +} + +/** + * YAML saving handler for the \ref CYAML_STATE_IN_SEQUENCE state. + * + * \param[in] ctx The CYAML saving context. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__write_sequence( + cyaml_ctx_t *ctx) +{ + cyaml_err_t err = CYAML_OK; + + if (ctx->state->sequence.entry < ctx->state->sequence.count) { + const cyaml_schema_value_t *schema = ctx->state->schema; + const cyaml_schema_value_t *value = schema->sequence.entry; + unsigned seq_count = 0; + size_t data_size; + size_t offset; + + if (value->type == CYAML_SEQUENCE) { + return CYAML_ERR_SEQUENCE_IN_SEQUENCE; + + } else if (value->type == CYAML_SEQUENCE_FIXED) { + seq_count = value->sequence.max; + } + + if (value->flags & CYAML_FLAG_POINTER) { + data_size = sizeof(NULL); + } else { + data_size = value->data_size; + if (value->type == CYAML_SEQUENCE_FIXED) { + data_size *= seq_count; + } + } + offset = data_size * ctx->state->sequence.entry; + + cyaml__log(ctx->config, CYAML_LOG_INFO, + "Save: Sequence entry %u of %u\n", + ctx->state->sequence.entry + 1, + ctx->state->sequence.count); + + /* Advance the entry before writing value, since writing the + * value can put a new state entry on the stack. */ + ctx->state->sequence.entry++; + + err = cyaml__write_value(ctx, value, + ctx->state->data + offset, + seq_count); + } else { + err = cyaml__stack_pop(ctx, true); + } + + return err; +} + +/** + * Check that common save params from client are valid. + * + * \param[in] config The client's CYAML library config. + * \param[in] schema The schema describing the content of data. + * \param[in] data Points to client's data. + * \param[in] seq_count Top level sequence count. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static inline cyaml_err_t cyaml__validate_save_params( + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + const cyaml_data_t *data, + unsigned seq_count) +{ + if (config == NULL) { + return CYAML_ERR_BAD_PARAM_NULL_CONFIG; + } + if (config->mem_fn == NULL) { + return CYAML_ERR_BAD_CONFIG_NULL_MEMFN; + } + if (schema == NULL) { + return CYAML_ERR_BAD_PARAM_NULL_SCHEMA; + } + if ((schema->type == CYAML_SEQUENCE) != (seq_count != 0)) { + return CYAML_ERR_BAD_PARAM_SEQ_COUNT; + } + if (!(schema->flags & CYAML_FLAG_POINTER)) { + return CYAML_ERR_TOP_LEVEL_NON_PTR; + } + if (data == NULL) { + return CYAML_ERR_BAD_PARAM_NULL_DATA; + } + return CYAML_OK; +} + +/** + * The main YAML saving function. + * + * The public interfaces are wrappers around this. + * + * \param[in] config Client's CYAML configuration structure. + * \param[in] schema CYAML schema for the YAML to be saved. + * \param[in] data The caller-owned data to be saved. + * \param[in] seq_count If top level type is sequence, this should be the + * entry count, otherwise it is ignored. + * \param[in] emitter An initialised `libyaml` emitter object + * with its output set. + * \return \ref CYAML_OK on success, or appropriate error code otherwise. + */ +static cyaml_err_t cyaml__save( + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + const cyaml_data_t *data, + unsigned seq_count, + yaml_emitter_t *emitter) +{ + cyaml_ctx_t ctx = { + .config = config, + .emitter = emitter, + .seq_count = seq_count, + }; + typedef cyaml_err_t (* const cyaml_write_fn)( + cyaml_ctx_t *ctx); + static const cyaml_write_fn fn[CYAML_STATE__COUNT] = { + [CYAML_STATE_START] = cyaml__write_start, + [CYAML_STATE_IN_STREAM] = cyaml__write_stream, + [CYAML_STATE_IN_DOC] = cyaml__write_doc, + [CYAML_STATE_IN_MAP_KEY] = cyaml__write_mapping, + [CYAML_STATE_IN_MAP_VALUE] = cyaml__write_mapping, + [CYAML_STATE_IN_SEQUENCE] = cyaml__write_sequence, + }; + cyaml_err_t err = CYAML_OK; + + err = cyaml__validate_save_params(config, schema, data, seq_count); + if (err != CYAML_OK) { + return err; + } + + err = cyaml__stack_push(&ctx, CYAML_STATE_START, schema, &data); + if (err != CYAML_OK) { + goto out; + } + + do { + cyaml__log(ctx.config, CYAML_LOG_DEBUG, + "Save: Handle state %s\n", + cyaml__state_to_str(ctx.state->state)); + err = fn[ctx.state->state](&ctx); + if (err != CYAML_OK) { + goto out; + } + } while (ctx.stack_idx > 1); + + cyaml__stack_pop(&ctx, true); + + assert(ctx.stack_idx == 0); + + if (!yaml_emitter_flush(emitter)) { + cyaml__log(config, CYAML_LOG_ERROR, + "Save: LibYAML: Failed to flush emitter: %s\n", + emitter->problem); + err = CYAML_ERR_LIBYAML_EMITTER; + } + +out: + if (err != CYAML_OK) { + cyaml__backtrace(&ctx); + } + while (ctx.stack_idx > 0) { + cyaml__stack_pop(&ctx, false); + } + cyaml__free(config, ctx.stack); + return err; +} + +/* Exported function, documented in include/cyaml/cyaml.h */ +cyaml_err_t cyaml_save_file( + const char *path, + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + const cyaml_data_t *data, + unsigned seq_count) +{ + FILE *file; + cyaml_err_t err; + yaml_emitter_t emitter; + + /* Initialize emitter */ + if (!yaml_emitter_initialize(&emitter)) { + return CYAML_ERR_LIBYAML_EMITTER_INIT; + } + + /* Open output file. */ + file = fopen(path, "w"); + if (file == NULL) { + yaml_emitter_delete(&emitter); + return CYAML_ERR_FILE_OPEN; + } + + /* Set output file */ + yaml_emitter_set_output_file(&emitter, file); + + /* Serialise to the output */ + err = cyaml__save(config, schema, data, seq_count, &emitter); + if (err != CYAML_OK) { + yaml_emitter_delete(&emitter); + fclose(file); + return err; + } + + /* Cleanup */ + yaml_emitter_delete(&emitter); + fclose(file); + + return CYAML_OK; +} + +/** CYAML save buffer context. */ +typedef struct cyaml_buffer_ctx { + /** Client's CYAML configuration structure. */ + const cyaml_config_t *config; + size_t len; /**< Current length of `data` allocation. */ + size_t used; /**< Current number of bytes used in `data`. */ + char *data; /**< Current allocation for serialised output. */ + cyaml_err_t err; /**< Any error encounted in buffer handling. */ +} cyaml_buffer_ctx_t; + +/** + * Write handler for libyaml. + * + * The write handler is called when the emitter needs to flush the accumulated + * characters to the output. The handler should write size bytes of the + * buffer to the output. + * + * \todo Could be more efficient about this, but for now, this is fine. + * + * \param[in] data A pointer to cyaml buffer context struture. + * \param[in] buffer The buffer with bytes to be written. + * \param[in] size The number of bytes to be written. + * \return 1 on sucess, 0 otherwise. + */ +static int cyaml__buffer_handler( + void *data, + unsigned char *buffer, + size_t size) +{ + cyaml_buffer_ctx_t *buffer_ctx = data; + enum { + RETURN_SUCCESS = 1, + RETURN_FAILURE = 0, + }; + + if (size > (buffer_ctx->len - buffer_ctx->used)) { + char *temp = cyaml__realloc( + buffer_ctx->config, + buffer_ctx->data, + buffer_ctx->len, + buffer_ctx->used + size, + false); + if (temp == NULL) { + buffer_ctx->err = CYAML_ERR_OOM; + return RETURN_FAILURE; + } + buffer_ctx->data = temp; + buffer_ctx->len = buffer_ctx->used + size; + } + + memcpy(buffer_ctx->data + buffer_ctx->used, buffer, size); + buffer_ctx->used += size; + + return RETURN_SUCCESS; +} + +/* Exported function, documented in include/cyaml/cyaml.h */ +cyaml_err_t cyaml_save_data( + char **output, + size_t *len, + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + const cyaml_data_t *data, + unsigned seq_count) +{ + cyaml_err_t err; + yaml_emitter_t emitter; + cyaml_buffer_ctx_t buffer_ctx = { + .config = config, + .err = CYAML_OK, + }; + + /* Initialize emitter */ + if (!yaml_emitter_initialize(&emitter)) { + return CYAML_ERR_LIBYAML_EMITTER_INIT; + } + + /* Set output buffer */ + yaml_emitter_set_output(&emitter, cyaml__buffer_handler, &buffer_ctx); + + /* Serialise to the output */ + err = cyaml__save(config, schema, data, seq_count, &emitter); + if (err != CYAML_OK) { + yaml_emitter_delete(&emitter); + if ((config != NULL) && (config->mem_fn != NULL)) { + cyaml__free(config, buffer_ctx.data); + } + if (buffer_ctx.err != CYAML_OK) { + err = buffer_ctx.err; + } + return err; + } + + /* Cleanup */ + yaml_emitter_delete(&emitter); + + *output = buffer_ctx.data; + *len = buffer_ctx.used; + + return CYAML_OK; +} diff --git a/src/utf8.c b/src/utf8.c new file mode 100644 index 0000000..6b21f8e --- /dev/null +++ b/src/utf8.c @@ -0,0 +1,259 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2018 Michael Drake + */ + +/** + * \file + * \brief CYAML functions for handling utf8 text. + */ + +#include +#include +#include + +#include "utf8.h" + +/** + * Get expected byte-length of UTF8 character. + * + * Finds the number of bytes expected for the UTF8 sequence starting with + * the given byte. + * + * \param[in] b First byte of UTF8 sequence. + * \return the byte width of the character or 0 if invalid. + */ +static inline unsigned cyaml_utf8_char_len(uint8_t b) +{ + if (!(b & 0x80)) { + return 1; + } else switch (b >> 3) { + case 0x18: + case 0x19: + case 0x1a: + case 0x1b: + return 2; + case 0x1c: + case 0x1d: + return 3; + case 0x1e: + return 4; + } + return 0; /* Invalid */ +} + +/* Exported function, documented in utf8.h. */ +unsigned cyaml_utf8_get_codepoint( + const uint8_t *s, + unsigned *len) +{ + unsigned c = 0; + bool sf = false; + + if (*len == 1) { + return s[0]; + } else if ((*len > 1) && (*len <= 4)) { + /* Compose first byte into codepoint. */ + c |= (s[0] & ((1u << (7 - *len)) - 1u)) << ((*len - 1) * 6); + + /* Handle continuation bytes. */ + for (unsigned i = 1; i < *len; i++) { + /* Check continuation byte begins with 0b10xxxxxx. */ + if ((s[i] & 0xc0) != 0x80) { + /* Need to shorten length so we don't consume + * this byte with this invalid character. */ + *len -= i; + goto invalid; + } + + /* Compose continuation byte into codepoint. */ + c |= (0x3fu & s[i]) << ((*len - i - 1) * 6); + } + } + + /* Non-shortest forms are forbidden. + * + * The following table shows the bits available for each + * byte sequence length, as well as the bit-delta to the + * shorter sequence. + * + * | Bytes | Bits | Bit delta | Data | + * | ----- | ---- | --------- | ----------------------- | + * | 1 | 7 | N/A | 0xxxxxxx | + * | 2 | 11 | 4 | 110xxxxx + 10xxxxxx x 1 | + * | 3 | 16 | 5 | 1110xxxx + 10xxxxxx x 2 | + * | 4 | 21 | 5 | 11110xxx + 10xxxxxx x 3 | + * + * So here we check that the top "bit-delta" bits are not all + * clear for the byte length, + */ + switch (*len) { + case 2: sf = (c & (((1 << 4) - 1) << (11 - 4))) != 0; break; + case 3: sf = (c & (((1 << 5) - 1) << (16 - 5))) != 0; break; + case 4: sf = (c & (((1 << 5) - 1) << (21 - 5))) != 0; break; + default: goto invalid; + } + + if (!sf) { + /* Codepoint representation was not shortest-form. */ + goto invalid; + } + + return c; + +invalid: + return 0xfffd; /* REPLACEMENT CHARACTER */ +} + +/** + * Convert a Unicode codepoint to lower case. + * + * \note This only handles some of the Unicode blocks. + * (Currently the Latin ones.) + * + * \param[in] c Codepoint to convert to lower-case, if applicable. + * \return the lower-cased codepoint. + */ +static unsigned cyaml_utf8_to_lower(unsigned c) +{ + if (((c >= 0x0041) && (c <= 0x005a)) /* Basic Latin */ || + ((c >= 0x00c0) && (c <= 0x00d6)) /* Latin-1 Supplement */ || + ((c >= 0x00d8) && (c <= 0x00de)) /* Latin-1 Supplement */ ) { + return c + 32u; + + } else if (((c >= 0x0100) && (c <= 0x012f)) /* Latin Extended-A */ || + ((c >= 0x0132) && (c <= 0x0137)) /* Latin Extended-A */ || + ((c >= 0x014a) && (c <= 0x0177)) /* Latin Extended-A */ || + ((c >= 0x0182) && (c <= 0x0185)) /* Latin Extended-B */ || + ((c >= 0x01a0) && (c <= 0x01a5)) /* Latin Extended-B */ || + ((c >= 0x01de) && (c <= 0x01ef)) /* Latin Extended-B */ || + ((c >= 0x01f8) && (c <= 0x021f)) /* Latin Extended-B */ || + ((c >= 0x0222) && (c <= 0x0233)) /* Latin Extended-B */ || + ((c >= 0x0246) && (c <= 0x024f)) /* Latin Extended-B */ ) { + return c & ~0x1u; + + } else if (((c >= 0x0139) && (c <= 0x0148) /* Latin Extended-A */) || + ((c >= 0x0179) && (c <= 0x017e) /* Latin Extended-A */) || + ((c >= 0x01b3) && (c <= 0x01b6) /* Latin Extended-B */) || + ((c >= 0x01cd) && (c <= 0x01dc) /* Latin Extended-B */)) { + return (c + 1) & ~0x1u; + + } else switch (c) { + case 0x0178: return 0x00ff; /* Latin Extended-A */ + case 0x0187: return 0x0188; /* Latin Extended-B */ + case 0x018b: return 0x018c; /* Latin Extended-B */ + case 0x018e: return 0x01dd; /* Latin Extended-B */ + case 0x0191: return 0x0192; /* Latin Extended-B */ + case 0x0198: return 0x0199; /* Latin Extended-B */ + case 0x01a7: return 0x01a8; /* Latin Extended-B */ + case 0x01ac: return 0x01ad; /* Latin Extended-B */ + case 0x01af: return 0x01b0; /* Latin Extended-B */ + case 0x01b7: return 0x0292; /* Latin Extended-B */ + case 0x01b8: return 0x01b9; /* Latin Extended-B */ + case 0x01bc: return 0x01bd; /* Latin Extended-B */ + case 0x01c4: return 0x01c6; /* Latin Extended-B */ + case 0x01c5: return 0x01c6; /* Latin Extended-B */ + case 0x01c7: return 0x01c9; /* Latin Extended-B */ + case 0x01c8: return 0x01c9; /* Latin Extended-B */ + case 0x01ca: return 0x01cc; /* Latin Extended-B */ + case 0x01cb: return 0x01cc; /* Latin Extended-B */ + case 0x01f1: return 0x01f3; /* Latin Extended-B */ + case 0x01f2: return 0x01f3; /* Latin Extended-B */ + case 0x01f4: return 0x01f5; /* Latin Extended-B */ + case 0x01f7: return 0x01bf; /* Latin Extended-B */ + case 0x0220: return 0x019e; /* Latin Extended-B */ + case 0x023b: return 0x023c; /* Latin Extended-B */ + case 0x023d: return 0x019a; /* Latin Extended-B */ + case 0x0241: return 0x0242; /* Latin Extended-B */ + case 0x0243: return 0x0180; /* Latin Extended-B */ + } + + return c; +} + +/** + * Find the difference between two codepoints. + * + * \param a First codepoint. + * \param b Second codepoint. + * \return the difference. + */ +static inline int cyaml_utf8_difference(unsigned a, unsigned b) +{ + return (((int)a) - ((int)b)); +} + +/* Exported function, documented in utf8.h. */ +int cyaml_utf8_casecmp( + const void * const str1, + const void * const str2) +{ + const uint8_t *s1 = str1; + const uint8_t *s2 = str2; + + while (true) { + unsigned len1; + unsigned len2; + unsigned cmp1; + unsigned cmp2; + + /* Check for end of strings. */ + if ((*s1 == 0) && (*s2 == 0)) { + return 0; /* Both strings ended; match. */ + + } else if (*s1 == 0) { + return 1; /* String 1 has ended. */ + + } else if (*s2 == 0) { + return -1;/* String 2 has ended. */ + } + + /* Get byte lengths of these characters. */ + len1 = cyaml_utf8_char_len(*s1); + len2 = cyaml_utf8_char_len(*s2); + + /* Compare values. */ + if ((len1 == 1) && (len2 == 1)) { + /* Common case: Both strings have ASCII values. */ + if (*s1 != *s2) { + /* They're different; need to lower case. */ + cmp1 = ((*s1 >= 'A') && (*s1 <= 'Z')) ? + (*s1 + 32u) : *s1; + cmp2 = ((*s2 >= 'A') && (*s2 <= 'Z')) ? + (*s2 + 32u) : *s2; + if (cmp1 != cmp2) { + return cyaml_utf8_difference( + cmp1, cmp2); + } + } + } else if ((len1 != 0) && (len2 != 0)) { + /* Neither string has invalid sequence; + * convert to UCS4 for comparison. */ + cmp1 = cyaml_utf8_get_codepoint(s1, &len1); + cmp2 = cyaml_utf8_get_codepoint(s2, &len2); + + if (cmp1 != cmp2) { + /* They're different; need to lower case. */ + cmp1 = cyaml_utf8_to_lower(cmp1); + cmp2 = cyaml_utf8_to_lower(cmp2); + if (cmp1 != cmp2) { + return cyaml_utf8_difference( + cmp1, cmp2); + } + } + } else { + if (len1 | len2) { + /* One of the strings has invalid sequence. */ + return cyaml_utf8_difference(len1, len2); + } else { + /* Both strings have an invalid sequence. */ + len1 = len2 = 1; + } + } + + /* Advance each string by their current character length. */ + s1 += len1; + s2 += len2; + } +} diff --git a/src/utf8.h b/src/utf8.h new file mode 100644 index 0000000..3fc9266 --- /dev/null +++ b/src/utf8.h @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2018 Michael Drake + */ + +/** + * \file + * \brief CYAML functions for handling utf8 text. + */ + +#ifndef CYAML_UTF8_H +#define CYAML_UTF8_H + +/** + * Get a codepoint from the input string. + * + * Caller must provide the expected length given the first input byte. + * + * If a multi-byte character contains an invalid continuation byte, the + * character length will be updated on exit to the number of bytes consumed, + * and the replacement character, U+FFFD will be returned. + * + * \param[in] s String to read first codepoint from. + * \param[in,out] len Expected length of first character, updated on exit. + * \return The codepoint or `0xfffd` if character is invalid. + */ +unsigned cyaml_utf8_get_codepoint( + const uint8_t *s, + unsigned *len); + +/** + * Case insensitive comparason. + * + * \note This has some limitations and only performs case insensitive + * comparason over some sectons of unicode. + * + * \param[in] str1 First string to be compared. + * \param[in] str2 Second string to be compared. + * \return 0 if and only if strings are equal. + */ +int cyaml_utf8_casecmp( + const void * const str1, + const void * const str2); + +#endif diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..427e055 --- /dev/null +++ b/src/util.c @@ -0,0 +1,122 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2017-2019 Michael Drake + */ + +/** + * \file + * \brief Utility functions. + */ + +#include +#include +#include +#include + +#include "util.h" + +/** Flag that indicates a release in \ref cyaml_version. */ +#define CYAML_RELEASE_FLAG (1u << 31) + +/** Stringification helper macro. */ +#define CYAML_STR_HELPER(_x) #_x + +/** Stringification macro. */ +#define CYAML_STR(_x) CYAML_STR_HELPER(_x) + +/* Version depends on whether we're a development build. */ +#if VERSION_DEVEL + /** Version string is composed from components in Makefile. */ + #define CYAML_VERSION_STR \ + CYAML_STR(VERSION_MAJOR) "." \ + CYAML_STR(VERSION_MINOR) "." \ + CYAML_STR(VERSION_PATCH) "-DEVEL" + + /* Exported constant, documented in include/cyaml/cyaml.h */ + const uint32_t cyaml_version = + ((VERSION_MAJOR << 16) | + (VERSION_MINOR << 8) | + (VERSION_PATCH << 0)); +#else + /** Version string is composed from components in Makefile. */ + #define CYAML_VERSION_STR \ + CYAML_STR(VERSION_MAJOR) "." \ + CYAML_STR(VERSION_MINOR) "." \ + CYAML_STR(VERSION_PATCH) + + /* Exported constant, documented in include/cyaml/cyaml.h */ + const uint32_t cyaml_version = + ((VERSION_MAJOR << 16) | + (VERSION_MINOR << 8) | + (VERSION_PATCH << 0) | + CYAML_RELEASE_FLAG); +#endif + +/* Exported constant, documented in include/cyaml/cyaml.h */ +const char *cyaml_version_str = CYAML_VERSION_STR; + +/* Exported function, documented in include/cyaml/cyaml.h */ +void cyaml_log( + cyaml_log_t level, + void *ctx, + const char *fmt, + va_list args) +{ + static const char * const strings[] = { + [CYAML_LOG_DEBUG] = "DEBUG", + [CYAML_LOG_INFO] = "INFO", + [CYAML_LOG_NOTICE] = "NOTICE", + [CYAML_LOG_WARNING] = "WARNING", + [CYAML_LOG_ERROR] = "ERROR", + }; + + CYAML_UNUSED(ctx); + + fprintf(stderr, "libcyaml: %7.7s: ", strings[level]); + vfprintf(stderr, fmt, args); +} + +/* Exported function, documented in include/cyaml/cyaml.h */ +const char * cyaml_strerror( + cyaml_err_t err) +{ + static const char * const strings[CYAML_ERR__COUNT] = { + [CYAML_OK] = "Success", + [CYAML_ERR_OOM] = "Memory allocation failed", + [CYAML_ERR_ALIAS] = "YAML alias unsupported", + [CYAML_ERR_FILE_OPEN] = "Could not open file", + [CYAML_ERR_INVALID_KEY] = "Invalid key", + [CYAML_ERR_INVALID_VALUE] = "Invalid value", + [CYAML_ERR_INVALID_ALIAS] = "No anchor found for alias", + [CYAML_ERR_INTERNAL_ERROR] = "Internal error", + [CYAML_ERR_UNEXPECTED_EVENT] = "Unexpected event", + [CYAML_ERR_STRING_LENGTH_MIN] = "String length too short", + [CYAML_ERR_STRING_LENGTH_MAX] = "String length too long", + [CYAML_ERR_INVALID_DATA_SIZE] = "Data size must be 0 < X <= 8 bytes", + [CYAML_ERR_TOP_LEVEL_NON_PTR] = "Top-level schema value must be pointer", + [CYAML_ERR_BAD_TYPE_IN_SCHEMA] = "Schema contains invalid type", + [CYAML_ERR_BAD_MIN_MAX_SCHEMA] = "Bad schema: min exceeds max", + [CYAML_ERR_BAD_PARAM_SEQ_COUNT] = "Bad parameter: seq_count", + [CYAML_ERR_BAD_PARAM_NULL_DATA] = "Bad parameter: NULL data", + [CYAML_ERR_BAD_BITVAL_IN_SCHEMA] = "Bit value beyond bitfield size", + [CYAML_ERR_SEQUENCE_ENTRIES_MIN] = "Sequence with too few entries", + [CYAML_ERR_SEQUENCE_ENTRIES_MAX] = "Sequence with too many entries", + [CYAML_ERR_SEQUENCE_FIXED_COUNT] = "Sequence fixed has unequal min max", + [CYAML_ERR_SEQUENCE_IN_SEQUENCE] = "Non-fixed sequence in sequence", + [CYAML_ERR_MAPPING_FIELD_MISSING] = "Missing required mapping field", + [CYAML_ERR_BAD_CONFIG_NULL_MEMFN] = "Bad config: NULL mem function", + [CYAML_ERR_BAD_PARAM_NULL_CONFIG] = "Bad parameter: NULL config", + [CYAML_ERR_BAD_PARAM_NULL_SCHEMA] = "Bad parameter: NULL schema", + [CYAML_ERR_LIBYAML_EMITTER_INIT] = "libyaml emitter init failed", + [CYAML_ERR_LIBYAML_PARSER_INIT] = "libyaml parser init failed", + [CYAML_ERR_LIBYAML_EVENT_INIT] = "libyaml event init failed", + [CYAML_ERR_LIBYAML_EMITTER] = "libyaml emitter error", + [CYAML_ERR_LIBYAML_PARSER] = "libyaml parser error", + }; + if ((unsigned)err >= CYAML_ERR__COUNT) { + return "Invalid error code"; + } + assert(strings[err] != NULL); + return strings[err]; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..60adba1 --- /dev/null +++ b/src/util.h @@ -0,0 +1,184 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2017-2019 Michael Drake + */ + +/** + * \file + * \brief CYAML common utility functions. + */ + +#ifndef CYAML_UTIL_H +#define CYAML_UTIL_H + +#include "cyaml/cyaml.h" +#include "utf8.h" + +/** Compile time assertion macro. */ +#define cyaml_static_assert(e) \ +{ \ + enum { \ + cyaml_static_assert_check = 1 / (!!(e)) \ + }; \ +} + +/** Macro to squash unused variable compiler warnings. */ +#define CYAML_UNUSED(_x) ((void)(_x)) + +/** CYAML bitfield type. */ +typedef uint32_t cyaml_bitfield_t; + +/** Number of bits in \ref cyaml_bitfield_t. */ +#define CYAML_BITFIELD_BITS (sizeof(cyaml_bitfield_t) * CHAR_BIT) + +/** CYAML state machine states. */ +enum cyaml_state_e { + CYAML_STATE_START, /**< Initial state. */ + CYAML_STATE_IN_STREAM, /**< In a stream. */ + CYAML_STATE_IN_DOC, /**< In a document. */ + CYAML_STATE_IN_MAP_KEY, /**< In a mapping. */ + CYAML_STATE_IN_MAP_VALUE, /**< In a mapping. */ + CYAML_STATE_IN_SEQUENCE, /**< In a sequence. */ + CYAML_STATE__COUNT, /**< Count of states, **not a valid + state itself**. */ +}; + +/** + * Convert a CYAML state into a human readable string. + * + * \param[in] state The state to convert. + * \return String representing state. + */ +static inline const char * cyaml__state_to_str(enum cyaml_state_e state) +{ + static const char * const strings[CYAML_STATE__COUNT] = { + [CYAML_STATE_START] = "start", + [CYAML_STATE_IN_STREAM] = "in stream", + [CYAML_STATE_IN_DOC] = "in doc", + [CYAML_STATE_IN_MAP_KEY] = "in mapping (key)", + [CYAML_STATE_IN_MAP_VALUE] = "in mapping (value)", + [CYAML_STATE_IN_SEQUENCE] = "in sequence", + }; + if ((unsigned)state >= CYAML_STATE__COUNT) { + return ""; + } + return strings[state]; +} + +/** + * Convert a CYAML type into a human readable string. + * + * \param[in] type The state to convert. + * \return String representing state. + */ +static inline const char * cyaml__type_to_str(cyaml_type_e type) +{ + static const char * const strings[CYAML__TYPE_COUNT] = { + [CYAML_INT] = "INT", + [CYAML_UINT] = "UINT", + [CYAML_BOOL] = "BOOL", + [CYAML_ENUM] = "ENUM", + [CYAML_FLAGS] = "FLAGS", + [CYAML_FLOAT] = "FLOAT", + [CYAML_STRING] = "STRING", + [CYAML_MAPPING] = "MAPPING", + [CYAML_BITFIELD] = "BITFIELD", + [CYAML_SEQUENCE] = "SEQUENCE", + [CYAML_SEQUENCE_FIXED] = "SEQUENCE_FIXED", + [CYAML_IGNORE] = "IGNORE", + }; + if ((unsigned)type >= CYAML__TYPE_COUNT) { + return ""; + } + return strings[type]; +} + +/** + * Log to client's logging function, if provided. + * + * \param[in] cfg CYAML client config structure. + * \param[in] level Log level of message to log. + * \param[in] fmt Format string for message to log. + * \param[in] ... Additional arguments used by fmt. + */ +static inline void cyaml__log( + const cyaml_config_t *cfg, + cyaml_log_t level, + char *fmt, ...) +{ + if (level >= cfg->log_level && cfg->log_fn != NULL) { + va_list args; + va_start(args, fmt); + cfg->log_fn(level, cfg->log_ctx, fmt, args); + va_end(args); + } +} + +/** + * Check if comparason should be case sensitive. + * + * As described in the API, schema flags take priority over config flags. + * + * \param[in] config Client's CYAML configuration structure. + * \param[in] schema The CYAML schema for the value to be compared. + * \return Whether to use case-sensitive comparason. + */ +static inline bool cyaml__is_case_sensitive( + const cyaml_config_t *config, + const cyaml_schema_value_t *schema) +{ + if (schema->flags & CYAML_FLAG_CASE_INSENSITIVE) { + return false; + + } else if (schema->flags & CYAML_FLAG_CASE_SENSITIVE) { + return true; + + } else if (config->flags & CYAML_CFG_CASE_INSENSITIVE) { + return false; + + } + + return true; +} + +/** + * Compare two strings. + * + * Depending on the client's configuration, and the value's schema, + * this will do either a case-sensitive or case-insensitive comparason. + * + * \param[in] config Client's CYAML configuration structure. + * \param[in] schema The CYAML schema for the value to be compared. + * \param[in] str1 First string to be compared. + * \param[in] str2 Second string to be compared. + * \return 0 if and only if strings are equal. + */ +static inline int cyaml__strcmp( + const cyaml_config_t *config, + const cyaml_schema_value_t *schema, + const void * const str1, + const void * const str2) +{ + if (cyaml__is_case_sensitive(config, schema)) { + return strcmp(str1, str2); + } + + return cyaml_utf8_casecmp(str1, str2); +} + +/** + * Check of all the bits of a mask are set in a cyaml value flag word. + * + * \param[in] flags The value flags to test. + * \param[in] mask Mask of the bits to test for in flags. + * \return true if all bits of mask are set in flags. + */ +static inline bool cyaml__flag_check_all( + enum cyaml_flag flags, + enum cyaml_flag mask) +{ + return ((flags & mask) == mask); +} + +#endif diff --git a/test/data/basic.yaml b/test/data/basic.yaml new file mode 100644 index 0000000..8aadb3e --- /dev/null +++ b/test/data/basic.yaml @@ -0,0 +1,19 @@ +animals: + - kind: cat + sounds: + - meow + - purr + - kind: hippo + sounds: + - wheeze + - grunt + - roar + - kind: snake + sounds: + - hiss +cakes: + - salted caramel cake + - lemon drizzle cake + - victoria sponge + - carrot cake + - yule log diff --git a/test/units/errs.c b/test/units/errs.c new file mode 100644 index 0000000..32f6053 --- /dev/null +++ b/test/units/errs.c @@ -0,0 +1,5918 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2018-2019 Michael Drake + */ + +#include +#include +#include +#include + +#include + +#include "ttest.h" + +/** Macro to squash unused variable compiler warnings. */ +#define UNUSED(_x) ((void)(_x)) + +/** Helper macro to count bytes of YAML input data. */ +#define YAML_LEN(_y) (sizeof(_y) - 1) + +/** + * Unit test context data. + */ +typedef struct test_data { + char **buffer; + cyaml_data_t **data; + unsigned *seq_count; + const struct cyaml_config *config; + const struct cyaml_schema_value *schema; +} test_data_t; + +/** + * Common clean up function to free data loaded by tests. + * + * \param[in] data The unit test context data. + */ +static void cyaml_cleanup(void *data) +{ + struct test_data *td = data; + unsigned seq_count = 0; + + if (td->seq_count != NULL) { + seq_count = *(td->seq_count); + } + + if (td->config->mem_fn != NULL && td->buffer != NULL) { + td->config->mem_fn(td->config->mem_ctx, *(td->buffer), 0); + } + + if (td->data != NULL) { + cyaml_free(td->config, td->schema, *(td->data), seq_count); + } +} + +/** + * Test loading with NULL data parameter. + * + * \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_null_data( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = ""; + struct target_struct { + int value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) NULL, NULL); + if (err != CYAML_ERR_BAD_PARAM_NULL_DATA) { + 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 NULL data parameter. + * + * \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_null_data( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + int value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + 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 = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, NULL, 0); + if (err != CYAML_ERR_BAD_PARAM_NULL_DATA) { + 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 NULL config parameter. + * + * \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_null_config( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = ""; + void *data_tgt = NULL; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = NULL, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), NULL, NULL, + (cyaml_data_t **) NULL, NULL); + if (err != CYAML_ERR_BAD_PARAM_NULL_CONFIG) { + 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 NULL config parameter. + * + * \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_null_config( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const struct target_struct { + unsigned test_uint; + } data = { + .test_uint = 555, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("test_uint", CYAML_FLAG_DEFAULT, + struct target_struct, test_uint), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, NULL, &top_schema, &data, 0); + if (err != CYAML_ERR_BAD_PARAM_NULL_CONFIG) { + 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 NULL memory allocation function. + * + * \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_null_mem_fn( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = ""; + cyaml_config_t cfg = *config; + void *data_tgt = NULL; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = &cfg, + .schema = NULL, + }; + cyaml_err_t err; + + cfg.mem_fn = NULL; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, NULL, + (cyaml_data_t **) NULL, NULL); + if (err != CYAML_ERR_BAD_CONFIG_NULL_MEMFN) { + 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 NULL memory allocation function. + * + * \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_null_mem_fn( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_config_t cfg = *config; + static const struct target_struct { + unsigned test_uint; + } data = { + .test_uint = 555, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("test_uint", CYAML_FLAG_DEFAULT, + struct target_struct, test_uint), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = &cfg, + }; + cyaml_err_t err; + + cfg.mem_fn = NULL; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, &cfg, &top_schema, &data, 0); + if (err != CYAML_ERR_BAD_CONFIG_NULL_MEMFN) { + 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 NULL schema. + * + * \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_null_schema( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = ""; + void *data_tgt = NULL; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = NULL, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, NULL, + (cyaml_data_t **) NULL, NULL); + if (err != CYAML_ERR_BAD_PARAM_NULL_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); +} + +/** + * Test saving with NULL schema. + * + * \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_null_schema( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const struct target_struct { + unsigned test_uint; + } data = { + .test_uint = 555, + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, NULL, &data, 0); + if (err != CYAML_ERR_BAD_PARAM_NULL_SCHEMA) { + 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 bad top level type (non-pointer). + * + * \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_top_level_non_pointer( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "7\n"; + int *value = NULL; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int) + }; + test_data_t td = { + .data = (cyaml_data_t **) &value, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &value, NULL); + if (err != CYAML_ERR_TOP_LEVEL_NON_PTR) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (value != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test saving with schema with bad top level type (non-pointer). + * + * \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_top_level_non_pointer( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const struct target_struct { + unsigned test_uint; + } data = { + .test_uint = 555, + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int) + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, &data, 0); + if (err != CYAML_ERR_TOP_LEVEL_NON_PTR) { + 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 bad top level sequence and no seq_count. + * + * \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_top_level_sequence_no_count( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key:\n"; + struct target_struct { + int *value; + unsigned value_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int) + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, int, + &entry_schema, 0, CYAML_UNLIMITED), + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_BAD_PARAM_SEQ_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 loading with schema with bad top level sequence and no seq_count. + * + * \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_top_level_not_sequence_count( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "7\n"; + int *value = NULL; + unsigned count = 0; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int) + }; + test_data_t td = { + .data = (cyaml_data_t **) &value, + .seq_count = &count, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &value, &count); + if (err != CYAML_ERR_BAD_PARAM_SEQ_COUNT) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (value != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test saving with schema with a non-sequence and non-zero sequence count. + * + * \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_top_level_not_sequence_count( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const struct target_struct { + unsigned *test_uint; + } data = { + .test_uint = NULL, + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_INT(CYAML_FLAG_POINTER, int) + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, &data, 42); + if (err != CYAML_ERR_BAD_PARAM_SEQ_COUNT) { + 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 bad type. + * + * \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_type( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key:\n"; + struct target_struct { + int value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = 99999, + .flags = CYAML_FLAG_DEFAULT, + .data_size = sizeof(data_tgt->value), + }, + .data_offset = offsetof(struct target_struct, value), + }, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_BAD_TYPE_IN_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); +} + +/** + * Test saving with schema with bad type. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_save_schema_bad_type( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + int value; + } data = { + .value = 99, + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = 99999, + .flags = CYAML_FLAG_DEFAULT, + .data_size = sizeof(data.value), + }, + .data_offset = offsetof(struct target_struct, value), + }, + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, &data, 0); + if (err != CYAML_ERR_BAD_TYPE_IN_SCHEMA) { + 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 string min greater than 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_string_min_max( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "value: foo\n"; + struct target_struct { + const char *value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("value", CYAML_FLAG_POINTER, + struct target_struct, value, + 10, 9), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_BAD_MIN_MAX_SCHEMA) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Only scalar mapping keys are allowed by libcyaml. + * + * \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_non_scalar_mapping_key( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "{1}: value"; + struct target_struct { + unsigned value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("key", CYAML_FLAG_DEFAULT, + struct target_struct, value), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_INTERNAL_ERROR) { + 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 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_load_schema_bad_data_size_1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key: 1\n"; + struct target_struct { + int value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_INT, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 0, + }, + .data_offset = offsetof(struct target_struct, value), + }, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 data size (9). + * + * \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_2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key: 1\n"; + struct target_struct { + int value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_INT, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 9, + }, + .data_offset = offsetof(struct target_struct, value), + }, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 data size (0) for flags. + * + * \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_3( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_strval_t strings[] = { + { "foo", 0 }, + { "bar", 1 }, + { "baz", 2 }, + { "bat", 3 }, + }; + static const unsigned char yaml[] = + "key:\n" + " - bat\n" + " - bar\n"; + struct target_struct { + int value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_FLAGS, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 0, + .enumeration = { + .strings = strings, + .count = CYAML_ARRAY_LEN(strings), + }, + }, + .data_offset = offsetof(struct target_struct, value), + }, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 data size (0) for sequence count. + * + * \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_4( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key:\n" + " - 2\n" + " - 4\n"; + struct target_struct { + int *value; + unsigned value_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_SEQUENCE, + .flags = CYAML_FLAG_POINTER, + .data_size = sizeof(*(data_tgt->value)), + .sequence = { + .min = 0, + .max = CYAML_UNLIMITED, + .entry = &entry_schema, + }, + }, + .data_offset = offsetof(struct target_struct, value), + .count_size = 0, + .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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 data size (9) for sequence count. + * + * \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_5( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key:\n" + " - 2\n" + " - 4\n"; + struct target_struct { + int *value; + unsigned value_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_SEQUENCE, + .flags = CYAML_FLAG_POINTER, + .data_size = sizeof(*(data_tgt->value)), + .sequence = { + .min = 0, + .max = CYAML_UNLIMITED, + .entry = &entry_schema, + }, + }, + .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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 data size (9) for sequence count. + * + * \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_6( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key:\n" + " - 2\n" + " - 4\n"; + struct target_struct { + int value[4]; + unsigned value_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_SEQUENCE, + .flags = CYAML_FLAG_DEFAULT, + .data_size = sizeof(*(data_tgt->value)), + .sequence = { + .min = 0, + .max = 4, + .entry = &entry_schema, + }, + }, + .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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 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_load_schema_bad_data_size_7( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key: 1\n"; + struct target_struct { + unsigned value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_UINT, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 0, + }, + .data_offset = offsetof(struct target_struct, value), + }, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 data size (9) for bitfield. + * + * \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_8( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_bitdef_t bitvals[] = { + { .name = "a", .offset = 0, .bits = 3 }, + { .name = "b", .offset = 3, .bits = 7 }, + { .name = "c", .offset = 10, .bits = 32 }, + { .name = "d", .offset = 42, .bits = 8 }, + { .name = "e", .offset = 50, .bits = 14 }, + }; + static const unsigned char yaml[] = + "test_bitfield:\n" + " a: 0x7\n" + " b: 0x7f\n" + " c: 0xffffffff\n" + " d: 0xff\n" + " e: 0x3fff\n"; + struct target_struct { + uint64_t value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "test_bitfield", + .value = { + .type = CYAML_BITFIELD, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 9, + .bitfield = { + .bitdefs = bitvals, + .count = CYAML_ARRAY_LEN(bitvals), + }, + }, + .data_offset = offsetof(struct target_struct, value), + }, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 data size (9) for flags. + * + * \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_9( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_strval_t strings[] = { + { "foo", 0 }, + { "bar", 1 }, + { "baz", 2 }, + { "bat", 3 }, + }; + static const unsigned char yaml[] = + "key:\n" + " - bat\n" + " - bar\n"; + struct target_struct { + int value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_FLAGS, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 9, + .enumeration = { + .strings = strings, + .count = CYAML_ARRAY_LEN(strings), + }, + }, + .data_offset = offsetof(struct target_struct, value), + }, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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). + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_save_schema_bad_data_size_1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const struct target_struct { + int value; + } data = { + .value = 9, + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_INT, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 0, + }, + .data_offset = offsetof(struct target_struct, value), + }, + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, &data, 0); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (buffer != NULL || len != 0) { + return ttest_fail(&tc, "Buffer/len not untouched."); + } + + return ttest_pass(&tc); +} + +/** + * Test saving with schema with data size (0). + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_save_schema_bad_data_size_2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const struct target_struct { + unsigned value; + } data = { + .value = 9, + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_UINT, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 0, + }, + .data_offset = offsetof(struct target_struct, value), + }, + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, &data, 0); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (buffer != NULL || len != 0) { + return ttest_fail(&tc, "Buffer/len not untouched."); + } + + return ttest_pass(&tc); +} + +/** + * Test saving with schema with data size (0). + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_save_schema_bad_data_size_3( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const struct target_struct { + bool value; + } data = { + .value = 1, + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_BOOL, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 0, + }, + .data_offset = offsetof(struct target_struct, value), + }, + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, &data, 0); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (buffer != NULL || len != 0) { + return ttest_fail(&tc, "Buffer/len not untouched."); + } + + return ttest_pass(&tc); +} + +/** + * Test saving with schema with data size (0). + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_save_schema_bad_data_size_4( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_e { + FIRST, SECOND, THIRD, FOURTH, COUNT + }; + static const cyaml_strval_t strings[COUNT] = { + [FIRST] = { "first", 0 }, + [SECOND] = { "second", 1 }, + [THIRD] = { "third", 2 }, + [FOURTH] = { "fourth", 3 }, + }; + static const struct target_struct { + enum test_e value; + } data = { + .value = 1, + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_ENUM, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 0, + .enumeration = { + .strings = strings, + .count = COUNT, + }, + }, + .data_offset = offsetof(struct target_struct, value), + }, + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, &data, 0); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (buffer != NULL || len != 0) { + return ttest_fail(&tc, "Buffer/len not untouched."); + } + + return ttest_pass(&tc); +} + +/** + * Test saving with schema with data size (5 for floating point 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_save_schema_bad_data_size_5( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const struct target_struct { + float value; + } data = { + .value = 3.14f, + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_FLOAT, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 5, + }, + .data_offset = offsetof(struct target_struct, value), + }, + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, &data, 0); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (buffer != NULL || len != 0) { + return ttest_fail(&tc, "Buffer/len not untouched."); + } + + return ttest_pass(&tc); +} + +/** + * Test saving with schema with data size (0). + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_save_schema_bad_data_size_6( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_f { + NONE = 0, + FIRST = (1 << 0), + SECOND = (1 << 1), + THIRD = (1 << 2), + FOURTH = (1 << 3), + }; + static const cyaml_strval_t strings[] = { + { "first", (1 << 0) }, + { "second", (1 << 1) }, + { "third", (1 << 2) }, + { "fourth", (1 << 3) }, + }; + static const struct target_struct { + enum test_f value; + } data = { + .value = THIRD, + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_FLAGS, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 0, + .enumeration = { + .strings = strings, + .count = 4, + }, + }, + .data_offset = offsetof(struct target_struct, value), + }, + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, &data, 0); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (buffer != NULL || len != 0) { + return ttest_fail(&tc, "Buffer/len not untouched."); + } + + return ttest_pass(&tc); +} + +/** + * Test saving with schema with a 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_save_schema_bad_data_size_7( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + unsigned *seq; + uint32_t seq_count; + } data = { + .seq = NULL, + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_UINT(CYAML_FLAG_DEFAULT, *(data.seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "sequence", + .value = { + .type = CYAML_SEQUENCE, + .flags = CYAML_FLAG_POINTER, + .data_size = sizeof(*(data.seq)), + .sequence = { + .entry = &entry_schema, + .min = 0, + .max = 10, + + }, + }, + .data_offset = offsetof(struct target_struct, seq), + .count_offset = offsetof(struct target_struct, seq_count), + .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 = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, &data, 0); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (buffer != NULL || len != 0) { + return ttest_fail(&tc, "Buffer/len not untouched."); + } + + return ttest_pass(&tc); +} + +/** + * Test saving with schema with data size (0). + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_save_schema_bad_data_size_8( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_bitdef_t bitvals[] = { + { .name = "a", .offset = 0, .bits = 3 }, + { .name = "b", .offset = 3, .bits = 7 }, + { .name = "c", .offset = 10, .bits = 32 }, + { .name = "d", .offset = 42, .bits = 8 }, + { .name = "e", .offset = 50, .bits = 14 }, + }; + static const struct target_struct { + uint64_t test_bitfield; + } data = { + .test_bitfield = 0xFFFFFFFFFFFFFFFFu, + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "test_bitfield", + .value = { + .type = CYAML_BITFIELD, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 0, + .bitfield = { + .bitdefs = bitvals, + .count = CYAML_ARRAY_LEN(bitvals), + }, + }, + .data_offset = offsetof(struct target_struct, + test_bitfield), + }, + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, + &data, 0); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (buffer != NULL || len != 0) { + return ttest_fail(&tc, "Buffer/len not untouched."); + } + + return ttest_pass(&tc); +} + +/** + * Test 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 = { + .seq = &value, + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_UINT(CYAML_FLAG_DEFAULT, *(data.seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "sequence", + .value = { + .type = CYAML_SEQUENCE_FIXED, + .flags = CYAML_FLAG_POINTER, + .data_size = sizeof(*(data.seq)), + .sequence = { + .entry = &entry_schema, + .min = 0, + .max = CYAML_UNLIMITED, + + }, + }, + .data_offset = offsetof(struct target_struct, seq), + }, + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, &data, 0); + if (err != CYAML_ERR_SEQUENCE_FIXED_COUNT) { + 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 data size for float. + * + * \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_float( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key: 1\n"; + struct target_struct { + int value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_FLOAT, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 7, + }, + .data_offset = offsetof(struct target_struct, value), + }, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence in sequence. + * + * \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_in_sequence( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "- -\n"; + unsigned **seq = NULL; + unsigned count = 0; + static const struct cyaml_schema_value inner_entry_schema = { + CYAML_VALUE_UINT(CYAML_FLAG_DEFAULT, **seq), + }; + static const struct cyaml_schema_value outer_entry_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, unsigned, + &inner_entry_schema, 0, CYAML_UNLIMITED) + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, unsigned *, + &outer_entry_schema, 0, CYAML_UNLIMITED) + }; + test_data_t td = { + .data = (cyaml_data_t **) &seq, + .seq_count = &count, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &seq, &count); + if (err != CYAML_ERR_SEQUENCE_IN_SEQUENCE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (seq != NULL) { + return ttest_fail(&tc, "Data non-NULL on error."); + } + + return ttest_pass(&tc); +} + +/** + * Test saving with schema with sequence in sequence. + * + * \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_in_sequence( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned seq[4] = { 1, 2, 3 }; + static const unsigned *data[] = { seq, seq }; + static const struct cyaml_schema_value inner_entry_schema = { + CYAML_VALUE_UINT(CYAML_FLAG_DEFAULT, **data), + }; + static const struct cyaml_schema_value outer_entry_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, unsigned, + &inner_entry_schema, 0, CYAML_UNLIMITED) + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, unsigned *, + &outer_entry_schema, 0, CYAML_UNLIMITED) + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, &data, 2); + if (err != CYAML_ERR_SEQUENCE_IN_SEQUENCE) { + 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 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_uint( + 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 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 schema expects flags, but numerical 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_1( + 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) }, + }; + static const unsigned char yaml[] = + "key:\n" + " - first\n" + " - -7\n"; + struct target_struct { + enum test_flags a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("key", CYAML_FLAG_DEFAULT, + struct target_struct, a, + strings, CYAML_ARRAY_LEN(strings)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 flags, but numerical 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_2( + 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) }, + }; + static const unsigned char yaml[] = + "key:\n" + " - first\n" + " - 0x100000000\n"; + struct target_struct { + enum test_flags a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("key", CYAML_FLAG_DEFAULT, + struct target_struct, a, + strings, CYAML_ARRAY_LEN(strings)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 flags, but numerical 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_3( + 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) }, + }; + static const unsigned char yaml[] = + "key:\n" + " - first\n" + " - 0x10000000000000000\n"; + struct target_struct { + enum test_flags a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("key", CYAML_FLAG_DEFAULT, + struct target_struct, a, + strings, CYAML_ARRAY_LEN(strings)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 saving a NULL when NULLs aren't allowed by the schema. + * + * \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_invalid_value_null_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const int d[] = { 7, 6, 5, 4, 3, 2, 1, 0 }; + static const int *data[] = { d + 0, d + 1, d + 2, NULL, + d + 4, d + 5, d + 6, d + 7, }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_POINTER, int) + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, int, + &entry_schema, 0, 3), + }; + char *buffer = NULL; + size_t len = 0; + cyaml_config_t cfg = *config; + test_data_t td = { + .buffer = &buffer, + .config = &cfg, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_STYLE_BLOCK; + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, &cfg, &top_schema, data, + CYAML_ARRAY_LEN(data)); + if (err != CYAML_ERR_INVALID_VALUE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading when schema expects bitfield, but numerical 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_bitfield_1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_bitdef_t bitvals[] = { + { .name = "a", .offset = 0, .bits = 3 }, + }; + static const unsigned char yaml[] = + "test_bitfield:\n" + " a: invalid\n"; + struct target_struct { + uint64_t test_value_bitfield; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BITFIELD("test_bitfield", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_bitfield, + bitvals, CYAML_ARRAY_LEN(bitvals)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 bitfield, but value is non-scalar. + * + * \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_bitfield_2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_bitdef_t bitvals[] = { + { .name = "a", .offset = 0, .bits = 3 }, + }; + static const unsigned char yaml[] = + "test_bitfield:\n" + " a: {}\n"; + struct target_struct { + uint64_t test_value_bitfield; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BITFIELD("test_bitfield", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_bitfield, + bitvals, CYAML_ARRAY_LEN(bitvals)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_UNEXPECTED_EVENT) { + 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 bitfield, but value is not in schema. + * + * \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_bitfield_3( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_bitdef_t bitvals[] = { + { .name = "a", .offset = 0, .bits = 3 }, + }; + static const unsigned char yaml[] = + "test_bitfield:\n" + " b: {}\n"; + struct target_struct { + uint64_t test_value_bitfield; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BITFIELD("test_bitfield", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_bitfield, + bitvals, CYAML_ARRAY_LEN(bitvals)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 bitfield, but value too big. + * + * \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_bitfield_4( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_bitdef_t bitvals[] = { + { .name = "a", .offset = 0, .bits = 3 }, + }; + static const unsigned char yaml[] = + "test_bitfield:\n" + " a: 0xf\n"; + struct target_struct { + uint64_t test_value_bitfield; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BITFIELD("test_bitfield", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_bitfield, + bitvals, CYAML_ARRAY_LEN(bitvals)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 bitfield, but value is of wrong type. + * + * \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_bitfield_5( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_bitdef_t bitvals[] = { + { .name = "a", .offset = 0, .bits = 3 }, + }; + static const unsigned char yaml[] = + "test_bitfield:\n" + " {}: {}\n"; + struct target_struct { + uint64_t test_value_bitfield; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BITFIELD("test_bitfield", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_bitfield, + bitvals, CYAML_ARRAY_LEN(bitvals)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_UNEXPECTED_EVENT) { + 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 bitfield, but value is of wrong type. + * + * \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_bitfield_6( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_bitdef_t bitvals[] = { + { .name = "a", .offset = 0, .bits = 3 }, + }; + static const unsigned char yaml[] = + "test_bitfield:\n" + " []\n"; + struct target_struct { + uint64_t test_value_bitfield; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BITFIELD("test_bitfield", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_bitfield, + bitvals, CYAML_ARRAY_LEN(bitvals)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 bitfield, but value outside data. + * + * \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_bitfield( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_bitdef_t bitvals[] = { + { .name = "a", .offset = 62, .bits = 4 }, + }; + static const unsigned char yaml[] = + "test_bitfield:\n" + " a: 1\n"; + struct target_struct { + uint64_t test_value_bitfield; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BITFIELD("test_bitfield", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_bitfield, + bitvals, CYAML_ARRAY_LEN(bitvals)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_BAD_BITVAL_IN_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); +} + +/** + * Test saving when schema has bitfield defined outside value data. + * + * \param[in] report The test report context. + * \param[in] config The CYAML config to use for the test. + * \return true if test passes, false otherwise. + */ +static bool test_err_save_schema_bad_bitfield( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_bitdef_t bitvals[] = { + { .name = "a", .offset = 30, .bits = 4 }, + }; + static const struct target_struct { + uint32_t test_bitfield; + } data = { + .test_bitfield = 0xFFFFFFFFu, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BITFIELD("test_bitfield", CYAML_FLAG_DEFAULT, + struct target_struct, test_bitfield, + bitvals, CYAML_ARRAY_LEN(bitvals)), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, + &data, 0); + if (err != CYAML_ERR_BAD_BITVAL_IN_SCHEMA) { + 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 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_range( + 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, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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_range( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 1.8e+308\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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 int 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_int_range_1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: -129\n"; + struct target_struct { + signed char a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 int 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_int_range_2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 128\n"; + struct target_struct { + signed char a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 int 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_int_range_3( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 0x10000\n"; + struct target_struct { + int16_t a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 int 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_int_range_4( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 0x100000000\n"; + struct target_struct { + int32_t a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 int 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_int_range_5( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 0x10000000000000000\n"; + struct target_struct { + int32_t a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 uint 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_uint_range_1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: -1\n"; + struct target_struct { + unsigned char 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 uint 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_uint_range_2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 256\n"; + struct target_struct { + unsigned char 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 uint 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_uint_range_3( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 0x10000\n"; + struct target_struct { + uint16_t 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 uint 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_uint_range_4( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 0x100000000\n"; + struct target_struct { + uint32_t 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 uint 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_uint_range_5( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 0x10000000000000000\n"; + struct target_struct { + uint32_t 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 string, but it's too short. + * + * \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_string_min_length( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: foo\n"; + struct target_struct { + char *a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("a", CYAML_FLAG_DEFAULT, + struct target_struct, a, 4, 4), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_STRING_LENGTH_MIN) { + 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 string, but it's too long. + * + * \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_string_max_length( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: fifth\n"; + struct target_struct { + char *a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("a", CYAML_FLAG_DEFAULT, + struct target_struct, a, 4, 4), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_STRING_LENGTH_MAX) { + 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 mapping field which is not present. + * + * \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_missing_mapping_field( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "a: 2\n"; + struct target_struct { + int a; + int b; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, + struct target_struct, a), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, + struct target_struct, b), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_MAPPING_FIELD_MISSING) { + 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 disallows mapping field. + * + * \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_unknown_mapping_field( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "wrong_key: 2\n"; + struct target_struct { + int a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("key", 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_INVALID_KEY) { + 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 sequence, it has too few entries. + * + * \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_entries( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key:\n" + " - 1\n" + " - 2\n"; + struct target_struct { + int *a; + unsigned a_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data_tgt->a)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("key", CYAML_FLAG_POINTER, + struct target_struct, a, &entry_schema, + 3, 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_SEQUENCE_ENTRIES_MIN) { + 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 sequence, it has too many entries. + * + * \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_max_entries( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key:\n" + " - 1\n" + " - 2\n" + " - 3\n"; + struct target_struct { + int *a; + unsigned a_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data_tgt->a)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("key", CYAML_FLAG_POINTER, + struct target_struct, a, &entry_schema, + 2, 2), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_SEQUENCE_ENTRIES_MAX) { + 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 flags and finds a mapping inside. + * + * \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_flags_mapping( + 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) }, + }; + static const unsigned char yaml[] = + "key:\n" + " - first\n" + " - map:\n" + " a:\n" + " b:\n"; + struct target_struct { + enum test_flags a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("key", CYAML_FLAG_STRICT, + struct target_struct, a, + strings, CYAML_ARRAY_LEN(strings)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_UNEXPECTED_EVENT) { + 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 enum, but string is not allowed. + * + * \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_enum_bad_string( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_enum { + TEST_ENUM_FIRST, + TEST_ENUM_SECOND, + TEST_ENUM_THIRD, + TEST_ENUM__COUNT, + }; + static const cyaml_strval_t strings[TEST_ENUM__COUNT] = { + [TEST_ENUM_FIRST] = { "first", 0 }, + [TEST_ENUM_SECOND] = { "second", 1 }, + [TEST_ENUM_THIRD] = { "third", 2 }, + }; + static const unsigned char yaml[] = + "key: fourth\n"; + struct target_struct { + enum test_enum a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_ENUM("key", CYAML_FLAG_DEFAULT, + struct target_struct, a, + strings, TEST_ENUM__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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 flags but YAML has bad flag string. + * + * \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_flags_bad_string( + 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) }, + }; + static const unsigned char yaml[] = + "key:\n" + " - first\n" + " - seventh\n"; + struct target_struct { + enum test_flags a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("key", CYAML_FLAG_DEFAULT, + struct target_struct, a, + strings, CYAML_ARRAY_LEN(strings)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 saving when schema expects strict enum, but value not allowed. + * + * \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_strict_enum_bad_value( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_enum { + TEST_ENUM_FIRST, + TEST_ENUM_SECOND, + TEST_ENUM_THIRD, + TEST_ENUM__COUNT, + }; + static const cyaml_strval_t strings[TEST_ENUM__COUNT] = { + [TEST_ENUM_FIRST] = { "first", 0 }, + [TEST_ENUM_SECOND] = { "second", 1 }, + [TEST_ENUM_THIRD] = { "third", 2 }, + }; + struct target_struct { + enum test_enum a; + } data = { + .a = 876, + }; + char *buffer = NULL; + size_t len = 0; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_ENUM("key", CYAML_FLAG_STRICT, + struct target_struct, a, + strings, TEST_ENUM__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 = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, + &data, 0); + if (err != CYAML_ERR_INVALID_VALUE) { + 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 when schema expects strict enum but YAML has bad string. + * + * \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_strict_enum_bad_string( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_enum { + TEST_ENUM_FIRST, + TEST_ENUM_SECOND, + TEST_ENUM_THIRD, + TEST_ENUM__COUNT, + }; + static const cyaml_strval_t strings[TEST_ENUM__COUNT] = { + [TEST_ENUM_FIRST] = { "first", 0 }, + [TEST_ENUM_SECOND] = { "second", 1 }, + [TEST_ENUM_THIRD] = { "third", 2 }, + }; + static const unsigned char yaml[] = + "key: fourth\n"; + struct target_struct { + enum test_enum a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_ENUM("key", CYAML_FLAG_STRICT, + struct target_struct, a, + strings, TEST_ENUM__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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 saving when schema expects strict flags, but value not allowed. + * + * \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_strict_flags_bad_value( + 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) }, + }; + struct target_struct { + enum test_flags a; + } data = { + .a = TEST_FLAGS_SECOND | TEST_FLAGS_FIFTH | (1 << 9), + }; + char *buffer = NULL; + size_t len = 0; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("key", CYAML_FLAG_STRICT, + struct target_struct, a, + strings, CYAML_ARRAY_LEN(strings)), + 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 = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, config, &top_schema, + &data, 0); + if (err != CYAML_ERR_INVALID_VALUE) { + 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 when schema expects strict flags but YAML has bad string. + * + * \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_strict_flags_bad_string( + 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) }, + }; + static const unsigned char yaml[] = + "key:\n" + " - first\n" + " - seventh\n"; + struct target_struct { + enum test_flags a; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("key", CYAML_FLAG_STRICT, + struct target_struct, a, + strings, CYAML_ARRAY_LEN(strings)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 int, but YAML has sequence. + * + * \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_expect_int_read_seq( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key:\n" + " - 90"; + struct target_struct { + int value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("key", CYAML_FLAG_DEFAULT, + struct target_struct, value), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 int, but YAML ends. + * + * \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_expect_int_read_end_1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key:\n"; + struct target_struct { + int value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("key", CYAML_FLAG_DEFAULT, + struct target_struct, value), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 int, but YAML ends. + * + * \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_expect_int_read_end_2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key: |" + "..."; + struct target_struct { + int value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("key", CYAML_FLAG_DEFAULT, + struct target_struct, value), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_LIBYAML_PARSER) { + 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 flags, but YAML has scalar. + * + * \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_expect_flags_read_scalar( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_strval_t strings[] = { + { "first", (1 << 0) }, + { "second", (1 << 1) }, + { "third", (1 << 2) }, + { "fourth", (1 << 3) }, + { "fifth", (1 << 4) }, + { "sixth", (1 << 5) }, + }; + static const unsigned char yaml[] = + "key: first\n"; + struct target_struct { + int value; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("key", CYAML_FLAG_DEFAULT, + struct target_struct, value, + strings, CYAML_ARRAY_LEN(strings)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 mapping, but YAML has scalar. + * + * \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_expect_mapping_read_scalar( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key: scalar\n"; + struct value_s { + int a; + }; + struct target_struct { + struct value_s test_value_mapping; + } *data_tgt = NULL; + static const struct cyaml_schema_field test_mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, struct value_s, a), + CYAML_FIELD_END + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_MAPPING("key", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_mapping, + test_mapping_schema), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence, but YAML has scalar. + * + * \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_expect_sequence_read_scalar( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "key: foo\n"; + struct target_struct { + int *a; + unsigned a_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data_tgt->a)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("key", CYAML_FLAG_POINTER, + struct target_struct, a, &entry_schema, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 all memory allocation failure at every possible point. + * + * \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_free_null( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + ttest_ctx_t tc = ttest_start(report, __func__, NULL, NULL); + + UNUSED(config); + + cyaml_mem(NULL, NULL, 0); + + return ttest_pass(&tc); +} + +/** Context for allocation failure tests. */ +struct test_cyaml_mem_ctx { + unsigned required; + unsigned fail; + unsigned current; +}; + +/* + * Allocation counter. + * + * Used to count all allocations made when loading an input. + * + * \param[in] ctx Allocation context. + * \param[in] ptr Pointer to allocation to resize or NULL. + * \param[in] size Size to set allocation to. + * \return Pointer to new allocation, or NULL on failure. + */ +static void * test_cyaml_mem_count_allocs( + void *ctx, + void *ptr, + size_t size) +{ + struct test_cyaml_mem_ctx *mem_ctx = ctx; + + if (size == 0) { + free(ptr); + return NULL; + } + + mem_ctx->required++; + + return realloc(ptr, size); +} + +/** + * Allocation failure tester. + * + * Used to make specific allocation fail. + * + * \param[in] ctx Allocation context. + * \param[in] ptr Pointer to allocation to resize or NULL. + * \param[in] size Size to set allocation to. + * \return Pointer to new allocation, or NULL on failure. + */ +static void * test_cyaml_mem_fail( + void *ctx, + void *ptr, + size_t size) +{ + struct test_cyaml_mem_ctx *mem_ctx = ctx; + + if (size == 0) { + free(ptr); + return NULL; + } + + if (mem_ctx->current == mem_ctx->fail) { + return NULL; + } + + mem_ctx->current++; + + return realloc(ptr, size); +} + +/** + * Test loading, with all memory allocation failure at every possible point. + * + * \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_alloc_oom_1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_config_t cfg = *config; + static const unsigned char yaml[] = + "animals:\n" + " - kind: cat\n" + " sound: meow\n" + " position: [ 1, 2, 1]\n" + " - kind: snake\n" + " sound: hiss\n" + " position: [ 3, 1, 0]\n"; + struct animal_s { + char *kind; + char *sound; + int *position; + }; + struct target_struct { + struct animal_s **animal; + uint32_t animal_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value position_entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), + }; + static const struct cyaml_schema_field animal_schema[] = { + CYAML_FIELD_STRING_PTR("kind", CYAML_FLAG_POINTER, + struct animal_s, kind, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("sound", CYAML_FLAG_POINTER, + struct animal_s, sound, 0, CYAML_UNLIMITED), + CYAML_FIELD_SEQUENCE_FIXED("position", CYAML_FLAG_POINTER, + struct animal_s, position, + &position_entry_schema, 3), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value animal_entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, **(data_tgt->animal), + animal_schema), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("animals", CYAML_FLAG_POINTER, + struct target_struct, animal, + &animal_entry_schema, 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 = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + struct test_cyaml_mem_ctx mem_ctx = { + .required = 0, + }; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + /* + * First we load the YAML with the counting allocation function, + * to find the number of allocations required to load the document. + * This is deterministic. + */ + cfg.mem_fn = test_cyaml_mem_count_allocs; + cfg.mem_ctx = &mem_ctx; + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (mem_ctx.required == 0) { + return ttest_fail(&tc, "There were no allocations."); + } + + /* Now free what was loaded. */ + cyaml_free(config, &top_schema, data_tgt, 0); + data_tgt = NULL; + + /* + * Now we load the document multiple times, forcing every possible + * allocation to fail. + */ + cfg.mem_fn = test_cyaml_mem_fail; + + for (mem_ctx.fail = 0; mem_ctx.fail < mem_ctx.required; + mem_ctx.fail++) { + mem_ctx.current = 0; + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_OOM) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + /* Now free what was loaded. */ + cyaml_free(config, &top_schema, data_tgt, 0); + data_tgt = NULL; + } + + return ttest_pass(&tc); +} + +/** + * Test loading, with all memory allocation failure at every possible point. + * + * Uses aliases and anchors, to exercies the event/anchor recording error + * paths too. + * + * \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_alloc_oom_2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_f { + NONE = 0, + FIRST = (1 << 0), + SECOND = (1 << 1), + THIRD = (1 << 2), + FOURTH = (1 << 3), + }; + static const cyaml_strval_t strings[] = { + { "first", (1 << 0) }, + { "second", (1 << 1) }, + { "third", (1 << 2) }, + { "fourth", (1 << 3) }, + }; + cyaml_config_t cfg = *config; + static const unsigned char yaml[] = + "anchors:\n" + " - &a1 {" + " kind: cat,\n" + " sound: meow,\n" + " position: &a2 [ 1, 2, 1],\n" + " flags: &a3 [\n" + " first,\n" + " &a4 second,\n" + " third,\n" + " fourth,\n" + " ]\n" + " }\n" + " - kind: snake\n" + " sound: &a5 hiss\n" + " position: &a6 [ 3, 1, 0]\n" + " flags: &a7 [\n" + " first,\n" + " second,\n" + " third,\n" + " fourth,\n" + " ]\n" + "animals:\n" + " - *a1\n" + " - kind: snake\n" + " sound: *a5\n" + " position: *a6\n" + " flags: *a7\n"; + struct animal_s { + char *kind; + char *sound; + int **position; + unsigned position_count; + enum test_f *flags; + }; + struct target_struct { + struct animal_s **animal; + uint32_t animal_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value position_entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_POINTER, int), + }; + static const struct cyaml_schema_field animal_schema[] = { + CYAML_FIELD_STRING_PTR("kind", CYAML_FLAG_POINTER, + struct animal_s, kind, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("sound", CYAML_FLAG_POINTER, + struct animal_s, sound, 0, CYAML_UNLIMITED), + CYAML_FIELD_SEQUENCE("position", CYAML_FLAG_POINTER, + struct animal_s, position, + &position_entry_schema, 0, CYAML_UNLIMITED), + CYAML_FIELD_FLAGS("flags", + CYAML_FLAG_STRICT | CYAML_FLAG_POINTER, + struct animal_s, flags, strings, 4), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value animal_entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, **(data_tgt->animal), + animal_schema), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_IGNORE("anchors", CYAML_FLAG_OPTIONAL), + CYAML_FIELD_SEQUENCE("animals", CYAML_FLAG_POINTER, + struct target_struct, animal, + &animal_entry_schema, 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), + }; + struct test_cyaml_mem_ctx mem_ctx = { + .required = 0, + }; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + /* + * First we load the YAML with the counting allocation function, + * to find the number of allocations required to load the document. + * This is deterministic. + */ + cfg.mem_fn = test_cyaml_mem_count_allocs; + cfg.mem_ctx = &mem_ctx; + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (mem_ctx.required == 0) { + return ttest_fail(&tc, "There were no allocations."); + } + + /* Now free what was loaded. */ + cyaml_free(config, &top_schema, data_tgt, 0); + data_tgt = NULL; + + /* + * Now we load the document multiple times, forcing every possible + * allocation to fail. + */ + cfg.mem_fn = test_cyaml_mem_fail; + + for (mem_ctx.fail = 0; mem_ctx.fail < mem_ctx.required; + mem_ctx.fail++) { + mem_ctx.current = 0; + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_OOM) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + /* Now free what was loaded. */ + cyaml_free(config, &top_schema, data_tgt, 0); + data_tgt = NULL; + } + + return ttest_pass(&tc); +} + +/** + * Test saving, with memory allocation failure at every possible point. + * + * \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_alloc_oom_1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_config_t cfg = *config; + static const unsigned char yaml[] = + "animals:\n" + " - kind: cat\n" + " sound: meow\n" + " position: [ 1, 2, 1]\n" + " - kind: snake\n" + " sound: hiss\n" + " position: [ 3, 1, 0]\n"; + struct animal_s { + char *kind; + char *sound; + int *position; + }; + struct target_struct { + struct animal_s **animal; + uint32_t animal_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value position_entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), + }; + static const struct cyaml_schema_field animal_schema[] = { + CYAML_FIELD_STRING_PTR("kind", CYAML_FLAG_POINTER, + struct animal_s, kind, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("sound", CYAML_FLAG_POINTER, + struct animal_s, sound, 0, CYAML_UNLIMITED), + CYAML_FIELD_SEQUENCE_FIXED("position", CYAML_FLAG_POINTER, + struct animal_s, position, + &position_entry_schema, 3), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value animal_entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, **(data_tgt->animal), + animal_schema), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("animals", CYAML_FLAG_POINTER, + struct target_struct, animal, + &animal_entry_schema, 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), + }; + struct test_cyaml_mem_ctx mem_ctx = { + .required = 0, + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .data = (cyaml_data_t **) &data_tgt, + .config = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + /* First we load the YAML, so we have something to test saving. */ + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + /* + * First we load the YAML with the counting allocation function, + * to find the number of allocations required to load the document. + * This is deterministic. + */ + cfg.mem_fn = test_cyaml_mem_count_allocs; + cfg.mem_ctx = &mem_ctx; + + err = cyaml_save_data(&buffer, &len, &cfg, &top_schema, data_tgt, 0); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (mem_ctx.required == 0) { + return ttest_fail(&tc, "There were no allocations."); + } + + /* Now free what was loaded. */ + cyaml_mem(&mem_ctx, buffer, 0); + buffer = NULL; + len = 0; + + /* + * Now we load the document multiple times, forcing every possible + * allocation to fail. + */ + cfg.mem_fn = test_cyaml_mem_fail; + + for (mem_ctx.fail = 0; mem_ctx.fail < mem_ctx.required; + mem_ctx.fail++) { + mem_ctx.current = 0; + err = cyaml_save_data(&buffer, &len, &cfg, &top_schema, + data_tgt, 0); + if (err != CYAML_ERR_OOM) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + /* Now free what was loaded. */ + cyaml_mem(&mem_ctx, buffer, 0); + buffer = NULL; + len = 0; + } + + return ttest_pass(&tc); +} + +/** + * Test loading, with all memory allocation failure at every possible point. + * + * \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_alloc_oom_2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_f { + NONE = 0, + FIRST = (1 << 0), + SECOND = (1 << 1), + THIRD = (1 << 2), + FOURTH = (1 << 3), + }; + static const cyaml_strval_t strings[] = { + { "first", (1 << 0) }, + { "second", (1 << 1) }, + { "third", (1 << 2) }, + { "fourth", (1 << 3) }, + }; + cyaml_config_t cfg = *config; + static const unsigned char yaml[] = + "animals:\n" + " - kind: cat\n" + " sound: meow\n" + " position: [ 1, 2, 1]\n" + " flags:\n" + " - first\n" + " - second\n" + " - third\n" + " - fourth\n" + " - kind: snake\n" + " sound: hiss\n" + " position: [ 3, 1, 0]\n" + " flags:\n" + " - first\n" + " - second\n" + " - third\n" + " - fourth\n"; + struct animal_s { + char *kind; + char *sound; + int **position; + unsigned position_count; + enum test_f *flags; + }; + struct target_struct { + struct animal_s **animal; + uint32_t animal_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value position_entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_POINTER, int), + }; + static const struct cyaml_schema_field animal_schema[] = { + CYAML_FIELD_STRING_PTR("kind", CYAML_FLAG_POINTER, + struct animal_s, kind, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("sound", CYAML_FLAG_POINTER, + struct animal_s, sound, 0, CYAML_UNLIMITED), + CYAML_FIELD_SEQUENCE("position", CYAML_FLAG_POINTER, + struct animal_s, position, + &position_entry_schema, 0, CYAML_UNLIMITED), + CYAML_FIELD_FLAGS("flags", + CYAML_FLAG_STRICT | CYAML_FLAG_POINTER, + struct animal_s, flags, strings, 4), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value animal_entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, **(data_tgt->animal), + animal_schema), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("animals", CYAML_FLAG_POINTER, + struct target_struct, animal, + &animal_entry_schema, 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), + }; + struct test_cyaml_mem_ctx mem_ctx = { + .required = 0, + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .data = (cyaml_data_t **) &data_tgt, + .config = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + /* First we load the YAML, so we have something to test saving. */ + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + /* + * First we load the YAML with the counting allocation function, + * to find the number of allocations required to load the document. + * This is deterministic. + */ + cfg.mem_fn = test_cyaml_mem_count_allocs; + cfg.mem_ctx = &mem_ctx; + + err = cyaml_save_data(&buffer, &len, &cfg, &top_schema, data_tgt, 0); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (mem_ctx.required == 0) { + return ttest_fail(&tc, "There were no allocations."); + } + + /* Now free what was loaded. */ + cyaml_mem(&mem_ctx, buffer, 0); + buffer = NULL; + len = 0; + + /* + * Now we load the document multiple times, forcing every possible + * allocation to fail. + */ + cfg.mem_fn = test_cyaml_mem_fail; + + for (mem_ctx.fail = 0; mem_ctx.fail < mem_ctx.required; + mem_ctx.fail++) { + mem_ctx.current = 0; + err = cyaml_save_data(&buffer, &len, &cfg, &top_schema, + data_tgt, 0); + if (err != CYAML_ERR_OOM) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + /* Now free what was loaded. */ + cyaml_mem(&mem_ctx, buffer, 0); + buffer = NULL; + len = 0; + } + + return ttest_pass(&tc); +} + +/** + * Test loading a flag with an aliased 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_flag_value_alias( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_config_t cfg = *config; + struct target_struct { + unsigned a; + unsigned b; + }; + static const cyaml_strval_t str[] = { + { "one", (1 << 0) }, + { "two", (1 << 1) }, + { "three", (1 << 2) }, + { "four", (1 << 3) }, + { "five", (1 << 4) }, + }; + static const unsigned char yaml[] = + "a: \n" + " - &foo one\n" + " - two\n" + " - five\n" + "b:\n" + " - *foo\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("a", CYAML_FLAG_DEFAULT, + struct target_struct, a, str, 5), + CYAML_FIELD_FLAGS("b", CYAML_FLAG_DEFAULT, + struct target_struct, b, str, 5), + 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 = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_NO_ALIAS; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_ALIAS) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a bitfield with an aliased 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_bitfield_value_alias_1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_config_t cfg = *config; + static const cyaml_bitdef_t bitvals[] = { + { .name = "7", .offset = 0, .bits = 1 }, + { .name = "a", .offset = 1, .bits = 2 }, + { .name = "b", .offset = 3, .bits = 7 }, + { .name = "c", .offset = 10, .bits = 32 }, + { .name = "d", .offset = 42, .bits = 8 }, + { .name = "e", .offset = 50, .bits = 14 }, + }; + static const unsigned char yaml[] = + "test_bitfield:\n" + " a: &foo 2\n" + " b: 0x7f\n" + " c: 0xffffffff\n" + " d: 0xff\n" + " e: 0x3fff\n" + "test_bitfield2:\n" + " *foo: 1\n" + " b: 0x7f\n" + " c: 0xffffffff\n" + " d: 0xff\n" + " e: 0x3fff\n"; + struct target_struct { + uint64_t test_value_bitfield; + uint64_t test_value_bitfield2; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BITFIELD("test_bitfield", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_bitfield, + bitvals, CYAML_ARRAY_LEN(bitvals)), + CYAML_FIELD_BITFIELD("test_bitfield2", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_bitfield2, + bitvals, CYAML_ARRAY_LEN(bitvals)), + 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 = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_NO_ALIAS; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_ALIAS) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a bitfield with an aliased 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_bitfield_value_alias_2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_config_t cfg = *config; + static const cyaml_bitdef_t bitvals[] = { + { .name = "7", .offset = 0, .bits = 1 }, + { .name = "a", .offset = 1, .bits = 2 }, + { .name = "b", .offset = 3, .bits = 7 }, + { .name = "c", .offset = 10, .bits = 32 }, + { .name = "d", .offset = 42, .bits = 8 }, + { .name = "e", .offset = 50, .bits = 14 }, + }; + static const unsigned char yaml[] = + "test_bitfield:\n" + " a: &foo 2\n" + " b: 0x7f\n" + " c: 0xffffffff\n" + " d: 0xff\n" + " e: 0x3fff\n" + "test_bitfield2:\n" + " a: *foo\n" + " b: 0x7f\n" + " c: 0xffffffff\n" + " d: 0xff\n" + " e: 0x3fff\n"; + struct target_struct { + uint64_t test_value_bitfield; + uint64_t test_value_bitfield2; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BITFIELD("test_bitfield", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_bitfield, + bitvals, CYAML_ARRAY_LEN(bitvals)), + CYAML_FIELD_BITFIELD("test_bitfield2", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_bitfield2, + bitvals, CYAML_ARRAY_LEN(bitvals)), + 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 = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_NO_ALIAS; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_ALIAS) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping with an aliased 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_mapping_key_alias( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_config_t cfg = *config; + struct target_struct { + char *a; + char *b; + char *c; + char *d; + }; + static const unsigned char yaml[] = + "a: &example b\n" + "*example: test\n" + "c: meh\n" + "d: foo\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("a", CYAML_FLAG_POINTER, + struct target_struct, a, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("b", CYAML_FLAG_POINTER, + struct target_struct, b, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("c", CYAML_FLAG_POINTER, + struct target_struct, c, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("d", CYAML_FLAG_POINTER, + struct target_struct, d, 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 = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_NO_ALIAS; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_ALIAS) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping with an aliased 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_mapping_value_alias_1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_config_t cfg = *config; + struct target_struct { + char *a; + char *b; + char *c; + char *d; + }; + static const unsigned char yaml[] = + "a: 9\n" + "b: 90\n" + "c: &foo 900\n" + "d: *foo\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("a", CYAML_FLAG_POINTER, + struct target_struct, a, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("b", CYAML_FLAG_POINTER, + struct target_struct, b, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("c", CYAML_FLAG_POINTER, + struct target_struct, c, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("d", CYAML_FLAG_POINTER, + struct target_struct, d, 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 = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_NO_ALIAS; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_ALIAS) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping with an aliased 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_mapping_value_alias_2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_config_t cfg = *config; + struct target_struct { + char *a; + char *b; + char *c; + char *d; + }; + static const unsigned char yaml[] = + "a: 9\n" + "b: &foo d\n" + "c: *d\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("a", CYAML_FLAG_POINTER, + struct target_struct, a, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("b", CYAML_FLAG_POINTER, + struct target_struct, b, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("d", CYAML_FLAG_POINTER, + struct target_struct, d, 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 = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_NO_ALIAS | CYAML_CFG_IGNORE_UNKNOWN_KEYS; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_ALIAS) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping with an aliased 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_mapping_value_alias_3( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_config_t cfg = *config; + struct target_struct { + char *a; + char *b; + }; + static const unsigned char yaml[] = + "a: 9\n" + "b: &foo d\n" + "c: [a, b, c, *foo]\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("a", CYAML_FLAG_POINTER, + struct target_struct, a, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("b", CYAML_FLAG_POINTER, + struct target_struct, b, 0, CYAML_UNLIMITED), + CYAML_FIELD_IGNORE("c", CYAML_FLAG_DEFAULT), + 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 = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_NO_ALIAS; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_ALIAS) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading with an aliased string 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_invalid_alias( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "test_string_anchor: &foo Hello World!\n" + "test_string: *bar\n" + "test_int: 9\n"; + struct target_struct { + char * test_value_string; + int test_value_int; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_IGNORE("test_string_anchor", CYAML_FLAG_OPTIONAL), + CYAML_FIELD_STRING_PTR("test_string", CYAML_FLAG_POINTER, + struct target_struct, test_value_string, + 0, CYAML_UNLIMITED), + CYAML_FIELD_INT("test_int", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_int), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_INVALID_ALIAS) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading an incomplete alias. + * + * \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_incomplete_alias( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "anchors:\n" + " - &a1 {\n" + " a: 777,\n" + " b: *a1,\n" + " }\n" + "test: *a1\n"; + struct my_test { + int a; + char *b; + }; + struct target_struct { + struct my_test test; + } *data_tgt = NULL; + static const struct cyaml_schema_field inner_mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, + struct my_test, a), + CYAML_FIELD_STRING_PTR("b", CYAML_FLAG_POINTER, + struct my_test, b, + 0, CYAML_UNLIMITED), + CYAML_FIELD_END + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_IGNORE("anchors", CYAML_FLAG_OPTIONAL), + CYAML_FIELD_MAPPING("test", CYAML_FLAG_DEFAULT, + struct target_struct, test, + inner_mapping_schema), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_INVALID_ALIAS) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Run the CYAML error 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 errs_tests( + ttest_report_ctx_t *rc, + cyaml_log_t log_level, + cyaml_log_fn_t log_fn) +{ + bool pass = true; + cyaml_config_t config = { + .log_fn = log_fn, + .mem_fn = cyaml_mem, + .log_level = log_level, + .flags = CYAML_CFG_DEFAULT, + }; + + /* Since we expect loads of error logging for these tests, + * suppress log output if required log level is greater + * than \ref CYAML_LOG_INFO. + */ + if (log_level > CYAML_LOG_INFO) { + config.log_fn = NULL; + } + + ttest_heading(rc, "Bad parameter tests"); + + pass &= test_err_load_null_data(rc, &config); + pass &= test_err_save_null_data(rc, &config); + pass &= test_err_load_null_config(rc, &config); + pass &= test_err_save_null_config(rc, &config); + pass &= test_err_load_null_mem_fn(rc, &config); + pass &= test_err_save_null_mem_fn(rc, &config); + pass &= test_err_load_null_schema(rc, &config); + pass &= test_err_save_null_schema(rc, &config); + pass &= test_err_load_schema_top_level_non_pointer(rc, &config); + pass &= test_err_save_schema_top_level_non_pointer(rc, &config); + pass &= test_err_load_schema_top_level_sequence_no_count(rc, &config); + pass &= test_err_load_schema_top_level_not_sequence_count(rc, &config); + pass &= test_err_save_schema_top_level_not_sequence_count(rc, &config); + + ttest_heading(rc, "Bad schema tests"); + + pass &= test_err_load_schema_bad_type(rc, &config); + pass &= test_err_save_schema_bad_type(rc, &config); + pass &= test_err_load_schema_bad_bitfield(rc, &config); + pass &= test_err_save_schema_bad_bitfield(rc, &config); + pass &= test_err_load_schema_string_min_max(rc, &config); + pass &= test_err_load_schema_bad_data_size_1(rc, &config); + pass &= test_err_load_schema_bad_data_size_2(rc, &config); + pass &= test_err_load_schema_bad_data_size_3(rc, &config); + pass &= test_err_load_schema_bad_data_size_4(rc, &config); + pass &= test_err_load_schema_bad_data_size_5(rc, &config); + pass &= test_err_load_schema_bad_data_size_6(rc, &config); + pass &= test_err_load_schema_bad_data_size_7(rc, &config); + pass &= test_err_load_schema_bad_data_size_8(rc, &config); + pass &= test_err_load_schema_bad_data_size_9(rc, &config); + pass &= test_err_save_schema_bad_data_size_1(rc, &config); + pass &= test_err_save_schema_bad_data_size_2(rc, &config); + pass &= test_err_save_schema_bad_data_size_3(rc, &config); + pass &= test_err_save_schema_bad_data_size_4(rc, &config); + pass &= test_err_save_schema_bad_data_size_5(rc, &config); + 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_load_schema_sequence_min_max(rc, &config); + pass &= test_err_save_schema_sequence_min_max(rc, &config); + pass &= test_err_load_schema_bad_data_size_float(rc, &config); + pass &= test_err_load_schema_sequence_in_sequence(rc, &config); + pass &= test_err_save_schema_sequence_in_sequence(rc, &config); + + ttest_heading(rc, "YAML / schema mismatch: bad values"); + + pass &= test_err_load_non_scalar_mapping_key(rc, &config); + pass &= test_err_load_schema_invalid_value_uint(rc, &config); + pass &= test_err_load_schema_invalid_value_string(rc, &config); + pass &= test_err_load_schema_invalid_value_flags_1(rc, &config); + pass &= test_err_load_schema_invalid_value_flags_2(rc, &config); + pass &= test_err_load_schema_invalid_value_flags_3(rc, &config); + pass &= test_err_save_schema_invalid_value_null_ptr(rc, &config); + pass &= test_err_load_schema_invalid_value_bitfield_1(rc, &config); + pass &= test_err_load_schema_invalid_value_bitfield_2(rc, &config); + pass &= test_err_load_schema_invalid_value_bitfield_3(rc, &config); + pass &= test_err_load_schema_invalid_value_bitfield_4(rc, &config); + pass &= test_err_load_schema_invalid_value_bitfield_5(rc, &config); + pass &= test_err_load_schema_invalid_value_bitfield_6(rc, &config); + pass &= test_err_load_schema_invalid_value_int_range_1(rc, &config); + pass &= test_err_load_schema_invalid_value_int_range_2(rc, &config); + pass &= test_err_load_schema_invalid_value_int_range_3(rc, &config); + pass &= test_err_load_schema_invalid_value_int_range_4(rc, &config); + pass &= test_err_load_schema_invalid_value_int_range_5(rc, &config); + pass &= test_err_load_schema_invalid_value_float_range(rc, &config); + pass &= test_err_load_schema_invalid_value_double_range(rc, &config); + pass &= test_err_load_schema_invalid_value_uint_range_1(rc, &config); + pass &= test_err_load_schema_invalid_value_uint_range_2(rc, &config); + pass &= test_err_load_schema_invalid_value_uint_range_3(rc, &config); + pass &= test_err_load_schema_invalid_value_uint_range_4(rc, &config); + pass &= test_err_load_schema_invalid_value_uint_range_5(rc, &config); + 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: string lengths"); + + pass &= test_err_load_schema_string_min_length(rc, &config); + pass &= test_err_load_schema_string_max_length(rc, &config); + + ttest_heading(rc, "YAML / schema mismatch: mapping fields"); + + pass &= test_err_load_schema_missing_mapping_field(rc, &config); + pass &= test_err_load_schema_unknown_mapping_field(rc, &config); + + ttest_heading(rc, "YAML / schema mismatch: sequence counts"); + + pass &= test_err_load_schema_sequence_min_entries(rc, &config); + pass &= test_err_load_schema_sequence_max_entries(rc, &config); + + ttest_heading(rc, "YAML / schema mismatch: bad flags/enum strings"); + + pass &= test_err_load_schema_flags_mapping(rc, &config); + pass &= test_err_load_schema_enum_bad_string(rc, &config); + pass &= test_err_load_schema_flags_bad_string(rc, &config); + pass &= test_err_save_schema_strict_enum_bad_value(rc, &config); + pass &= test_err_load_schema_strict_enum_bad_string(rc, &config); + pass &= test_err_save_schema_strict_flags_bad_value(rc, &config); + pass &= test_err_load_schema_strict_flags_bad_string(rc, &config); + + ttest_heading(rc, "YAML / schema mismatch: expected value type tests"); + + pass &= test_err_load_schema_expect_int_read_seq(rc, &config); + pass &= test_err_load_schema_expect_int_read_end_1(rc, &config); + pass &= test_err_load_schema_expect_int_read_end_2(rc, &config); + pass &= test_err_load_schema_expect_flags_read_scalar(rc, &config); + pass &= test_err_load_schema_expect_mapping_read_scalar(rc, &config); + pass &= test_err_load_schema_expect_sequence_read_scalar(rc, &config); + + ttest_heading(rc, "Memory allocation handling tests"); + + pass &= test_err_free_null(rc, &config); + pass &= test_err_load_alloc_oom_1(rc, &config); + pass &= test_err_load_alloc_oom_2(rc, &config); + pass &= test_err_save_alloc_oom_1(rc, &config); + pass &= test_err_save_alloc_oom_2(rc, &config); + + ttest_heading(rc, "Alias tests"); + + pass &= test_err_load_invalid_alias(rc, &config); + pass &= test_err_load_incomplete_alias(rc, &config); + pass &= test_err_load_flag_value_alias(rc, &config); + pass &= test_err_load_mapping_key_alias(rc, &config); + pass &= test_err_load_mapping_value_alias_1(rc, &config); + pass &= test_err_load_mapping_value_alias_2(rc, &config); + pass &= test_err_load_mapping_value_alias_3(rc, &config); + pass &= test_err_load_bitfield_value_alias_1(rc, &config); + pass &= test_err_load_bitfield_value_alias_2(rc, &config); + + return pass; +} diff --git a/test/units/file.c b/test/units/file.c new file mode 100644 index 0000000..f5a5919 --- /dev/null +++ b/test/units/file.c @@ -0,0 +1,429 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2018 Michael Drake + */ + +#include +#include +#include + +#include + +#include "ttest.h" + +/** + * Unit test context data. + */ +typedef struct test_data { + cyaml_data_t **data; + unsigned *seq_count; + const struct cyaml_config *config; + const struct cyaml_schema_value *schema; +} test_data_t; + +/** + * Common clean up function to free data loaded by tests. + * + * \param[in] data The unit test context data. + */ +static void cyaml_cleanup(void *data) +{ + struct test_data *td = data; + unsigned seq_count = 0; + + if (td->seq_count != NULL) { + seq_count = *(td->seq_count); + } + + if (td->data != NULL) { + cyaml_free(td->config, td->schema, *(td->data), seq_count); + } +} + +/** + * Test loading a non-existent file. + * + * \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_file_load_bad_path( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + char *cakes; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_file("/cyaml/path/shouldn't/exist.yaml", + config, &top_schema, (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_ERR_FILE_OPEN) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a non-existent file. + * + * \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_file_save_bad_path( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + char *cakes; + } *data = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + 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 = NULL, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_file("/cyaml/path/shouldn't/exist.yaml", + config, &top_schema, data, 0); + if (err != CYAML_ERR_FILE_OPEN) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading the basic YAML file. + * + * \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_file_load_basic( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct animal { + char *kind; + char **sounds; + unsigned sounds_count; + }; + struct target_struct { + struct animal *animals; + unsigned animals_count; + char **cakes; + unsigned cakes_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value sounds_entry_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char, 0, CYAML_UNLIMITED), + }; + static const struct cyaml_schema_field animal_mapping_schema[] = { + CYAML_FIELD_STRING_PTR("kind", CYAML_FLAG_POINTER, + struct animal, kind, 0, CYAML_UNLIMITED), + CYAML_FIELD_SEQUENCE("sounds", CYAML_FLAG_POINTER, + struct animal, sounds, + &sounds_entry_schema, 0, CYAML_UNLIMITED), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value animals_entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT, + struct animal, animal_mapping_schema), + }; + static const struct cyaml_schema_value cakes_entry_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char, 0, CYAML_UNLIMITED), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("animals", CYAML_FLAG_POINTER, + struct target_struct, animals, + &animals_entry_schema, 0, CYAML_UNLIMITED), + CYAML_FIELD_SEQUENCE("cakes", CYAML_FLAG_POINTER, + struct target_struct, cakes, + &cakes_entry_schema, 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_file("test/data/basic.yaml", config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading and then saving the basic YAML file. + * + * \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_file_load_save_basic( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct animal { + char *kind; + char **sounds; + unsigned sounds_count; + }; + struct target_struct { + struct animal *animals; + unsigned animals_count; + char **cakes; + unsigned cakes_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value sounds_entry_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char, 0, CYAML_UNLIMITED), + }; + static const struct cyaml_schema_field animal_mapping_schema[] = { + CYAML_FIELD_STRING_PTR("kind", CYAML_FLAG_POINTER, + struct animal, kind, 0, CYAML_UNLIMITED), + CYAML_FIELD_SEQUENCE("sounds", CYAML_FLAG_POINTER, + struct animal, sounds, + &sounds_entry_schema, 0, CYAML_UNLIMITED), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value animals_entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT, + struct animal, animal_mapping_schema), + }; + static const struct cyaml_schema_value cakes_entry_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char, 0, CYAML_UNLIMITED), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("animals", CYAML_FLAG_POINTER, + struct target_struct, animals, + &animals_entry_schema, 0, CYAML_UNLIMITED), + CYAML_FIELD_SEQUENCE("cakes", CYAML_FLAG_POINTER, + struct target_struct, cakes, + &cakes_entry_schema, 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_file("test/data/basic.yaml", config, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + err = cyaml_save_file("build/load_save.yaml", config, &top_schema, + data_tgt, 0); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test loading the basic YAML file, with a mismatching schema. + * + * \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_file_load_basic_invalid( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct animal { + char *kind; + int *sounds; + unsigned sounds_count; + }; + struct target_struct { + struct animal *animals; + unsigned animals_count; + char **cakes; + unsigned cakes_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value sounds_entry_schema = { + /* The data has a string, but we're expecting int here. */ + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), + }; + static const struct cyaml_schema_field animal_mapping_schema[] = { + CYAML_FIELD_STRING_PTR("kind", CYAML_FLAG_POINTER, + struct animal, kind, 0, CYAML_UNLIMITED), + CYAML_FIELD_SEQUENCE("sounds", CYAML_FLAG_POINTER, + struct animal, sounds, + &sounds_entry_schema, 0, CYAML_UNLIMITED), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value animals_entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT, + struct animal, animal_mapping_schema), + }; + static const struct cyaml_schema_value cakes_entry_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char, 0, CYAML_UNLIMITED), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("animals", CYAML_FLAG_POINTER, + struct target_struct, animals, + &animals_entry_schema, 0, CYAML_UNLIMITED), + CYAML_FIELD_SEQUENCE("cakes", CYAML_FLAG_POINTER, + struct target_struct, cakes, + &cakes_entry_schema, 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_file("test/data/basic.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 saving to a file when an erro occurs. + * + * \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_file_save_basic_invalid( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const struct target_struct { + int value; + } data = { + .value = 9, + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "key", + .value = { + .type = CYAML_INT, + .flags = CYAML_FLAG_DEFAULT, + .data_size = 0, + }, + .data_offset = offsetof(struct target_struct, value), + }, + 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 = { + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_file("build/save.yaml", config, &top_schema, &data, 0); + if (err != CYAML_ERR_INVALID_DATA_SIZE) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Run the YAML file 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 file_tests( + ttest_report_ctx_t *rc, + cyaml_log_t log_level, + cyaml_log_fn_t log_fn) +{ + bool pass = true; + cyaml_config_t config = { + .log_fn = log_fn, + .mem_fn = cyaml_mem, + .log_level = log_level, + .flags = CYAML_CFG_DEFAULT, + }; + + ttest_heading(rc, "File loading tests"); + + pass &= test_file_load_basic(rc, &config); + pass &= test_file_load_save_basic(rc, &config); + + /* Since we expect loads of error logging for these tests, + * suppress log output if required log level is greater + * than \ref CYAML_LOG_INFO. + */ + if (log_level > CYAML_LOG_INFO) { + config.log_fn = NULL; + } + + pass &= test_file_load_bad_path(rc, &config); + pass &= test_file_save_bad_path(rc, &config); + pass &= test_file_load_basic_invalid(rc, &config); + pass &= test_file_save_basic_invalid(rc, &config); + + return pass; +} diff --git a/test/units/free.c b/test/units/free.c new file mode 100644 index 0000000..9d34eff --- /dev/null +++ b/test/units/free.c @@ -0,0 +1,152 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2017-2018 Michael Drake + */ + +#include +#include +#include + +#include + +#include "ttest.h" + +/** Macro to squash unused variable compiler warnings. */ +#define UNUSED(_x) ((void)(_x)) + +/** + * Test cyaml_free with NULL data. + * + * \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_free_null_data( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_err_t err; + struct target_struct { + int test_value_int; + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("test_int", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_int), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + ttest_ctx_t tc = ttest_start(report, __func__, NULL, NULL); + + err = cyaml_free(config, &top_schema, NULL, 0); + if (err != CYAML_OK) { + return ttest_fail(&tc, "Free failed: %s", cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test cyaml_free with NULL config. + * + * \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_free_null_config( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_err_t err; + ttest_ctx_t tc = ttest_start(report, __func__, NULL, NULL); + + UNUSED(config); + + err = cyaml_free(NULL, NULL, NULL, 0); + if (err != CYAML_ERR_BAD_PARAM_NULL_CONFIG) { + return ttest_fail(&tc, "Free failed: %s", cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test cyaml_free with NULL memory allocation function. + * + * \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_free_null_mem_fn( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_err_t err; + cyaml_config_t cfg = *config; + ttest_ctx_t tc = ttest_start(report, __func__, NULL, NULL); + + cfg.mem_fn = NULL; + + err = cyaml_free(&cfg, NULL, NULL, 0); + if (err != CYAML_ERR_BAD_CONFIG_NULL_MEMFN) { + return ttest_fail(&tc, "Free failed: %s", cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Test cyaml_free with NULL schema. + * + * \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_free_null_schema( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_err_t err; + ttest_ctx_t tc = ttest_start(report, __func__, NULL, NULL); + + err = cyaml_free(config, NULL, NULL, 0); + if (err != CYAML_ERR_BAD_PARAM_NULL_SCHEMA) { + return ttest_fail(&tc, "Free failed: %s", cyaml_strerror(err)); + } + + return ttest_pass(&tc); +} + +/** + * Run the CYAML freeing 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 free_tests( + ttest_report_ctx_t *rc, + cyaml_log_t log_level, + cyaml_log_fn_t log_fn) +{ + bool pass = true; + cyaml_config_t config = { + .log_fn = log_fn, + .mem_fn = cyaml_mem, + .log_level = log_level, + .flags = CYAML_CFG_DEFAULT, + }; + + ttest_heading(rc, "Free tests"); + + pass &= test_free_null_data(rc, &config); + pass &= test_free_null_mem_fn(rc, &config); + pass &= test_free_null_config(rc, &config); + pass &= test_free_null_schema(rc, &config); + + return pass; +} diff --git a/test/units/load.c b/test/units/load.c new file mode 100644 index 0000000..1556606 --- /dev/null +++ b/test/units/load.c @@ -0,0 +1,6530 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2017-2019 Michael Drake + */ + +#include +#include +#include +#include + +#include + +#include "../../src/data.h" +#include "ttest.h" + +/** Macro to squash unused variable compiler warnings. */ +#define UNUSED(_x) ((void)(_x)) + +/** Helper macro to count bytes of YAML input data. */ +#define YAML_LEN(_y) (sizeof(_y) - 1) + +/** + * Unit test context data. + */ +typedef struct test_data { + cyaml_data_t **data; + unsigned *seq_count; + const struct cyaml_config *config; + const struct cyaml_schema_value *schema; +} test_data_t; + +/** + * Common clean up function to free data loaded by tests. + * + * \param[in] data The unit test context data. + */ +static void cyaml_cleanup(void *data) +{ + struct test_data *td = data; + unsigned seq_count = 0; + + if (td->seq_count != NULL) { + seq_count = *(td->seq_count); + } + + if (td->data != NULL) { + cyaml_free(td->config, td->schema, *(td->data), seq_count); + } +} + +/** + * Test loading a positive signed integer. + * + * \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_int_pos( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int value = 90; + static const unsigned char yaml[] = + "test_int: 90\n"; + struct target_struct { + int test_value_int; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("test_int", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_int), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_int != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a negative signed integer. + * + * \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_int_neg( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int value = -77; + static const unsigned char yaml[] = + "test_int: -77\n"; + struct target_struct { + int test_value_int; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("test_int", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_int), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_int != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a pointer to a positive signed integer. + * + * \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_int_pos_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int value = 90; + static const unsigned char yaml[] = + "test_int: 90\n"; + struct target_struct { + int *test_value_int; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT_PTR("test_int", CYAML_FLAG_POINTER, + struct target_struct, test_value_int), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_int != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a pointer to a negative signed integer. + * + * \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_int_neg_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int value = -77; + static const unsigned char yaml[] = + "test_int: -77\n"; + struct target_struct { + int *test_value_int; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT_PTR("test_int", CYAML_FLAG_POINTER, + struct target_struct, test_value_int), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_int != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading an unsigned integer. + * + * \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_uint( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + unsigned value = 9999; + static const unsigned char yaml[] = + "test_uint: 9999\n"; + struct target_struct { + unsigned test_value_uint; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("test_uint", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_uint), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_uint != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a pointer to an unsigned integer. + * + * \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_uint_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + unsigned value = 9999; + static const unsigned char yaml[] = + "test_uint: 9999\n"; + struct target_struct { + unsigned *test_value_uint; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT_PTR("test_uint", CYAML_FLAG_POINTER, + struct target_struct, test_value_uint), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_uint != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a floating point value as a float. + * + * \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_float( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + float value = 3.14159f; + static const unsigned char yaml[] = + "test_fp: 3.14159\n"; + struct target_struct { + float test_value_fp; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT("test_fp", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_fp), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_fp != value) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %f, got: %f", + value, data_tgt->test_value_fp); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a floating point value as a pointer to float. + * + * \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_float_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + float value = 3.14159f; + static const unsigned char yaml[] = + "test_fp: 3.14159\n"; + struct target_struct { + float *test_value_fp; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT_PTR("test_fp", CYAML_FLAG_POINTER, + struct target_struct, test_value_fp), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_fp != value) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %lf, got: %lf", + value, data_tgt->test_value_fp); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a floating point value as a double. + * + * \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_double( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + double value = 3.14159; + static const unsigned char yaml[] = + "test_fp: 3.14159\n"; + struct target_struct { + double test_value_fp; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT("test_fp", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_fp), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_fp != value) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %lf, got: %lf", + value, data_tgt->test_value_fp); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a floating point value as a pointer to double. + * + * \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_double_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + double value = 3.14159; + static const unsigned char yaml[] = + "test_fp: 3.14159\n"; + struct target_struct { + double *test_value_fp; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT_PTR("test_fp", CYAML_FLAG_POINTER, + struct target_struct, test_value_fp), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_fp != value) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %lf, got: %lf", + value, data_tgt->test_value_fp); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a boolean value (true). + * + * \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_bool_true( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + bool value = true; + static const unsigned char yaml[] = + "test_bool: true\n"; + struct target_struct { + unsigned test_value_bool; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BOOL("test_bool", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_bool), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_bool != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a boolean value (false). + * + * \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_bool_false( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + bool value = false; + static const unsigned char yaml[] = + "test_bool: false\n"; + struct target_struct { + unsigned test_value_bool; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BOOL("test_bool", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_bool), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_bool != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a pointer to a boolean value (true). + * + * \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_bool_true_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + bool value = true; + static const unsigned char yaml[] = + "test_bool: true\n"; + struct target_struct { + unsigned *test_value_bool; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BOOL_PTR("test_bool", CYAML_FLAG_POINTER, + struct target_struct, test_value_bool), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_bool != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a pointer to a boolean value (false). + * + * \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_bool_false_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + bool value = false; + static const unsigned char yaml[] = + "test_bool: false\n"; + struct target_struct { + unsigned *test_value_bool; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BOOL_PTR("test_bool", CYAML_FLAG_POINTER, + struct target_struct, test_value_bool), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_bool != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading an enumeration. + * + * \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_enum( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_enum { + TEST_ENUM_FIRST, + TEST_ENUM_SECOND, + TEST_ENUM_THIRD, + TEST_ENUM__COUNT, + } value = TEST_ENUM_SECOND; + static const cyaml_strval_t strings[TEST_ENUM__COUNT] = { + [TEST_ENUM_FIRST] = { "first", 0 }, + [TEST_ENUM_SECOND] = { "second", 1 }, + [TEST_ENUM_THIRD] = { "third", 2 }, + }; + static const unsigned char yaml[] = + "test_enum: second\n"; + struct target_struct { + enum test_enum test_value_enum; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_ENUM("test_enum", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_enum, + strings, TEST_ENUM__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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_enum != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a pointer to an enumeration. + * + * \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_enum_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_enum { + TEST_ENUM_FIRST, + TEST_ENUM_SECOND, + TEST_ENUM_THIRD, + TEST_ENUM__COUNT, + } value = TEST_ENUM_SECOND; + static const cyaml_strval_t strings[TEST_ENUM__COUNT] = { + [TEST_ENUM_FIRST] = { "first", 0 }, + [TEST_ENUM_SECOND] = { "second", 1 }, + [TEST_ENUM_THIRD] = { "third", 2 }, + }; + static const unsigned char yaml[] = + "test_enum: second\n"; + struct target_struct { + enum test_enum *test_value_enum; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_ENUM_PTR("test_enum", CYAML_FLAG_POINTER, + struct target_struct, test_value_enum, + strings, TEST_ENUM__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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_enum != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sparse enumeration. + * + * \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_enum_sparse( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_enum { + TEST_ENUM_FIRST = 3, + TEST_ENUM_SECOND = 77, + TEST_ENUM_THIRD = 183, + TEST_ENUM_FOURTH = 9900, + } value = TEST_ENUM_SECOND; + static const cyaml_strval_t strings[] = { + { "first", TEST_ENUM_FIRST }, + { "second", TEST_ENUM_SECOND }, + { "third", TEST_ENUM_THIRD }, + { "fourth", TEST_ENUM_FOURTH }, + }; + static const unsigned char yaml[] = + "test_enum: second\n"; + struct target_struct { + enum test_enum test_value_enum; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_ENUM("test_enum", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_enum, + strings, CYAML_ARRAY_LEN(strings)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_enum != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a string 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_load_mapping_entry_string( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *value = "Hello World!"; + static const unsigned char yaml[] = + "test_string: Hello World!\n"; + struct target_struct { + char test_value_string[50]; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING("test_string", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_string, 0), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (strcmp(data_tgt->test_value_string, value) != 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_tgt->test_value_string); + for (unsigned i = 0; i < sizeof(data_tgt->test_value_string); 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. + * + * \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_string_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *value = "null"; + static const unsigned char yaml[] = + "test_string: null\n"; + struct target_struct { + char * test_value_string; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("test_string", CYAML_FLAG_POINTER, + struct target_struct, test_value_string, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (strcmp(data_tgt->test_value_string, value) != 0) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading an empty string to an allocated char pointer. + * + * \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_string_ptr_empty( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *value = ""; + static const unsigned char yaml[] = + "test_string:\n"; + struct target_struct { + char * test_value_string; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("test_string", CYAML_FLAG_POINTER, + struct target_struct, test_value_string, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (strcmp(data_tgt->test_value_string, value) != 0) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a null string to an allocated nullable char pointer. + * + * \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_string_ptr_null_str( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *value = NULL; + static const unsigned char yaml[] = + "test_string: null\n"; + struct target_struct { + char * test_value_string; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("test_string", + CYAML_FLAG_POINTER_NULL_STR, + struct target_struct, test_value_string, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_string != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading an empty string to an allocated nullable char pointer. + * + * \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_string_ptr_null_empty( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *value = NULL; + static const unsigned char yaml[] = + "test_string:\n"; + struct target_struct { + char * test_value_string; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("test_string", CYAML_FLAG_POINTER_NULL, + struct target_struct, test_value_string, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_string != value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading an ignored value with descendants. + * + * \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_ignore_deep( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "ignore:\n" + " foo: bar\n" + " bar:\n" + " - 1\n" + " - 2\n"; + struct target_struct { + bool foo; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_IGNORE("ignore", CYAML_FLAG_DEFAULT), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->foo != false) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading an ignored 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_ignore_scalar( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "ignore: foo\n"; + struct target_struct { + bool foo; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_IGNORE("ignore", CYAML_FLAG_DEFAULT), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->foo != false) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a flag word. + * + * \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_flags( + 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), + } value = TEST_FLAGS_SECOND | TEST_FLAGS_FIFTH | 1024; + static const cyaml_strval_t strings[] = { + { "none", TEST_FLAGS_NONE }, + { "first", TEST_FLAGS_FIRST }, + { "second", TEST_FLAGS_SECOND }, + { "third", TEST_FLAGS_THIRD }, + { "fourth", TEST_FLAGS_FOURTH }, + { "fifth", TEST_FLAGS_FIFTH }, + { "sixth", TEST_FLAGS_SIXTH }, + }; + static const unsigned char yaml[] = + "test_flags:\n" + " - second\n" + " - fifth\n" + " - 1024\n"; + struct target_struct { + enum test_flags test_value_flags; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("test_flags", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_flags, + strings, CYAML_ARRAY_LEN(strings)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_flags != value) { + return ttest_fail(&tc, "Incorrect value: " + "expected: 0x%x, got: 0x%x\n", + value, data_tgt->test_value_flags); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a pointer to a flag word. + * + * \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_flags_ptr( + 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), + } value = TEST_FLAGS_SECOND | TEST_FLAGS_FIFTH | 1024; + static const cyaml_strval_t strings[] = { + { "none", TEST_FLAGS_NONE }, + { "first", TEST_FLAGS_FIRST }, + { "second", TEST_FLAGS_SECOND }, + { "third", TEST_FLAGS_THIRD }, + { "fourth", TEST_FLAGS_FOURTH }, + { "fifth", TEST_FLAGS_FIFTH }, + { "sixth", TEST_FLAGS_SIXTH }, + }; + static const unsigned char yaml[] = + "test_flags:\n" + " - second\n" + " - fifth\n" + " - 1024\n"; + struct target_struct { + enum test_flags *test_value_flags; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS_PTR("test_flags", CYAML_FLAG_POINTER, + struct target_struct, test_value_flags, + strings, CYAML_ARRAY_LEN(strings)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_flags != value) { + return ttest_fail(&tc, "Incorrect value: " + "expected: 0x%x, got: 0x%x\n", + value, data_tgt->test_value_flags); + } + + return ttest_pass(&tc); +} + +/** + * Test loading an empty flag word. + * + * \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_flags_empty( + 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), + } value = TEST_FLAGS_NONE; + static const cyaml_strval_t strings[] = { + { "none", TEST_FLAGS_NONE }, + { "first", TEST_FLAGS_FIRST }, + { "second", TEST_FLAGS_SECOND }, + { "third", TEST_FLAGS_THIRD }, + { "fourth", TEST_FLAGS_FOURTH }, + { "fifth", TEST_FLAGS_FIFTH }, + { "sixth", TEST_FLAGS_SIXTH }, + }; + static const unsigned char yaml[] = + "test_flags: []\n"; + struct target_struct { + enum test_flags test_value_flags; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("test_flags", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_flags, + strings, CYAML_ARRAY_LEN(strings)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_flags != value) { + return ttest_fail(&tc, "Incorrect value: " + "expected: 0x%x, got: 0x%x\n", + value, data_tgt->test_value_flags); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sparse flag word. + * + * \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_flags_sparse( + 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 << 4), + TEST_FLAGS_THIRD = (1 << 7), + TEST_FLAGS_FOURTH = (1 << 11), + TEST_FLAGS_FIFTH = (1 << 14), + TEST_FLAGS_SIXTH = (1 << 20), + } value = TEST_FLAGS_SECOND | TEST_FLAGS_FIFTH; + static const cyaml_strval_t strings[] = { + { "none", TEST_FLAGS_NONE }, + { "first", TEST_FLAGS_FIRST }, + { "second", TEST_FLAGS_SECOND }, + { "third", TEST_FLAGS_THIRD }, + { "fourth", TEST_FLAGS_FOURTH }, + { "fifth", TEST_FLAGS_FIFTH }, + { "sixth", TEST_FLAGS_SIXTH }, + }; + static const unsigned char yaml[] = + "test_flags:\n" + " - second\n" + " - fifth\n"; + struct target_struct { + enum test_flags test_value_flags; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("test_flags", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_flags, + strings, CYAML_ARRAY_LEN(strings)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_flags != value) { + return ttest_fail(&tc, "Incorrect value: " + "expected: 0x%x, got: 0x%x\n", + value, data_tgt->test_value_flags); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a bitfield. + * + * \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_bitfield( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + uint64_t value = 0xFFFFFFFFFFFFFFFFu; + static const cyaml_bitdef_t bitvals[] = { + { .name = "a", .offset = 0, .bits = 3 }, + { .name = "b", .offset = 3, .bits = 7 }, + { .name = "c", .offset = 10, .bits = 32 }, + { .name = "d", .offset = 42, .bits = 8 }, + { .name = "e", .offset = 50, .bits = 14 }, + }; + static const unsigned char yaml[] = + "test_bitfield:\n" + " a: 0x7\n" + " b: 0x7f\n" + " c: 0xffffffff\n" + " d: 0xff\n" + " e: 0x3fff\n"; + struct target_struct { + uint64_t test_value_bitfield; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BITFIELD("test_bitfield", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_bitfield, + bitvals, CYAML_ARRAY_LEN(bitvals)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_bitfield != value) { + return ttest_fail(&tc, "Incorrect value: " + "expected: 0x%x, got: 0x%x\n", + value, data_tgt->test_value_bitfield); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a pointer to a bitfield. + * + * \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_bitfield_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + uint64_t value = 0xFFFFFFFFFFFFFFFFu; + static const cyaml_bitdef_t bitvals[] = { + { .name = "a", .offset = 0, .bits = 3 }, + { .name = "b", .offset = 3, .bits = 7 }, + { .name = "c", .offset = 10, .bits = 32 }, + { .name = "d", .offset = 42, .bits = 8 }, + { .name = "e", .offset = 50, .bits = 14 }, + }; + static const unsigned char yaml[] = + "test_bitfield:\n" + " a: 0x7\n" + " b: 0x7f\n" + " c: 0xffffffff\n" + " d: 0xff\n" + " e: 0x3fff\n"; + struct target_struct { + uint64_t *test_value_bitfield; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BITFIELD_PTR("test_bitfield", CYAML_FLAG_POINTER, + struct target_struct, test_value_bitfield, + bitvals, CYAML_ARRAY_LEN(bitvals)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->test_value_bitfield != value) { + return ttest_fail(&tc, "Incorrect value: " + "expected: 0x%x, got: 0x%x\n", + value, data_tgt->test_value_bitfield); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping, to an internal structure. + * + * \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_mapping( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct value_s { + short a; + long b; + } value; + static const unsigned char yaml[] = + "mapping:\n" + " a: 123\n" + " b: 9999\n"; + struct target_struct { + struct value_s test_value_mapping; + } *data_tgt = NULL; + static const struct cyaml_schema_field test_mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, struct value_s, a), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, struct value_s, b), + CYAML_FIELD_END + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_MAPPING("mapping", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_mapping, + test_mapping_schema), + 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; + + memset(&value, 0, sizeof(value)); + value.a = 123; + value.b = 9999; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (memcmp(&data_tgt->test_value_mapping, &value, sizeof(value)) != 0) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping, to an allocated structure. + * + * \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_mapping_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct value_s { + short a; + long b; + } value; + static const unsigned char yaml[] = + "mapping:\n" + " a: 123\n" + " b: 9999\n"; + struct target_struct { + struct value_s *test_value_mapping; + } *data_tgt = NULL; + static const struct cyaml_schema_field test_mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, struct value_s, a), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, struct value_s, b), + CYAML_FIELD_END + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_MAPPING_PTR("mapping", CYAML_FLAG_POINTER, + struct target_struct, test_value_mapping, + test_mapping_schema), + 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; + + memset(&value, 0, sizeof(value)); + value.a = 123; + value.b = 9999; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (memcmp(data_tgt->test_value_mapping, &value, sizeof(value)) != 0) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of integers into an int[]. + * + * \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_sequence_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[] = { 1, 1, 2, 3, 5, 8 }; + static const unsigned char yaml[] = + "sequence:\n" + " - 1\n" + " - 1\n" + " - 2\n" + " - 3\n" + " - 5\n" + " - 8\n"; + struct target_struct { + int seq[CYAML_ARRAY_LEN(ref)]; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data_tgt->seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of enums into an enum test_enum[]. + * + * \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_sequence_enum( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_enum { + TEST_ENUM_FIRST, + TEST_ENUM_SECOND, + TEST_ENUM_THIRD, + TEST_ENUM__COUNT, + } ref[] = { TEST_ENUM_FIRST, TEST_ENUM_SECOND, TEST_ENUM_THIRD }; + static const cyaml_strval_t strings[TEST_ENUM__COUNT] = { + [TEST_ENUM_FIRST] = { "first", 0 }, + [TEST_ENUM_SECOND] = { "second", 1 }, + [TEST_ENUM_THIRD] = { "third", 2 }, + }; + static const unsigned char yaml[] = + "sequence:\n" + " - first\n" + " - second\n" + " - third\n"; + struct target_struct { + enum test_enum seq[CYAML_ARRAY_LEN(ref)]; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_ENUM(CYAML_FLAG_DEFAULT, + *(data_tgt->seq), strings, TEST_ENUM__COUNT), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of unsigned integers into an unsigned[]. + * + * \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_sequence_uint( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + unsigned ref[] = { 99999, 99998, 99997, 99996, 99995, 99994 }; + static const unsigned char yaml[] = + "sequence:\n" + " - 99999\n" + " - 99998\n" + " - 99997\n" + " - 99996\n" + " - 99995\n" + " - 99994\n"; + struct target_struct { + unsigned seq[CYAML_ARRAY_LEN(ref)]; + 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[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of boolean values into an bool[]. + * + * \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_sequence_bool( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + bool ref[] = { true, false, true, false, true, false, true, false }; + static const unsigned char yaml[] = + "sequence:\n" + " - true\n" + " - false\n" + " - yes\n" + " - no\n" + " - enable\n" + " - disable\n" + " - 1\n" + " - 0\n"; + struct target_struct { + bool seq[CYAML_ARRAY_LEN(ref)]; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_BOOL(CYAML_FLAG_DEFAULT, *(data_tgt->seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of flag sequences into an array of flag words. + * + * \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_sequence_flags( + 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), + } ref[3] = { + TEST_FLAGS_SECOND | TEST_FLAGS_FIFTH | 1024, + TEST_FLAGS_FIRST, + TEST_FLAGS_FOURTH | TEST_FLAGS_SIXTH + }; + #define TEST_FLAGS__COUNT 6 + static const cyaml_strval_t strings[TEST_FLAGS__COUNT] = { + { "first", (1 << 0) }, + { "second", (1 << 1) }, + { "third", (1 << 2) }, + { "fourth", (1 << 3) }, + { "fifth", (1 << 4) }, + { "sixth", (1 << 5) }, + }; + static const unsigned char yaml[] = + "sequence:\n" + " - - second\n" + " - fifth\n" + " - 1024\n" + " - - first\n" + " - - fourth\n" + " - sixth\n"; + struct target_struct { + enum test_flags seq[3]; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_FLAGS(CYAML_FLAG_DEFAULT, *(data_tgt->seq), + strings, TEST_FLAGS__COUNT), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(data_tgt->seq)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of strings into an array of char[7]. + * + * \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_sequence_string( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *ref[] = { + "This", + "is", + "merely", + "a", + "test", + }; + static const unsigned char yaml[] = + "sequence:\n" + " - This\n" + " - is\n" + " - merely\n" + " - a\n" + " - test\n"; + struct target_struct { + char seq[5][7]; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_DEFAULT, *(data_tgt->seq), 0, 6), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (strcmp(data_tgt->seq[i], ref[i]) != 0) { + return ttest_fail(&tc, "Incorrect value (i=%u)", i); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of strings into an array of allocated strings. + * + * \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_sequence_string_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *ref[] = { + "This", + "is", + "merely", + "a", + "test", + }; + static const unsigned char yaml[] = + "sequence:\n" + " - This\n" + " - is\n" + " - merely\n" + " - a\n" + " - test\n"; + struct target_struct { + char *seq[5]; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_POINTER, *(data_tgt->seq), + 0, CYAML_UNLIMITED), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (strcmp(data_tgt->seq[i], ref[i]) != 0) { + return ttest_fail(&tc, "Incorrect value (i=%u)", i); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of mappings into an array of structures. + * + * \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_sequence_mapping( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct value_s { + short a; + long b; + } ref[3]; + static const unsigned char yaml[] = + "sequence:\n" + " - a: 123\n" + " b: 9999\n" + " - a: 4000\n" + " b: 62000\n" + " - a: 1\n" + " b: 765\n"; + struct target_struct { + struct value_s seq[3]; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_field test_mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, struct value_s, a), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, struct value_s, b), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT, + struct value_s, test_mapping_schema), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(data_tgt->seq)), + 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; + + memset(ref, 0, sizeof(ref)); + ref[0].a = 123; + ref[0].b = 9999; + ref[1].a = 4000; + ref[1].b = 62000; + ref[2].a = 1; + ref[2].b = 765; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + if (memcmp(data_tgt->seq, ref, sizeof(ref)) != 0) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of mappings into an array of pointers to structs. + * + * \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_sequence_mapping_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct value_s { + short a; + long b; + } ref[3] = { + { .a = 123, .b = 9999, }, + { .a = 4000, .b = 62000, }, + { .a = 1, .b = 765, }, + }; + static const unsigned char yaml[] = + "sequence:\n" + " - a: 123\n" + " b: 9999\n" + " - a: 4000\n" + " b: 62000\n" + " - a: 1\n" + " b: 765\n"; + struct target_struct { + struct value_s *seq[3]; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_field test_mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, struct value_s, a), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, struct value_s, b), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct value_s, test_mapping_schema), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(data_tgt->seq)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (ref[i].a != data_tgt->seq[i]->a) { + return ttest_fail(&tc, "Incorrect value"); + } + if (ref[i].b != data_tgt->seq[i]->b) { + return ttest_fail(&tc, "Incorrect value"); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of sequences of int into int[4][3]. + * + * \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_sequence_sequence_fixed_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[4][3] = { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 }, + { 10, 11, 12 }, + }; + static const unsigned char yaml[] = + "sequence:\n" + " - [ 1, 2, 3 ]\n" + " - [ 4, 5, 6 ]\n" + " - [ 7, 8, 9 ]\n" + " - [ 10, 11, 12 ]\n"; + struct target_struct { + int seq[4][3]; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema_int = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, **(data_tgt->seq)), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_SEQUENCE_FIXED( + CYAML_FLAG_DEFAULT, **(data_tgt->seq), + &entry_schema_int, CYAML_ARRAY_LEN(*ref)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count: " + "expected %u, got %u", + CYAML_ARRAY_LEN(ref), data_tgt->seq_count); + } + + for (unsigned j = 0; j < CYAML_ARRAY_LEN(ref); j++) { + for (unsigned i = 0; i < CYAML_ARRAY_LEN(*ref); i++) { + if (data_tgt->seq[j][i] != ref[j][i]) { + return ttest_fail(&tc, + "Incorrect value " + "(i=%u, j=%u): " + "got: %i, expected: %i", i, j, + data_tgt->seq[j][i], ref[j][i]); + } + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of sequences of int into int*[4]. + * + * \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_sequence_sequence_fixed_ptr_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[4][3] = { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 }, + { 10, 11, 12 }, + }; + static const unsigned char yaml[] = + "sequence:\n" + " - [ 1, 2, 3 ]\n" + " - [ 4, 5, 6 ]\n" + " - [ 7, 8, 9 ]\n" + " - [ 10, 11, 12 ]\n"; + struct target_struct { + int *seq[4]; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema_int = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, **(data_tgt->seq)), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_SEQUENCE_FIXED( + CYAML_FLAG_POINTER, **(data_tgt->seq), + &entry_schema_int, CYAML_ARRAY_LEN(*ref)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count: " + "expected %u, got %u", + CYAML_ARRAY_LEN(ref), data_tgt->seq_count); + } + + for (unsigned j = 0; j < CYAML_ARRAY_LEN(ref); j++) { + for (unsigned i = 0; i < CYAML_ARRAY_LEN(*ref); i++) { + if (data_tgt->seq[j][i] != ref[j][i]) { + return ttest_fail(&tc, + "Incorrect value " + "(i=%u, j=%u): " + "got: %i, expected: %i", i, j, + data_tgt->seq[j][i], ref[j][i]); + } + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of sequences of int into one-dimensional int[]. + * + * \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_sequence_sequence_fixed_flat_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[4][3] = { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 }, + { 10, 11, 12 }, + }; + static const unsigned char yaml[] = + "sequence:\n" + " - [ 1, 2, 3 ]\n" + " - [ 4, 5, 6 ]\n" + " - [ 7, 8, 9 ]\n" + " - [ 10, 11, 12 ]\n"; + struct target_struct { + int seq[12]; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema_int = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_SEQUENCE_FIXED( + CYAML_FLAG_DEFAULT, int, + &entry_schema_int, CYAML_ARRAY_LEN(*ref)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "sequence", + .value = { + .type = CYAML_SEQUENCE, + .flags = CYAML_FLAG_DEFAULT, + .data_size = sizeof(int[3]), + .sequence = { + .entry = &entry_schema, + .min = 0, + .max = CYAML_UNLIMITED, + } + }, + .data_offset = offsetof(struct target_struct, seq), + .count_size = sizeof(data_tgt->seq_count), + .count_offset = offsetof(struct target_struct, 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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)); + } + + /* Note: count is count of entries of the outer sequence entries, + * so, 4, not 12. */ + if (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count: " + "expected %u, got %u", + CYAML_ARRAY_LEN(ref), data_tgt->seq_count); + } + + for (unsigned j = 0; j < CYAML_ARRAY_LEN(ref); j++) { + for (unsigned i = 0; i < CYAML_ARRAY_LEN(*ref); i++) { + if (data_tgt->seq[j * CYAML_ARRAY_LEN(*ref) + i] != ref[j][i]) { + return ttest_fail(&tc, + "Incorrect value " + "(i=%u, j=%u): " + "got: %i, expected: %i", i, j, + data_tgt->seq[j * CYAML_ARRAY_LEN(*ref) + i], + ref[j][i]); + } + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of integers to allocated int* 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_load_mapping_entry_sequence_ptr_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[] = { 1, 1, 2, 3, 5, 8 }; + static const unsigned char yaml[] = + "sequence:\n" + " - 1\n" + " - 1\n" + " - 2\n" + " - 3\n" + " - 5\n" + " - 8\n"; + struct target_struct { + int *seq; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data_tgt->seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of enums to allocated enum test_enum* 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_load_mapping_entry_sequence_ptr_enum( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_enum { + TEST_ENUM_FIRST, + TEST_ENUM_SECOND, + TEST_ENUM_THIRD, + TEST_ENUM__COUNT, + } ref[] = { TEST_ENUM_FIRST, TEST_ENUM_SECOND, TEST_ENUM_THIRD }; + static const cyaml_strval_t strings[TEST_ENUM__COUNT] = { + [TEST_ENUM_FIRST] = { "first", 0 }, + [TEST_ENUM_SECOND] = { "second", 1 }, + [TEST_ENUM_THIRD] = { "third", 2 }, + }; + static const unsigned char yaml[] = + "sequence:\n" + " - first\n" + " - second\n" + " - third\n"; + struct target_struct { + enum test_enum *seq; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_ENUM(CYAML_FLAG_DEFAULT, + *(data_tgt->seq), strings, TEST_ENUM__COUNT), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of unsigned integers to allocated unsigned* 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_load_mapping_entry_sequence_ptr_uint( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + unsigned ref[] = { 99999, 99998, 99997, 99996, 99995, 99994 }; + static const unsigned char yaml[] = + "sequence:\n" + " - 99999\n" + " - 99998\n" + " - 99997\n" + " - 99996\n" + " - 99995\n" + " - 99994\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[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of boolean values to allocated bool* 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_load_mapping_entry_sequence_ptr_bool( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + bool ref[] = { true, false, true, false, true, false, true, false }; + static const unsigned char yaml[] = + "sequence:\n" + " - true\n" + " - false\n" + " - yes\n" + " - no\n" + " - enable\n" + " - disable\n" + " - 1\n" + " - 0\n"; + struct target_struct { + bool *seq; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_BOOL(CYAML_FLAG_DEFAULT, *(data_tgt->seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of flag sequences to allocated flag words 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_load_mapping_entry_sequence_ptr_flags( + 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), + } ref[3] = { + TEST_FLAGS_SECOND | TEST_FLAGS_FIFTH | 1024, + TEST_FLAGS_FIRST, + TEST_FLAGS_FOURTH | TEST_FLAGS_SIXTH + }; + #define TEST_FLAGS__COUNT 6 + static const cyaml_strval_t strings[TEST_FLAGS__COUNT] = { + { "first", (1 << 0) }, + { "second", (1 << 1) }, + { "third", (1 << 2) }, + { "fourth", (1 << 3) }, + { "fifth", (1 << 4) }, + { "sixth", (1 << 5) }, + }; + static const unsigned char yaml[] = + "sequence:\n" + " - - second\n" + " - fifth\n" + " - 1024\n" + " - - first\n" + " - - fourth\n" + " - sixth\n"; + struct target_struct { + enum test_flags *seq; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_FLAGS(CYAML_FLAG_DEFAULT, *(data_tgt->seq), + strings, TEST_FLAGS__COUNT), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of strings to allocated array of char[7]. + * + * \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_sequence_ptr_string( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *ref[] = { + "This", + "is", + "merely", + "a", + "test", + }; + static const unsigned char yaml[] = + "sequence:\n" + " - This\n" + " - is\n" + " - merely\n" + " - a\n" + " - test\n"; + struct target_struct { + char (*seq)[7]; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_DEFAULT, *(data_tgt->seq), 0, 6), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (strcmp(data_tgt->seq[i], ref[i]) != 0) { + return ttest_fail(&tc, "Incorrect value (i=%u)", i); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of strings to allocated array of + * allocated strings. + * + * \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_sequence_ptr_string_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *ref[] = { + "This", + "is", + "merely", + "a", + "test", + }; + static const unsigned char yaml[] = + "sequence:\n" + " - This\n" + " - is\n" + " - merely\n" + " - a\n" + " - test\n"; + struct target_struct { + char **seq; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_POINTER, *(data_tgt->seq), + 0, CYAML_UNLIMITED), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (strcmp(data_tgt->seq[i], ref[i]) != 0) { + return ttest_fail(&tc, "Incorrect value (i=%u)", i); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of mappings to allocated array mapping structs. + * + * \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_sequence_ptr_mapping( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct value_s { + short a; + long b; + } ref[3] = { + { .a = 123, .b = 9999, }, + { .a = 4000, .b = 62000, }, + { .a = 1, .b = 765, }, + }; + static const unsigned char yaml[] = + "sequence:\n" + " - a: 123\n" + " b: 9999\n" + " - a: 4000\n" + " b: 62000\n" + " - a: 1\n" + " b: 765\n"; + struct target_struct { + struct value_s *seq; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_field test_mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, struct value_s, a), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, struct value_s, b), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT, + struct value_s, test_mapping_schema), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (ref[i].a != data_tgt->seq[i].a) { + return ttest_fail(&tc, "Incorrect value"); + } + if (ref[i].b != data_tgt->seq[i].b) { + return ttest_fail(&tc, "Incorrect value"); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of mappings to allocated array of pointers to + * mapping structs. + * + * \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_sequence_ptr_mapping_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct value_s { + short a; + long b; + } ref[3] = { + { .a = 123, .b = 9999, }, + { .a = 4000, .b = 62000, }, + { .a = 1, .b = 765, }, + }; + static const unsigned char yaml[] = + "sequence:\n" + " - a: 123\n" + " b: 9999\n" + " - a: 4000\n" + " b: 62000\n" + " - a: 1\n" + " b: 765\n"; + struct target_struct { + struct value_s **seq; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_field test_mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, struct value_s, a), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, struct value_s, b), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct value_s, test_mapping_schema), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (ref[i].a != data_tgt->seq[i]->a) { + return ttest_fail(&tc, "Incorrect value"); + } + if (ref[i].b != data_tgt->seq[i]->b) { + return ttest_fail(&tc, "Incorrect value"); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of sequences of integers to allocated array + * of int[3]. + * + * \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_sequence_ptr_sequence_fixed_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[4][3] = { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 }, + { 10, 11, 12 }, + }; + static const unsigned char yaml[] = + "sequence:\n" + " - [ 1, 2, 3 ]\n" + " - [ 4, 5, 6 ]\n" + " - [ 7, 8, 9 ]\n" + " - [ 10, 11, 12 ]\n"; + struct target_struct { + int (*seq)[3]; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema_int = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_SEQUENCE_FIXED( + CYAML_FLAG_DEFAULT, int, + &entry_schema_int, CYAML_ARRAY_LEN(*ref)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count: " + "expected %u, got %u", + CYAML_ARRAY_LEN(ref), data_tgt->seq_count); + } + + for (unsigned j = 0; j < CYAML_ARRAY_LEN(ref); j++) { + for (unsigned i = 0; i < CYAML_ARRAY_LEN(*ref); i++) { + if (data_tgt->seq[j][i] != ref[j][i]) { + return ttest_fail(&tc, + "Incorrect value " + "(i=%u, j=%u): " + "got: %i, expected: %i", i, j, + data_tgt->seq[j][i], ref[j][i]); + } + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of sequences of integers to allocated array + * of allocated arrays of integers. + * + * \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_sequence_ptr_sequence_fixed_ptr_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[4][3] = { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 }, + { 10, 11, 12 }, + }; + static const unsigned char yaml[] = + "sequence:\n" + " - [ 1, 2, 3 ]\n" + " - [ 4, 5, 6 ]\n" + " - [ 7, 8, 9 ]\n" + " - [ 10, 11, 12 ]\n"; + struct target_struct { + int **seq; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema_int = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_SEQUENCE_FIXED( + CYAML_FLAG_POINTER, int, + &entry_schema_int, CYAML_ARRAY_LEN(*ref)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count: " + "expected %u, got %u", + CYAML_ARRAY_LEN(ref), data_tgt->seq_count); + } + + for (unsigned j = 0; j < CYAML_ARRAY_LEN(ref); j++) { + for (unsigned i = 0; i < CYAML_ARRAY_LEN(*ref); i++) { + if (data_tgt->seq[j][i] != ref[j][i]) { + return ttest_fail(&tc, + "Incorrect value " + "(i=%u, j=%u): " + "got: %i, expected: %i", i, j, + data_tgt->seq[j][i], ref[j][i]); + } + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of sequences of integers a one-dimensional allocated + * array of integers. + * + * \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_sequence_ptr_sequence_fixed_flat_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[4][3] = { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 }, + { 10, 11, 12 }, + }; + static const unsigned char yaml[] = + "sequence:\n" + " - [ 1, 2, 3 ]\n" + " - [ 4, 5, 6 ]\n" + " - [ 7, 8, 9 ]\n" + " - [ 10, 11, 12 ]\n"; + struct target_struct { + int *seq; + uint32_t seq_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema_int = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_SEQUENCE_FIXED( + CYAML_FLAG_DEFAULT, int, + &entry_schema_int, CYAML_ARRAY_LEN(*ref)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "sequence", + .value = { + .type = CYAML_SEQUENCE, + .flags = CYAML_FLAG_POINTER, + .data_size = sizeof(int[3]), + .sequence = { + .entry = &entry_schema, + .min = 0, + .max = CYAML_UNLIMITED, + } + }, + .data_offset = offsetof(struct target_struct, seq), + .count_size = sizeof(data_tgt->seq_count), + .count_offset = offsetof(struct target_struct, 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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)); + } + + /* Note: count is count of entries of the outer sequence entries, + * so, 4, not 12. */ + if (CYAML_ARRAY_LEN(ref) != data_tgt->seq_count) { + return ttest_fail(&tc, "Incorrect sequence count: " + "expected %u, got %u", + CYAML_ARRAY_LEN(ref), data_tgt->seq_count); + } + + for (unsigned j = 0; j < CYAML_ARRAY_LEN(ref); j++) { + for (unsigned i = 0; i < CYAML_ARRAY_LEN(*ref); i++) { + if (data_tgt->seq[j * CYAML_ARRAY_LEN(*ref) + i] != ref[j][i]) { + return ttest_fail(&tc, + "Incorrect value " + "(i=%u, j=%u): " + "got: %i, expected: %i", i, j, + data_tgt->seq[j * CYAML_ARRAY_LEN(*ref) + i], + ref[j][i]); + } + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading sequence of pointers to integer values with NULLs. + * + * \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_sequence_null_str_values_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const int expected[] = { + 777, 6, 5, 4, 3, 2, 1, 0, + }; + static const bool expected_nulls[] = { + false, false, false, true, false, false, true, false, + }; + static const unsigned char yaml[] = + "- 777\n" + "- 6\n" + "- 5\n" + "- ~\n" + "- 3\n" + "- 2\n" + "- null\n" + "- 0\n"; + int **value = NULL; + unsigned count = 0; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_POINTER_NULL_STR, **value) + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, *value, + &entry_schema, 0, CYAML_UNLIMITED) + }; + test_data_t td = { + .data = (cyaml_data_t **) &value, + .seq_count = &count, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &value, &count); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (count != CYAML_ARRAY_LEN(expected)) { + return ttest_fail(&tc, "Unexpected sequence count."); + } + + if (value == NULL) { + return ttest_fail(&tc, "Data NULL on success."); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(expected); i ++) { + if (expected_nulls[i]) { + if (value[i] != NULL) { + return ttest_fail(&tc, "Expected NULL."); + } + continue; + } else { + if (value[i] == NULL) { + return ttest_fail(&tc, "Unexpected NULL."); + } + if ((*(value)[i]) != expected[i]) { + return ttest_fail(&tc, "Bad value."); + } + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading sequence of pointers to integer values with NULLs. + * + * \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_sequence_null_values_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const int expected[] = { + 777, 6, 5, 4, 3, 2, 1, 0, + }; + static const bool expected_nulls[] = { + false, false, false, true, false, false, true, false, + }; + static const unsigned char yaml[] = + "- 777\n" + "- 6\n" + "- 5\n" + "- \n" + "- 3\n" + "- 2\n" + "- \n" + "- 0\n"; + int **value = NULL; + unsigned count = 0; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_POINTER_NULL, **value) + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, *value, + &entry_schema, 0, CYAML_UNLIMITED) + }; + test_data_t td = { + .data = (cyaml_data_t **) &value, + .seq_count = &count, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &value, &count); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (count != CYAML_ARRAY_LEN(expected)) { + return ttest_fail(&tc, "Unexpected sequence count."); + } + + if (value == NULL) { + return ttest_fail(&tc, "Data NULL on success."); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(expected); i ++) { + if (expected_nulls[i]) { + if (value[i] != NULL) { + return ttest_fail(&tc, "Expected NULL."); + } + continue; + } else { + if (value[i] == NULL) { + return ttest_fail(&tc, "Unexpected NULL."); + } + if ((*(value)[i]) != expected[i]) { + return ttest_fail(&tc, "Bad value."); + } + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading sequence of pointers to integer values with NULLs. + * + * \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_sequence_null_str_values_uint( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const int expected[] = { + 7, 6, 5555, 4, 3, 2, 1, 0, + }; + static const bool expected_nulls[] = { + false, true, false, true, false, false, true, false, + }; + static const unsigned char yaml[] = + "- 7\n" + "- \n" + "- 5555\n" + "- Null\n" + "- 3\n" + "- 2\n" + "- NULL\n" + "- 0\n"; + int **value = NULL; + unsigned count = 0; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_POINTER_NULL_STR, + **value) + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, *value, + &entry_schema, 0, CYAML_UNLIMITED) + }; + test_data_t td = { + .data = (cyaml_data_t **) &value, + .seq_count = &count, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &value, &count); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (count != CYAML_ARRAY_LEN(expected)) { + return ttest_fail(&tc, "Unexpected sequence count."); + } + + if (value == NULL) { + return ttest_fail(&tc, "Data NULL on success."); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(expected); i ++) { + if (expected_nulls[i]) { + if (value[i] != NULL) { + return ttest_fail(&tc, "Expected NULL."); + } + continue; + } else { + if (value[i] == NULL) { + return ttest_fail(&tc, "Unexpected NULL."); + } + if ((*(value)[i]) != expected[i]) { + return ttest_fail(&tc, "Bad value."); + } + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of integers with 1 byte sequence count. + * + * \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_sequence_count_1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[] = { 1, 1, 2, 3, 5, 8 }; + static const unsigned char yaml[] = + "sequence:\n" + " - 1\n" + " - 1\n" + " - 2\n" + " - 3\n" + " - 5\n" + " - 8\n"; + struct target_struct { + int seq[CYAML_ARRAY_LEN(ref)]; + uint8_t seq_count[1]; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data_tgt->seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != cyaml_data_read(sizeof(data_tgt->seq_count), + data_tgt->seq_count, &err)) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of integers with 2 byte sequence count. + * + * \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_sequence_count_2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[] = { 1, 1, 2, 3, 5, 8 }; + static const unsigned char yaml[] = + "sequence:\n" + " - 1\n" + " - 1\n" + " - 2\n" + " - 3\n" + " - 5\n" + " - 8\n"; + struct target_struct { + int seq[CYAML_ARRAY_LEN(ref)]; + uint8_t seq_count[2]; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data_tgt->seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != cyaml_data_read(sizeof(data_tgt->seq_count), + data_tgt->seq_count, &err)) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of integers with 3 byte sequence count. + * + * \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_sequence_count_3( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[] = { 1, 1, 2, 3, 5, 8 }; + static const unsigned char yaml[] = + "sequence:\n" + " - 1\n" + " - 1\n" + " - 2\n" + " - 3\n" + " - 5\n" + " - 8\n"; + struct target_struct { + int seq[CYAML_ARRAY_LEN(ref)]; + uint8_t seq_count[3]; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data_tgt->seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != cyaml_data_read(sizeof(data_tgt->seq_count), + data_tgt->seq_count, &err)) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of integers with 4 byte sequence count. + * + * \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_sequence_count_4( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[] = { 1, 1, 2, 3, 5, 8 }; + static const unsigned char yaml[] = + "sequence:\n" + " - 1\n" + " - 1\n" + " - 2\n" + " - 3\n" + " - 5\n" + " - 8\n"; + struct target_struct { + int seq[CYAML_ARRAY_LEN(ref)]; + uint8_t seq_count[4]; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data_tgt->seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != cyaml_data_read(sizeof(data_tgt->seq_count), + data_tgt->seq_count, &err)) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of integers with 5 byte sequence count. + * + * \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_sequence_count_5( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[] = { 1, 1, 2, 3, 5, 8 }; + static const unsigned char yaml[] = + "sequence:\n" + " - 1\n" + " - 1\n" + " - 2\n" + " - 3\n" + " - 5\n" + " - 8\n"; + struct target_struct { + int seq[CYAML_ARRAY_LEN(ref)]; + uint8_t seq_count[5]; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data_tgt->seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != cyaml_data_read(sizeof(data_tgt->seq_count), + data_tgt->seq_count, &err)) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of integers with 6 byte sequence count. + * + * \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_sequence_count_6( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[] = { 1, 1, 2, 3, 5, 8 }; + static const unsigned char yaml[] = + "sequence:\n" + " - 1\n" + " - 1\n" + " - 2\n" + " - 3\n" + " - 5\n" + " - 8\n"; + struct target_struct { + int seq[CYAML_ARRAY_LEN(ref)]; + uint8_t seq_count[6]; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data_tgt->seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != cyaml_data_read(sizeof(data_tgt->seq_count), + data_tgt->seq_count, &err)) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of integers with 7 byte sequence count. + * + * \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_sequence_count_7( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[] = { 1, 1, 2, 3, 5, 8 }; + static const unsigned char yaml[] = + "sequence:\n" + " - 1\n" + " - 1\n" + " - 2\n" + " - 3\n" + " - 5\n" + " - 8\n"; + struct target_struct { + int seq[CYAML_ARRAY_LEN(ref)]; + uint8_t seq_count[7]; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data_tgt->seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != cyaml_data_read(sizeof(data_tgt->seq_count), + data_tgt->seq_count, &err)) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence of integers with 8 byte sequence count. + * + * \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_sequence_count_8( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + int ref[] = { 1, 1, 2, 3, 5, 8 }; + static const unsigned char yaml[] = + "sequence:\n" + " - 1\n" + " - 1\n" + " - 2\n" + " - 3\n" + " - 5\n" + " - 8\n"; + struct target_struct { + int seq[CYAML_ARRAY_LEN(ref)]; + uint8_t seq_count[8]; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data_tgt->seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != cyaml_data_read(sizeof(data_tgt->seq_count), + data_tgt->seq_count, &err)) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->seq[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->seq[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading with schema with scalar top level type. + * + * \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_schema_top_level_scalar( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "7\n"; + int *value = NULL; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_INT(CYAML_FLAG_POINTER, int) + }; + test_data_t td = { + .data = (cyaml_data_t **) &value, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &value, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (value == NULL) { + return ttest_fail(&tc, "Data NULL on success."); + } + + if (*value != 7) { + return ttest_fail(&tc, "Bad value."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading with schema with string top level type. + * + * \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_schema_top_level_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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &value, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (value == NULL) { + return ttest_fail(&tc, "Data NULL on success."); + } + + if (strcmp(value, "Hello") != 0) { + return ttest_fail(&tc, "Bad value."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading with schema with sequence_fixed top level type. + * + * \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_schema_top_level_sequence( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "- 7\n" + "- 6\n" + "- 5\n"; + int *value = NULL; + unsigned count = 0; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int) + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, int, + &entry_schema, 0, CYAML_UNLIMITED) + }; + test_data_t td = { + .data = (cyaml_data_t **) &value, + .seq_count = &count, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &value, &count); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (count != 3) { + return ttest_fail(&tc, "Unexpected sequence count."); + } + + if (value == NULL) { + return ttest_fail(&tc, "Data NULL on success."); + } + + if ((value[0] != 7) && (value[1] != 6) && (value[2] != 5)) { + return ttest_fail(&tc, "Bad value."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading with schema with sequence_fixed top level type. + * + * \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_schema_top_level_sequence_fixed( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char yaml[] = + "- 7\n" + "- 6\n" + "- 5\n"; + int *value = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int) + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE_FIXED(CYAML_FLAG_POINTER, int, + &entry_schema, 3) + }; + test_data_t td = { + .data = (cyaml_data_t **) &value, + .config = config, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema, + (cyaml_data_t **) &value, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (value == NULL) { + return ttest_fail(&tc, "Data NULL on success."); + } + + if ((value[0] != 7) && (value[1] != 6) && (value[2] != 5)) { + return ttest_fail(&tc, "Bad value."); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a stream with more than one document. + * + * \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_multiple_documents_ignored( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + signed char a; + } data = { + .a = 9, + }; + static const unsigned char yaml[] = + "a: 9\n" + "---\n" + "b: foo\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->a != data.a) { + return ttest_fail(&tc, "Incorrect value for entry a"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping multiple fields. + * + * \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_with_multiple_fields( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + signed char a; + short b; + int c; + long d; + long long e; + } data = { + .a = 9, + .b = 90, + .c = 900, + .d = 9000, + .e = 90000, + }; + static const unsigned char yaml[] = + "a: 9\n" + "b: 90\n" + "c: 900\n" + "d: 9000\n" + "e: 90000\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, + struct target_struct, a), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, + struct target_struct, b), + CYAML_FIELD_INT("c", CYAML_FLAG_DEFAULT, + struct target_struct, c), + CYAML_FIELD_INT("d", CYAML_FLAG_DEFAULT, + struct target_struct, d), + CYAML_FIELD_INT("e", CYAML_FLAG_DEFAULT, + struct target_struct, e), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->a != data.a) { + return ttest_fail(&tc, "Incorrect value for entry a"); + } + if (data_tgt->b != data.b) { + return ttest_fail(&tc, "Incorrect value for entry b"); + } + if (data_tgt->c != data.c) { + return ttest_fail(&tc, "Incorrect value for entry c"); + } + if (data_tgt->d != data.d) { + return ttest_fail(&tc, "Incorrect value for entry d"); + } + if (data_tgt->e != data.e) { + return ttest_fail(&tc, "Incorrect value for entry e"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping with optional fields. + * + * \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_with_optional_fields( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + long values[] = { 4, 3, 2, 1 }; + struct target_struct { + char *a; + char b[10]; + int c; + long d[4]; + long *e; + unsigned e_count; + char *f; + char *g; + char h[10]; + int i; + long j[4]; + long *k; + unsigned k_count; + } data = { + .a = "Hello", + .b = "World!", + .c = 0, + .d = { 0, 0, 0, 0 }, + .e = values, + .f = "Required!", + .g = NULL, + .h = "\0", + .i = 9876, + .j = { 1, 2, 3, 4 }, + .k = NULL, + }; + static const unsigned char yaml[] = + "a: Hello\n" + "b: World!\n" + "e: [ 4, 3, 2, 1 ]\n" + "f: Required!\n" + "i: 9876\n" + "j: [ 1, 2, 3, 4 ]\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_value sequence_entry = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, sizeof(long)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("a", + CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL, + struct target_struct, a, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING("b", CYAML_FLAG_OPTIONAL, + struct target_struct, b, 0), + CYAML_FIELD_INT("c", CYAML_FLAG_OPTIONAL, + struct target_struct, c), + CYAML_FIELD_SEQUENCE_FIXED("d", CYAML_FLAG_OPTIONAL, + struct target_struct, d, &sequence_entry, 4), + CYAML_FIELD_SEQUENCE("e", + CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL, + struct target_struct, e, &sequence_entry, + 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("f", + CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL, + struct target_struct, f, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("g", + CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL, + struct target_struct, g, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING("h", CYAML_FLAG_OPTIONAL, + struct target_struct, h, 0), + CYAML_FIELD_INT("i", CYAML_FLAG_OPTIONAL, + struct target_struct, i), + CYAML_FIELD_SEQUENCE_FIXED("j", CYAML_FLAG_OPTIONAL, + struct target_struct, j, &sequence_entry, 4), + CYAML_FIELD_SEQUENCE("k", + CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL, + struct target_struct, k, &sequence_entry, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (strcmp(data_tgt->a, data.a) != 0) { + return ttest_fail(&tc, "Incorrect value for entry a: " + "Expected: %s, got: %s", + data.a, data_tgt->a); + } + if (strcmp(data_tgt->b, data.b) != 0) { + return ttest_fail(&tc, "Incorrect value for entry b"); + } + if (data_tgt->c != data.c) { + return ttest_fail(&tc, "Incorrect value for entry c"); + } + for (unsigned i = 0; i < 4; i++) { + if (data_tgt->d[i] != data.d[i]) { + return ttest_fail(&tc, "Incorrect value for entry d"); + } + } + for (unsigned i = 0; i < 4; i++) { + if (data_tgt->e[i] != data.e[i]) { + return ttest_fail(&tc, "Incorrect value for entry e " + "Index: %u: Expected: %ld, got: %ld", + i, data.e[i], data_tgt->e[i]); + } + } + if (strcmp(data_tgt->f, data.f) != 0) { + return ttest_fail(&tc, "Incorrect value for entry f: " + "Expected: %s, got: %s", + data.f, data_tgt->f); + } + if (data_tgt->g != data.g) { + return ttest_fail(&tc, "Incorrect value for entry g: " + "Expected: %s, got: %s", + data.g, data_tgt->g); + } + if (strcmp(data_tgt->h, data.h) != 0) { + return ttest_fail(&tc, "Incorrect value for entry h"); + } + if (data_tgt->i != data.i) { + return ttest_fail(&tc, "Incorrect value for entry i"); + } + for (unsigned i = 0; i < 4; i++) { + if (data_tgt->j[i] != data.j[i]) { + return ttest_fail(&tc, "Incorrect value for entry j"); + } + } + if (data_tgt->k != data.k) { + return ttest_fail(&tc, "Incorrect value for entry k"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping with only optional fields. + * + * \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_only_optional_fields( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + int c; + int i; + }; + static const unsigned char yaml[] = + "\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("c", CYAML_FLAG_OPTIONAL, + struct target_struct, c), + CYAML_FIELD_INT("i", CYAML_FLAG_OPTIONAL, + struct target_struct, i), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 != NULL) { + return ttest_fail(&tc, "Shouldn't have allocated anything"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping with unknown keys ignored by config. + * + * \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_ignored_unknown_keys( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + short b; + int c; + long d; + long long e; + } data = { + .b = 90, + .c = 900, + .d = 9000, + .e = 90000, + }; + static const unsigned char yaml[] = + "a: 9\n" + "b: 90\n" + "c: 900\n" + "d: 9000\n" + "e: 90000\n" + "f: 900000\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, + struct target_struct, b), + CYAML_FIELD_INT("c", CYAML_FLAG_DEFAULT, + struct target_struct, c), + CYAML_FIELD_INT("d", CYAML_FLAG_DEFAULT, + struct target_struct, d), + CYAML_FIELD_INT("e", CYAML_FLAG_DEFAULT, + struct target_struct, e), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + cyaml_config_t cfg = *config; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + cfg.flags |= CYAML_CFG_IGNORE_UNKNOWN_KEYS; + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt->b != data.b) { + return ttest_fail(&tc, "Incorrect value for entry b"); + } + if (data_tgt->c != data.c) { + return ttest_fail(&tc, "Incorrect value for entry c"); + } + if (data_tgt->d != data.d) { + return ttest_fail(&tc, "Incorrect value for entry d"); + } + if (data_tgt->e != data.e) { + return ttest_fail(&tc, "Incorrect value for entry e"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence with max size 4, and only 2 entries in YAML. + * + * \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_sequence_without_max_entries( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + const char *seq[4]; + unsigned seq_count; + } data = { + .seq = { "1", "2", NULL, NULL }, + .seq_count = 2, + }; + static const unsigned char yaml[] = + "seq: [ 1, 2 ]\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_value sequence_entry = { + CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char *, + 0, CYAML_UNLIMITED) + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("seq", CYAML_FLAG_OPTIONAL, + struct target_struct, seq, &sequence_entry, + 0, 4), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->seq_count != 2) { + return ttest_fail(&tc, "Incorrect sequence entry count"); + } + for (unsigned i = 0; i < 4; i++) { + if ((data_tgt->seq[i] == NULL) != (data.seq[i] == NULL)) { + return ttest_fail(&tc, "Incorrect value for entry %u", + i); + } + if (data_tgt->seq[i] != NULL) { + if (strcmp(data_tgt->seq[i], data.seq[i]) != 0) { + return ttest_fail(&tc, + "Incorrect value for entry %u", + i); + } + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading without a logging function. + * + * \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_no_log( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + cyaml_config_t cfg = *config; + struct target_struct { + const char *seq[4]; + unsigned seq_count; + } data = { + .seq = { "1", "2", NULL, NULL }, + .seq_count = 2, + }; + static const unsigned char yaml[] = + "seq: [ 1, 2 ]\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_value sequence_entry = { + CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char *, + 0, CYAML_UNLIMITED) + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("seq", CYAML_FLAG_OPTIONAL, + struct target_struct, seq, &sequence_entry, + 0, 4), + 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 = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.log_fn = NULL; + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt->seq_count != 2) { + return ttest_fail(&tc, "Incorrect sequence entry count"); + } + for (unsigned i = 0; i < 4; i++) { + if ((data_tgt->seq[i] == NULL) != (data.seq[i] == NULL)) { + return ttest_fail(&tc, "Incorrect value for entry %u", + i); + } + if (data_tgt->seq[i] != NULL) { + if (strcmp(data_tgt->seq[i], data.seq[i]) != 0) { + return ttest_fail(&tc, + "Incorrect value for entry %u", + i); + } + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading a sequence with arbitrary C struct member name for entry count. + * + * \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_schema_sequence_entry_count_member( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + unsigned ref[] = { 99999, 99998, 99997, 99996, 99995, 99994 }; + static const unsigned char yaml[] = + "sequence:\n" + " - 99999\n" + " - 99998\n" + " - 99997\n" + " - 99996\n" + " - 99995\n" + " - 99994\n"; + struct target_struct { + unsigned *entries; + uint32_t n_entries; + } *data_tgt = NULL; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_UINT(CYAML_FLAG_DEFAULT, *(data_tgt->entries)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE_COUNT("sequence", CYAML_FLAG_POINTER, + struct target_struct, entries, n_entries, + &entry_schema, 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (CYAML_ARRAY_LEN(ref) != data_tgt->n_entries) { + return ttest_fail(&tc, "Incorrect sequence count"); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(ref); i++) { + if (data_tgt->entries[i] != ref[i]) { + return ttest_fail(&tc, "Incorrect value (i=%u): " + "got: %i, expected: %i", i, + data_tgt->entries[i], ref[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading an enum with case insensitive string matching configured. + * + * \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_enum_insensitive( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_enum { + TEST_ENUM_FIRST, + TEST_ENUM_SECOND, + TEST_ENUM_THIRD, + TEST_ENUM__COUNT, + } ref = TEST_ENUM_SECOND; + static const cyaml_strval_t strings[TEST_ENUM__COUNT] = { + { "first", TEST_ENUM_FIRST }, + { "second", TEST_ENUM_SECOND }, + { "third", TEST_ENUM_THIRD }, + }; + static const unsigned char yaml[] = + "SECOND\n"; + enum test_enum *data_tgt = NULL; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_ENUM(CYAML_FLAG_POINTER, + *data_tgt, strings, TEST_ENUM__COUNT), + }; + cyaml_config_t cfg = *config; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_CASE_INSENSITIVE; + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (*data_tgt != ref) { + return ttest_fail(&tc, "Incorrect value for enum"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a flags value with case insensitive string matching configured. + * + * \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_flags_insensitive( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_enum { + TEST_FLAG_FIRST = (1 << 1), + TEST_FLAG_SECOND = (1 << 3), + TEST_FLAG_THIRD = (1 << 5), + } ref = TEST_FLAG_FIRST | TEST_FLAG_THIRD; + static const cyaml_strval_t strings[] = { + { "first", TEST_FLAG_FIRST }, + { "second", TEST_FLAG_SECOND }, + { "third", TEST_FLAG_THIRD }, + }; + static const unsigned char yaml[] = + "- First\n" + "- Third\n"; + enum test_enum *data_tgt = NULL; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_FLAGS(CYAML_FLAG_POINTER, + *data_tgt, strings, CYAML_ARRAY_LEN(strings)), + }; + cyaml_config_t cfg = *config; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_CASE_INSENSITIVE; + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (*data_tgt != ref) { + return ttest_fail(&tc, "Incorrect value for enum"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping with case insensitive string matching configured. + * + * \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_fields_cfg_insensitive_1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + signed char a; + short b; + int c; + long d; + long long e; + } data = { + .a = 9, + .b = 90, + .c = 900, + .d = 9000, + .e = 90000, + }; + static const unsigned char yaml[] = + "Lollipop: 9\n" + "Squiggle: 90\n" + "Unicorns: 900\n" + "Cheerful: 9000\n" + "LibCYAML: 90000\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("lollipop", CYAML_FLAG_DEFAULT, + struct target_struct, a), + CYAML_FIELD_INT("squiggle", CYAML_FLAG_DEFAULT, + struct target_struct, b), + CYAML_FIELD_INT("unicorns", CYAML_FLAG_DEFAULT, + struct target_struct, c), + CYAML_FIELD_INT("cheerful", CYAML_FLAG_DEFAULT, + struct target_struct, d), + CYAML_FIELD_INT("libcyaml", CYAML_FLAG_DEFAULT, + struct target_struct, e), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + cyaml_config_t cfg = *config; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_CASE_INSENSITIVE; + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt->a != data.a) { + return ttest_fail(&tc, "Incorrect value for entry a"); + } + if (data_tgt->b != data.b) { + return ttest_fail(&tc, "Incorrect value for entry b"); + } + if (data_tgt->c != data.c) { + return ttest_fail(&tc, "Incorrect value for entry c"); + } + if (data_tgt->d != data.d) { + return ttest_fail(&tc, "Incorrect value for entry d"); + } + if (data_tgt->e != data.e) { + return ttest_fail(&tc, "Incorrect value for entry e"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping with case insensitive string matching configured. + * + * \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_fields_cfg_insensitive_2( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + signed char a; + short b; + int c; + long d; + long long e; + } data = { + .a = 9, + .b = 90, + .c = 900, + .d = 9000, + .e = 90000, + }; + static const unsigned char yaml[] = + "Plinth: 9\n" + "..Cusp?: 90\n" + "..Cusp!: 900\n" + "Bleat: 9000\n" + "Foo~-|Bar: 90000\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("plinth", CYAML_FLAG_DEFAULT, + struct target_struct, a), + CYAML_FIELD_INT("..cusp?", CYAML_FLAG_DEFAULT, + struct target_struct, b), + CYAML_FIELD_INT("..cusp!", CYAML_FLAG_DEFAULT, + struct target_struct, c), + CYAML_FIELD_INT("bleat", CYAML_FLAG_DEFAULT, + struct target_struct, d), + CYAML_FIELD_INT("foO~-|baR", CYAML_FLAG_DEFAULT, + struct target_struct, e), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + cyaml_config_t cfg = *config; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_CASE_INSENSITIVE; + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt->a != data.a) { + return ttest_fail(&tc, "Incorrect value for entry a"); + } + if (data_tgt->b != data.b) { + return ttest_fail(&tc, "Incorrect value for entry b"); + } + if (data_tgt->c != data.c) { + return ttest_fail(&tc, "Incorrect value for entry c"); + } + if (data_tgt->d != data.d) { + return ttest_fail(&tc, "Incorrect value for entry d"); + } + if (data_tgt->e != data.e) { + return ttest_fail(&tc, "Incorrect value for entry e"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping with case insensitive string matching configured. + * + * \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_fields_cfg_insensitive_3( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + signed char a; + short b; + int c; + long d; + long long e; + } data = { + .a = 9, + .b = 90, + .c = 900, + .d = 9000, + .e = 90000, + }; + static const unsigned char yaml[] = + "Pling: 9\n" + "Plin: 90\n" + "Pli: 900\n" + "Pl: 9000\n" + "P: 90000\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("plin", CYAML_FLAG_DEFAULT, + struct target_struct, b), + CYAML_FIELD_INT("pli", CYAML_FLAG_DEFAULT, + struct target_struct, c), + CYAML_FIELD_INT("pl", CYAML_FLAG_DEFAULT, + struct target_struct, d), + CYAML_FIELD_INT("p", CYAML_FLAG_DEFAULT, + struct target_struct, e), + CYAML_FIELD_INT("pling", 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), + }; + cyaml_config_t cfg = *config; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_CASE_INSENSITIVE; + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt->a != data.a) { + return ttest_fail(&tc, "Incorrect value for entry a"); + } + if (data_tgt->b != data.b) { + return ttest_fail(&tc, "Incorrect value for entry b"); + } + if (data_tgt->c != data.c) { + return ttest_fail(&tc, "Incorrect value for entry c"); + } + if (data_tgt->d != data.d) { + return ttest_fail(&tc, "Incorrect value for entry d"); + } + if (data_tgt->e != data.e) { + return ttest_fail(&tc, "Incorrect value for entry e"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping with case sensitive string matching for 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_fields_value_sensitive_1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + signed char a; + short b; + int c; + long d; + long long e; + } data = { + .a = 9, + .b = 90, + .c = 900, + .d = 9000, + .e = 90000, + }; + static const unsigned char yaml[] = + "pling: 9\n" + "PLing: 90\n" + "PLINg: 900\n" + "pliNG: 9000\n" + "PLING: 90000\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("pling", CYAML_FLAG_DEFAULT, + struct target_struct, a), + CYAML_FIELD_INT("PLing", CYAML_FLAG_DEFAULT, + struct target_struct, b), + CYAML_FIELD_INT("PLINg", CYAML_FLAG_DEFAULT, + struct target_struct, c), + CYAML_FIELD_INT("pliNG", CYAML_FLAG_DEFAULT, + struct target_struct, d), + CYAML_FIELD_INT("PLING", CYAML_FLAG_DEFAULT, + struct target_struct, e), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING( + CYAML_FLAG_POINTER | CYAML_FLAG_CASE_SENSITIVE, + struct target_struct, mapping_schema), + }; + cyaml_config_t cfg = *config; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_CASE_INSENSITIVE; + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt->a != data.a) { + return ttest_fail(&tc, "Incorrect value for entry a"); + } + if (data_tgt->b != data.b) { + return ttest_fail(&tc, "Incorrect value for entry b"); + } + if (data_tgt->c != data.c) { + return ttest_fail(&tc, "Incorrect value for entry c"); + } + if (data_tgt->d != data.d) { + return ttest_fail(&tc, "Incorrect value for entry d"); + } + if (data_tgt->e != data.e) { + return ttest_fail(&tc, "Incorrect value for entry e"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading a mapping with case insensitive string matching for 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_fields_value_insensitive_1( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct target_struct { + signed char a; + short b; + int c; + long d; + long long e; + } data = { + .a = 9, + .b = 90, + .c = 900, + .d = 9000, + .e = 90000, + }; + static const unsigned char yaml[] = + "Pling: 9\n" + "Plin: 90\n" + "Pli: 900\n" + "Pl: 9000\n" + "P: 90000\n"; + struct target_struct *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("plin", CYAML_FLAG_DEFAULT, + struct target_struct, b), + CYAML_FIELD_INT("pli", CYAML_FLAG_DEFAULT, + struct target_struct, c), + CYAML_FIELD_INT("pl", CYAML_FLAG_DEFAULT, + struct target_struct, d), + CYAML_FIELD_INT("p", CYAML_FLAG_DEFAULT, + struct target_struct, e), + CYAML_FIELD_INT("pling", CYAML_FLAG_DEFAULT, + struct target_struct, a), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING( + CYAML_FLAG_POINTER | + CYAML_FLAG_CASE_INSENSITIVE, + struct target_struct, mapping_schema), + }; + cyaml_config_t cfg = *config; + test_data_t td = { + .data = (cyaml_data_t **) &data_tgt, + .config = &cfg, + .schema = &top_schema, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_CASE_INSENSITIVE; + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_load_data(yaml, YAML_LEN(yaml), &cfg, &top_schema, + (cyaml_data_t **) &data_tgt, NULL); + if (err != CYAML_OK) { + return ttest_fail(&tc, cyaml_strerror(err)); + } + + if (data_tgt->a != data.a) { + return ttest_fail(&tc, "Incorrect value for entry a"); + } + if (data_tgt->b != data.b) { + return ttest_fail(&tc, "Incorrect value for entry b"); + } + if (data_tgt->c != data.c) { + return ttest_fail(&tc, "Incorrect value for entry c"); + } + if (data_tgt->d != data.d) { + return ttest_fail(&tc, "Incorrect value for entry d"); + } + if (data_tgt->e != data.e) { + return ttest_fail(&tc, "Incorrect value for entry e"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading with an unused anchor. + * + * \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_unused_anchor( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *string_value = "Hello World!"; + const int int_value = 9; + static const unsigned char yaml[] = + "test_string: &foo Hello World!\n" + "test_int: 9\n"; + struct target_struct { + char * test_value_string; + int test_value_int; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("test_string", CYAML_FLAG_POINTER, + struct target_struct, test_value_string, + 0, CYAML_UNLIMITED), + CYAML_FIELD_INT("test_int", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_int), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (strcmp(data_tgt->test_value_string, string_value) != 0) { + return ttest_fail(&tc, "Incorrect value"); + } + + if (data_tgt->test_value_int != int_value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading with an aliased string 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_anchor_scalar_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *string_value = "Hello World!"; + const int int_value = 9; + static const unsigned char yaml[] = + "test_int_anchor: &foo 9\n" + "test_string: Hello World!\n" + "test_int: *foo\n"; + struct target_struct { + char * test_value_string; + int test_value_int; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_IGNORE("test_int_anchor", CYAML_FLAG_OPTIONAL), + CYAML_FIELD_STRING_PTR("test_string", CYAML_FLAG_POINTER, + struct target_struct, test_value_string, + 0, CYAML_UNLIMITED), + CYAML_FIELD_INT("test_int", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_int), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (strcmp(data_tgt->test_value_string, string_value) != 0) { + return ttest_fail(&tc, "Incorrect value"); + } + + if (data_tgt->test_value_int != int_value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading with an aliased string 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_anchor_scalar_string( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *string_value = "Hello World!"; + const int int_value = 9; + static const unsigned char yaml[] = + "test_string_anchor: &foo Hello World!\n" + "test_string: *foo\n" + "test_int: 9\n"; + struct target_struct { + char * test_value_string; + int test_value_int; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_IGNORE("test_string_anchor", CYAML_FLAG_OPTIONAL), + CYAML_FIELD_STRING_PTR("test_string", CYAML_FLAG_POINTER, + struct target_struct, test_value_string, + 0, CYAML_UNLIMITED), + CYAML_FIELD_INT("test_int", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_int), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (strcmp(data_tgt->test_value_string, string_value) != 0) { + return ttest_fail(&tc, "Incorrect value"); + } + + if (data_tgt->test_value_int != int_value) { + return ttest_fail(&tc, "Incorrect value"); + } + + return ttest_pass(&tc); +} + +/** + * Test loading multiple anchored and alisased scalars. + * + * \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_anchor_multiple_scalars( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *string_value1 = "Hello Me!"; + const char *string_value2 = "Hello World!"; + const int int_value = 99; + static const unsigned char yaml[] = + "anchors:\n" + " - &a1 Hello World!\n" + " - &a2 Hello Me!\n" + " - &a3 99\n" + "test_string1: *a2\n" + "test_int: *a3\n" + "test_string2: *a1\n"; + struct target_struct { + char * test_value_string1; + char * test_value_string2; + int test_value_int; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_IGNORE("anchors", CYAML_FLAG_OPTIONAL), + CYAML_FIELD_STRING_PTR("test_string1", CYAML_FLAG_POINTER, + struct target_struct, test_value_string1, + 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("test_string2", CYAML_FLAG_POINTER, + struct target_struct, test_value_string2, + 0, CYAML_UNLIMITED), + CYAML_FIELD_INT("test_int", CYAML_FLAG_DEFAULT, + struct target_struct, test_value_int), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (strcmp(data_tgt->test_value_string1, string_value1) != 0) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %s, got: %s", + string_value1, data_tgt->test_value_string1); + } + + if (strcmp(data_tgt->test_value_string2, string_value2) != 0) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %s, got: %s", + string_value2, data_tgt->test_value_string2); + } + + if (data_tgt->test_value_int != int_value) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %i, got: %i", + int_value, data_tgt->test_value_int); + } + + return ttest_pass(&tc); +} + +/** + * Test loading an aliased mapping. + * + * \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_anchor_mapping( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *test_a = "Hello Me!"; + const int test_b = 777; + static const unsigned char yaml[] = + "anchors:\n" + " - &a2 Hello Me!\n" + " - &a1 {\n" + " a: *a2,\n" + " b: 777,\n" + " }\n" + "test: *a1\n"; + struct my_test { + int b; + char *a; + }; + struct target_struct { + struct my_test test; + } *data_tgt = NULL; + static const struct cyaml_schema_field inner_mapping_schema[] = { + CYAML_FIELD_STRING_PTR("a", CYAML_FLAG_POINTER, + struct my_test, a, + 0, CYAML_UNLIMITED), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, + struct my_test, b), + CYAML_FIELD_END + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_IGNORE("anchors", CYAML_FLAG_OPTIONAL), + CYAML_FIELD_MAPPING("test", CYAML_FLAG_DEFAULT, + struct target_struct, test, + inner_mapping_schema), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (strcmp(data_tgt->test.a, test_a) != 0) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %s, got: %s", + test_a, data_tgt->test.a); + } + + if (data_tgt->test.b != test_b) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %i, got: %i", + test_b, data_tgt->test.b); + } + + return ttest_pass(&tc); +} + +/** + * Test loading an aliased sequence. + * + * \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_anchor_sequence( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const int test_a[] = { 1, 22, 333, 4444 }; + static const unsigned char yaml[] = + "anchors:\n" + " - &a1 [\n" + " 1,\n" + " 22,\n" + " 333,\n" + " 4444,\n" + " ]\n" + "test: *a1\n"; + struct target_struct { + int *a; + unsigned a_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value sequence_entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_IGNORE("anchors", CYAML_FLAG_OPTIONAL), + CYAML_FIELD_SEQUENCE("test", CYAML_FLAG_POINTER, + struct target_struct, a, + &sequence_entry_schema, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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->a_count != CYAML_ARRAY_LEN(test_a)) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %u, got: %u", + data_tgt->a_count, CYAML_ARRAY_LEN(test_a)); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(test_a); i++) { + if (data_tgt->a[i] != test_a[i]) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %i, got: %i", + data_tgt->a[i], test_a[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading with anchors within anchors, etc. + * + * \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_anchor_deep_mapping_sequence( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *test_a = "Hello Me!"; + const int test_b[] = { 1, 22, 333, 4444 }; + static const unsigned char yaml[] = + "anchors:\n" + " - &a1 Hello Me!\n" + " - &a2 {\n" + " a: *a1,\n" + " b: &a3 [ 1, 22, 333, 4444 ],\n" + " c: *a1,\n" + " d: *a3,\n" + " }\n" + "test_a: *a2\n" + "test_b: *a3\n"; + struct my_test { + char *a; + int *b; + unsigned b_count; + char *c; + int *d; + unsigned d_count; + }; + struct target_struct { + struct my_test test_a; + int *test_b; + unsigned test_b_count; + } *data_tgt = NULL; + static const struct cyaml_schema_value sequence_entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), + }; + static const struct cyaml_schema_field inner_mapping_schema[] = { + CYAML_FIELD_STRING_PTR("a", CYAML_FLAG_POINTER, + struct my_test, a, 0, CYAML_UNLIMITED), + CYAML_FIELD_SEQUENCE("b", CYAML_FLAG_POINTER, struct my_test, b, + &sequence_entry_schema, 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("c", CYAML_FLAG_POINTER, + struct my_test, c, 0, CYAML_UNLIMITED), + CYAML_FIELD_SEQUENCE("d", CYAML_FLAG_POINTER, struct my_test, d, + &sequence_entry_schema, 0, CYAML_UNLIMITED), + CYAML_FIELD_END + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_IGNORE("anchors", CYAML_FLAG_OPTIONAL), + CYAML_FIELD_MAPPING("test_a", CYAML_FLAG_DEFAULT, + struct target_struct, test_a, + inner_mapping_schema), + CYAML_FIELD_SEQUENCE("test_b", CYAML_FLAG_POINTER, + struct target_struct, test_b, + &sequence_entry_schema, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (strcmp(data_tgt->test_a.a, test_a) != 0) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %s, got: %s", + test_a, data_tgt->test_a.a); + } + + if (strcmp(data_tgt->test_a.c, test_a) != 0) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %s, got: %s", + test_a, data_tgt->test_a.c); + } + + if (data_tgt->test_b_count != CYAML_ARRAY_LEN(test_b)) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %u, got: %u", + data_tgt->test_b_count, + CYAML_ARRAY_LEN(test_b)); + } + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(test_b); i++) { + if (data_tgt->test_b[i] != test_b[i]) { + return ttest_fail(&tc, "Incorrect value: " + "expected: %i, got: %i", + data_tgt->test_b[i], test_b[i]); + } + } + + return ttest_pass(&tc); +} + +/** + * Test loading when an anchor is updated. + * + * The newest definition should be used. + * + * \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_anchor_updated_anchor( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + const char *string_value1 = "Hello Me!"; + const char *string_value2 = "Hello World!"; + static const unsigned char yaml[] = + "a: &a1 Hello Me!\n" + "b: *a1\n" + "c: &a1 Hello World!\n" + "d: *a1\n"; + struct target_struct { + char *a; + char *b; + char *c; + char *d; + } *data_tgt = NULL; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("a", CYAML_FLAG_POINTER, + struct target_struct, a, + 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("b", CYAML_FLAG_POINTER, + struct target_struct, b, + 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("c", CYAML_FLAG_POINTER, + struct target_struct, c, + 0, CYAML_UNLIMITED), + CYAML_FIELD_STRING_PTR("d", CYAML_FLAG_POINTER, + struct target_struct, d, + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 (strcmp(data_tgt->a, string_value1) != 0) { + return ttest_fail(&tc, "Incorrect value (a): " + "expected: %s, got: %s", + string_value1, data_tgt->a); + } + + if (strcmp(data_tgt->b, string_value1) != 0) { + return ttest_fail(&tc, "Incorrect value (b): " + "expected: %s, got: %s", + string_value1, data_tgt->b); + } + + if (strcmp(data_tgt->c, string_value2) != 0) { + return ttest_fail(&tc, "Incorrect value (c): " + "expected: %s, got: %s", + string_value2, data_tgt->c); + } + + if (strcmp(data_tgt->d, string_value2) != 0) { + return ttest_fail(&tc, "Incorrect value (d): " + "expected: %s, got: %s", + string_value2, data_tgt->d); + } + + return ttest_pass(&tc); +} + +/** + * Run the YAML loading 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 load_tests( + ttest_report_ctx_t *rc, + cyaml_log_t log_level, + cyaml_log_fn_t log_fn) +{ + bool pass = true; + cyaml_config_t config = { + .log_fn = log_fn, + .mem_fn = cyaml_mem, + .log_level = log_level, + .flags = CYAML_CFG_DEFAULT, + }; + + ttest_heading(rc, "Load single entry mapping tests: simple types"); + + pass &= test_load_mapping_entry_enum(rc, &config); + pass &= test_load_mapping_entry_uint(rc, &config); + 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_int_pos(rc, &config); + pass &= test_load_mapping_entry_int_neg(rc, &config); + pass &= test_load_mapping_entry_enum_ptr(rc, &config); + pass &= test_load_mapping_entry_uint_ptr(rc, &config); + pass &= test_load_mapping_entry_float_ptr(rc, &config); + pass &= test_load_mapping_entry_bool_true(rc, &config); + 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_int_pos_ptr(rc, &config); + pass &= test_load_mapping_entry_int_neg_ptr(rc, &config); + pass &= test_load_mapping_entry_enum_sparse(rc, &config); + pass &= test_load_mapping_entry_ignore_deep(rc, &config); + pass &= test_load_mapping_entry_ignore_scalar(rc, &config); + pass &= test_load_mapping_entry_bool_true_ptr(rc, &config); + pass &= test_load_mapping_entry_bool_false_ptr(rc, &config); + pass &= test_load_mapping_entry_string_ptr_empty(rc, &config); + pass &= test_load_mapping_entry_string_ptr_null_str(rc, &config); + pass &= test_load_mapping_entry_string_ptr_null_empty(rc, &config); + + ttest_heading(rc, "Load single entry mapping tests: complex types"); + + pass &= test_load_mapping_entry_flags(rc, &config); + pass &= test_load_mapping_entry_mapping(rc, &config); + pass &= test_load_mapping_entry_bitfield(rc, &config); + pass &= test_load_mapping_entry_flags_ptr(rc, &config); + pass &= test_load_mapping_entry_mapping_ptr(rc, &config); + pass &= test_load_mapping_entry_flags_empty(rc, &config); + pass &= test_load_mapping_entry_bitfield_ptr(rc, &config); + pass &= test_load_mapping_entry_flags_sparse(rc, &config); + + ttest_heading(rc, "Load single entry mapping tests: sequences"); + + pass &= test_load_mapping_entry_sequence_int(rc, &config); + pass &= test_load_mapping_entry_sequence_enum(rc, &config); + pass &= test_load_mapping_entry_sequence_uint(rc, &config); + pass &= test_load_mapping_entry_sequence_bool(rc, &config); + pass &= test_load_mapping_entry_sequence_flags(rc, &config); + pass &= test_load_mapping_entry_sequence_string(rc, &config); + pass &= test_load_mapping_entry_sequence_mapping(rc, &config); + pass &= test_load_mapping_entry_sequence_string_ptr(rc, &config); + pass &= test_load_mapping_entry_sequence_mapping_ptr(rc, &config); + pass &= test_load_mapping_entry_sequence_sequence_fixed_int(rc, &config); + pass &= test_load_mapping_entry_sequence_sequence_fixed_ptr_int(rc, &config); + pass &= test_load_mapping_entry_sequence_sequence_fixed_flat_int(rc, &config); + + ttest_heading(rc, "Load single entry mapping tests: ptr sequences"); + + pass &= test_load_mapping_entry_sequence_ptr_int(rc, &config); + pass &= test_load_mapping_entry_sequence_ptr_enum(rc, &config); + pass &= test_load_mapping_entry_sequence_ptr_uint(rc, &config); + pass &= test_load_mapping_entry_sequence_ptr_bool(rc, &config); + pass &= test_load_mapping_entry_sequence_ptr_flags(rc, &config); + pass &= test_load_mapping_entry_sequence_ptr_string(rc, &config); + pass &= test_load_mapping_entry_sequence_ptr_mapping(rc, &config); + pass &= test_load_mapping_entry_sequence_ptr_string_ptr(rc, &config); + pass &= test_load_mapping_entry_sequence_ptr_mapping_ptr(rc, &config); + pass &= test_load_mapping_entry_sequence_ptr_sequence_fixed_int(rc, &config); + pass &= test_load_mapping_entry_sequence_ptr_sequence_fixed_ptr_int(rc, &config); + pass &= test_load_mapping_entry_sequence_ptr_sequence_fixed_flat_int(rc, &config); + + ttest_heading(rc, "Load tests: ptr sequence with null values"); + + pass &= test_load_sequence_null_values_int(rc, &config); + pass &= test_load_sequence_null_str_values_int(rc, &config); + pass &= test_load_sequence_null_str_values_uint(rc, &config); + + ttest_heading(rc, "Load tests: sequence count sizes"); + + pass &= test_load_mapping_entry_sequence_count_1(rc, &config); + pass &= test_load_mapping_entry_sequence_count_2(rc, &config); + pass &= test_load_mapping_entry_sequence_count_3(rc, &config); + pass &= test_load_mapping_entry_sequence_count_4(rc, &config); + pass &= test_load_mapping_entry_sequence_count_5(rc, &config); + pass &= test_load_mapping_entry_sequence_count_6(rc, &config); + pass &= test_load_mapping_entry_sequence_count_7(rc, &config); + pass &= test_load_mapping_entry_sequence_count_8(rc, &config); + + ttest_heading(rc, "Load tests: various"); + + pass &= test_load_no_log(rc, &config); + pass &= test_load_schema_top_level_scalar(rc, &config); + pass &= test_load_schema_top_level_string(rc, &config); + pass &= test_load_schema_top_level_sequence(rc, &config); + pass &= test_load_multiple_documents_ignored(rc, &config); + pass &= test_load_mapping_with_multiple_fields(rc, &config); + pass &= test_load_mapping_with_optional_fields(rc, &config); + pass &= test_load_mapping_only_optional_fields(rc, &config); + pass &= test_load_mapping_ignored_unknown_keys(rc, &config); + pass &= test_load_sequence_without_max_entries(rc, &config); + pass &= test_load_schema_top_level_sequence_fixed(rc, &config); + pass &= test_load_schema_sequence_entry_count_member(rc, &config); + + ttest_heading(rc, "Load tests: case sensitivity"); + + pass &= test_load_enum_insensitive(rc, &config); + pass &= test_load_flags_insensitive(rc, &config); + pass &= test_load_mapping_fields_cfg_insensitive_1(rc, &config); + pass &= test_load_mapping_fields_cfg_insensitive_2(rc, &config); + pass &= test_load_mapping_fields_cfg_insensitive_3(rc, &config); + pass &= test_load_mapping_fields_value_sensitive_1(rc, &config); + pass &= test_load_mapping_fields_value_insensitive_1(rc, &config); + + ttest_heading(rc, "Load tests: anchors and aliases (scalars)"); + + pass &= test_load_unused_anchor(rc, &config); + pass &= test_load_anchor_scalar_int(rc, &config); + pass &= test_load_anchor_scalar_string(rc, &config); + pass &= test_load_anchor_multiple_scalars(rc, &config); + + ttest_heading(rc, "Load tests: anchors and aliases (non scalars)"); + + pass &= test_load_anchor_mapping(rc, &config); + pass &= test_load_anchor_sequence(rc, &config); + pass &= test_load_anchor_deep_mapping_sequence(rc, &config); + + ttest_heading(rc, "Load tests: anchors and aliases (edge cases)"); + + pass &= test_load_anchor_updated_anchor(rc, &config); + + return pass; +} diff --git a/test/units/save.c b/test/units/save.c new file mode 100644 index 0000000..e2873b4 --- /dev/null +++ b/test/units/save.c @@ -0,0 +1,4028 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2018-2019 Michael Drake + */ + +#include +#include +#include +#include + +#include + +#include "../../src/data.h" +#include "ttest.h" + +/** Macro to squash unused variable compiler warnings. */ +#define UNUSED(_x) ((void)(_x)) + +/** Helper macro to count bytes of YAML input data. */ +#define YAML_LEN(_y) (sizeof(_y) - 1) + +/** + * Unit test context data. + */ +typedef struct test_data { + char **buffer; + const struct cyaml_config *config; +} test_data_t; + +/** + * Common clean up function to free data allocated by tests. + * + * \param[in] data The unit test context data. + */ +static void cyaml_cleanup(void *data) +{ + struct test_data *td = data; + + if (td->config->mem_fn != NULL && td->buffer != NULL) { + td->config->mem_fn(td->config->mem_ctx, *(td->buffer), 0); + } +} + +/** + * Test saving an unsigned integer. + * + * \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_uint( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "test_uint: 555\n" + "...\n"; + static const struct target_struct { + unsigned test_uint; + } data = { + .test_uint = 555, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("test_uint", CYAML_FLAG_DEFAULT, + struct target_struct, test_uint), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 float. + * + * \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_float( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "test_float: 3.14\n" + "...\n"; + static const struct target_struct { + float test_float; + } data = { + .test_float = 3.14f, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT("test_float", CYAML_FLAG_DEFAULT, + struct target_struct, test_float), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 double. + * + * \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_double( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "test_float: 3.14\n" + "...\n"; + static const struct target_struct { + double test_float; + } data = { + .test_float = 3.14, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLOAT("test_float", CYAML_FLAG_DEFAULT, + struct target_struct, test_float), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 string. + * + * \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_string( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "test_string: This is a test, of sorts.\n" + "...\n"; + static const struct target_struct { + char test_string[32]; + } data = { + .test_string = "This is a test, of sorts.", + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING("test_string", CYAML_FLAG_DEFAULT, + struct target_struct, test_string, 0), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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. + * + * \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_int_pos( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "test_int: 90\n" + "...\n"; + static const struct target_struct { + int test_int; + } data = { + .test_int = 90, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("test_int", CYAML_FLAG_DEFAULT, + struct target_struct, test_int), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 negative signed integer. + * + * \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_int_neg( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "test_int: -14\n" + "...\n"; + static const struct target_struct { + int test_int; + } data = { + .test_int = -14, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("test_int", CYAML_FLAG_DEFAULT, + struct target_struct, test_int), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 negative signed 64-bit integer. + * + * \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_int_64( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "test_int: -9223372036854775800\n" + "...\n"; + static const struct target_struct { + int64_t test_int; + } data = { + .test_int = -9223372036854775800, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_INT("test_int", CYAML_FLAG_DEFAULT, + struct target_struct, test_int), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 boolean. + * + * \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_bool_true( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "test_bool: true\n" + "...\n"; + static const struct target_struct { + bool test_bool; + } data = { + .test_bool = true, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BOOL("test_bool", CYAML_FLAG_DEFAULT, + struct target_struct, test_bool), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 boolean. + * + * \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_bool_false( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "test_bool: false\n" + "...\n"; + static const struct target_struct { + bool test_bool; + } data = { + .test_bool = false, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BOOL("test_bool", CYAML_FLAG_DEFAULT, + struct target_struct, test_bool), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 string pointer. + * + * \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_string_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "test_string: This is a test, of sorts.\n" + "...\n"; + static const struct target_struct { + const char *test_string; + } data = { + .test_string = "This is a test, of sorts.", + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_STRING_PTR("test_string", CYAML_FLAG_POINTER, + struct target_struct, test_string, + 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), + }; + char *buffer; + size_t len; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 strict enum. + * + * \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_enum_strict( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_e { + FIRST, SECOND, THIRD, FOURTH, COUNT + }; + static const cyaml_strval_t strings[COUNT] = { + { "first", 0 }, + { "second", 1 }, + { "third", 2 }, + { "fourth", 3 }, + }; + static const unsigned char ref[] = + "---\n" + "test_enum: third\n" + "...\n"; + static const struct target_struct { + enum test_e test_enum; + } data = { + .test_enum = THIRD, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_ENUM("test_enum", CYAML_FLAG_STRICT, + struct target_struct, test_enum, strings, 4), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 numerical enum. + * + * \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_enum_number( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_e { + FIRST, SECOND, THIRD, FOURTH + }; + static const cyaml_strval_t strings[] = { + { "first", 0 }, + { "second", 1 }, + { "third", 2 }, + { "fourth", 3 }, + }; + static const unsigned char ref[] = + "---\n" + "test_enum: 99\n" + "...\n"; + static const struct target_struct { + enum test_e test_enum; + } data = { + .test_enum = 99, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_ENUM("test_enum", CYAML_FLAG_DEFAULT, + struct target_struct, test_enum, strings, 4), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sparse, unordered enum. + * + * \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_enum_sparse( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_e { + FIRST = -33, + SECOND = 1, + THIRD = 256, + FOURTH = 3, + FIFTH = 999, + }; + static const cyaml_strval_t strings[] = { + { "first", FIRST }, + { "second", SECOND }, + { "third", THIRD }, + { "fourth", FOURTH }, + { "fifth", FIFTH }, + }; + static const unsigned char ref[] = + "---\n" + "test_enum: third\n" + "...\n"; + static const struct target_struct { + enum test_e test_enum; + } data = { + .test_enum = THIRD, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_ENUM("test_enum", CYAML_FLAG_STRICT, + struct target_struct, test_enum, + strings, CYAML_ARRAY_LEN(strings)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 mapping. + * + * \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_mapping( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct value_s { + short a; + long b; + }; + static const unsigned char ref[] = + "---\n" + "mapping:\n" + " a: 123\n" + " b: 9999\n" + "...\n"; + static const struct target_struct { + struct value_s test_mapping; + } data = { + .test_mapping = { + .a = 123, + .b = 9999 + }, + }; + static const struct cyaml_schema_field test_mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, struct value_s, a), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, struct value_s, b), + CYAML_FIELD_END + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_MAPPING("mapping", CYAML_FLAG_DEFAULT, + struct target_struct, test_mapping, + test_mapping_schema), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 mapping pointer. + * + * \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_mapping_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct value_s { + short a; + long b; + } value = { + .a = 123, + .b = 9999 + }; + static const unsigned char ref[] = + "---\n" + "mapping:\n" + " a: 123\n" + " b: 9999\n" + "...\n"; + const struct target_struct { + struct value_s *test_mapping; + } data = { + .test_mapping = &value, + }; + static const struct cyaml_schema_field test_mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, struct value_s, a), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, struct value_s, b), + CYAML_FIELD_END + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_MAPPING_PTR("mapping", CYAML_FLAG_POINTER, + struct target_struct, test_mapping, + test_mapping_schema), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 strict flags 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_save_mapping_entry_flags_strict( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_f { + NONE = 0, + FIRST = (1 << 0), + SECOND = (1 << 1), + THIRD = (1 << 2), + FOURTH = (1 << 3), + }; + static const cyaml_strval_t strings[] = { + { "first", (1 << 0) }, + { "second", (1 << 1) }, + { "third", (1 << 2) }, + { "fourth", (1 << 3) }, + }; + static const unsigned char ref[] = + "---\n" + "test_flags:\n" + "- first\n" + "- fourth\n" + "...\n"; + static const struct target_struct { + enum test_f test_flags; + } data = { + .test_flags = FIRST | FOURTH, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("test_flags", CYAML_FLAG_STRICT, + struct target_struct, test_flags, strings, 4), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 numerical flags 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_save_mapping_entry_flags_number( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_f { + NONE = 0, + FIRST = (1 << 0), + SECOND = (1 << 1), + THIRD = (1 << 2), + FOURTH = (1 << 3), + }; + static const cyaml_strval_t strings[] = { + { "first", (1 << 0) }, + { "second", (1 << 1) }, + { "third", (1 << 2) }, + { "fourth", (1 << 3) }, + }; + static const unsigned char ref[] = + "---\n" + "test_flags:\n" + "- first\n" + "- fourth\n" + "- 1024\n" + "...\n"; + static const struct target_struct { + enum test_f test_flags; + } data = { + .test_flags = FIRST | FOURTH | 1024, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("test_flags", CYAML_FLAG_DEFAULT, + struct target_struct, test_flags, strings, 4), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sparse flags 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_save_mapping_entry_flags_sparse( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_f { + NONE = 0, + FIRST = (1 << 3), + SECOND = (1 << 9), + THIRD = (1 << 10), + FOURTH = (1 << 21), + }; + static const cyaml_strval_t strings[] = { + { "none", NONE }, + { "first", FIRST }, + { "second", SECOND }, + { "third", THIRD }, + { "fourth", FOURTH }, + }; + static const unsigned char ref[] = + "---\n" + "test_flags:\n" + "- first\n" + "- fourth\n" + "...\n"; + static const struct target_struct { + enum test_f test_flags; + } data = { + .test_flags = FIRST | FOURTH, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_FLAGS("test_flags", CYAML_FLAG_DEFAULT, + struct target_struct, test_flags, + strings, CYAML_ARRAY_LEN(strings)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 bitfield 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_save_mapping_entry_bitfield( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_bitdef_t bitvals[] = { + { .name = "a", .offset = 0, .bits = 3 }, + { .name = "b", .offset = 3, .bits = 7 }, + { .name = "c", .offset = 10, .bits = 32 }, + { .name = "d", .offset = 42, .bits = 8 }, + { .name = "e", .offset = 50, .bits = 14 }, + }; + static const unsigned char ref[] = + "---\n" + "test_bitfield:\n" + " a: 0x7\n" + " b: 0x7f\n" + " c: 0xffffffff\n" + " d: 0xff\n" + " e: 0x3fff\n" + "...\n"; + static const struct target_struct { + uint64_t test_bitfield; + } data = { + .test_bitfield = 0xFFFFFFFFFFFFFFFFu, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BITFIELD("test_bitfield", CYAML_FLAG_DEFAULT, + struct target_struct, test_bitfield, + bitvals, CYAML_ARRAY_LEN(bitvals)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sparse bitfield 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_save_mapping_entry_bitfield_sparse( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const cyaml_bitdef_t bitvals[] = { + { .name = "a", .offset = 0, .bits = 3 }, + { .name = "b", .offset = 3, .bits = 7 }, + { .name = "c", .offset = 10, .bits = 32 }, + { .name = "d", .offset = 42, .bits = 8 }, + { .name = "e", .offset = 50, .bits = 14 }, + }; + static const unsigned char ref[] = + "---\n" + "test_bitfield:\n" + " a: 0x7\n" + " b: 0x7f\n" + " e: 0x3fff\n" + "...\n"; + static const struct target_struct { + uint64_t test_bitfield; + } data = { + .test_bitfield = ( 0x7llu << 0) | + ( 0x7Fllu << 3) | + (0x3FFFllu << 50), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_BITFIELD("test_bitfield", CYAML_FLAG_DEFAULT, + struct target_struct, test_bitfield, + bitvals, CYAML_ARRAY_LEN(bitvals)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of integers. + * + * \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_sequence_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- 1\n" + "- 1\n" + "- 2\n" + "- 3\n" + "- 5\n" + "- 8\n" + "...\n"; + static const struct target_struct { + int seq[6]; + uint32_t seq_count; + } data = { + .seq = { 1, 1, 2, 3, 5, 8 }, + .seq_count = CYAML_ARRAY_LEN(data.seq), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data.seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of unsigned integers. + * + * \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_sequence_uint( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- 1\n" + "- 1\n" + "- 2\n" + "- 3\n" + "- 5\n" + "- 8\n" + "...\n"; + static const struct target_struct { + unsigned seq[6]; + uint32_t seq_count; + } data = { + .seq = { 1, 1, 2, 3, 5, 8 }, + .seq_count = CYAML_ARRAY_LEN(data.seq), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_UINT(CYAML_FLAG_DEFAULT, *(data.seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of enums. + * + * \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_sequence_enum( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_enum { + TEST_ENUM_FIRST, + TEST_ENUM_SECOND, + TEST_ENUM_THIRD, + TEST_ENUM__COUNT, + }; + static const cyaml_strval_t strings[TEST_ENUM__COUNT] = { + [TEST_ENUM_FIRST] = { "first", 0 }, + [TEST_ENUM_SECOND] = { "second", 1 }, + [TEST_ENUM_THIRD] = { "third", 2 }, + }; + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- first\n" + "- second\n" + "- third\n" + "...\n"; + static const struct target_struct { + enum test_enum seq[3]; + uint32_t seq_count; + } data = { + .seq = { TEST_ENUM_FIRST, TEST_ENUM_SECOND, TEST_ENUM_THIRD }, + .seq_count = CYAML_ARRAY_LEN(data.seq), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_ENUM(CYAML_FLAG_DEFAULT, *(data.seq), + strings, TEST_ENUM__COUNT), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of boolean values. + * + * \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_sequence_bool( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- true\n" + "- false\n" + "- true\n" + "- false\n" + "- true\n" + "- false\n" + "- true\n" + "- false\n" + "...\n"; + static const struct target_struct { + bool seq[8]; + uint32_t seq_count; + } data = { + .seq = { true, false, true, false, true, false, true, false }, + .seq_count = CYAML_ARRAY_LEN(data.seq), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_BOOL(CYAML_FLAG_DEFAULT, *(data.seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of flags. + * + * \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_sequence_flags( + 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[] = { + { "none", TEST_FLAGS_NONE }, + { "first", TEST_FLAGS_FIRST }, + { "second", TEST_FLAGS_SECOND }, + { "third", TEST_FLAGS_THIRD }, + { "fourth", TEST_FLAGS_FOURTH }, + { "fifth", TEST_FLAGS_FIFTH }, + { "sixth", TEST_FLAGS_SIXTH }, + }; + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- - second\n" + " - fifth\n" + " - 1024\n" + "- - first\n" + "- - fourth\n" + " - sixth\n" + "...\n"; + static const struct target_struct { + enum test_flags seq[3]; + uint32_t seq_count; + } data = { + .seq = { + TEST_FLAGS_SECOND | TEST_FLAGS_FIFTH | 1024, + TEST_FLAGS_FIRST, + TEST_FLAGS_FOURTH | TEST_FLAGS_SIXTH }, + .seq_count = CYAML_ARRAY_LEN(data.seq), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_FLAGS(CYAML_FLAG_DEFAULT, *(data.seq), + strings, CYAML_ARRAY_LEN(strings)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of strings. + * + * \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_sequence_string( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- This\n" + "- is\n" + "- merely\n" + "- a\n" + "- test\n" + "...\n"; + static const struct target_struct { + char seq[5][7]; + uint32_t seq_count; + } data = { + .seq = { + "This", + "is", + "merely", + "a", + "test", }, + .seq_count = CYAML_ARRAY_LEN(data.seq), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_DEFAULT, *(data.seq), 0, 6), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of strings. + * + * \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_sequence_string_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- This\n" + "- is\n" + "- merely\n" + "- a\n" + "- test\n" + "...\n"; + static const struct target_struct { + char *seq[5]; + uint32_t seq_count; + } data = { + .seq = { + "This", + "is", + "merely", + "a", + "test", }, + .seq_count = CYAML_ARRAY_LEN(data.seq), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_POINTER, *(data.seq), + 0, CYAML_UNLIMITED), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of mappings. + * + * \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_sequence_mapping( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct value_s { + short a; + long b; + }; + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- a: 123\n" + " b: 9999\n" + "- a: 4000\n" + " b: 62000\n" + "- a: 1\n" + " b: 765\n" + "...\n"; + static const struct target_struct { + struct value_s seq[3]; + uint32_t seq_count; + } data = { + .seq = { + [0] = { .a = 123, .b = 9999 }, + [1] = { .a = 4000, .b = 62000 }, + [2] = { .a = 1, .b = 765 }, }, + .seq_count = CYAML_ARRAY_LEN(data.seq), + }; + static const struct cyaml_schema_field test_mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, struct value_s, a), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, struct value_s, b), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT, + struct value_s, test_mapping_schema), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of mappings pointers. + * + * \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_sequence_mapping_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const struct value_s { + short a; + long b; + } v[3] = { + { .a = 123, .b = 9999, }, + { .a = 4000, .b = 62000, }, + { .a = 1, .b = 765, }, + }; + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- a: 123\n" + " b: 9999\n" + "- a: 4000\n" + " b: 62000\n" + "- a: 1\n" + " b: 765\n" + "...\n"; + static const struct target_struct { + const struct value_s *seq[3]; + uint32_t seq_count; + } data = { + .seq = { + [0] = &v[0], + [1] = &v[1], + [2] = &v[2], }, + .seq_count = CYAML_ARRAY_LEN(data.seq), + }; + static const struct cyaml_schema_field test_mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, struct value_s, a), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, struct value_s, b), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct value_s, test_mapping_schema), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of fixed-length sequences. + * + * \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_sequence_sequence_fixed_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- - 1\n" + " - 2\n" + " - 3\n" + "- - 4\n" + " - 5\n" + " - 6\n" + "- - 7\n" + " - 8\n" + " - 9\n" + "- - 10\n" + " - 11\n" + " - 12\n" + "...\n"; + static const struct target_struct { + int seq[4][3]; + uint32_t seq_count; + } data = { + .seq = { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 }, + { 10, 11, 12 }, + }, + .seq_count = CYAML_ARRAY_LEN(data.seq), + }; + static const struct cyaml_schema_value entry_schema_int = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, **(data.seq)), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_SEQUENCE_FIXED( + CYAML_FLAG_DEFAULT, **(data.seq), + &entry_schema_int, CYAML_ARRAY_LEN(*data.seq)) + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of fixed-length sequences pointers. + * + * \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_sequence_sequence_fixed_ptr_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const int v[4][3] = { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 }, + { 10, 11, 12 }, + }; + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- - 1\n" + " - 2\n" + " - 3\n" + "- - 4\n" + " - 5\n" + " - 6\n" + "- - 7\n" + " - 8\n" + " - 9\n" + "- - 10\n" + " - 11\n" + " - 12\n" + "...\n"; + static const struct target_struct { + const int *seq[4]; + uint32_t seq_count; + } data = { + .seq = { + v[0], + v[1], + v[2], + v[3], + }, + .seq_count = CYAML_ARRAY_LEN(data.seq), + }; + static const struct cyaml_schema_value entry_schema_int = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, **(data.seq)), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_SEQUENCE_FIXED( + CYAML_FLAG_POINTER, **(data.seq), + &entry_schema_int, CYAML_ARRAY_LEN(*v)) + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_DEFAULT, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 flattened sequence of fixed-length sequences. + * + * \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_sequence_sequence_fixed_flat_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- - 1\n" + " - 2\n" + " - 3\n" + "- - 4\n" + " - 5\n" + " - 6\n" + "- - 7\n" + " - 8\n" + " - 9\n" + "- - 10\n" + " - 11\n" + " - 12\n" + "...\n"; + static const struct target_struct { + int seq[12]; + uint32_t seq_count; + } data = { + .seq = { + 1, 2, 3, + 4, 5, 6, + 7, 8, 9, + 10, 11, 12, + }, + /* Note: count is count of entries of the outer sequence + * entries, so, 4, not 12. */ + .seq_count = CYAML_ARRAY_LEN(data.seq) / 3, + }; + static const struct cyaml_schema_value entry_schema_int = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_SEQUENCE_FIXED( + CYAML_FLAG_DEFAULT, int, + &entry_schema_int, 3), + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "sequence", + .value = { + .type = CYAML_SEQUENCE, + .flags = CYAML_FLAG_DEFAULT, + .data_size = sizeof(int[3]), + .sequence = { + .entry = &entry_schema, + .min = 0, + .max = CYAML_UNLIMITED, + } + }, + .data_offset = offsetof(struct target_struct, seq), + .count_size = sizeof(data.seq_count), + .count_offset = offsetof(struct target_struct, seq_count), + }, + CYAML_FIELD_END, + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer; + size_t len; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of integers. + * + * \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_sequence_ptr_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- 1\n" + "- 1\n" + "- 2\n" + "- 3\n" + "- 5\n" + "- 8\n" + "...\n"; + static const int seq[6] = { 1, 1, 2, 3, 5, 8 }; + static const struct target_struct { + const int *seq; + uint32_t seq_count; + } data = { + .seq = seq, + .seq_count = CYAML_ARRAY_LEN(seq), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, *(data.seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of unsigned integers. + * + * \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_sequence_ptr_uint( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- 1\n" + "- 1\n" + "- 2\n" + "- 3\n" + "- 5\n" + "- 8\n" + "...\n"; + static const unsigned seq[6] = { 1, 1, 2, 3, 5, 8 }; + static const struct target_struct { + const unsigned *seq; + uint32_t seq_count; + } data = { + .seq = seq, + .seq_count = CYAML_ARRAY_LEN(seq), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_UINT(CYAML_FLAG_DEFAULT, *(data.seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of enums. + * + * \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_sequence_ptr_enum( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + enum test_enum { + TEST_ENUM_FIRST, + TEST_ENUM_SECOND, + TEST_ENUM_THIRD, + TEST_ENUM__COUNT, + }; + static const cyaml_strval_t strings[TEST_ENUM__COUNT] = { + [TEST_ENUM_FIRST] = { "first", 0 }, + [TEST_ENUM_SECOND] = { "second", 1 }, + [TEST_ENUM_THIRD] = { "third", 2 }, + }; + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- first\n" + "- second\n" + "- third\n" + "...\n"; + static const enum test_enum seq[3] = { + TEST_ENUM_FIRST, + TEST_ENUM_SECOND, + TEST_ENUM_THIRD + }; + static const struct target_struct { + const enum test_enum *seq; + uint32_t seq_count; + } data = { + .seq = seq, + .seq_count = CYAML_ARRAY_LEN(seq), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_ENUM(CYAML_FLAG_DEFAULT, *(data.seq), + strings, TEST_ENUM__COUNT), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of boolean values. + * + * \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_sequence_ptr_bool( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- true\n" + "- false\n" + "- true\n" + "- false\n" + "- true\n" + "- false\n" + "- true\n" + "- false\n" + "...\n"; + static const bool seq[8] = { + true, false, true, false, + true, false, true, false + }; + static const struct target_struct { + const bool *seq; + uint32_t seq_count; + } data = { + .seq = seq, + .seq_count = CYAML_ARRAY_LEN(seq), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_BOOL(CYAML_FLAG_DEFAULT, *(data.seq)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of flags. + * + * \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_sequence_ptr_flags( + 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[] = { + { "none", TEST_FLAGS_NONE }, + { "first", TEST_FLAGS_FIRST }, + { "second", TEST_FLAGS_SECOND }, + { "third", TEST_FLAGS_THIRD }, + { "fourth", TEST_FLAGS_FOURTH }, + { "fifth", TEST_FLAGS_FIFTH }, + { "sixth", TEST_FLAGS_SIXTH }, + }; + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- - second\n" + " - fifth\n" + " - 1024\n" + "- - first\n" + "- - fourth\n" + " - sixth\n" + "...\n"; + static const enum test_flags seq[3] = { + TEST_FLAGS_SECOND | TEST_FLAGS_FIFTH | 1024, + TEST_FLAGS_FIRST, + TEST_FLAGS_FOURTH | TEST_FLAGS_SIXTH, + }; + static const struct target_struct { + const enum test_flags *seq; + uint32_t seq_count; + } data = { + .seq = seq, + .seq_count = CYAML_ARRAY_LEN(seq), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_FLAGS(CYAML_FLAG_DEFAULT, *(data.seq), + strings, CYAML_ARRAY_LEN(strings)), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of strings. + * + * \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_sequence_ptr_string( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- This\n" + "- is\n" + "- merely\n" + "- a\n" + "- test\n" + "...\n"; + static const char seq[][7] = { + "This", + "is", + "merely", + "a", + "test", + }; + static const struct target_struct { + const char (*seq)[7]; + uint32_t seq_count; + } data = { + .seq = seq, + .seq_count = CYAML_ARRAY_LEN(seq), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_DEFAULT, *(data.seq), 0, 6), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of strings. + * + * \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_sequence_ptr_string_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- This\n" + "- is\n" + "- merely\n" + "- a\n" + "- test\n" + "...\n"; + static const char *seq[] = { + "This", + "is", + "merely", + "a", + "test", + }; + static const struct target_struct { + const char **seq; + uint32_t seq_count; + } data = { + .seq = seq, + .seq_count = CYAML_ARRAY_LEN(seq), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_STRING(CYAML_FLAG_POINTER, *(data.seq), + 0, CYAML_UNLIMITED), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of mappings. + * + * \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_sequence_ptr_mapping( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + struct value_s { + short a; + long b; + }; + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- a: 123\n" + " b: 9999\n" + "- a: 4000\n" + " b: 62000\n" + "- a: 1\n" + " b: 765\n" + "...\n"; + static const struct value_s seq[3] = { + [0] = { .a = 123, .b = 9999 }, + [1] = { .a = 4000, .b = 62000 }, + [2] = { .a = 1, .b = 765 }, + }; + static const struct target_struct { + const struct value_s *seq; + uint32_t seq_count; + } data = { + .seq = seq, + .seq_count = CYAML_ARRAY_LEN(seq), + }; + static const struct cyaml_schema_field test_mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, struct value_s, a), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, struct value_s, b), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT, + struct value_s, test_mapping_schema), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of mappings pointers. + * + * \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_sequence_ptr_mapping_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const struct value_s { + short a; + long b; + } v[3] = { + { .a = 123, .b = 9999, }, + { .a = 4000, .b = 62000, }, + { .a = 1, .b = 765, }, + }; + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- a: 123\n" + " b: 9999\n" + "- a: 4000\n" + " b: 62000\n" + "- a: 1\n" + " b: 765\n" + "...\n"; + static const struct value_s *seq[3] = { + [0] = &v[0], + [1] = &v[1], + [2] = &v[2], + }; + static const struct target_struct { + const struct value_s **seq; + uint32_t seq_count; + } data = { + .seq = seq, + .seq_count = CYAML_ARRAY_LEN(seq), + }; + static const struct cyaml_schema_field test_mapping_schema[] = { + CYAML_FIELD_INT("a", CYAML_FLAG_DEFAULT, struct value_s, a), + CYAML_FIELD_INT("b", CYAML_FLAG_DEFAULT, struct value_s, b), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct value_s, test_mapping_schema), + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of fixed-length sequences. + * + * \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_sequence_ptr_sequence_fixed_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- - 1\n" + " - 2\n" + " - 3\n" + "- - 4\n" + " - 5\n" + " - 6\n" + "- - 7\n" + " - 8\n" + " - 9\n" + "- - 10\n" + " - 11\n" + " - 12\n" + "...\n"; + static const int seq[4][3] = { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 }, + { 10, 11, 12 }, + }; + static const struct target_struct { + const int (*seq)[3]; + uint32_t seq_count; + } data = { + .seq = seq, + .seq_count = CYAML_ARRAY_LEN(seq), + }; + static const struct cyaml_schema_value entry_schema_int = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, **(data.seq)), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_SEQUENCE_FIXED( + CYAML_FLAG_DEFAULT, **(data.seq), + &entry_schema_int, CYAML_ARRAY_LEN(*data.seq)) + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence of fixed-length sequences pointers. + * + * \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_sequence_ptr_sequence_fixed_ptr_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const int v[4][3] = { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 }, + { 10, 11, 12 }, + }; + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- - 1\n" + " - 2\n" + " - 3\n" + "- - 4\n" + " - 5\n" + " - 6\n" + "- - 7\n" + " - 8\n" + " - 9\n" + "- - 10\n" + " - 11\n" + " - 12\n" + "...\n"; + static const int *seq[] = { + v[0], + v[1], + v[2], + v[3], + }; + static const struct target_struct { + const int **seq; + uint32_t seq_count; + } data = { + .seq = seq, + .seq_count = CYAML_ARRAY_LEN(seq), + }; + static const struct cyaml_schema_value entry_schema_int = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, **(data.seq)), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_SEQUENCE_FIXED( + CYAML_FLAG_POINTER, **(data.seq), + &entry_schema_int, CYAML_ARRAY_LEN(*v)) + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_SEQUENCE("sequence", CYAML_FLAG_POINTER, + struct target_struct, seq, &entry_schema, + 0, CYAML_ARRAY_LEN(ref)), + 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 = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 flattened sequence of fixed-length sequences. + * + * \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_sequence_ptr_sequence_fixed_flat_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "sequence:\n" + "- - 1\n" + " - 2\n" + " - 3\n" + "- - 4\n" + " - 5\n" + " - 6\n" + "- - 7\n" + " - 8\n" + " - 9\n" + "- - 10\n" + " - 11\n" + " - 12\n" + "...\n"; + static const int seq[12] = { + 1, 2, 3, + 4, 5, 6, + 7, 8, 9, + 10, 11, 12, + }; + static const struct target_struct { + const int *seq; + uint32_t seq_count; + } data = { + .seq = seq, + /* Note: count is count of entries of the outer sequence + * entries, so, 4, not 12. */ + .seq_count = CYAML_ARRAY_LEN(seq) / 3, + }; + static const struct cyaml_schema_value entry_schema_int = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int), + }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_SEQUENCE_FIXED( + CYAML_FLAG_DEFAULT, int, + &entry_schema_int, 3), + }; + static const struct cyaml_schema_field mapping_schema[] = { + { + .key = "sequence", + .value = { + .type = CYAML_SEQUENCE, + .flags = CYAML_FLAG_POINTER, + .data_size = sizeof(int[3]), + .sequence = { + .entry = &entry_schema, + .min = 0, + .max = CYAML_UNLIMITED, + } + }, + .data_offset = offsetof(struct target_struct, seq), + .count_size = sizeof(data.seq_count), + .count_offset = offsetof(struct target_struct, seq_count), + }, + CYAML_FIELD_END, + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer; + size_t len; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 optional mapping field. + * + * \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_optional_uint( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "test_uint: 555\n" + "...\n"; + static const struct target_struct { + unsigned test_uint; + } data = { + .test_uint = 555, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("test_uint", CYAML_FLAG_OPTIONAL, + struct target_struct, test_uint), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 optional mapping field with pointer 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_save_mapping_entry_optional_uint_ptr( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "test_uint: 555\n" + "...\n"; + static unsigned test_uint = 555; + static const struct target_struct { + unsigned *test_uint; + } data = { + .test_uint = &test_uint, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT_PTR("test_uint", + CYAML_FLAG_OPTIONAL | CYAML_FLAG_POINTER, + struct target_struct, test_uint), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 optional mapping field with NULL pointer 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_save_mapping_entry_optional_uint_ptr_null( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "--- {}\n" + "...\n"; + static const struct target_struct { + unsigned *test_uint; + } data = { + .test_uint = NULL, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT_PTR("test_uint", + CYAML_FLAG_OPTIONAL | CYAML_FLAG_POINTER, + struct target_struct, test_uint), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 an unsigned integer. + * + * \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_ignored( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "test_uint: 555\n" + "...\n"; + static const struct target_struct { + unsigned test_uint; + } data = { + .test_uint = 555, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("test_uint", CYAML_FLAG_DEFAULT, + struct target_struct, test_uint), + CYAML_FIELD_IGNORE("foo", CYAML_FLAG_DEFAULT), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence containing a NULL 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_save_sequence_null_values_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "- 7\n" + "- 6\n" + "- 5\n" + "- \n" + "- 3\n" + "- 2\n" + "- \n" + "- 0\n" + "...\n"; + static const int d[] = { 7, 6, 5, 4, 3, 2, 1, 0 }; + static const int *data[] = { d + 0, d + 1, d + 2, NULL, + d + 4, d + 5, NULL, d + 7, }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_POINTER_NULL, int) + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, int, + &entry_schema, 0, 3), + }; + char *buffer = NULL; + size_t len = 0; + cyaml_config_t cfg = *config; + test_data_t td = { + .buffer = &buffer, + .config = &cfg, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_STYLE_BLOCK; + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, &cfg, &top_schema, data, + CYAML_ARRAY_LEN(data)); + 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 sequence containing a NULL 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_save_sequence_null_str_values_int( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "- 7\n" + "- 6\n" + "- 5\n" + "- null\n" + "- 3\n" + "- 2\n" + "- 1\n" + "- 0\n" + "...\n"; + static const int d[] = { 7, 6, 5, 4, 3, 2, 1, 0 }; + static const int *data[] = { d + 0, d + 1, d + 2, NULL, + d + 4, d + 5, d + 6, d + 7, }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_POINTER_NULL_STR, int) + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, int, + &entry_schema, 0, 3), + }; + char *buffer = NULL; + size_t len = 0; + cyaml_config_t cfg = *config; + test_data_t td = { + .buffer = &buffer, + .config = &cfg, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_STYLE_BLOCK; + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, &cfg, &top_schema, data, + CYAML_ARRAY_LEN(data)); + 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 with schema with sequence_fixed top level type. + * + * \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_schema_top_level_sequence_fixed( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "- 7\n" + "- 6\n" + "- 5\n" + "...\n"; + int data[3] = { 7, 6, 5 }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int) + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE_FIXED(CYAML_FLAG_POINTER, int, + &entry_schema, 3) + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 sequence with flow style configured. + * + * \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_sequence_config_flow_style( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "--- [7, 6, 5]\n" + "...\n"; + int data[3] = { 7, 6, 5 }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int) + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, int, + &entry_schema, 0, 3), + }; + char *buffer = NULL; + size_t len = 0; + cyaml_config_t cfg = *config; + test_data_t td = { + .buffer = &buffer, + .config = &cfg, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_STYLE_FLOW; + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, &cfg, &top_schema, data, 3); + 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 sequence with block style configured. + * + * \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_sequence_config_block_style( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "- 7\n" + "- 6\n" + "- 5\n" + "...\n"; + int data[3] = { 7, 6, 5 }; + static const struct cyaml_schema_value entry_schema = { + CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int) + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_SEQUENCE(CYAML_FLAG_POINTER, int, + &entry_schema, 0, 3), + }; + char *buffer = NULL; + size_t len = 0; + cyaml_config_t cfg = *config; + test_data_t td = { + .buffer = &buffer, + .config = &cfg, + }; + cyaml_err_t err; + + cfg.flags |= CYAML_CFG_STYLE_BLOCK; + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + err = cyaml_save_data(&buffer, &len, &cfg, &top_schema, data, 3); + 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 mapping with flow style 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_save_mapping_value_flow_style( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const char ref[] = + "--- {a: 555, b: 99, c: 7}\n" + "...\n"; + static const struct target_struct { + unsigned a; + unsigned b; + unsigned c; + } data = { + .a = 555, + .b = 99, + .c = 7, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("a", CYAML_FLAG_DEFAULT, + struct target_struct, a), + CYAML_FIELD_UINT("b", CYAML_FLAG_DEFAULT, + struct target_struct, b), + CYAML_FIELD_UINT("c", CYAML_FLAG_DEFAULT, + struct target_struct, c), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER | CYAML_FLAG_FLOW, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + cyaml_config_t cfg = *config; + test_data_t td = { + .buffer = &buffer, + .config = &cfg, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 mapping with block style 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_save_mapping_value_block_style( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "---\n" + "a: 555\n" + "b: 99\n" + "c: 7\n" + "...\n"; + static const struct target_struct { + unsigned a; + unsigned b; + unsigned c; + } data = { + .a = 555, + .b = 99, + .c = 7, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("a", CYAML_FLAG_DEFAULT, + struct target_struct, a), + CYAML_FIELD_UINT("b", CYAML_FLAG_DEFAULT, + struct target_struct, b), + CYAML_FIELD_UINT("c", CYAML_FLAG_DEFAULT, + struct target_struct, c), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER | CYAML_FLAG_BLOCK, + struct target_struct, mapping_schema), + }; + char *buffer = NULL; + size_t len = 0; + test_data_t td = { + .buffer = &buffer, + .config = config, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + 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 without document delimiters. + * + * \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_no_document_delimiters( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + static const unsigned char ref[] = + "test_uint: 555\n"; + static const struct target_struct { + unsigned test_uint; + } data = { + .test_uint = 555, + }; + static const struct cyaml_schema_field mapping_schema[] = { + CYAML_FIELD_UINT("test_uint", CYAML_FLAG_DEFAULT, + struct target_struct, test_uint), + CYAML_FIELD_END + }; + static const struct cyaml_schema_value top_schema = { + CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, + struct target_struct, mapping_schema), + }; + cyaml_config_t cfg = *config; + char *buffer; + size_t len; + test_data_t td = { + .buffer = &buffer, + .config = &cfg, + }; + cyaml_err_t err; + + ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td); + + cfg.flags &= ~((unsigned)CYAML_CFG_DOCUMENT_DELIM); + err = cyaml_save_data(&buffer, &len, &cfg, &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); +} + +/** + * Run the YAML saving 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 save_tests( + ttest_report_ctx_t *rc, + cyaml_log_t log_level, + cyaml_log_fn_t log_fn) +{ + bool pass = true; + cyaml_config_t config = { + .log_fn = log_fn, + .mem_fn = cyaml_mem, + .log_level = log_level, + .flags = CYAML_CFG_DOCUMENT_DELIM, + }; + + ttest_heading(rc, "Save single entry mapping tests: simple types"); + + pass &= test_save_mapping_entry_uint(rc, &config); + 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_int_64(rc, &config); + pass &= test_save_mapping_entry_int_pos(rc, &config); + pass &= test_save_mapping_entry_int_neg(rc, &config); + pass &= test_save_mapping_entry_bool_true(rc, &config); + pass &= test_save_mapping_entry_bool_false(rc, &config); + pass &= test_save_mapping_entry_string_ptr(rc, &config); + pass &= test_save_mapping_entry_enum_strict(rc, &config); + pass &= test_save_mapping_entry_enum_number(rc, &config); + pass &= test_save_mapping_entry_enum_sparse(rc, &config); + + ttest_heading(rc, "Save single entry mapping tests: complex types"); + + pass &= test_save_mapping_entry_mapping(rc, &config); + pass &= test_save_mapping_entry_bitfield(rc, &config); + pass &= test_save_mapping_entry_mapping_ptr(rc, &config); + pass &= test_save_mapping_entry_flags_strict(rc, &config); + pass &= test_save_mapping_entry_flags_number(rc, &config); + pass &= test_save_mapping_entry_flags_sparse(rc, &config); + pass &= test_save_mapping_entry_bitfield_sparse(rc, &config); + + ttest_heading(rc, "Save single entry mapping tests: sequences"); + + pass &= test_save_mapping_entry_sequence_int(rc, &config); + pass &= test_save_mapping_entry_sequence_uint(rc, &config); + pass &= test_save_mapping_entry_sequence_enum(rc, &config); + pass &= test_save_mapping_entry_sequence_bool(rc, &config); + pass &= test_save_mapping_entry_sequence_flags(rc, &config); + pass &= test_save_mapping_entry_sequence_string(rc, &config); + pass &= test_save_mapping_entry_sequence_mapping(rc, &config); + pass &= test_save_mapping_entry_sequence_string_ptr(rc, &config); + pass &= test_save_mapping_entry_sequence_mapping_ptr(rc, &config); + pass &= test_save_mapping_entry_sequence_sequence_fixed_int(rc, &config); + pass &= test_save_mapping_entry_sequence_sequence_fixed_ptr_int(rc, &config); + pass &= test_save_mapping_entry_sequence_sequence_fixed_flat_int(rc, &config); + + ttest_heading(rc, "Save single entry mapping tests: ptr sequences"); + + pass &= test_save_mapping_entry_sequence_ptr_int(rc, &config); + pass &= test_save_mapping_entry_sequence_ptr_enum(rc, &config); + pass &= test_save_mapping_entry_sequence_ptr_uint(rc, &config); + pass &= test_save_mapping_entry_sequence_ptr_bool(rc, &config); + pass &= test_save_mapping_entry_sequence_ptr_flags(rc, &config); + pass &= test_save_mapping_entry_sequence_ptr_string(rc, &config); + pass &= test_save_mapping_entry_sequence_ptr_mapping(rc, &config); + pass &= test_save_mapping_entry_sequence_ptr_string_ptr(rc, &config); + pass &= test_save_mapping_entry_sequence_ptr_mapping_ptr(rc, &config); + pass &= test_save_mapping_entry_sequence_ptr_sequence_fixed_int(rc, &config); + pass &= test_save_mapping_entry_sequence_ptr_sequence_fixed_ptr_int(rc, &config); + pass &= test_save_mapping_entry_sequence_ptr_sequence_fixed_flat_int(rc, &config); + + ttest_heading(rc, "Save tests: ptr sequence with null values"); + + pass &= test_save_sequence_null_values_int(rc, &config); + pass &= test_save_sequence_null_str_values_int(rc, &config); + + ttest_heading(rc, "Save tests: optional mapping fields"); + + pass &= test_save_mapping_entry_optional_uint(rc, &config); + pass &= test_save_mapping_entry_optional_uint_ptr(rc, &config); + pass &= test_save_mapping_entry_optional_uint_ptr_null(rc, &config); + + ttest_heading(rc, "Save tests: various"); + + pass &= test_save_mapping_entry_ignored(rc, &config); + pass &= test_save_no_document_delimiters(rc, &config); + pass &= test_save_mapping_value_flow_style(rc, &config); + pass &= test_save_mapping_value_block_style(rc, &config); + pass &= test_save_sequence_config_flow_style(rc, &config); + pass &= test_save_sequence_config_block_style(rc, &config); + pass &= test_save_schema_top_level_sequence_fixed(rc, &config); + + return pass; +} diff --git a/test/units/test.c b/test/units/test.c new file mode 100644 index 0000000..21ea6e3 --- /dev/null +++ b/test/units/test.c @@ -0,0 +1,120 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2017-2018 Michael Drake + */ + +#include +#include +#include +#include +#include + +#include + +#include "ttest.h" + +/** In load.c */ +extern bool load_tests( + ttest_report_ctx_t *rc, + cyaml_log_t log_level, + cyaml_log_fn_t log_fn); + +/** In file.c */ +extern bool file_tests( + ttest_report_ctx_t *rc, + cyaml_log_t log_level, + cyaml_log_fn_t log_fn); + +/** In free.c */ +extern bool free_tests( + ttest_report_ctx_t *rc, + cyaml_log_t log_level, + cyaml_log_fn_t log_fn); + +/** In utf8.c */ +extern bool utf8_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, + cyaml_log_t log_level, + cyaml_log_fn_t log_fn); + +/** In errs.c */ +extern bool errs_tests( + ttest_report_ctx_t *rc, + cyaml_log_t log_level, + cyaml_log_fn_t log_fn); + +/** In save.c */ +extern bool save_tests( + ttest_report_ctx_t *rc, + cyaml_log_t log_level, + cyaml_log_fn_t log_fn); + +/** + * Print program usage + * + * \param[in] prog_name Name of program. + */ +void usage(const char *prog_name) +{ + fprintf(stderr, "Usage: %s [-q|-v|-d]\n", prog_name); +} + +/** + * Main entry point from OS. + * + * \param[in] argc Argument count. + * \param[in] argv Vector of string arguments. + * \return Program return code. + */ +int main(int argc, char *argv[]) +{ + bool pass = true; + bool quiet = false; + ttest_report_ctx_t rc; + cyaml_log_fn_t log_fn = cyaml_log; + cyaml_log_t log_level = CYAML_LOG_ERROR; + enum { + ARG_PROG_NAME, + ARG_VERBOSE, + ARG__COUNT, + }; + + if (argc > ARG__COUNT) { + usage(argv[ARG_PROG_NAME]); + return EXIT_FAILURE; + + } else if (argc > ARG_VERBOSE) { + if (strcmp(argv[ARG_VERBOSE], "-q") == 0) { + quiet = true; + log_fn = NULL; + } else if (strcmp(argv[ARG_VERBOSE], "-v") == 0) { + log_level = CYAML_LOG_INFO; + } else if (strcmp(argv[ARG_VERBOSE], "-d") == 0) { + log_level = CYAML_LOG_DEBUG; + } else { + usage(argv[ARG_PROG_NAME]); + return EXIT_FAILURE; + } + } + + rc = ttest_init(quiet); + + pass &= utf8_tests(&rc, log_level, log_fn); + pass &= util_tests(&rc, log_level, log_fn); + pass &= free_tests(&rc, log_level, log_fn); + pass &= load_tests(&rc, log_level, log_fn); + pass &= errs_tests(&rc, log_level, log_fn); + pass &= file_tests(&rc, log_level, log_fn); + pass &= save_tests(&rc, log_level, log_fn); + + ttest_report(&rc); + + return (pass) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/test/units/ttest.h b/test/units/ttest.h new file mode 100644 index 0000000..9c0c283 --- /dev/null +++ b/test/units/ttest.h @@ -0,0 +1,232 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2013 Michael Drake + */ + +#ifndef TTEST_H +#define TTEST_H + +/** + * Test cleanup client callback. + * + * The caller passes this callback function to ttest_start, and it is called + * in whichever tlsa-test function finishes the test (ttest_pass, ttest_fail, + * or ttest_todo). + * + * \param[in] Client data to clean up. + */ +typedef void (*ttest_cleanup_fn)(void *data); + +/** + * Internal tlsa-test report context. + * + * Clients should not touch this directly, it is only exposed in this + * header because tlsa-test is arranged to be header-only, for convenience + * of utilisation. + */ +typedef struct ttest_report_ctx { + bool quiet; /**< Whether to print only the report summary. */ + unsigned tests; /**< Number of tests started. */ + unsigned todo; /**< Number of tests marked as unimplemented. */ + unsigned passed; /**< Number of tests passed. */ +} ttest_report_ctx_t; + +/** + * Internal tlsa-test test context. + * + * Clients should not touch this directly, it is only exposed in this + * header because tlsa-test is arranged to be header-only, for convenience + * of utilisation. + */ +typedef struct ttest_ctx { + ttest_report_ctx_t *report; /**< The tlsa-test report context. */ + const char *name; /**< The unit test name. */ + ttest_cleanup_fn cleanup; /**< Client's unit test cleanup function. */ + void *cleanup_data; /**< Client's unit test cleanup context. */ +} ttest_ctx_t; + +/** + * Initialise a tlsa-test report context. + * + * \param[in] quiet Whether report should be a minimal summary. + * \return initialised tlsa-test report context. + */ +static inline ttest_report_ctx_t ttest_init( + bool quiet) +{ + ttest_report_ctx_t rc = { + .quiet = quiet, + }; + + return rc; +} + +/** + * Start a unit test. + * + * The when complete, the test must be competed by calling either ttest_pass, + * ttest_fail, or ttest_todo. + * + * \param[in] report The tlsa-test report context. + * \param[in] name Name for this unit test. + * \param[in] cleanup Cleanup function to call on test completion. + * \param[in] cleanup_data Pointer to client cleanup context. + * \return Returns the unit test context. The test context must be passed to + * the test completion function. + */ +static inline ttest_ctx_t ttest_start( + ttest_report_ctx_t *report, + const char *name, + ttest_cleanup_fn cleanup, + void *cleanup_data) +{ + ttest_ctx_t tc = { + .name = name, + .report = report, + .cleanup = cleanup, + .cleanup_data = cleanup_data, + }; + + report->tests++; + + return tc; +} + +/** + * Pass a unit test. + * + * This function competes a unit test. It should be called when a test passes. + * This function always returns true. + * + * \param[in] tc Unit test context, returned by ttest_start. + * \return true. + */ +static inline bool ttest_pass( + const ttest_ctx_t *tc) +{ + assert(tc != NULL); + assert(tc->report != NULL); + + tc->report->passed++; + + if (tc->cleanup != NULL) { + tc->cleanup(tc->cleanup_data); + } + + if (tc->report->quiet == false) { + fprintf(stderr, " PASS: %s\n", tc->name); + } + + return true; +} + +/** + * Fail a unit test. + * + * This function competes a unit test. It should be called when a test fails. + * This function always returns false. Prints the test result. + * + * \param[in] tc Unit test context, returned by ttest_start. + * \param[in] reason Format string to explain reason for test failure. + * \param[in] ... Additional arguments for formatted rendering. + * \return false. + */ +static inline bool ttest_fail( + const ttest_ctx_t *tc, + const char *reason, ...) +{ + va_list args; + + assert(tc != NULL); + assert(tc->report != NULL); + + fprintf(stderr, " FAIL: %s (", tc->name); + va_start(args, reason); + vfprintf(stderr, reason, args); + va_end(args); + fprintf(stderr, ")\n"); + + /* Cleanup after printing result, in case `reason` refers to cleaned up + * memory. */ + if (tc->cleanup != NULL) { + tc->cleanup(tc->cleanup_data); + } + + return false; +} + +/** + * Make a unit test as unimplemented. + * + * This function competes a unit test. Should be called on unimplemented tests. + * This function always returns true. + * + * \param[in] tc Unit test context, returned by ttest_start. + * \return true. + */ +static inline bool ttest_todo( + const ttest_ctx_t *tc) +{ + assert(tc != NULL); + assert(tc->report != NULL); + + tc->report->todo++; + + if (tc->cleanup != NULL) { + tc->cleanup(tc->cleanup_data); + } + + if (tc->report->quiet == false) { + fprintf(stderr, " TODO: %s\n", tc->name); + } + + return true; +} + +/** + * Print a visual divider in the test output. + */ +static inline void ttest_divider(void) +{ + fprintf(stderr, "========================================" + "========================================\n"); +} + +/** + * Print a test heading. + * + * \param[in] tr The tlsa-test report context. + * \param[in] heading The heading to print. + */ +static inline void ttest_heading( + const ttest_report_ctx_t *tr, + const char *heading) +{ + if (!tr->quiet) { + ttest_divider(); + fprintf(stderr, "TEST: %s\n", heading); + ttest_divider(); + } +} + +/** + * Print the test report summary. + * + * \param[in] tr The tlsa-test report context. + */ +static inline void ttest_report( + const ttest_report_ctx_t *tr) +{ + ttest_divider(); + if (tr->todo > 0) { + fprintf(stderr, "TODO: %u test%s unimplemented.\n", + tr->todo, (tr->todo > 1) ? "s" : ""); + } + fprintf(stderr, "%s: %u of %u tests passed.\n", + (tr->passed == tr->tests - tr->todo) ? "PASS" : "FAIL", + tr->passed, tr->tests - tr->todo); + ttest_divider(); +} + +#endif diff --git a/test/units/utf8.c b/test/units/utf8.c new file mode 100644 index 0000000..2e57d62 --- /dev/null +++ b/test/units/utf8.c @@ -0,0 +1,271 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2018 Michael Drake + */ + +#include +#include +#include + +#include + +#include "../../src/utf8.h" + +#include "ttest.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) + +/** + * Test utf-8 decoding. + * + * \param[in] report The test report context. + * \return true if test passes, false otherwise. + */ +static bool test_utf8_get_codepoint( + ttest_report_ctx_t *report) +{ + static const struct tests { + unsigned c; + const char *s; + unsigned l; + } t[] = { + { 0xfffd, "\ufffd", SLEN("\ufffd") }, + { 0xfffd, "\xC1\x9C", SLEN("\xC1\x9C") }, + { 0x1f638, u8"😸", SLEN(u8"😸") }, + { 0xfffd, u8"😸", 0 }, + { 0xfffd, u8"😸", 5 }, + }; + bool pass = true; + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(t); i++) { + unsigned l; + unsigned c; + ttest_ctx_t tc; + char name[sizeof(__func__) + 32]; + sprintf(name, "%s_%u", __func__, i); + + tc = ttest_start(report, name, NULL, NULL); + + l = t[i].l; + c = cyaml_utf8_get_codepoint((uint8_t *)t[i].s, &l); + if (c != t[i].c) { + pass &= ttest_fail(&tc, "Incorrect codepoint for %s " + "(expecting %4.4x, got %4.4x)", + t[i].s, t[i].c, c); + continue; + } + + pass &= ttest_pass(&tc); + } + + return pass; +} + +/** + * Test comparing the same strings. + * + * \param[in] report The test report context. + * \return true if test passes, false otherwise. + */ +static bool test_utf8_strcmp_same( + ttest_report_ctx_t *report) +{ + const char *strings[] = { + "Simple", + "test", + "This is a LONGER string, if you see what I mean.", + "29087 lsdkfj ", + u8"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß", + u8"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ", + u8"¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿", + "\xc3\0", + u8"αβγδε", + u8"¯\\_(ツ)_/¯", + "\xfa", + u8"😸", + }; + bool pass = true; + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(strings); i++) { + ttest_ctx_t tc; + char name[sizeof(__func__) + 32]; + sprintf(name, "%s_%u", __func__, i); + + tc = ttest_start(report, name, NULL, NULL); + + if (cyaml_utf8_casecmp(strings[i], strings[i]) != 0) { + pass &= ttest_fail(&tc, "Failed to match: %s", + strings[i]); + continue; + } + + pass &= ttest_pass(&tc); + } + + return pass; +} + +/** + * Test comparing strings that match. + * + * \param[in] report The test report context. + * \return true if test passes, false otherwise. + */ +static bool test_utf8_strcmp_matches( + ttest_report_ctx_t *report) +{ + static const struct string_pairs { + const char *a; + const char *b; + } pairs[] = { + { "", "" }, + { "This is a TEST", "this is A test" }, + { u8"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ", u8"àáâãäåæçèéêëìíîïðñòóôõö" }, + { u8"ĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞ", u8"āăąćĉċčďđēĕėęěĝğ" }, + { u8"ijĵķĸĺļľ", u8"IJĴĶĸĹĻĽ" }, + { u8"ŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶ", u8"ŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷ" }, + { u8"űųŵŷŸźżž", u8"űųŵŷÿŹŻŽ" }, + { u8"ƂƄ ơƣƥ", u8"ƃƅ ƠƢƤ" }, + { u8"ǞǠǢǤǦǨǪǬǮ", u8"ǟǡǣǥǧǩǫǭǯ" }, + { u8"ǸǺǼǾȀȂȄȆȈȊȌȎȐȒȔȖȘȚȜȞ", u8"ǹǻǽǿȁȃȅȇȉȋȍȏȑȓȕȗșțȝȟ" }, + { u8"ȢȤȦȨȪȬȮȰȲ", u8"ȣȥȧȩȫȭȯȱȳ" }, + { u8"ɇɉɋɍɏ", u8"ɆɈɊɌɎ" }, + { u8"ƯźżžƳƵ", u8"ưŹŻŽƴƶ" }, + { u8"ǍǏǑǓǕǗǙǛ", u8"ǎǐǒǔǖǘǚǜ" }, + { u8"\u0178", u8"\u00ff" }, + { u8"\u0187", u8"\u0188" }, + { u8"\u018b", u8"\u018c" }, + { u8"\u018e", u8"\u01dd" }, + { u8"\u0191", u8"\u0192" }, + { u8"\u0198", u8"\u0199" }, + { u8"\u01a7", u8"\u01a8" }, + { u8"\u01ac", u8"\u01ad" }, + { u8"\u01af", u8"\u01b0" }, + { u8"\u01b7", u8"\u0292" }, + { u8"\u01b8", u8"\u01b9" }, + { u8"\u01bc", u8"\u01bd" }, + { u8"\u01c4", u8"\u01c6" }, + { u8"\u01c5", u8"\u01c6" }, + { u8"\u01c7", u8"\u01c9" }, + { u8"\u01c8", u8"\u01c9" }, + { u8"\u01ca", u8"\u01cc" }, + { u8"\u01cb", u8"\u01cc" }, + { u8"\u01f1", u8"\u01f3" }, + { u8"\u01f2", u8"\u01f3" }, + { u8"\u01f4", u8"\u01f5" }, + { u8"\u01f7", u8"\u01bf" }, + { u8"\u0220", u8"\u019e" }, + { u8"\u023b", u8"\u023c" }, + { u8"\u023d", u8"\u019a" }, + { u8"\u0241", u8"\u0242" }, + { u8"\u0243", u8"\u0180" }, + { "\xF0\x9F\x98\xB8", "\xF0\x9F\x98\xB8" }, + { "\xF0\x00\x98\xB8", "\xF0\x00\x98\xB8" }, + { "\xF0\x9F\x00\xB8", "\xF0\x9F\x00\xB8" }, + { "\xF0\x9F\x98\x00", "\xF0\x9F\x98\x00" }, + { "\xE2\x9F\x9A", "\xE2\x9F\x9A" }, + { "\xE2\x00\x9A", "\xE2\x00\x9A" }, + { "\xE2\x9F\x00", "\xE2\x9F\x00" }, + { "A\xc2""C", "A\xc2""C" }, + { "A\xc2""C", u8"A\ufffdC" }, + { u8"A\ufffdC", "A\xc2""C" }, + }; + bool pass = true; + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(pairs); i++) { + ttest_ctx_t tc; + char name[sizeof(__func__) + 32]; + sprintf(name, "%s_%u", __func__, i); + + tc = ttest_start(report, name, NULL, NULL); + + if (cyaml_utf8_casecmp(pairs[i].a, pairs[i].b) != 0) { + pass &= ttest_fail(&tc, "Failed to match strings: " + "%s and %s", pairs[i].a, pairs[i].b); + continue; + } + + pass &= ttest_pass(&tc); + } + + return pass; +} + +/** + * Test comparing strings that match. + * + * \param[in] report The test report context. + * \return true if test passes, false otherwise. + */ +static bool test_utf8_strcmp_mismatches( + ttest_report_ctx_t *report) +{ + static const struct string_pairs { + const char *a; + const char *b; + } pairs[] = { + { "Invalid", "\xfa" }, + { "Cat", u8"😸" }, + { "cat", u8"😸" }, + { "1 cat", u8"😸" }, + { "[cat]", u8"😸" }, + { "Ü cat", u8"😸" }, + { "Ü cat", u8"😸" }, + { "\\", "\xC1\x9C" }, + }; + bool pass = true; + + for (unsigned i = 0; i < CYAML_ARRAY_LEN(pairs); i++) { + ttest_ctx_t tc; + char name[sizeof(__func__) + 32]; + sprintf(name, "%s_%u", __func__, i); + + tc = ttest_start(report, name, NULL, NULL); + + if (cyaml_utf8_casecmp(pairs[i].a, pairs[i].b) == 0) { + pass &= ttest_fail(&tc, "Failed to detect mismatch: " + "%s and %s", pairs[i].a, pairs[i].b); + continue; + } + + pass &= ttest_pass(&tc); + } + + return pass; +} + +/** + * Run the CYAML util 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 utf8_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, "UTF-8 tests: Codepoint composition"); + + pass &= test_utf8_get_codepoint(rc); + + ttest_heading(rc, "UTF-8 tests: String comparison"); + + pass &= test_utf8_strcmp_same(rc); + pass &= test_utf8_strcmp_matches(rc); + pass &= test_utf8_strcmp_mismatches(rc); + + return pass; +} diff --git a/test/units/util.c b/test/units/util.c new file mode 100644 index 0000000..e9eba97 --- /dev/null +++ b/test/units/util.c @@ -0,0 +1,246 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (C) 2018 Michael Drake + */ + +#include +#include +#include +#include + +#include + +#include "../../src/mem.h" +#include "../../src/util.h" + +#include "ttest.h" + +/** + * Test cyaml memory functions. + * + * \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_util_memory_funcs( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + ttest_ctx_t tc = ttest_start(report, __func__, NULL, NULL); + unsigned char *mem, *tmp; + + /* Create test allocation */ + mem = cyaml__alloc(config, 0xff, true); + if (mem == NULL) { + return ttest_fail(&tc, "Memory allocation failed."); + } + + /* Check allocation was zeroed. */ + for (unsigned i = 0; i < 0x7f; i++) { + if (mem[i] != 0) { + return ttest_fail(&tc, "Allocation not cleaned."); + } + } + + /* Set our own known values */ + for (unsigned i = 0; i < 0xff; i++) { + mem[i] = 0xff; + } + + /* Shrink allocation */ + tmp = cyaml__realloc(config, mem, 0xff, 0x7f, true); + if (tmp == NULL) { + return ttest_fail(&tc, "Allocation shrink failed."); + } + mem = tmp; + + /* Check for our own values. */ + for (unsigned i = 0; i < 0x7f; i++) { + if (mem[i] != 0xff) { + return ttest_fail(&tc, "Data trampled by realloc."); + } + } + + /* Free test allocation. */ + cyaml__free(config, mem); + + return ttest_pass(&tc); +} + +/** + * Test cyaml memory functions. + * + * \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_util_strdup( + ttest_report_ctx_t *report, + const cyaml_config_t *config) +{ + ttest_ctx_t tc = ttest_start(report, __func__, NULL, NULL); + const char *orig = "Hello!"; + char *copy; + + /* Create test allocation */ + copy = cyaml__strdup(config, orig); + if (copy == NULL) { + return ttest_fail(&tc, "Memory allocation failed."); + } + + /* Confirm string duplication. */ + if (strcmp(orig, copy) != 0) { + return ttest_fail(&tc, "String not copied correctly."); + } + + /* Free test allocation. */ + cyaml__free(config, copy); + + return ttest_pass(&tc); +} + +/** + * Test invalid state machine state. + * + * \param[in] report The test report context. + * \return true if test passes, false otherwise. + */ +static bool test_util_state_invalid( + ttest_report_ctx_t *report) +{ + ttest_ctx_t tc = ttest_start(report, __func__, NULL, NULL); + + if (strcmp(cyaml__state_to_str(CYAML_STATE__COUNT), + "") != 0) { + return ttest_fail(&tc, "Wrong string for invalid state."); + } + + if (strcmp(cyaml__state_to_str(CYAML_STATE__COUNT + 1), + "") != 0) { + return ttest_fail(&tc, "Wrong string for invalid state."); + } + + return ttest_pass(&tc); +} + +/** + * Test CYAML_OK error code. + * + * \param[in] report The test report context. + * \return true if test passes, false otherwise. + */ +static bool test_util_err_success_zero( + ttest_report_ctx_t *report) +{ + ttest_ctx_t tc = ttest_start(report, __func__, NULL, NULL); + + if (CYAML_OK != 0) { + return ttest_fail(&tc, "CYAML_OK value not zero"); + } + + return ttest_pass(&tc); +} + +/** + * Test valid cyaml_strerror strings. + * + * \param[in] report The test report context. + * \return true if test passes, false otherwise. + */ +static bool test_util_err_strings_valid( + ttest_report_ctx_t *report) +{ + ttest_ctx_t tc = ttest_start(report, __func__, NULL, NULL); + + if (cyaml_strerror(CYAML_OK) == NULL) { + return ttest_fail(&tc, "CYAML_OK string is NULL"); + } + + if (strcmp(cyaml_strerror(CYAML_OK), "Success") != 0) { + return ttest_fail(&tc, "CYAML_OK string not 'Success'"); + } + + for (unsigned i = 1; i <= CYAML_ERR__COUNT; i++) { + if (cyaml_strerror(i) == NULL) { + return ttest_fail(&tc, "Error code %u string is NULL", + i); + } + + for (unsigned j = 0; j < i; j++) { + if (strcmp(cyaml_strerror(j), cyaml_strerror(i)) == 0) { + return ttest_fail(&tc, "Two error codes " + "have same string (%u and %u)", + i, j); + } + } + } + + return ttest_pass(&tc); +} + +/** + * Test invalid cyaml_strerror strings. + * + * \param[in] report The test report context. + * \return true if test passes, false otherwise. + */ +static bool test_util_err_strings_invalid( + ttest_report_ctx_t *report) +{ + ttest_ctx_t tc = ttest_start(report, __func__, NULL, NULL); + + if (strcmp(cyaml_strerror(CYAML_ERR__COUNT), + "Invalid error code") != 0) { + return ttest_fail(&tc, "CYAML_ERR__COUNT string not " + "'Invalid error code'"); + } + + if (strcmp(cyaml_strerror(CYAML_ERR__COUNT + 1), + "Invalid error code") != 0) { + return ttest_fail(&tc, "CYAML_ERR__COUNT + 1 string not " + "'Invalid error code'"); + } + + if (strcmp(cyaml_strerror((cyaml_err_t)-1), "Invalid error code") != 0) { + return ttest_fail(&tc, "-1 string not 'Invalid error code'"); + } + + return ttest_pass(&tc); +} + +/** + * Run the CYAML util 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 util_tests( + ttest_report_ctx_t *rc, + cyaml_log_t log_level, + cyaml_log_fn_t log_fn) +{ + bool pass = true; + cyaml_config_t config = { + .log_fn = log_fn, + .mem_fn = cyaml_mem, + .log_level = log_level, + .flags = CYAML_CFG_DEFAULT, + }; + + ttest_heading(rc, "Memory utility functions"); + + pass &= test_util_strdup(rc, &config); + pass &= test_util_memory_funcs(rc, &config); + + ttest_heading(rc, "Error code tests"); + + pass &= test_util_state_invalid(rc); + pass &= test_util_err_success_zero(rc); + pass &= test_util_err_strings_valid(rc); + pass &= test_util_err_strings_invalid(rc); + + return pass; +} -- 2.45.2