| /* |
| * Copyright (C) 2015 Tobias Brunner |
| * HSR Hochschule fuer Technik Rapperswil |
| * |
| * Copyright (C) 2014 Martin Willi |
| * Copyright (C) 2014 revosec AG |
| * |
| * 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 2 of the License, or (at your |
| * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. |
| * |
| * 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. |
| */ |
| |
| #include "vici_message.h" |
| #include "vici_builder.h" |
| |
| #include <bio/bio_reader.h> |
| #include <bio/bio_writer.h> |
| |
| #include <errno.h> |
| |
| typedef struct private_vici_message_t private_vici_message_t; |
| |
| /** |
| * Private data of an vici_message_t object. |
| */ |
| struct private_vici_message_t { |
| |
| /** |
| * Public vici_message_t interface. |
| */ |
| vici_message_t public; |
| |
| /** |
| * Message encoding |
| */ |
| chunk_t encoding; |
| |
| /** |
| * Free encoding during destruction? |
| */ |
| bool cleanup; |
| |
| /** |
| * Allocated strings we maintain for get_str() |
| */ |
| linked_list_t *strings; |
| }; |
| |
| ENUM(vici_type_names, VICI_START, VICI_END, |
| "start", |
| "section-start", |
| "section-end", |
| "key-value", |
| "list-start", |
| "list-item", |
| "list-end", |
| "end" |
| ); |
| |
| /** |
| * See header. |
| */ |
| bool vici_stringify(chunk_t chunk, char *buf, size_t size) |
| { |
| if (!chunk_printable(chunk, NULL, 0)) |
| { |
| return FALSE; |
| } |
| snprintf(buf, size, "%.*s", (int)chunk.len, chunk.ptr); |
| return TRUE; |
| } |
| |
| /** |
| * See header. |
| */ |
| bool vici_verify_type(vici_type_t type, u_int section, bool list) |
| { |
| if (list) |
| { |
| if (type != VICI_LIST_END && type != VICI_LIST_ITEM) |
| { |
| DBG1(DBG_ENC, "'%N' within list", vici_type_names, type); |
| return FALSE; |
| } |
| } |
| else |
| { |
| if (type == VICI_LIST_ITEM || type == VICI_LIST_END) |
| { |
| DBG1(DBG_ENC, "'%N' outside list", vici_type_names, type); |
| return FALSE; |
| } |
| } |
| if (type == VICI_SECTION_END && section == 0) |
| { |
| DBG1(DBG_ENC, "'%N' outside of section", vici_type_names, type); |
| return FALSE; |
| } |
| if (type == VICI_END && section) |
| { |
| DBG1(DBG_ENC, "'%N' within section", vici_type_names, type); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| /** |
| * Enumerator parsing message |
| */ |
| typedef struct { |
| /* implements enumerator */ |
| enumerator_t public; |
| /** reader to parse from */ |
| bio_reader_t *reader; |
| /** section nesting level */ |
| int section; |
| /** currently parsing list? */ |
| bool list; |
| /** string currently enumerating */ |
| char name[257]; |
| } parse_enumerator_t; |
| |
| METHOD(enumerator_t, parse_enumerate, bool, |
| parse_enumerator_t *this, va_list args) |
| { |
| vici_type_t *out; |
| chunk_t *value; |
| char **name; |
| uint8_t type; |
| chunk_t data; |
| |
| VA_ARGS_VGET(args, out, name, value); |
| |
| if (!this->reader->remaining(this->reader) || |
| !this->reader->read_uint8(this->reader, &type)) |
| { |
| *out = VICI_END; |
| return TRUE; |
| } |
| if (!vici_verify_type(type, this->section, this->list)) |
| { |
| return FALSE; |
| } |
| |
| switch (type) |
| { |
| case VICI_SECTION_START: |
| if (!this->reader->read_data8(this->reader, &data) || |
| !vici_stringify(data, this->name, sizeof(this->name))) |
| { |
| DBG1(DBG_ENC, "invalid '%N' encoding", vici_type_names, type); |
| return FALSE; |
| } |
| *name = this->name; |
| this->section++; |
| break; |
| case VICI_SECTION_END: |
| this->section--; |
| break; |
| case VICI_KEY_VALUE: |
| if (!this->reader->read_data8(this->reader, &data) || |
| !vici_stringify(data, this->name, sizeof(this->name)) || |
| !this->reader->read_data16(this->reader, value)) |
| { |
| DBG1(DBG_ENC, "invalid '%N' encoding", vici_type_names, type); |
| return FALSE; |
| } |
| *name = this->name; |
| break; |
| case VICI_LIST_START: |
| if (!this->reader->read_data8(this->reader, &data) || |
| !vici_stringify(data, this->name, sizeof(this->name))) |
| { |
| DBG1(DBG_ENC, "invalid '%N' encoding", vici_type_names, type); |
| return FALSE; |
| } |
| *name = this->name; |
| this->list = TRUE; |
| break; |
| case VICI_LIST_ITEM: |
| this->reader->read_data16(this->reader, value); |
| break; |
| case VICI_LIST_END: |
| this->list = FALSE; |
| break; |
| case VICI_END: |
| return TRUE; |
| default: |
| DBG1(DBG_ENC, "unknown encoding type: %u", type); |
| return FALSE; |
| } |
| |
| *out = type; |
| |
| return TRUE; |
| } |
| |
| METHOD(enumerator_t, parse_destroy, void, |
| parse_enumerator_t *this) |
| { |
| this->reader->destroy(this->reader); |
| free(this); |
| } |
| |
| METHOD(vici_message_t, create_enumerator, enumerator_t*, |
| private_vici_message_t *this) |
| { |
| parse_enumerator_t *enumerator; |
| |
| INIT(enumerator, |
| .public = { |
| .enumerate = enumerator_enumerate_default, |
| .venumerate = _parse_enumerate, |
| .destroy = _parse_destroy, |
| }, |
| .reader = bio_reader_create(this->encoding), |
| ); |
| |
| return &enumerator->public; |
| } |
| |
| /** |
| * Find a value for given vararg key |
| */ |
| static bool find_value(private_vici_message_t *this, chunk_t *value, |
| char *fmt, va_list args) |
| { |
| enumerator_t *enumerator; |
| char buf[128], *name, *key, *dot, *next; |
| int section = 0, keysection = 0; |
| bool found = FALSE; |
| chunk_t current; |
| vici_type_t type; |
| |
| vsnprintf(buf, sizeof(buf), fmt, args); |
| next = buf; |
| |
| enumerator = create_enumerator(this); |
| |
| /* descent into section */ |
| while (TRUE) |
| { |
| dot = strchr(next, '.'); |
| if (!dot) |
| { |
| key = next; |
| break; |
| } |
| *dot = '\0'; |
| key = next; |
| next = dot + 1; |
| keysection++; |
| |
| while (enumerator->enumerate(enumerator, &type, &name, ¤t)) |
| { |
| switch (type) |
| { |
| case VICI_SECTION_START: |
| section++; |
| if (section == keysection && streq(name, key)) |
| { |
| break; |
| } |
| continue; |
| case VICI_SECTION_END: |
| section--; |
| continue; |
| case VICI_END: |
| break; |
| default: |
| continue; |
| } |
| break; |
| } |
| } |
| |
| /* find key/value in current section */ |
| while (enumerator->enumerate(enumerator, &type, &name, ¤t)) |
| { |
| switch (type) |
| { |
| case VICI_KEY_VALUE: |
| if (section == keysection && streq(key, name)) |
| { |
| *value = current; |
| found = TRUE; |
| break; |
| } |
| continue; |
| case VICI_SECTION_START: |
| section++; |
| continue; |
| case VICI_SECTION_END: |
| section--; |
| continue; |
| case VICI_END: |
| break; |
| default: |
| continue; |
| } |
| break; |
| } |
| |
| enumerator->destroy(enumerator); |
| |
| return found; |
| } |
| |
| METHOD(vici_message_t, vget_str, char*, |
| private_vici_message_t *this, char *def, char *fmt, va_list args) |
| { |
| chunk_t value; |
| bool found; |
| char *str; |
| |
| found = find_value(this, &value, fmt, args); |
| if (found) |
| { |
| if (chunk_printable(value, NULL, 0)) |
| { |
| str = strndup(value.ptr, value.len); |
| /* keep a reference to string, so caller doesn't have to care */ |
| this->strings->insert_last(this->strings, str); |
| return str; |
| } |
| } |
| return def; |
| } |
| |
| METHOD(vici_message_t, get_str, char*, |
| private_vici_message_t *this, char *def, char *fmt, ...) |
| { |
| va_list args; |
| char *str; |
| |
| va_start(args, fmt); |
| str = vget_str(this, def, fmt, args); |
| va_end(args); |
| return str; |
| } |
| |
| METHOD(vici_message_t, vget_int, int, |
| private_vici_message_t *this, int def, char *fmt, va_list args) |
| { |
| chunk_t value; |
| bool found; |
| char buf[32], *pos; |
| int ret; |
| |
| found = find_value(this, &value, fmt, args); |
| if (found) |
| { |
| if (value.len == 0) |
| { |
| return def; |
| } |
| if (chunk_printable(value, NULL, 0)) |
| { |
| snprintf(buf, sizeof(buf), "%.*s", (int)value.len, value.ptr); |
| errno = 0; |
| ret = strtol(buf, &pos, 0); |
| if (errno == 0 && pos == buf + strlen(buf)) |
| { |
| return ret; |
| } |
| } |
| } |
| return def; |
| } |
| |
| METHOD(vici_message_t, get_int, int, |
| private_vici_message_t *this, int def, char *fmt, ...) |
| { |
| va_list args; |
| int val; |
| |
| va_start(args, fmt); |
| val = vget_int(this, def, fmt, args); |
| va_end(args); |
| return val; |
| } |
| |
| METHOD(vici_message_t, vget_bool, bool, |
| private_vici_message_t *this, bool def, char *fmt, va_list args) |
| { |
| chunk_t value; |
| bool found; |
| char buf[16]; |
| |
| found = find_value(this, &value, fmt, args); |
| if (found) |
| { |
| if (value.len == 0) |
| { |
| return def; |
| } |
| if (chunk_printable(value, NULL, 0)) |
| { |
| snprintf(buf, sizeof(buf), "%.*s", (int)value.len, value.ptr); |
| return settings_value_as_bool(buf, def); |
| } |
| } |
| return def; |
| } |
| |
| METHOD(vici_message_t, get_bool, bool, |
| private_vici_message_t *this, bool def, char *fmt, ...) |
| { |
| va_list args; |
| bool val; |
| |
| va_start(args, fmt); |
| val = vget_bool(this, def, fmt, args); |
| va_end(args); |
| return val; |
| } |
| |
| METHOD(vici_message_t, vget_value, chunk_t, |
| private_vici_message_t *this, chunk_t def, char *fmt, va_list args) |
| { |
| chunk_t value; |
| bool found; |
| |
| found = find_value(this, &value, fmt, args); |
| if (found) |
| { |
| return value; |
| } |
| return def; |
| } |
| |
| METHOD(vici_message_t, get_value, chunk_t, |
| private_vici_message_t *this, chunk_t def, char *fmt, ...) |
| { |
| va_list args; |
| chunk_t value; |
| |
| va_start(args, fmt); |
| value = vget_value(this, def, fmt, args); |
| va_end(args); |
| return value; |
| } |
| |
| METHOD(vici_message_t, get_encoding, chunk_t, |
| private_vici_message_t *this) |
| { |
| return this->encoding; |
| } |
| |
| /** |
| * Private parse context data |
| */ |
| struct vici_parse_context_t { |
| /** current section nesting level */ |
| int level; |
| /** parse enumerator */ |
| enumerator_t *e; |
| }; |
| |
| METHOD(vici_message_t, parse, bool, |
| private_vici_message_t *this, vici_parse_context_t *ctx, |
| vici_section_cb_t section, vici_value_cb_t kv, vici_value_cb_t li, |
| void *user) |
| { |
| vici_parse_context_t root = {}; |
| char *name, *list = NULL; |
| vici_type_t type; |
| chunk_t value; |
| int base; |
| bool ok = TRUE; |
| |
| if (!ctx) |
| { |
| ctx = &root; |
| root.e = create_enumerator(this); |
| } |
| |
| base = ctx->level; |
| |
| while (ok) |
| { |
| ok = ctx->e->enumerate(ctx->e, &type, &name, &value); |
| if (ok) |
| { |
| switch (type) |
| { |
| case VICI_START: |
| /* should never occur */ |
| continue; |
| case VICI_KEY_VALUE: |
| if (ctx->level == base && kv) |
| { |
| name = strdup(name); |
| this->strings->insert_last(this->strings, name); |
| ok = kv(user, &this->public, name, value); |
| } |
| continue; |
| case VICI_LIST_START: |
| if (ctx->level == base) |
| { |
| list = strdup(name); |
| this->strings->insert_last(this->strings, list); |
| } |
| continue; |
| case VICI_LIST_ITEM: |
| if (list && li) |
| { |
| name = strdup(name); |
| this->strings->insert_last(this->strings, name); |
| ok = li(user, &this->public, list, value); |
| } |
| continue; |
| case VICI_LIST_END: |
| if (ctx->level == base) |
| { |
| list = NULL; |
| } |
| continue; |
| case VICI_SECTION_START: |
| if (ctx->level++ == base && section) |
| { |
| name = strdup(name); |
| this->strings->insert_last(this->strings, name); |
| ok = section(user, &this->public, ctx, name); |
| } |
| continue; |
| case VICI_SECTION_END: |
| if (ctx->level-- == base) |
| { |
| break; |
| } |
| continue; |
| case VICI_END: |
| break; |
| } |
| } |
| break; |
| } |
| |
| if (ctx == &root) |
| { |
| root.e->destroy(root.e); |
| } |
| return ok; |
| } |
| |
| METHOD(vici_message_t, dump, bool, |
| private_vici_message_t *this, char *label, bool pretty, FILE *out) |
| { |
| enumerator_t *enumerator; |
| int ident = 0, delta; |
| vici_type_t type, last_type = VICI_START; |
| char *name, *term, *sep, *separ, *assign; |
| chunk_t value; |
| |
| /* pretty print uses indentation on multiple lines */ |
| if (pretty) |
| { |
| delta = 2; |
| term = "\n"; |
| separ = ""; |
| assign = " = "; |
| } |
| else |
| { |
| delta = 0; |
| term = ""; |
| separ = " "; |
| assign = "="; |
| } |
| |
| fprintf(out, "%s {%s", label, term); |
| ident += delta; |
| |
| enumerator = create_enumerator(this); |
| while (enumerator->enumerate(enumerator, &type, &name, &value)) |
| { |
| switch (type) |
| { |
| case VICI_START: |
| /* should never occur */ |
| break; |
| case VICI_SECTION_START: |
| sep = (last_type != VICI_SECTION_START && |
| last_type != VICI_START) ? separ : ""; |
| fprintf(out, "%*s%s%s {%s", ident, "", sep, name, term); |
| ident += delta; |
| break; |
| case VICI_SECTION_END: |
| ident -= delta; |
| fprintf(out, "%*s}%s", ident, "", term); |
| break; |
| case VICI_KEY_VALUE: |
| sep = (last_type != VICI_SECTION_START && |
| last_type != VICI_START) ? separ : ""; |
| if (chunk_printable(value, NULL, ' ')) |
| { |
| fprintf(out, "%*s%s%s%s%.*s%s", ident, "", sep, name, |
| assign, (int)value.len, value.ptr, term); |
| } |
| else |
| { |
| fprintf(out, "%*s%s%s%s0x%+#B%s", ident, "", sep, name, |
| assign, &value, term); |
| } |
| break; |
| case VICI_LIST_START: |
| sep = (last_type != VICI_SECTION_START && |
| last_type != VICI_START) ? separ : ""; |
| fprintf(out, "%*s%s%s%s[%s", ident, "", sep, name, assign, term); |
| ident += delta; |
| break; |
| case VICI_LIST_END: |
| ident -= delta; |
| fprintf(out, "%*s]%s", ident, "", term); |
| break; |
| case VICI_LIST_ITEM: |
| sep = (last_type != VICI_LIST_START) ? separ : ""; |
| if (chunk_printable(value, NULL, ' ')) |
| { |
| fprintf(out, "%*s%s%.*s%s", ident, "", sep, |
| (int)value.len, value.ptr, term); |
| } |
| else |
| { |
| fprintf(out, "%*s%s0x%+#B%s", ident, "", sep, |
| &value, term); |
| } |
| break; |
| case VICI_END: |
| fprintf(out, "}\n"); |
| enumerator->destroy(enumerator); |
| return TRUE; |
| } |
| last_type = type; |
| } |
| enumerator->destroy(enumerator); |
| return FALSE; |
| } |
| |
| CALLBACK(clear_strings, void, |
| char *str) |
| { |
| memwipe(str, strlen(str)); |
| free(str); |
| } |
| |
| METHOD(vici_message_t, destroy, void, |
| private_vici_message_t *this) |
| { |
| if (this->cleanup) |
| { |
| chunk_clear(&this->encoding); |
| } |
| this->strings->destroy_function(this->strings, clear_strings); |
| free(this); |
| } |
| |
| /** |
| * See header |
| */ |
| vici_message_t *vici_message_create_from_data(chunk_t data, bool cleanup) |
| { |
| private_vici_message_t *this; |
| |
| INIT(this, |
| .public = { |
| .create_enumerator = _create_enumerator, |
| .get_str = _get_str, |
| .vget_str = _vget_str, |
| .get_int = _get_int, |
| .vget_int = _vget_int, |
| .get_bool = _get_bool, |
| .vget_bool = _vget_bool, |
| .get_value = _get_value, |
| .vget_value = _vget_value, |
| .get_encoding = _get_encoding, |
| .parse = _parse, |
| .dump = _dump, |
| .destroy = _destroy, |
| }, |
| .strings = linked_list_create(), |
| .encoding = data, |
| .cleanup = cleanup, |
| ); |
| |
| return &this->public; |
| } |
| |
| /** |
| * See header |
| */ |
| vici_message_t *vici_message_create_from_enumerator(enumerator_t *enumerator) |
| { |
| vici_builder_t *builder; |
| vici_type_t type; |
| char *name; |
| chunk_t value; |
| |
| builder = vici_builder_create(); |
| while (enumerator->enumerate(enumerator, &type, &name, &value)) |
| { |
| switch (type) |
| { |
| case VICI_SECTION_START: |
| case VICI_LIST_START: |
| builder->add(builder, type, name); |
| continue; |
| case VICI_KEY_VALUE: |
| builder->add(builder, type, name, value); |
| continue; |
| case VICI_LIST_ITEM: |
| builder->add(builder, type, value); |
| continue; |
| case VICI_SECTION_END: |
| case VICI_LIST_END: |
| default: |
| builder->add(builder, type); |
| continue; |
| case VICI_END: |
| break; |
| } |
| break; |
| } |
| enumerator->destroy(enumerator); |
| |
| return builder->finalize(builder); |
| } |
| |
| /** |
| * See header |
| */ |
| vici_message_t *vici_message_create_from_args(vici_type_t type, ...) |
| { |
| vici_builder_t *builder; |
| va_list args; |
| char *name; |
| chunk_t value; |
| |
| builder = vici_builder_create(); |
| va_start(args, type); |
| while (type != VICI_END) |
| { |
| switch (type) |
| { |
| case VICI_LIST_START: |
| case VICI_SECTION_START: |
| name = va_arg(args, char*); |
| builder->add(builder, type, name); |
| break; |
| case VICI_KEY_VALUE: |
| name = va_arg(args, char*); |
| value = va_arg(args, chunk_t); |
| builder->add(builder, type, name, value); |
| break; |
| case VICI_LIST_ITEM: |
| value = va_arg(args, chunk_t); |
| builder->add(builder, type, value); |
| break; |
| case VICI_SECTION_END: |
| case VICI_LIST_END: |
| default: |
| builder->add(builder, type); |
| break; |
| } |
| type = va_arg(args, vici_type_t); |
| } |
| va_end(args); |
| return builder->finalize(builder); |
| } |