From patchwork Fri Aug 26 18:23:32 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jan Kiszka X-Patchwork-Id: 111812 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [140.186.70.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 8E455B6F80 for ; Sat, 27 Aug 2011 04:23:53 +1000 (EST) Received: from localhost ([::1]:33668 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Qx14G-0004WP-8k for incoming@patchwork.ozlabs.org; Fri, 26 Aug 2011 14:23:48 -0400 Received: from eggs.gnu.org ([140.186.70.92]:50762) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Qx149-0004Vr-E9 for qemu-devel@nongnu.org; Fri, 26 Aug 2011 14:23:42 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Qx147-0004Lx-9N for qemu-devel@nongnu.org; Fri, 26 Aug 2011 14:23:41 -0400 Received: from thoth.sbs.de ([192.35.17.2]:30470) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Qx146-0004Lj-O9 for qemu-devel@nongnu.org; Fri, 26 Aug 2011 14:23:39 -0400 Received: from mail1.siemens.de (localhost [127.0.0.1]) by thoth.sbs.de (8.13.6/8.13.6) with ESMTP id p7QINX7w008630; Fri, 26 Aug 2011 20:23:34 +0200 Received: from mchn199C.mchp.siemens.de ([139.25.109.49]) by mail1.siemens.de (8.13.6/8.13.6) with ESMTP id p7QINWxf018639; Fri, 26 Aug 2011 20:23:33 +0200 Message-ID: <4E57E4A4.6070306@siemens.com> Date: Fri, 26 Aug 2011 20:23:32 +0200 From: Jan Kiszka User-Agent: Mozilla/5.0 (X11; U; Linux i686 (x86_64); de; rv:1.8.1.12) Gecko/20080226 SUSE/2.0.0.12-1.1 Thunderbird/2.0.0.12 Mnenhy/0.7.5.666 MIME-Version: 1.0 To: Anthony Liguori , qemu-devel References: <955e34e8282741639dbaad14be609feb66d9f464.1314370059.git.jan.kiszka@siemens.com> In-Reply-To: <955e34e8282741639dbaad14be609feb66d9f464.1314370059.git.jan.kiszka@siemens.com> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6, seldom 2.4 (older, 4) X-Received-From: 192.35.17.2 Cc: Luiz Capitulino Subject: [Qemu-devel] [PATCH v2 4/6] QMP: Add QBuffer X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org This introduces a buffer object for use with QMP. As a buffer is not natively encodable in JSON, we encode it as a base64 string and encapsulate the result in the new QMP object class "buffer". The first use case for this is pushing the content of buffers that are part of a device state into a qdict. CC: Luiz Capitulino Signed-off-by: Jan Kiszka --- Changes in v2: - fixed unit tests Makefile | 5 +- Makefile.objs | 2 +- QMP/qmp-spec.txt | 10 +++- check-qbuffer.c | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ configure | 2 +- qbuffer.c | 117 ++++++++++++++++++++++++++++++++++++ qbuffer.h | 33 ++++++++++ qjson.c | 15 +++++ qobject.h | 1 + 9 files changed, 352 insertions(+), 5 deletions(-) create mode 100644 check-qbuffer.c create mode 100644 qbuffer.c create mode 100644 qbuffer.h diff --git a/Makefile b/Makefile index 8606849..9dc2cf4 100644 --- a/Makefile +++ b/Makefile @@ -151,7 +151,7 @@ qemu-io$(EXESUF): qemu-io.o cmd.o qemu-tool.o qemu-error.o $(oslib-obj-y) $(trac qemu-img-cmds.h: $(SRC_PATH)/qemu-img-cmds.hx $(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $@") -check-qint.o check-qstring.o check-qdict.o check-qlist.o check-qfloat.o check-qjson.o test-coroutine.o: $(GENERATED_HEADERS) +check-qint.o check-qstring.o check-qdict.o check-qlist.o check-qfloat.o check-qjson.o test-coroutine.o check-qbuffer.o: $(GENERATED_HEADERS) CHECK_PROG_DEPS = $(oslib-obj-y) $(trace-obj-y) qemu-tool.o @@ -160,7 +160,8 @@ check-qstring: check-qstring.o qstring.o $(CHECK_PROG_DEPS) check-qdict: check-qdict.o qdict.o qfloat.o qint.o qstring.o qbool.o qlist.o $(CHECK_PROG_DEPS) check-qlist: check-qlist.o qlist.o qint.o $(CHECK_PROG_DEPS) check-qfloat: check-qfloat.o qfloat.o $(CHECK_PROG_DEPS) -check-qjson: check-qjson.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjson.o json-streamer.o json-lexer.o json-parser.o error.o qerror.o qemu-error.o $(CHECK_PROG_DEPS) +check-qbuffer: check-qbuffer.o qbuffer.o qstring.o base64.o $(CHECK_PROG_DEPS) +check-qjson: check-qjson.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjson.o qbuffer.o base64.o json-streamer.o json-lexer.o json-parser.o error.o qerror.o qemu-error.o $(CHECK_PROG_DEPS) test-coroutine: test-coroutine.o qemu-timer-common.o async.o $(coroutine-obj-y) $(CHECK_PROG_DEPS) $(qapi-obj-y): $(GENERATED_HEADERS) diff --git a/Makefile.objs b/Makefile.objs index 41febf6..dc79057 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -1,6 +1,6 @@ ####################################################################### # QObject -qobject-obj-y = qint.o qstring.o qdict.o qlist.o qfloat.o qbool.o +qobject-obj-y = qint.o qstring.o qdict.o qlist.o qfloat.o qbool.o qbuffer.o qobject-obj-y += qjson.o json-lexer.o json-streamer.o json-parser.o qobject-obj-y += qerror.o error.o base64.o diff --git a/QMP/qmp-spec.txt b/QMP/qmp-spec.txt index fa1dd62..820e39d 100644 --- a/QMP/qmp-spec.txt +++ b/QMP/qmp-spec.txt @@ -153,7 +153,15 @@ JSON objects that contain the key-value pair '"__class__": json-string' are reserved for QMP-specific complex object classes that. QMP specifies which further keys each of these objects include and how they are encoded. -So far, no complex object class is specified. +2.6.1 Buffer class +------------------ + +This QMP object class allows to transport binary data. A buffer object +consists of the following keys: + +{ "__class__": "buffer", "data": json-string } + +The data string is base64 encoded according to RFC 4648. 3. QMP Examples =============== diff --git a/check-qbuffer.c b/check-qbuffer.c new file mode 100644 index 0000000..0876e9f --- /dev/null +++ b/check-qbuffer.c @@ -0,0 +1,172 @@ +/* + * QBuffer unit-tests. + * + * Copyright (C) 2010 Siemens AG + * + * Authors: + * Jan Kiszka + * + * This work is licensed under the terms of the GNU GPL version 2. + * See the COPYING file in the top-level directory. + * + */ +#include + +#include "qbuffer.h" +#include "qemu-common.h" + +const char data[] = "some data"; + +START_TEST(qbuffer_from_data_test) +{ + QBuffer *qbuffer; + + qbuffer = qbuffer_from_data(data, sizeof(data)); + fail_unless(qbuffer != NULL); + fail_unless(qbuffer->base.refcnt == 1); + fail_unless(memcmp(data, qbuffer->data, sizeof(data)) == 0); + fail_unless(qbuffer->size == sizeof(data)); + fail_unless(qobject_type(QOBJECT(qbuffer)) == QTYPE_QBUFFER); + + /* destroy doesn't exit yet */ + g_free(qbuffer->data); + g_free(qbuffer); +} +END_TEST + +START_TEST(qbuffer_destroy_test) +{ + QBuffer *qbuffer = qbuffer_from_data(data, sizeof(data)); + + QDECREF(qbuffer); +} +END_TEST + +START_TEST(qbuffer_get_data_test) +{ + QBuffer *qbuffer; + const void *ret_data; + + qbuffer = qbuffer_from_data(data, sizeof(data)); + ret_data = qbuffer_get_data(qbuffer); + fail_unless(memcmp(ret_data, data, sizeof(data)) == 0); + + QDECREF(qbuffer); +} +END_TEST + +START_TEST(qbuffer_get_size_test) +{ + QBuffer *qbuffer; + + qbuffer = qbuffer_from_data(data, sizeof(data)); + fail_unless(qbuffer_get_size(qbuffer) == sizeof(data)); + + QDECREF(qbuffer); +} +END_TEST + +START_TEST(qbuffer_from_qstring_test) +{ + const struct { + const char *encoded; + const char *decoded; + } pattern[3] = { + { + .encoded = "SGVsbG8sIFFCdWZmZXIhCg==", + .decoded = "Hello, QBuffer!", + }, + { + .encoded = "SGVsbG8gUUJ1ZmZlcgo=", + .decoded = "Hello QBuffer", + }, + { + .encoded = "SGVsbG8gUUJ1ZmZlciEK===", + .decoded = "Hello QBuffer!", + }, + }; + QBuffer *qbuffer; + QString *qstring; + int i; + + for (i = 0; i < ARRAY_SIZE(pattern); i++) { + qstring = qstring_from_str(pattern[i].encoded); + qbuffer = qbuffer_from_qstring(qstring); + QDECREF(qstring); + + fail_unless(qbuffer != NULL); + fail_unless(memcmp(qbuffer_get_data(qbuffer), pattern[i].decoded, + sizeof(pattern[i].decoded)) == 0); + + QDECREF(qbuffer); + } +} +END_TEST + +START_TEST(qbuffer_from_invalid_qstring_test) +{ + const char *pattern[] = { + "SGVsbG8sIFFCdWZmZXIhC", + "SGVsbG8gU=UJ1ZmZlcgo", + "SGVsbG8gUUJ1*ZmZlciEK", + }; + QBuffer *qbuffer; + QString *qstring; + int i; + + for (i = 0; i < ARRAY_SIZE(pattern); i++) { + qstring = qstring_from_str(pattern[i]); + qbuffer = qbuffer_from_qstring(qstring); + QDECREF(qstring); + + fail_unless(qbuffer == NULL); + } +} +END_TEST + +START_TEST(qobject_to_qbuffer_test) +{ + QBuffer *qbuffer; + + qbuffer = qbuffer_from_data(data, sizeof(data)); + fail_unless(qobject_to_qbuffer(QOBJECT(qbuffer)) == qbuffer); + + QDECREF(qbuffer); +} +END_TEST + +static Suite *qbuffer_suite(void) +{ + Suite *s; + TCase *qbuffer_public_tcase; + + s = suite_create("QBuffer test-suite"); + + qbuffer_public_tcase = tcase_create("Public Interface"); + suite_add_tcase(s, qbuffer_public_tcase); + tcase_add_test(qbuffer_public_tcase, qbuffer_from_data_test); + tcase_add_test(qbuffer_public_tcase, qbuffer_destroy_test); + tcase_add_test(qbuffer_public_tcase, qbuffer_get_data_test); + tcase_add_test(qbuffer_public_tcase, qbuffer_get_size_test); + tcase_add_test(qbuffer_public_tcase, qbuffer_from_qstring_test); + tcase_add_test(qbuffer_public_tcase, qbuffer_from_invalid_qstring_test); + tcase_add_test(qbuffer_public_tcase, qobject_to_qbuffer_test); + + return s; +} + +int main(void) +{ + int nf; + Suite *s; + SRunner *sr; + + s = qbuffer_suite(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + nf = srunner_ntests_failed(sr); + srunner_free(sr); + + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/configure b/configure index 1340c33..3521898 100755 --- a/configure +++ b/configure @@ -2630,7 +2630,7 @@ if test "$softmmu" = yes ; then tools="qemu-ga\$(EXESUF) $tools" fi if [ "$check_utests" = "yes" ]; then - tools="check-qint check-qstring check-qdict check-qlist $tools" + tools="check-qint check-qstring check-qdict check-qlist check-qbuffer $tools" tools="check-qfloat check-qjson $tools" fi fi diff --git a/qbuffer.c b/qbuffer.c new file mode 100644 index 0000000..4252a1e --- /dev/null +++ b/qbuffer.c @@ -0,0 +1,117 @@ +/* + * QBuffer Module + * + * Copyright (C) 2010 Siemens AG + * + * Authors: + * Jan Kiszka + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qbuffer.h" +#include "qobject.h" +#include "qemu-common.h" +#include "base64.h" + +static void qbuffer_destroy_obj(QObject *obj); + +static const QType qbuffer_type = { + .code = QTYPE_QBUFFER, + .destroy = qbuffer_destroy_obj, +}; + +/** + * qbuffer_from_data(): Create a new QBuffer from an existing data blob + * + * Returns strong reference. + */ +QBuffer *qbuffer_from_data(const void *data, size_t size) +{ + QBuffer *qb; + + qb = g_malloc(sizeof(*qb)); + qb->data = g_malloc(size); + memcpy(qb->data, data, size); + qb->size = size; + QOBJECT_INIT(qb, &qbuffer_type); + + return qb; +} + +/** + * qbuffer_from_qstring(): Create a new QBuffer from a QString object that + * contains the data as a stream of hex-encoded bytes + * + * Returns strong reference. + */ +QBuffer *qbuffer_from_qstring(const QString *string) +{ + const char *str = qstring_get_str(string); + size_t str_len; + QBuffer *qb; + + qb = g_malloc(sizeof(*qb)); + + str_len = strlen(str); + while (str_len > 0 && str[str_len - 1] == '=') { + str_len--; + } + qb->size = (str_len / 4) * 3 + ((str_len % 4) * 3) / 4; + qb->data = g_malloc(qb->size); + + QOBJECT_INIT(qb, &qbuffer_type); + + if (base64_decode(str, str_len, qb->data) < 0) { + qbuffer_destroy_obj(QOBJECT(qb)); + return NULL; + } + + return qb; +} + +/** + * qbuffer_get_data(): Return pointer to stored data + * + * NOTE: Should be used with caution, if the object is deallocated + * this pointer becomes invalid. + */ +const void *qbuffer_get_data(const QBuffer *qb) +{ + return qb->data; +} + +/** + * qbuffer_get_size(): Return size of stored data + */ +size_t qbuffer_get_size(const QBuffer *qb) +{ + return qb->size; +} + +/** + * qobject_to_qbool(): Convert a QObject into a QBuffer + */ +QBuffer *qobject_to_qbuffer(const QObject *obj) +{ + if (qobject_type(obj) != QTYPE_QBUFFER) { + return NULL; + } + + return container_of(obj, QBuffer, base); +} + +/** + * qbuffer_destroy_obj(): Free all memory allocated by a QBuffer object + */ +static void qbuffer_destroy_obj(QObject *obj) +{ + QBuffer *qb; + + assert(obj != NULL); + qb = qobject_to_qbuffer(obj); + g_free(qb->data); + g_free(qb); +} diff --git a/qbuffer.h b/qbuffer.h new file mode 100644 index 0000000..2e01078 --- /dev/null +++ b/qbuffer.h @@ -0,0 +1,33 @@ +/* + * QBuffer Module + * + * Copyright (C) 2010 Siemens AG + * + * Authors: + * Jan Kiszka + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef QBUFFER_H +#define QBUFFER_H + +#include +#include "qobject.h" +#include "qstring.h" + +typedef struct QBuffer { + QObject_HEAD; + void *data; + size_t size; +} QBuffer; + +QBuffer *qbuffer_from_data(const void *data, size_t size); +QBuffer *qbuffer_from_qstring(const QString *string); +const void *qbuffer_get_data(const QBuffer *qb); +size_t qbuffer_get_size(const QBuffer *qb); +QBuffer *qobject_to_qbuffer(const QObject *obj); + +#endif /* QBUFFER_H */ diff --git a/qjson.c b/qjson.c index f9c8e77..d415f83 100644 --- a/qjson.c +++ b/qjson.c @@ -19,7 +19,9 @@ #include "qlist.h" #include "qbool.h" #include "qfloat.h" +#include "qbuffer.h" #include "qdict.h" +#include "base64.h" typedef struct JSONParsingState { @@ -268,6 +270,19 @@ static void to_json(const QObject *obj, QString *str, int pretty, int indent) } break; } + case QTYPE_QBUFFER: { + QBuffer *val = qobject_to_qbuffer(obj); + size_t data_size = qbuffer_get_size(val); + size_t str_len = ((data_size + 2) / 3) * 4; + char *buffer = g_malloc(str_len + 1); + + base64_encode(qbuffer_get_data(val), data_size, buffer); + qstring_append(str, "{\"__class__\": \"buffer\", \"data\": \""); + qstring_append(str, buffer); + qstring_append(str, "\"}"); + g_free(buffer); + break; + } case QTYPE_QERROR: /* XXX: should QError be emitted? */ case QTYPE_NONE: diff --git a/qobject.h b/qobject.h index d42386d..4ec932b 100644 --- a/qobject.h +++ b/qobject.h @@ -44,6 +44,7 @@ typedef enum { QTYPE_QFLOAT, QTYPE_QBOOL, QTYPE_QERROR, + QTYPE_QBUFFER, } qtype_code; struct QObject;