diff options
Diffstat (limited to 'src/libknot/yparser/ypschema.c')
-rw-r--r-- | src/libknot/yparser/ypschema.c | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/src/libknot/yparser/ypschema.c b/src/libknot/yparser/ypschema.c new file mode 100644 index 0000000..0ac5b57 --- /dev/null +++ b/src/libknot/yparser/ypschema.c @@ -0,0 +1,598 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "libknot/yparser/ypschema.h" +#include "libknot/yparser/yptrafo.h" +#include "libknot/attribute.h" +#include "libknot/errcode.h" + +static size_t schema_count( + const yp_item_t *src) +{ + size_t count = 0; + for (const yp_item_t *item = src; item->name != NULL; item++) { + count++; + } + + return count; +} + +/*! Initializes the referenced item. */ +static int set_ref_item( + yp_item_t *dst, + const yp_item_t *schema) +{ + if (schema == NULL) { + return KNOT_EINVAL; + } + + // Get referenced section. + const yp_name_t *ref_name = dst->var.r.ref_name; + const yp_item_t *ref = yp_schema_find(ref_name, NULL, schema); + if (ref == NULL) { + return KNOT_YP_EINVAL_ITEM; + } + dst->var.r.ref = ref; + + // Get referenced group if supported. + const yp_name_t *grp_ref_name = dst->var.r.grp_ref_name; + if (grp_ref_name != NULL) { + const yp_item_t *grp_ref = yp_schema_find(grp_ref_name, NULL, schema); + if (grp_ref == NULL) { + return KNOT_YP_EINVAL_ITEM; + } + dst->var.r.grp_ref = grp_ref; + } + + return KNOT_EOK; +} + +/*! Copies the sub_items list and initializes pointer to the identifier item. */ +static int set_grp_item( + yp_item_t *dst, + const yp_item_t *src, + const yp_item_t *schema) +{ + // Count subitems. + size_t count = schema_count(src->var.g.sub_items); + + // Allocate space for subitems + terminal zero item. + size_t memsize = (count + 1) * sizeof(yp_item_t); + dst->sub_items = malloc(memsize); + if (dst->sub_items == NULL) { + return KNOT_ENOMEM; + } + memset(dst->sub_items, 0, memsize); + + // Copy subitems. + for (size_t i = 0; i < count; i++) { + // The first item is an identifier if multi group. + if (i == 0 && (dst->flags & YP_FMULTI)) { + dst->var.g.id = &dst->sub_items[0]; + } + + // Copy sub-item. + dst->sub_items[i] = src->var.g.sub_items[i]; + + // Initialize sub-item. + int ret = KNOT_EOK; + switch (dst->sub_items[i].type) { + case YP_TREF: + ret = set_ref_item(dst->sub_items + i, schema); + break; + case YP_TGRP: // Deeper hierarchy is not supported. + ret = KNOT_ENOTSUP; + break; + default: + break; + } + + // Set the parent item. + dst->sub_items[i].parent = dst; + + if (ret != KNOT_EOK) { + free(dst->sub_items); + dst->sub_items = NULL; + return ret; + } + } + + if (src->flags & YP_FALLOC) { + dst->var.g.sub_items = malloc(memsize); + if (dst->var.g.sub_items == NULL) { + free(dst->sub_items); + dst->sub_items = NULL; + return KNOT_ENOMEM; + } + memcpy((void *)dst->var.g.sub_items, src->var.g.sub_items, memsize); + } + + return KNOT_EOK; +} + +static int set_item( + yp_item_t *dst, + const yp_item_t *src, + const yp_item_t *schema) +{ + // Check maximal item name length. + if ((uint8_t)src->name[0] > YP_MAX_ITEM_NAME_LEN) { + return KNOT_ERANGE; + } + + // Copy the static data. + *dst = *src; + + // Copy item name into dynamic memory. + if (src->flags & YP_FALLOC) { + dst->name = malloc(src->name[0] + 2); + if (dst->name == NULL) { + return KNOT_ENOMEM; + } + memcpy((void *)dst->name, src->name, src->name[0] + 2); + } + + int ret; + + // Item type specific preparation. + switch (src->type) { + case YP_TREF: + ret = set_ref_item(dst, schema); + break; + case YP_TGRP: + ret = set_grp_item(dst, src, schema); + break; + default: + ret = KNOT_EOK; + } + + if (ret != KNOT_EOK && src->flags & YP_FALLOC) { + free((void *)dst->name); + } + + return ret; +} + +static void unset_item( + yp_item_t *item) +{ + if (item->flags & YP_FALLOC) { + free((void *)item->name); + } + if (item->type & YP_TGRP) { + free(item->sub_items); + if (item->flags & YP_FALLOC) { + free((void *)item->var.g.sub_items); + } + } + + memset(item, 0, sizeof(yp_item_t)); +} + +static int schema_copy( + yp_item_t *dst, + const yp_item_t *src, + const yp_item_t *schema) +{ + // Copy the schema. + for (int i = 0; src[i].name != NULL; i++) { + int ret = set_item(&dst[i], &src[i], schema); + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; +} + +_public_ +int yp_schema_copy( + yp_item_t **dst, + const yp_item_t *src) +{ + if (dst == NULL || src == NULL) { + return KNOT_EINVAL; + } + + // Allocate space for new schema (+ terminal NULL item). + size_t size = (schema_count(src) + 1) * sizeof(yp_item_t); + yp_item_t *out = malloc(size); + if (out == NULL) { + return KNOT_ENOMEM; + } + memset(out, 0, size); + + // Copy the schema. + int ret = schema_copy(out, src, out); + if (ret != KNOT_EOK) { + free(out); + return ret; + } + + *dst = out; + + return KNOT_EOK; +} + +_public_ +int yp_schema_merge( + yp_item_t **dst, + const yp_item_t *src1, + const yp_item_t *src2) +{ + if (dst == NULL || src1 == NULL || src2 == NULL) { + return KNOT_EINVAL; + } + + size_t count1 = schema_count(src1); + size_t count2 = schema_count(src2); + + // Allocate space for new schema (+ terminal NULL item). + size_t size = (count1 + count2 + 1) * sizeof(yp_item_t); + yp_item_t *out = malloc(size); + if (out == NULL) { + return KNOT_ENOMEM; + } + memset(out, 0, size); + + // Copy the first schema. + int ret = schema_copy(out, src1, out); + if (ret != KNOT_EOK) { + free(out); + return ret; + } + + // Copy the second schema. + ret = schema_copy(out + count1, src2, out); + if (ret != KNOT_EOK) { + free(out); + return ret; + } + + *dst = out; + + return KNOT_EOK; +} + +_public_ +void yp_schema_purge_dynamic( + yp_item_t *schema) +{ + if (schema == NULL) { + return; + } + + for (yp_item_t *item = schema; item->name != NULL; item++) { + if (item->flags & YP_FALLOC) { + unset_item(item); + } + } +} + +_public_ +void yp_schema_free( + yp_item_t *schema) +{ + if (schema == NULL) { + return; + } + + for (yp_item_t *item = schema; item->name != NULL; item++) { + unset_item(item); + } + free(schema); +} + +/*! Search the schema for an item with the given name. */ +static const yp_item_t* find_item( + const char *name, + size_t name_len, + const yp_item_t *schema) +{ + if (name == NULL || schema == NULL) { + return NULL; + } + + for (const yp_item_t *item = schema; item->name != NULL; item++) { + if (item->name[0] != name_len) { + continue; + } + if (memcmp(item->name + 1, name, name_len) == 0) { + return item; + } + } + + return NULL; +} + +_public_ +const yp_item_t* yp_schema_find( + const yp_name_t *name, + const yp_name_t *parent_name, + const yp_item_t *schema) +{ + if (name == NULL || schema == NULL) { + return NULL; + } + + if (parent_name == NULL) { + return find_item(name + 1, name[0], schema); + } else { + const yp_item_t *parent = find_item(parent_name + 1, + parent_name[0], schema); + if (parent == NULL) { + return NULL; + } + return find_item(name + 1, name[0], parent->sub_items); + } +} + +_public_ +yp_check_ctx_t* yp_schema_check_init( + yp_item_t **schema) +{ + if (schema == NULL) { + return NULL; + } + + yp_check_ctx_t *ctx = malloc(sizeof(yp_check_ctx_t)); + if (ctx == NULL) { + return NULL; + } + memset(ctx, 0, sizeof(yp_check_ctx_t)); + + ctx->schema = schema; + + return ctx; +} + +static void reset_ctx( + yp_check_ctx_t *ctx, + size_t index) +{ + assert(index < YP_MAX_NODE_DEPTH); + + yp_node_t *node = &ctx->nodes[index]; + + node->parent = (index > 0) ? &ctx->nodes[index - 1] : NULL; + node->item = NULL; + node->id_len = 0; + node->data_len = 0; + + ctx->current = index; +} + +static int check_item( + const char *key, + size_t key_len, + const char *data, + size_t data_len, + yp_check_ctx_t *ctx, + bool allow_key1_without_id) +{ + yp_node_t *node = &ctx->nodes[ctx->current]; + yp_node_t *parent = node->parent; + bool is_id = false; + + if (parent != NULL) { + // Check for invalid indentation. + if (parent->item == NULL) { + return KNOT_YP_EINVAL_INDENT; + } + + // Check if valid group parent. + if (parent->item->type != YP_TGRP) { + return KNOT_YP_EINVAL_ITEM; + } + + // Check if valid subitem. + node->item = find_item(key, key_len, parent->item->sub_items); + } else { + node->item = find_item(key, key_len, *ctx->schema); + } + if (node->item == NULL) { + return KNOT_YP_EINVAL_ITEM; + } + + // Check if the parent requires id specification. + if (parent != NULL && parent->item->var.g.id != NULL) { + // Check if id. + if (node->item == parent->item->var.g.id) { + is_id = true; + // Move current to the parent. + --(ctx->current); + // Check for missing id. + } else if (parent->id_len == 0 && !allow_key1_without_id) { + return KNOT_YP_ENOID; + } + } + + // Return if no data provided. + if (data == NULL) { + return KNOT_EOK; + } + + // Group cannot have data. + if (data_len != 0 && node->item->type == YP_TGRP) { + return KNOT_YP_ENOTSUP_DATA; + } + + // Convert item data to binary format. + const yp_item_t *item = (node->item->type != YP_TREF) ? + node->item : node->item->var.r.ref->var.g.id; + if (is_id) { + // Textual id must not be empty. + if (data_len == 0) { + return KNOT_YP_ENODATA; + } + + parent->id_len = sizeof(((yp_node_t *)NULL)->id); + int ret = yp_item_to_bin(item, data, data_len, parent->id, + &parent->id_len); + + // Binary id must not be empty. + if (ret == KNOT_EOK && parent->id_len == 0) { + return KNOT_YP_EINVAL_DATA; + } + + return ret; + } else { + node->data_len = sizeof(((yp_node_t *)NULL)->data); + int ret = yp_item_to_bin(item, data, data_len, node->data, + &node->data_len); + return ret; + } +} + +_public_ +int yp_schema_check_parser( + yp_check_ctx_t *ctx, + const yp_parser_t *parser) +{ + if (ctx == NULL || parser == NULL) { + return KNOT_EINVAL; + } + + int ret; + + switch (parser->event) { + case YP_EKEY0: + reset_ctx(ctx, 0); + ret = check_item(parser->key, parser->key_len, parser->data, + parser->data_len, ctx, false); + break; + case YP_EKEY1: + reset_ctx(ctx, 1); + ret = check_item(parser->key, parser->key_len, parser->data, + parser->data_len, ctx, false); + if (ret != KNOT_EOK) { + break; + } + + // Check for KEY1 event with id item. + if (ctx->current != 1) { + return KNOT_YP_ENOTSUP_ID; + } + + break; + case YP_EID: + reset_ctx(ctx, 1); + ret = check_item(parser->key, parser->key_len, parser->data, + parser->data_len, ctx, false); + if (ret != KNOT_EOK) { + break; + } + + // Check for ID event with nonid item. + if (ctx->current != 0) { + return KNOT_YP_EINVAL_ID; + } + + break; + default: + ret = KNOT_EPARSEFAIL; + break; + } + + return ret; +} + +_public_ +int yp_schema_check_str( + yp_check_ctx_t *ctx, + const char *key0, + const char *key1, + const char *id, + const char *data) +{ + if (ctx == NULL) { + return KNOT_EINVAL; + } + + size_t key0_len = (key0 != NULL) ? strlen(key0) : 0; + size_t key1_len = (key1 != NULL) ? strlen(key1) : 0; + size_t id_len = (id != NULL) ? strlen(id) : 0; + size_t data_len = (data != NULL) ? strlen(data) : 0; + + // Key0 must always be non-empty. + if (key0_len == 0) { + return KNOT_YP_EINVAL_ITEM; + } + + // Process key0. + reset_ctx(ctx, 0); + if (key1_len == 0) { + int ret = check_item(key0, key0_len, data, data_len, ctx, false); + if (ret != KNOT_EOK) { + return ret; + } + } else { + int ret = check_item(key0, key0_len, NULL, 0, ctx, false); + if (ret != KNOT_EOK) { + return ret; + } + } + + // Process id. + if (id_len != 0) { + if (ctx->nodes[0].item->type != YP_TGRP || + ctx->nodes[0].item->var.g.id == NULL) { + return KNOT_YP_ENOTSUP_ID; + } + const yp_name_t *name = ctx->nodes[0].item->var.g.id->name; + + reset_ctx(ctx, 1); + int ret = check_item(name + 1, name[0], id, id_len, ctx, true); + if (ret != KNOT_EOK) { + return ret; + } + + // Check for non-id item (should not happen). + assert(ctx->current == 0); + + // Check for group id with data. + if (key1_len == 0 && data != NULL) { + return KNOT_YP_ENOTSUP_DATA; + } + } + + // Process key1. + if (key1_len != 0) { + reset_ctx(ctx, 1); + int ret = check_item(key1, key1_len, data, data_len, ctx, true); + if (ret != KNOT_EOK) { + return ret; + } + + // Check for id in key1 with extra data. + if (ctx->current != 1 && id_len != 0 && data != NULL) { + return KNOT_YP_ENOTSUP_DATA; + } + } + + return KNOT_EOK; +} + +_public_ +void yp_schema_check_deinit( + yp_check_ctx_t* ctx) +{ + free(ctx); +} |