#define _GNU_SOURCE
#include "schema_features.h"
#include <assert.h>
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "log.h"
#include "ly_common.h"
#include "set.h"
#include "tree.h"
#include "tree_edit.h"
#include "tree_schema.h"
#include "tree_schema_internal.h"
#define IFF_RECORDS_IN_BYTE 4
#define IFF_RECORD_BITS 2
#define IFF_RECORD_MASK 0x3
uint8_t
lysc_iff_getop(uint8_t *list, size_t pos)
{
uint8_t *item;
uint8_t mask = IFF_RECORD_MASK, result;
item = &list[pos / IFF_RECORDS_IN_BYTE];
result = (*item) & (mask << IFF_RECORD_BITS * (pos % IFF_RECORDS_IN_BYTE));
return result >> IFF_RECORD_BITS * (pos % IFF_RECORDS_IN_BYTE);
}
static LY_ERR
lysc_iffeature_value_(const struct lysc_iffeature *iff, size_t *index_e, size_t *index_f)
{
uint8_t op;
LY_ERR a, b;
op = lysc_iff_getop(iff->expr, *index_e);
(*index_e)++;
switch (op) {
case LYS_IFF_F:
return (iff->features[(*index_f)++]->flags & LYS_FENABLED) ? LY_SUCCESS : LY_ENOT;
case LYS_IFF_NOT:
return lysc_iffeature_value_(iff, index_e, index_f) == LY_SUCCESS ? LY_ENOT : LY_SUCCESS;
case LYS_IFF_AND:
case LYS_IFF_OR:
a = lysc_iffeature_value_(iff, index_e, index_f);
b = lysc_iffeature_value_(iff, index_e, index_f);
if (op == LYS_IFF_AND) {
if ((a == LY_SUCCESS) && (b == LY_SUCCESS)) {
return LY_SUCCESS;
} else {
return LY_ENOT;
}
} else {
if ((a == LY_SUCCESS) || (b == LY_SUCCESS)) {
return LY_SUCCESS;
} else {
return LY_ENOT;
}
}
}
return LY_ENOT;
}
LIBYANG_API_DEF LY_ERR
lysc_iffeature_value(const struct lysc_iffeature *iff)
{
size_t index_e = 0, index_f = 0;
LY_CHECK_ARG_RET(NULL, iff, LY_EINVAL);
if (iff->expr) {
return lysc_iffeature_value_(iff, &index_e, &index_f);
}
return LY_ENOT;
}
LIBYANG_API_DEF LY_ERR
lys_identity_iffeature_value(const struct lysc_ident *ident)
{
LY_ARRAY_COUNT_TYPE u, v;
ly_bool enabled;
const struct lysp_ident *idents_p, *found_ident = NULL;
struct lysp_include *includes;
LY_CHECK_ARG_RET(NULL, ident, ident->module->parsed, LY_EINVAL);
idents_p = ident->module->parsed->identities;
LY_ARRAY_FOR(idents_p, u) {
if (idents_p[u].name == ident->name) {
found_ident = &idents_p[u];
break;
}
}
if (!found_ident) {
includes = ident->module->parsed->includes;
LY_ARRAY_FOR(includes, u) {
idents_p = includes[u].submodule->identities;
LY_ARRAY_FOR(idents_p, v) {
if (idents_p[v].name == ident->name) {
found_ident = &idents_p[v];
break;
}
}
}
}
assert(found_ident);
LY_CHECK_RET(lys_eval_iffeatures(ident->module->ctx, found_ident->iffeatures, &enabled));
if (!enabled) {
return LY_ENOT;
}
return LY_SUCCESS;
}
LIBYANG_API_DEF struct lysp_feature *
lysp_feature_next(const struct lysp_feature *last, const struct lysp_module *pmod, uint32_t *idx)
{
struct lysp_feature *features;
LY_CHECK_ARG_RET(NULL, pmod, NULL);
if (!*idx) {
features = pmod->features;
} else if ((*idx - 1) < LY_ARRAY_COUNT(pmod->includes)) {
features = pmod->includes[*idx - 1].submodule->features;
} else {
return NULL;
}
if (features && (!last || (&features[LY_ARRAY_COUNT(features) - 1] != last))) {
return !last ? &features[0] : (struct lysp_feature *)last + 1;
}
++(*idx);
return lysp_feature_next(NULL, pmod, idx);
}
static struct lysp_feature *
lysp_feature_find(const struct lysp_module *pmod, const char *name, size_t len, ly_bool prefixed)
{
const struct lys_module *mod;
const char *ptr;
struct lysp_feature *f = NULL;
uint32_t idx = 0;
assert(pmod);
if (prefixed && (ptr = ly_strnchr(name, ':', len))) {
mod = ly_resolve_prefix(pmod->mod->ctx, name, ptr - name, LY_VALUE_SCHEMA, (void *)pmod);
LY_CHECK_RET(!mod, NULL);
pmod = mod->parsed;
len = len - (ptr - name) - 1;
name = ptr + 1;
}
if (pmod->is_submod) {
pmod = pmod->mod->parsed;
}
while ((f = lysp_feature_next(f, pmod, &idx))) {
if (!ly_strncmp(f->name, name, len)) {
return f;
}
}
return NULL;
}
LIBYANG_API_DEF LY_ERR
lys_feature_value(const struct lys_module *module, const char *feature)
{
const struct lysp_feature *f;
LY_ARRAY_COUNT_TYPE u;
LY_CHECK_ARG_RET(NULL, module, module->parsed || module->compiled, feature, !strchr(feature, ':'), LY_EINVAL);
if (module->parsed) {
f = lysp_feature_find(module->parsed, feature, strlen(feature), 0);
LY_CHECK_RET(!f, LY_ENOTFOUND);
if (f->flags & LYS_FENABLED) {
return LY_SUCCESS;
}
} else {
LY_ARRAY_FOR(module->compiled->features, u) {
if (!strcmp(module->compiled->features[u], feature)) {
return LY_SUCCESS;
}
}
}
return LY_ENOT;
}
struct iff_stack {
size_t size;
size_t index;
uint8_t *stack;
};
#define IFF_STACK_SIZE_STEP 4
static LY_ERR
iff_stack_push(struct iff_stack *stack, uint8_t value)
{
if (stack->index == stack->size) {
stack->size += IFF_STACK_SIZE_STEP;
stack->stack = ly_realloc(stack->stack, stack->size * sizeof *stack->stack);
LY_CHECK_ERR_RET(!stack->stack, LOGMEM(NULL); stack->size = 0, LY_EMEM);
}
stack->stack[stack->index++] = value;
return LY_SUCCESS;
}
static uint8_t
iff_stack_pop(struct iff_stack *stack)
{
assert(stack && stack->index);
stack->index--;
return stack->stack[stack->index];
}
static void
iff_stack_clean(struct iff_stack *stack)
{
stack->size = 0;
free(stack->stack);
}
static void
iff_setop(uint8_t *list, uint8_t op, size_t pos)
{
uint8_t *item;
uint8_t mask = IFF_RECORD_MASK;
assert(op <= IFF_RECORD_MASK);
item = &list[pos / IFF_RECORDS_IN_BYTE];
mask = mask << IFF_RECORD_BITS * (pos % IFF_RECORDS_IN_BYTE);
*item = (*item) & ~mask;
*item = (*item) | (op << IFF_RECORD_BITS * (pos % IFF_RECORDS_IN_BYTE));
}
#define LYS_IFF_LP 0x04
#define LYS_IFF_RP 0x08
static LY_ERR
lys_compile_iffeature(const struct ly_ctx *ctx, const struct lysp_qname *qname, struct lysc_iffeature *iff)
{
LY_ERR rc = LY_SUCCESS;
const char *c = qname->str;
int64_t i, j;
int8_t op_len, last_not = 0, checkversion = 0;
LY_ARRAY_COUNT_TYPE f_size = 0, expr_size = 0, f_exp = 1;
uint8_t op;
struct iff_stack stack = {0, 0, NULL};
struct lysp_feature *f;
assert(c);
for (i = j = 0; c[i]; i++) {
if (c[i] == '(') {
j++;
checkversion = 1;
continue;
} else if (c[i] == ')') {
j--;
continue;
} else if (isspace(c[i])) {
checkversion = 1;
continue;
}
if (!strncmp(&c[i], "not", op_len = ly_strlen_const("not")) ||
!strncmp(&c[i], "and", op_len = ly_strlen_const("and")) ||
!strncmp(&c[i], "or", op_len = ly_strlen_const("or"))) {
uint64_t spaces;
for (spaces = 0; c[i + op_len + spaces] && isspace(c[i + op_len + spaces]); spaces++) {}
if (c[i + op_len + spaces] == '\0') {
LOGVAL(ctx, NULL, LYVE_SYNTAX_YANG, "Invalid value \"%s\" of if-feature - unexpected end of expression.",
qname->str);
return LY_EVALID;
} else if (!isspace(c[i + op_len])) {
last_not = 0;
f_size++;
} else if (c[i] == 'n') {
if (last_not) {
expr_size = expr_size - 2;
last_not = 0;
} else {
last_not = 1;
}
} else {
if (f_exp != f_size) {
LOGVAL(ctx, NULL, LYVE_SYNTAX_YANG,
"Invalid value \"%s\" of if-feature - missing feature/expression before \"%.*s\" operation.",
qname->str, op_len, &c[i]);
return LY_EVALID;
}
f_exp++;
last_not = 0;
}
i += op_len;
} else {
f_size++;
last_not = 0;
}
expr_size++;
while (!isspace(c[i])) {
if (!c[i] || (c[i] == ')') || (c[i] == '(')) {
i--;
break;
}
i++;
}
}
if (j) {
LOGVAL(ctx, NULL, LYVE_SYNTAX_YANG, "Invalid value \"%s\" of if-feature - non-matching opening and closing parentheses.",
qname->str);
return LY_EVALID;
}
if (f_exp != f_size) {
LOGVAL(ctx, NULL, LYVE_SYNTAX_YANG, "Invalid value \"%s\" of if-feature - number of features in expression does not match "
"the required number of operands for the operations.", qname->str);
return LY_EVALID;
}
if (checkversion || (expr_size > 1)) {
if (qname->mod->version != LYS_VERSION_1_1) {
LOGVAL(ctx, NULL, LYVE_SYNTAX_YANG, "Invalid value \"%s\" of if-feature - YANG 1.1 expression in YANG 1.0 module.",
qname->str);
return LY_EVALID;
}
}
LY_ARRAY_CREATE_RET(ctx, iff->features, f_size, LY_EMEM);
iff->expr = calloc((j = (expr_size / IFF_RECORDS_IN_BYTE) + ((expr_size % IFF_RECORDS_IN_BYTE) ? 1 : 0)), sizeof *iff->expr);
stack.stack = malloc(expr_size * sizeof *stack.stack);
LY_CHECK_ERR_GOTO(!stack.stack || !iff->expr, LOGMEM(ctx); rc = LY_EMEM, cleanup);
stack.size = expr_size;
f_size--; expr_size--;
for (i--; i >= 0; i--) {
if (c[i] == ')') {
iff_stack_push(&stack, LYS_IFF_RP);
continue;
} else if (c[i] == '(') {
while ((op = iff_stack_pop(&stack)) != LYS_IFF_RP) {
iff_setop(iff->expr, op, expr_size--);
}
continue;
} else if (isspace(c[i])) {
continue;
}
j = i + 1;
while (i >= 0 && !isspace(c[i]) && c[i] != '(') {
i--;
}
i++;
if (!strncmp(&c[i], "not", ly_strlen_const("not")) && isspace(c[i + ly_strlen_const("not")])) {
if (stack.index && (stack.stack[stack.index - 1] == LYS_IFF_NOT)) {
iff_stack_pop(&stack);
} else {
iff_stack_push(&stack, LYS_IFF_NOT);
}
} else if (!strncmp(&c[i], "and", ly_strlen_const("and")) && isspace(c[i + ly_strlen_const("and")])) {
while (stack.index && stack.stack[stack.index - 1] <= LYS_IFF_AND) {
op = iff_stack_pop(&stack);
iff_setop(iff->expr, op, expr_size--);
}
iff_stack_push(&stack, LYS_IFF_AND);
} else if (!strncmp(&c[i], "or", 2) && isspace(c[i + 2])) {
while (stack.index && stack.stack[stack.index - 1] <= LYS_IFF_OR) {
op = iff_stack_pop(&stack);
iff_setop(iff->expr, op, expr_size--);
}
iff_stack_push(&stack, LYS_IFF_OR);
} else {
iff_setop(iff->expr, LYS_IFF_F, expr_size--);
f = lysp_feature_find(qname->mod, &c[i], j - i, 1);
if (!f) {
LOGVAL(ctx, NULL, LYVE_SYNTAX_YANG, "Invalid value \"%s\" of if-feature - unable to find feature \"%.*s\".",
qname->str, (int)(j - i), &c[i]);
rc = LY_EVALID;
goto cleanup;
}
iff->features[f_size] = f;
LY_ARRAY_INCREMENT(iff->features);
f_size--;
}
}
while (stack.index) {
op = iff_stack_pop(&stack);
iff_setop(iff->expr, op, expr_size--);
}
if (++expr_size || ++f_size) {
LOGVAL(ctx, NULL, LYVE_SYNTAX_YANG, "Invalid value \"%s\" of if-feature - processing error.", qname->str);
rc = LY_EINT;
}
cleanup:
if (rc) {
LY_ARRAY_FREE(iff->features);
iff->features = NULL;
free(iff->expr);
iff->expr = NULL;
}
iff_stack_clean(&stack);
return rc;
}
LY_ERR
lys_eval_iffeatures(const struct ly_ctx *ctx, const struct lysp_qname *iffeatures, ly_bool *enabled)
{
LY_ERR ret;
LY_ARRAY_COUNT_TYPE u;
struct lysc_iffeature iff;
*enabled = 1;
if (!iffeatures) {
return LY_SUCCESS;
}
LY_ARRAY_FOR(iffeatures, u) {
memset(&iff, 0, sizeof iff);
LY_CHECK_RET(lys_compile_iffeature(ctx, &iffeatures[u], &iff));
ret = lysc_iffeature_value(&iff);
lysc_iffeature_free(ctx, &iff);
if (ret == LY_ENOT) {
*enabled = 0;
break;
} else if (ret) {
return ret;
}
}
return LY_SUCCESS;
}
LY_ERR
lys_check_features(const struct lysp_module *pmod)
{
LY_ERR r;
uint32_t i = 0;
struct lysp_feature *f = NULL;
while ((f = lysp_feature_next(f, pmod, &i))) {
if (!(f->flags & LYS_FENABLED) || !f->iffeatures) {
continue;
}
assert(f->iffeatures_c);
r = lysc_iffeature_value(f->iffeatures_c);
if (r == LY_ENOT) {
LOGERR(pmod->mod->ctx, LY_EDENIED, "Feature \"%s\" cannot be enabled because its \"if-feature\" is not satisfied.",
f->name);
return LY_EDENIED;
} else if (r) {
return r;
}
}
return LY_SUCCESS;
}
LY_ERR
lys_set_features(struct lysp_module *pmod, const char **features)
{
uint32_t i = 0, j;
struct lysp_feature *f = 0;
ly_bool change = 0;
if (!features) {
} else if (!features[0]) {
while ((f = lysp_feature_next(f, pmod, &i))) {
if (f->flags & LYS_FENABLED) {
f->flags &= ~LYS_FENABLED;
change = 1;
}
}
} else if (!strcmp(features[0], "*")) {
while ((f = lysp_feature_next(f, pmod, &i))) {
if (!(f->flags & LYS_FENABLED)) {
f->flags |= LYS_FENABLED;
change = 1;
}
}
} else {
for (j = 0; features[j]; ++j) {
if (!lysp_feature_find(pmod, features[j], strlen(features[j]), 0)) {
LOGERR(pmod->mod->ctx, LY_EINVAL, "Feature \"%s\" not found in module \"%s\".", features[j], pmod->mod->name);
return LY_EINVAL;
}
}
while ((f = lysp_feature_next(f, pmod, &i))) {
for (j = 0; features[j]; ++j) {
if (!strcmp(f->name, features[j])) {
break;
}
}
if (features[j] && !(f->flags & LYS_FENABLED)) {
f->flags |= LYS_FENABLED;
change = 1;
} else if (!features[j] && (f->flags & LYS_FENABLED)) {
f->flags &= ~LYS_FENABLED;
change = 1;
}
}
}
if (!change) {
return LY_EEXIST;
}
return LY_SUCCESS;
}
static LY_ERR
lys_compile_feature_circular_check(const struct ly_ctx *ctx, struct lysp_feature *feature, struct lysp_feature **depfeatures)
{
LY_ERR ret = LY_SUCCESS;
LY_ARRAY_COUNT_TYPE u, v;
struct ly_set recursion = {0};
struct lysp_feature *drv;
if (!depfeatures) {
return LY_SUCCESS;
}
for (u = 0; u < LY_ARRAY_COUNT(depfeatures); ++u) {
if (feature == depfeatures[u]) {
LOGVAL(ctx, NULL, LYVE_REFERENCE, "Feature \"%s\" is indirectly referenced from itself.", feature->name);
ret = LY_EVALID;
goto cleanup;
}
ret = ly_set_add(&recursion, depfeatures[u], 0, NULL);
LY_CHECK_GOTO(ret, cleanup);
}
for (v = 0; v < recursion.count; ++v) {
drv = recursion.objs[v];
for (u = 0; u < LY_ARRAY_COUNT(drv->depfeatures); ++u) {
if (feature == drv->depfeatures[u]) {
LOGVAL(ctx, NULL, LYVE_REFERENCE, "Feature \"%s\" is indirectly referenced from itself.", feature->name);
ret = LY_EVALID;
goto cleanup;
}
ly_set_add(&recursion, drv->depfeatures[u], 0, NULL);
LY_CHECK_GOTO(ret, cleanup);
}
}
cleanup:
ly_set_erase(&recursion, NULL);
return ret;
}
LY_ERR
lys_compile_feature_iffeatures(struct lysp_module *pmod)
{
LY_ARRAY_COUNT_TYPE u, v;
struct lysp_feature *f = NULL, **df;
uint32_t idx = 0;
while ((f = lysp_feature_next(f, pmod, &idx))) {
if (!f->iffeatures) {
continue;
}
LY_ARRAY_CREATE_RET(pmod->mod->ctx, f->iffeatures_c, LY_ARRAY_COUNT(f->iffeatures), LY_EMEM);
LY_ARRAY_FOR(f->iffeatures, u) {
LY_ARRAY_INCREMENT(f->iffeatures_c);
LY_CHECK_RET(lys_compile_iffeature(pmod->mod->ctx, &(f->iffeatures)[u], &(f->iffeatures_c)[u]));
}
LY_ARRAY_FOR(f->iffeatures_c, u) {
LY_ARRAY_FOR(f->iffeatures_c[u].features, v) {
if (f == f->iffeatures_c[u].features[v]) {
LOGVAL(pmod->mod->ctx, NULL, LYVE_REFERENCE, "Feature \"%s\" is referenced from itself.", f->name);
return LY_EVALID;
}
LY_CHECK_RET(lys_compile_feature_circular_check(pmod->mod->ctx, f->iffeatures_c[u].features[v], f->depfeatures));
LY_ARRAY_NEW_RET(pmod->mod->ctx, f->iffeatures_c[u].features[v]->depfeatures, df, LY_EMEM);
*df = f;
}
}
}
return LY_SUCCESS;
}