Systemd/src/libsystemd/sd-bus/test-bus-marshal.c

423 lines
14 KiB
C
Raw Normal View History

/* SPDX-License-Identifier: LGPL-2.1+ */
#include <math.h>
#include <stdlib.h>
#if HAVE_GLIB
#include <gio/gio.h>
#endif
#if HAVE_DBUS
#include <dbus/dbus.h>
#endif
#include "sd-bus.h"
#include "alloc-util.h"
#include "bus-dump.h"
#include "bus-label.h"
#include "bus-message.h"
#include "bus-util.h"
#include "fd-util.h"
#include "hexdecoct.h"
#include "log.h"
#include "tests.h"
#include "util.h"
bus: implement bus_path_{en,de}code_unique() Whenever we provide a bus API that allows clients to create and manage server-side objects, we need to provide a unique name for these objects. There are two ways to provide them: 1) Let the server choose a name and return it as method reply. 2) Let the client pass its name of choice in the method arguments. The first method is the easiest one to implement. However, it suffers from a race condition: If a client creates an object asynchronously, it cannot destroy that object until it received the method reply. It cannot know the name of the new object, thus, it cannot destroy it. Furthermore, this method enforces a round-trip. If the client _depends_ on the method call to succeed (eg., it would close() the connection if it failed), the client usually has no reason to wait for the method reply. Instead, the client can immediately schedule further method calls on the newly created object (in case the API guarantees in-order method-call handling). The second method fixes both problems: The client passes an object name with the method-call. The server uses it to create the object. Therefore, the client can schedule object destruction even if the object-creation hasn't finished, yet (again, requiring in-order method-call handling). Furthermore, the client can schedule further method calls on the newly created object, before the constructor returned. There're two problems to solve, though: 1) Object names are usually defined via dbus object paths, which are usually globally namespaced. Therefore, multiple clients must be able to choose unique object names without interference. 2) If multiple libraries share the same bus connection, they must be able to choose unique object names without interference. The first problem is solved easily by prefixing a name with the unique-bus-name of a connection. The server side must enforce this and reject any other name. The second problem is solved by providing unique suffixes from within sd-bus. As long as sd-bus always returns a fresh new ID, if requested, multiple libraries will never interfere. This implementation re-uses bus->cookie as ID generator, which already provides unique IDs for each bus connection. This patch introduces two new helpers: bus_path_encode_unique(sd_bus *bus, const char *prefix, const char *sender_id, const char *external_id, char **ret_path); This creates a new object-path via the template '/prefix/sender_id/external_id'. That is, it appends two new labels to the given prefix. If 'sender_id' is NULL, it will use bus->unique_name, if 'external_id' is NULL, it will allocate a fresh, unique cookie from bus->cookie. bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external); This reverses what bus_path_encode_unique() did. It parses 'path' from the template '/prefix/sender/external' and returns both suffix-labels in 'ret_sender' and 'ret_external'. In case the template does not match, 0 is returned and both output arguments are set to NULL. Otherwise, 1 is returned and the output arguments contain the decoded labels. Note: Client-side allocated IDs are inspired by the Wayland protocol (which itself was inspired by X11). Wayland uses those IDs heavily to avoid round-trips. Clients can create server-side objects and send method calls without any round-trip and waiting for any object IDs to be returned. But unlike Wayland, DBus uses gobally namespaced object names. Therefore, we have to add the extra step by adding the unique-name of the bus connection.
2015-04-10 17:44:30 +02:00
static void test_bus_path_encode_unique(void) {
_cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL;
assert_se(bus_path_encode_unique(NULL, "/foo/bar", "some.sender", "a.suffix", &a) >= 0 && streq_ptr(a, "/foo/bar/some_2esender/a_2esuffix"));
assert_se(bus_path_decode_unique(a, "/foo/bar", &b, &c) > 0 && streq_ptr(b, "some.sender") && streq_ptr(c, "a.suffix"));
assert_se(bus_path_decode_unique(a, "/bar/foo", &d, &d) == 0 && !d);
assert_se(bus_path_decode_unique("/foo/bar/onlyOneSuffix", "/foo/bar", &d, &d) == 0 && !d);
assert_se(bus_path_decode_unique("/foo/bar/_/_", "/foo/bar", &d, &e) > 0 && streq_ptr(d, "") && streq_ptr(e, ""));
}
static void test_bus_path_encode(void) {
_cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *f = NULL;
assert_se(sd_bus_path_encode("/foo/bar", "waldo", &a) >= 0 && streq(a, "/foo/bar/waldo"));
assert_se(sd_bus_path_decode(a, "/waldo", &b) == 0 && b == NULL);
assert_se(sd_bus_path_decode(a, "/foo/bar", &b) > 0 && streq(b, "waldo"));
assert_se(sd_bus_path_encode("xxxx", "waldo", &c) < 0);
assert_se(sd_bus_path_encode("/foo/", "waldo", &c) < 0);
assert_se(sd_bus_path_encode("/foo/bar", "", &c) >= 0 && streq(c, "/foo/bar/_"));
assert_se(sd_bus_path_decode(c, "/foo/bar", &d) > 0 && streq(d, ""));
assert_se(sd_bus_path_encode("/foo/bar", "foo.bar", &e) >= 0 && streq(e, "/foo/bar/foo_2ebar"));
assert_se(sd_bus_path_decode(e, "/foo/bar", &f) > 0 && streq(f, "foo.bar"));
}
static void test_bus_path_encode_many(void) {
_cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *f = NULL;
assert_se(sd_bus_path_decode_many("/foo/bar", "/prefix/%", NULL) == 0);
assert_se(sd_bus_path_decode_many("/prefix/bar", "/prefix/%bar", NULL) == 1);
assert_se(sd_bus_path_decode_many("/foo/bar", "/prefix/%/suffix", NULL) == 0);
assert_se(sd_bus_path_decode_many("/prefix/foobar/suffix", "/prefix/%/suffix", &a) == 1 && streq_ptr(a, "foobar"));
assert_se(sd_bus_path_decode_many("/prefix/one_foo_two/mid/three_bar_four/suffix", "/prefix/one_%_two/mid/three_%_four/suffix", &b, &c) == 1 && streq_ptr(b, "foo") && streq_ptr(c, "bar"));
assert_se(sd_bus_path_decode_many("/prefix/one_foo_two/mid/three_bar_four/suffix", "/prefix/one_%_two/mid/three_%_four/suffix", NULL, &d) == 1 && streq_ptr(d, "bar"));
assert_se(sd_bus_path_decode_many("/foo/bar", "/foo/bar/%", NULL) == 0);
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/bar%", NULL) == 0);
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/bar", NULL) == 0);
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%bar", NULL) == 0);
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/bar/suffix") == 1);
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%%/suffix", NULL, NULL) == 0); /* multiple '%' are treated verbatim */
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/suffi", NULL) == 0);
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/suffix", &e) == 1 && streq_ptr(e, "bar"));
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/%", NULL, NULL) == 1);
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%/%", NULL, NULL, NULL) == 1);
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "%/%/%", NULL, NULL, NULL) == 0);
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%", NULL, NULL) == 0);
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%/", NULL, NULL) == 0);
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/", NULL) == 0);
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%", NULL) == 0);
assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "%", NULL) == 0);
assert_se(sd_bus_path_encode_many(&f, "/prefix/one_%_two/mid/three_%_four/suffix", "foo", "bar") >= 0 && streq_ptr(f, "/prefix/one_foo_two/mid/three_bar_four/suffix"));
}
static void test_bus_label_escape_one(const char *a, const char *b) {
_cleanup_free_ char *t = NULL, *x = NULL, *y = NULL;
assert_se(t = bus_label_escape(a));
assert_se(streq(t, b));
assert_se(x = bus_label_unescape(t));
assert_se(streq(a, x));
assert_se(y = bus_label_unescape(b));
assert_se(streq(a, y));
}
static void test_bus_label_escape(void) {
test_bus_label_escape_one("foo123bar", "foo123bar");
test_bus_label_escape_one("foo.bar", "foo_2ebar");
test_bus_label_escape_one("foo_2ebar", "foo_5f2ebar");
test_bus_label_escape_one("", "_");
test_bus_label_escape_one("_", "_5f");
test_bus_label_escape_one("1", "_31");
test_bus_label_escape_one(":1", "_3a1");
}
int main(int argc, char *argv[]) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *copy = NULL;
2013-03-20 03:15:03 +01:00
int r, boolean;
2014-01-12 12:15:29 +01:00
const char *x, *x2, *y, *z, *a, *b, *c, *d, *a_signature;
uint8_t u, v;
void *buffer = NULL;
size_t sz;
char *h;
const int32_t integer_array[] = { -1, -2, 0, 1, 2 }, *return_array;
char *s;
_cleanup_free_ char *first = NULL, *second = NULL, *third = NULL;
_cleanup_fclose_ FILE *ms = NULL;
size_t first_size = 0, second_size = 0, third_size = 0;
_cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
double dbl;
uint64_t u64;
test_setup_logging(LOG_INFO);
r = sd_bus_default_user(&bus);
if (r < 0)
r = sd_bus_default_system(&bus);
if (r < 0)
return log_tests_skipped("Failed to connect to bus");
r = sd_bus_message_new_method_call(bus, &m, "foobar.waldo", "/", "foobar.waldo", "Piep");
assert_se(r >= 0);
r = sd_bus_message_append(m, "");
assert_se(r >= 0);
r = sd_bus_message_append(m, "s", "a string");
assert_se(r >= 0);
r = sd_bus_message_append(m, "s", NULL);
assert_se(r >= 0);
2014-01-12 12:15:29 +01:00
r = sd_bus_message_append(m, "asg", 2, "string #1", "string #2", "sba(tt)ss");
assert_se(r >= 0);
r = sd_bus_message_append(m, "sass", "foobar", 5, "foo", "bar", "waldo", "piep", "pap", "after");
assert_se(r >= 0);
r = sd_bus_message_append(m, "a{yv}", 2, 3, "s", "foo", 5, "s", "waldo");
assert_se(r >= 0);
r = sd_bus_message_append(m, "y(ty)y(yt)y", 8, 777ULL, 7, 9, 77, 7777ULL, 10);
2015-07-16 11:00:55 +02:00
assert_se(r >= 0);
r = sd_bus_message_append(m, "()");
assert_se(r >= 0);
r = sd_bus_message_append(m, "ba(ss)", 255, 3, "aaa", "1", "bbb", "2", "ccc", "3");
assert_se(r >= 0);
r = sd_bus_message_open_container(m, 'a', "s");
assert_se(r >= 0);
r = sd_bus_message_append_basic(m, 's', "foobar");
assert_se(r >= 0);
r = sd_bus_message_append_basic(m, 's', "waldo");
assert_se(r >= 0);
r = sd_bus_message_close_container(m);
assert_se(r >= 0);
r = sd_bus_message_append_string_space(m, 5, &s);
assert_se(r >= 0);
strcpy(s, "hallo");
r = sd_bus_message_append_array(m, 'i', integer_array, sizeof(integer_array));
assert_se(r >= 0);
r = sd_bus_message_append_array(m, 'u', NULL, 0);
assert_se(r >= 0);
r = sd_bus_message_append(m, "a(stdo)", 1, "foo", 815ULL, 47.0, "/");
assert_se(r >= 0);
r = sd_bus_message_seal(m, 4711, 0);
assert_se(r >= 0);
bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
ms = open_memstream(&first, &first_size);
bus_message_dump(m, ms, 0);
fflush(ms);
assert_se(!ferror(ms));
r = bus_message_get_blob(m, &buffer, &sz);
assert_se(r >= 0);
h = hexmem(buffer, sz);
assert_se(h);
log_info("message size = %zu, contents =\n%s", sz, h);
free(h);
#if HAVE_GLIB
tests: skip g_dbus_message_new_from_blob under asan Some versions of asan report the following false positive when strict_string_checks=1 is passed: ================================================================= ==3297==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x7f64e4090286 bp 0x7ffe46acd9a0 sp 0x7ffe46acd118 T0) ==3297==The signal is caused by a READ memory access. ==3297==Hint: address points to the zero page. #0 0x7f64e4090285 in __strlen_sse2 (/lib64/libc.so.6+0xaa285) #1 0x7f64e5a51e46 (/lib64/libasan.so.4+0x41e46) #2 0x7f64e4e5e3a0 (/lib64/libglib-2.0.so.0+0x383a0) #3 0x7f64e4e5e536 in g_dgettext (/lib64/libglib-2.0.so.0+0x38536) #4 0x7f64e48fac5f (/lib64/libgio-2.0.so.0+0xc1c5f) #5 0x7f64e4c03978 in g_type_class_ref (/lib64/libgobject-2.0.so.0+0x30978) #6 0x7f64e4be9567 in g_object_new_with_properties (/lib64/libgobject-2.0.so.0+0x16567) #7 0x7f64e4be9fd0 in g_object_new (/lib64/libgobject-2.0.so.0+0x16fd0) #8 0x7f64e48fd43e in g_dbus_message_new_from_blob (/lib64/libgio-2.0.so.0+0xc443e) #9 0x564a6aa0de52 in main ../src/libsystemd/sd-bus/test-bus-marshal.c:228 #10 0x7f64e4007009 in __libc_start_main (/lib64/libc.so.6+0x21009) #11 0x564a6aa0a569 in _start (/home/vagrant/systemd/build/test-bus-marshal+0x5569) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV (/lib64/libc.so.6+0xaa285) in __strlen_sse2 ==3297==ABORTING It's an external library and errors in external libraries are generally not very useful for looking for internal bugs. It would be better not to change the code and use standard suppression techinques decribed at https://clang.llvm.org/docs/AddressSanitizer.html#suppressing-reports-in-external-libraries, but, unfortunaley, none of them seems to be able to suppress fatal errors in asan intself.
2018-03-09 15:51:45 +01:00
#ifndef __SANITIZE_ADDRESS__
{
GDBusMessage *g;
char *p;
#if !defined(GLIB_VERSION_2_36)
g_type_init();
#endif
g = g_dbus_message_new_from_blob(buffer, sz, 0, NULL);
p = g_dbus_message_print(g, 0);
log_info("%s", p);
g_free(p);
g_object_unref(g);
}
#endif
tests: skip g_dbus_message_new_from_blob under asan Some versions of asan report the following false positive when strict_string_checks=1 is passed: ================================================================= ==3297==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x7f64e4090286 bp 0x7ffe46acd9a0 sp 0x7ffe46acd118 T0) ==3297==The signal is caused by a READ memory access. ==3297==Hint: address points to the zero page. #0 0x7f64e4090285 in __strlen_sse2 (/lib64/libc.so.6+0xaa285) #1 0x7f64e5a51e46 (/lib64/libasan.so.4+0x41e46) #2 0x7f64e4e5e3a0 (/lib64/libglib-2.0.so.0+0x383a0) #3 0x7f64e4e5e536 in g_dgettext (/lib64/libglib-2.0.so.0+0x38536) #4 0x7f64e48fac5f (/lib64/libgio-2.0.so.0+0xc1c5f) #5 0x7f64e4c03978 in g_type_class_ref (/lib64/libgobject-2.0.so.0+0x30978) #6 0x7f64e4be9567 in g_object_new_with_properties (/lib64/libgobject-2.0.so.0+0x16567) #7 0x7f64e4be9fd0 in g_object_new (/lib64/libgobject-2.0.so.0+0x16fd0) #8 0x7f64e48fd43e in g_dbus_message_new_from_blob (/lib64/libgio-2.0.so.0+0xc443e) #9 0x564a6aa0de52 in main ../src/libsystemd/sd-bus/test-bus-marshal.c:228 #10 0x7f64e4007009 in __libc_start_main (/lib64/libc.so.6+0x21009) #11 0x564a6aa0a569 in _start (/home/vagrant/systemd/build/test-bus-marshal+0x5569) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV (/lib64/libc.so.6+0xaa285) in __strlen_sse2 ==3297==ABORTING It's an external library and errors in external libraries are generally not very useful for looking for internal bugs. It would be better not to change the code and use standard suppression techinques decribed at https://clang.llvm.org/docs/AddressSanitizer.html#suppressing-reports-in-external-libraries, but, unfortunaley, none of them seems to be able to suppress fatal errors in asan intself.
2018-03-09 15:51:45 +01:00
#endif
#if HAVE_DBUS
{
DBusMessage *w;
DBusError error;
dbus_error_init(&error);
w = dbus_message_demarshal(buffer, sz, &error);
if (!w)
log_error("%s", error.message);
else
dbus_message_unref(w);
dbus_error_free(&error);
}
#endif
2013-03-20 05:29:20 +01:00
m = sd_bus_message_unref(m);
r = bus_message_from_malloc(bus, buffer, sz, NULL, 0, NULL, &m);
2013-03-20 05:29:20 +01:00
assert_se(r >= 0);
bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
fclose(ms);
ms = open_memstream(&second, &second_size);
bus_message_dump(m, ms, 0);
fflush(ms);
assert_se(!ferror(ms));
assert_se(first_size == second_size);
assert_se(memcmp(first, second, first_size) == 0);
2013-03-20 03:15:03 +01:00
assert_se(sd_bus_message_rewind(m, true) >= 0);
2014-01-12 12:15:29 +01:00
r = sd_bus_message_read(m, "ssasg", &x, &x2, 2, &y, &z, &a_signature);
2013-03-20 03:15:03 +01:00
assert_se(r > 0);
assert_se(streq(x, "a string"));
assert_se(streq(x2, ""));
2013-03-20 03:15:03 +01:00
assert_se(streq(y, "string #1"));
assert_se(streq(z, "string #2"));
2014-01-12 12:15:29 +01:00
assert_se(streq(a_signature, "sba(tt)ss"));
2013-03-20 03:15:03 +01:00
r = sd_bus_message_read(m, "sass", &x, 5, &y, &z, &a, &b, &c, &d);
assert_se(r > 0);
assert_se(streq(x, "foobar"));
assert_se(streq(y, "foo"));
assert_se(streq(z, "bar"));
assert_se(streq(a, "waldo"));
assert_se(streq(b, "piep"));
assert_se(streq(c, "pap"));
assert_se(streq(d, "after"));
r = sd_bus_message_read(m, "a{yv}", 2, &u, "s", &x, &v, "s", &y);
assert_se(r > 0);
assert_se(u == 3);
assert_se(streq(x, "foo"));
assert_se(v == 5);
assert_se(streq(y, "waldo"));
2015-07-16 11:00:55 +02:00
r = sd_bus_message_read(m, "y(ty)", &v, &u64, &u);
assert_se(r > 0);
assert_se(v == 8);
assert_se(u64 == 777);
assert_se(u == 7);
r = sd_bus_message_read(m, "y(yt)", &v, &u, &u64);
assert_se(r > 0);
assert_se(v == 9);
assert_se(u == 77);
assert_se(u64 == 7777);
r = sd_bus_message_read(m, "y", &v);
assert_se(r > 0);
assert_se(v == 10);
r = sd_bus_message_read(m, "()");
assert_se(r > 0);
2013-03-20 03:15:03 +01:00
r = sd_bus_message_read(m, "ba(ss)", &boolean, 3, &x, &y, &a, &b, &c, &d);
assert_se(r > 0);
assert_se(boolean);
assert_se(streq(x, "aaa"));
assert_se(streq(y, "1"));
assert_se(streq(a, "bbb"));
assert_se(streq(b, "2"));
assert_se(streq(c, "ccc"));
assert_se(streq(d, "3"));
assert_se(sd_bus_message_verify_type(m, 'a', "s") > 0);
2013-03-20 03:15:03 +01:00
r = sd_bus_message_read(m, "as", 2, &x, &y);
assert_se(r > 0);
assert_se(streq(x, "foobar"));
assert_se(streq(y, "waldo"));
r = sd_bus_message_read_basic(m, 's', &s);
assert_se(r > 0);
assert_se(streq(s, "hallo"));
r = sd_bus_message_read_array(m, 'i', (const void**) &return_array, &sz);
assert_se(r > 0);
assert_se(sz == sizeof(integer_array));
assert_se(memcmp(integer_array, return_array, sz) == 0);
r = sd_bus_message_read_array(m, 'u', (const void**) &return_array, &sz);
assert_se(r > 0);
assert_se(sz == 0);
r = sd_bus_message_read(m, "a(stdo)", 1, &x, &u64, &dbl, &y);
assert_se(r > 0);
assert_se(streq(x, "foo"));
assert_se(u64 == 815ULL);
assert_se(fabs(dbl - 47.0) < 0.1);
assert_se(streq(y, "/"));
2013-03-20 03:15:03 +01:00
r = sd_bus_message_peek_type(m, NULL, NULL);
assert_se(r == 0);
r = sd_bus_message_new_method_call(bus, &copy, "foobar.waldo", "/", "foobar.waldo", "Piep");
assert_se(r >= 0);
r = sd_bus_message_rewind(m, true);
assert_se(r >= 0);
r = sd_bus_message_copy(copy, m, true);
assert_se(r >= 0);
r = sd_bus_message_seal(copy, 4712, 0);
assert_se(r >= 0);
fclose(ms);
ms = open_memstream(&third, &third_size);
bus_message_dump(copy, ms, 0);
fflush(ms);
assert_se(!ferror(ms));
printf("<%.*s>\n", (int) first_size, first);
printf("<%.*s>\n", (int) third_size, third);
assert_se(first_size == third_size);
assert_se(memcmp(first, third, third_size) == 0);
r = sd_bus_message_rewind(m, true);
assert_se(r >= 0);
assert_se(sd_bus_message_verify_type(m, 's', NULL) > 0);
2014-01-12 12:15:29 +01:00
r = sd_bus_message_skip(m, "ssasg");
assert_se(r > 0);
assert_se(sd_bus_message_verify_type(m, 's', NULL) > 0);
r = sd_bus_message_skip(m, "sass");
assert_se(r >= 0);
assert_se(sd_bus_message_verify_type(m, 'a', "{yv}") > 0);
r = sd_bus_message_skip(m, "a{yv}y(ty)y(yt)y()");
assert_se(r >= 0);
assert_se(sd_bus_message_verify_type(m, 'b', NULL) > 0);
r = sd_bus_message_read(m, "b", &boolean);
assert_se(r > 0);
assert_se(boolean);
r = sd_bus_message_enter_container(m, 0, NULL);
assert_se(r > 0);
r = sd_bus_message_read(m, "(ss)", &x, &y);
assert_se(r > 0);
r = sd_bus_message_read(m, "(ss)", &a, &b);
assert_se(r > 0);
r = sd_bus_message_read(m, "(ss)", &c, &d);
assert_se(r > 0);
r = sd_bus_message_read(m, "(ss)", &x, &y);
assert_se(r == 0);
r = sd_bus_message_exit_container(m);
assert_se(r >= 0);
assert_se(streq(x, "aaa"));
assert_se(streq(y, "1"));
assert_se(streq(a, "bbb"));
assert_se(streq(b, "2"));
assert_se(streq(c, "ccc"));
assert_se(streq(d, "3"));
test_bus_label_escape();
test_bus_path_encode();
bus: implement bus_path_{en,de}code_unique() Whenever we provide a bus API that allows clients to create and manage server-side objects, we need to provide a unique name for these objects. There are two ways to provide them: 1) Let the server choose a name and return it as method reply. 2) Let the client pass its name of choice in the method arguments. The first method is the easiest one to implement. However, it suffers from a race condition: If a client creates an object asynchronously, it cannot destroy that object until it received the method reply. It cannot know the name of the new object, thus, it cannot destroy it. Furthermore, this method enforces a round-trip. If the client _depends_ on the method call to succeed (eg., it would close() the connection if it failed), the client usually has no reason to wait for the method reply. Instead, the client can immediately schedule further method calls on the newly created object (in case the API guarantees in-order method-call handling). The second method fixes both problems: The client passes an object name with the method-call. The server uses it to create the object. Therefore, the client can schedule object destruction even if the object-creation hasn't finished, yet (again, requiring in-order method-call handling). Furthermore, the client can schedule further method calls on the newly created object, before the constructor returned. There're two problems to solve, though: 1) Object names are usually defined via dbus object paths, which are usually globally namespaced. Therefore, multiple clients must be able to choose unique object names without interference. 2) If multiple libraries share the same bus connection, they must be able to choose unique object names without interference. The first problem is solved easily by prefixing a name with the unique-bus-name of a connection. The server side must enforce this and reject any other name. The second problem is solved by providing unique suffixes from within sd-bus. As long as sd-bus always returns a fresh new ID, if requested, multiple libraries will never interfere. This implementation re-uses bus->cookie as ID generator, which already provides unique IDs for each bus connection. This patch introduces two new helpers: bus_path_encode_unique(sd_bus *bus, const char *prefix, const char *sender_id, const char *external_id, char **ret_path); This creates a new object-path via the template '/prefix/sender_id/external_id'. That is, it appends two new labels to the given prefix. If 'sender_id' is NULL, it will use bus->unique_name, if 'external_id' is NULL, it will allocate a fresh, unique cookie from bus->cookie. bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external); This reverses what bus_path_encode_unique() did. It parses 'path' from the template '/prefix/sender/external' and returns both suffix-labels in 'ret_sender' and 'ret_external'. In case the template does not match, 0 is returned and both output arguments are set to NULL. Otherwise, 1 is returned and the output arguments contain the decoded labels. Note: Client-side allocated IDs are inspired by the Wayland protocol (which itself was inspired by X11). Wayland uses those IDs heavily to avoid round-trips. Clients can create server-side objects and send method calls without any round-trip and waiting for any object IDs to be returned. But unlike Wayland, DBus uses gobally namespaced object names. Therefore, we have to add the extra step by adding the unique-name of the bus connection.
2015-04-10 17:44:30 +02:00
test_bus_path_encode_unique();
test_bus_path_encode_many();
return 0;
}