From 895a5695294975c87e1695f41be997fbeac85563 Mon Sep 17 00:00:00 2001 From: Sebastian Harl Date: Sat, 20 Dec 2014 16:05:50 +0100 Subject: [PATCH] proto: Added sdb_proto_marshal_data. This function encodes a datum into the wire format. --- src/include/utils/proto.h | 22 +++++- src/utils/proto.c | 144 ++++++++++++++++++++++++++++++++++++ t/Makefile.am | 1 + t/unit/libsysdb_test.c | 1 + t/unit/libsysdb_test.h | 4 + t/unit/utils/proto_test.c | 152 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 t/unit/utils/proto_test.c diff --git a/src/include/utils/proto.h b/src/include/utils/proto.h index a001010..4968977 100644 --- a/src/include/utils/proto.h +++ b/src/include/utils/proto.h @@ -28,7 +28,7 @@ #ifndef SDB_UTILS_PROTO_H #define SDB_UTILS_PROTO_H 1 -#include "utils/strbuf.h" +#include "core/data.h" #include #include @@ -44,14 +44,30 @@ extern "C" { * the header (64 bits) and the entire message. * * Returns: - * - the number of bytes of the full encoded message on success (even if less - * than that fit into and was written to the buffer) + * - The number of bytes of the full encoded message on success. The function + * does not write more than 'buf_len' bytes. If the output was truncated + * then the return value is the number of bytes which would have been + * written if enough space had been available. * - a negative value on error */ ssize_t sdb_proto_marshal(char *buf, size_t buf_len, uint32_t code, uint32_t msg_len, const char *msg); +/* + * sdb_proto_marshal_data: + * Encode a datum into the wire format and write it to buf. + * + * Returns: + * - The number of bytes of the full encoded datum on success. The function + * does not write more than 'buf_len' bytes. If the output was truncated + * then the return value is the number of bytes which would have been + * written if enough space had been available. + * - a negative value else + */ +ssize_t +sdb_proto_marshal_data(char *buf, size_t buf_len, sdb_data_t *datum); + /* * sdb_proto_unmarshal_header: * Read and decode a message header from the specified string. diff --git a/src/utils/proto.c b/src/utils/proto.c index b5cfe04..cb88887 100644 --- a/src/utils/proto.c +++ b/src/utils/proto.c @@ -25,6 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "core/data.h" +#include "core/time.h" #include "utils/error.h" #include "utils/proto.h" @@ -38,6 +40,62 @@ #include +/* + * private helper functions + */ + +/* In case there's not enough buffer space, the marshal functions have to + * return the number of bytes that would have been written if enough space had + * been available. */ + +static ssize_t +marshal_int(char *buf, size_t buf_len, int64_t v) +{ + if (buf_len >= sizeof(v)) { +#if __BYTE_ORDER != __BIG_ENDIAN + v = (((int64_t)ntohl((int32_t)v)) << 32) + + ((int64_t)ntohl((int32_t)(v >> 32))); +#endif + memcpy(buf, &v, sizeof(v)); + } + return sizeof(v); +} /* marshal_int */ + +static ssize_t +marshal_double(char __attribute__((unused)) *buf, + size_t __attribute__((unused)) buf_len, + double __attribute__((unused)) v) +{ + /* XXX: find a good network representation */ + errno = ENOTSUP; + return -1; +} /* marshal_double */ + +static ssize_t +marshal_datetime(char *buf, size_t buf_len, sdb_time_t v) +{ + return marshal_int(buf, buf_len, (int64_t)v); +} /* marshal_datetime */ + +static ssize_t +marshal_binary(char *buf, size_t buf_len, size_t len, const unsigned char *v) +{ + uint32_t tmp = htonl((uint32_t)len); + if (buf_len >= len) { + memcpy(buf, &tmp, sizeof(tmp)); + memcpy(buf + sizeof(tmp), v, len); + } + return sizeof(tmp) + len; +} /* marshal_binary */ + +static ssize_t +marshal_string(char *buf, size_t buf_len, const char *v) +{ + /* The actual string including the terminating null byte. */ + return marshal_binary(buf, buf_len, + strlen(v) + 1, (const unsigned char *)v); +} /* marshal_string */ + /* * public API */ @@ -64,6 +122,92 @@ sdb_proto_marshal(char *buf, size_t buf_len, uint32_t code, return len; } /* sdb_proto_marshal */ +ssize_t +sdb_proto_marshal_data(char *buf, size_t buf_len, sdb_data_t *datum) +{ + ssize_t len = 0, n = 0; + uint32_t tmp; + size_t i; + int type; + + if (buf_len >= sizeof(tmp)) { + tmp = htonl((uint32_t)datum->type); + memcpy(buf, &tmp, sizeof(tmp)); + buf += sizeof(tmp); + buf_len -= sizeof(tmp); + } + else + buf_len = 0; + len += sizeof(tmp); + + if (datum->type == SDB_TYPE_NULL) + return len; + + if (datum->type == SDB_TYPE_INTEGER) + n = marshal_int(buf, buf_len, datum->data.integer); + else if (datum->type == SDB_TYPE_DECIMAL) + n = marshal_double(buf, buf_len, datum->data.decimal); + else if (datum->type == SDB_TYPE_STRING) + n = marshal_string(buf, buf_len, datum->data.string); + else if (datum->type == SDB_TYPE_DATETIME) + n = marshal_datetime(buf, buf_len, datum->data.datetime); + else if (datum->type == SDB_TYPE_BINARY) + n = marshal_binary(buf, buf_len, + datum->data.binary.length, datum->data.binary.datum); + + if (n < 0) + return n; + else if (n > 0) + return len + n; + + if (! (datum->type & SDB_TYPE_ARRAY)) { + errno = EINVAL; + return -1; + } + + /* arrays */ + if (buf_len >= sizeof(tmp)) { + tmp = htonl((uint32_t)datum->data.array.length); + memcpy(buf, &tmp, sizeof(tmp)); + buf += sizeof(tmp); + buf_len -= sizeof(tmp); + } + else + buf_len = 0; + len += sizeof(tmp); + + type = datum->type & 0xff; + for (i = 0; i < datum->data.array.length; ++i) { + if (type == SDB_TYPE_INTEGER) { + int64_t *v = datum->data.array.values; + n = marshal_int(buf, buf_len, v[i]); + } + else if (type == SDB_TYPE_DECIMAL) { + double *v = datum->data.array.values; + n = marshal_double(buf, buf_len, v[i]); + } + else if (type == SDB_TYPE_STRING) { + char **v = datum->data.array.values; + n = marshal_string(buf, buf_len, v[i]); + } + else { + errno = ENOTSUP; + return -1; + } + + if (n < 0) + return -1; + if (buf_len >= (size_t)n) { + buf += n; + buf_len -= n; + } + else + buf_len = 0; + len += n; + } + return len; +} /* sdb_proto_marshal_data */ + int sdb_proto_unmarshal_header(const char *buf, size_t buf_len, uint32_t *code, uint32_t *msg_len) diff --git a/t/Makefile.am b/t/Makefile.am index f7a8340..fd997e9 100644 --- a/t/Makefile.am +++ b/t/Makefile.am @@ -45,6 +45,7 @@ unit_libsysdb_test_SOURCES = \ unit/utils/dbi_test.c \ unit/utils/llist_test.c \ unit/utils/os_test.c \ + unit/utils/proto_test.c \ unit/utils/strbuf_test.c unit_libsysdb_test_CFLAGS = $(AM_CFLAGS) @CHECK_CFLAGS@ -I$(top_srcdir)/t/unit unit_libsysdb_test_LDADD = $(top_builddir)/src/libsysdb.la @CHECK_LIBS@ diff --git a/t/unit/libsysdb_test.c b/t/unit/libsysdb_test.c index af1c3e8..2a7a477 100644 --- a/t/unit/libsysdb_test.c +++ b/t/unit/libsysdb_test.c @@ -56,6 +56,7 @@ main(void) { util_dbi_suite, NULL }, { util_llist_suite, NULL }, { util_os_suite, NULL }, + { util_proto_suite, NULL }, { util_strbuf_suite, NULL }, }; diff --git a/t/unit/libsysdb_test.h b/t/unit/libsysdb_test.h index af84b50..1e18503 100644 --- a/t/unit/libsysdb_test.h +++ b/t/unit/libsysdb_test.h @@ -105,6 +105,10 @@ util_llist_suite(void); Suite * util_os_suite(void); +/* t/utils/proto_test */ +Suite * +util_proto_suite(void); + /* t/utils/strbuf_test */ Suite * util_strbuf_suite(void); diff --git a/t/unit/utils/proto_test.c b/t/unit/utils/proto_test.c new file mode 100644 index 0000000..d0516b3 --- /dev/null +++ b/t/unit/utils/proto_test.c @@ -0,0 +1,152 @@ +/* + * SysDB - t/unit/utils/proto_test.c + * Copyright (C) 2014 Sebastian 'tokkee' Harl + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "utils/proto.h" +#include "libsysdb_test.h" + +#include +#include +#include + +START_TEST(test_marshal_data) +{ +#define INT_TYPE "\0\0\0\1" +#define DECIMAL_TYPE "\0\0\0\2" +#define STRING_TYPE "\0\0\0\3" +#define DATETIME_TYPE "\0\0\0\4" +#define BINARY_TYPE "\0\0\0\5" + +#define NULL_ARRAY "\0\0\1\0" +#define INT_ARRAY "\0\0\1\1" +#define DECIMAL_ARRAY "\0\0\1\2" +#define STRING_ARRAY "\0\0\1\3" +#define DATETIME_ARRAY "\0\0\1\4" +#define BINARY_ARRAY "\0\0\1\5" + + regex_t dummy_re; + int64_t int_values[] = { 47, 11, 23 }; + char *string_values[] = { "foo", "abcd" }; + + struct { + sdb_data_t datum; + ssize_t expected_len; + char *expected; + } golden_data[] = { + { + { SDB_TYPE_NULL, { .integer = 0 } }, + 4, "\0\0\0\0", + }, + { + { SDB_TYPE_INTEGER, { .integer = 4711 } }, + 12, INT_TYPE "\0\0\0\0\0\0\x12\x67", + }, + { + { SDB_TYPE_DECIMAL, { .integer = 4711 } }, + -1, NULL, /* not supported yet */ + }, + { + { SDB_TYPE_STRING, { .string = "some string" } }, + /* length includes the null byte */ + 20, STRING_TYPE "\0\0\0\xc" "some string\0", + }, + { + { SDB_TYPE_DATETIME, { .datetime = 1418923804000000 } }, + 12, DATETIME_TYPE "\x0\x5\xa\x80\xf1\x4c\xff\x0", + }, + { + { SDB_TYPE_BINARY, { .binary = { + 4, (unsigned char *)"\x42\x0\xa\x1b" } } }, + 12, BINARY_TYPE "\0\0\0\x4" "\x42\x0\xa\x1b", + }, + { + { SDB_TYPE_REGEX, { .re = { "dummy", dummy_re } } }, + -1, NULL, /* not supported */ + }, + { + { SDB_TYPE_INTEGER | SDB_TYPE_ARRAY, { .array = { + 3, int_values } } }, + 32, INT_ARRAY "\0\0\0\x3" "\0\0\0\0\0\0\0\x2f" + "\0\0\0\0\0\0\0\xb" "\0\0\0\0\0\0\0\x17" + }, + { + { SDB_TYPE_STRING | SDB_TYPE_ARRAY, { .array = { + 2, string_values } } }, + 25, STRING_ARRAY "\0\0\0\x2" "\0\0\0\x4" "foo\0" + "\0\0\0\x5" "abcd\0" + }, + }; + + size_t i; + + for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) { + ssize_t len = sdb_proto_marshal_data(NULL, 0, &golden_data[i].datum); + char buf[len > 0 ? len : 1]; + char v[sdb_data_strlen(&golden_data[i].datum)]; + + if (sdb_data_format(&golden_data[i].datum, v, sizeof(v), 0) < 0) + snprintf(v, sizeof(v), ""); + + fail_unless(len == golden_data[i].expected_len, + "sdb_proto_marshal_data(NULL, 0, %s) = %zi; expected: %zi", + v, len, golden_data[i].expected_len); + + if (len < 0) + continue; + + len = sdb_proto_marshal_data(buf, sizeof(buf), &golden_data[i].datum); + fail_unless(len == golden_data[i].expected_len, + "sdb_proto_marshal_data(, , %s) = %zi; expected: %zi", + v, len, golden_data[i].expected_len); + if (memcmp(buf, golden_data[i].expected, len) != 0) { + size_t pos; + for (pos = 0; pos < (size_t)len; ++pos) + if (buf[pos] != golden_data[i].expected[pos]) + break; + fail("sdb_proto_marshal_data(%s) -> \"%s\"; expected: \"%s\" " + "(bytes %zu differ: '%x' != '%x')", + v, buf, golden_data[i].expected, + pos, (int)buf[pos], (int)golden_data[i].expected[pos]); + } + } +} +END_TEST + +Suite * +util_proto_suite(void) +{ + Suite *s = suite_create("utils::proto"); + TCase *tc; + + tc = tcase_create("core"); + tcase_add_test(tc, test_marshal_data); + suite_add_tcase(s, tc); + + return s; +} /* util_proto_suite */ + +/* vim: set tw=78 sw=4 ts=4 noexpandtab : */ + -- 2.30.2