lib/repo-finder: Add Avahi based OstreeRepoFinder implementation

This is a more complex implementation of OstreeRepoFinder which resolves
ref names to remote URIs by looking for refs advertised by peers on the
local network using DNS-SD records and mDNS (Avahi). The idea is to
allow OS and app updates to be propagated over local networks, without
the internet.

It requires an OSTree server and code to generate the DNS-SD adverts in
order to be fully functional — support for this will be added
separately.

Unit tests are included.

Includes fixes by Krzesimir Nowak <krzesimir@kinvolk.io>.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

Closes: #924
Approved by: cgwalters
This commit is contained in:
Philip Withnall 2017-04-19 00:13:28 +01:00 committed by Atomic Bot
parent 7ee4e1295a
commit e3d4eeacbc
16 changed files with 2075 additions and 6 deletions

View File

@ -43,6 +43,7 @@ libostree_public_headers += \
src/libostree/ostree-ref.h \
src/libostree/ostree-remote.h \
src/libostree/ostree-repo-finder.h \
src/libostree/ostree-repo-finder-avahi.h \
src/libostree/ostree-repo-finder-config.h \
src/libostree/ostree-repo-finder-mount.h \
$(NULL)

View File

@ -155,6 +155,7 @@ libostree_1_la_SOURCES += \
src/libostree/ostree-ref.h \
src/libostree/ostree-remote.h \
src/libostree/ostree-repo-finder.h \
src/libostree/ostree-repo-finder-avahi.h \
src/libostree/ostree-repo-finder-config.h \
src/libostree/ostree-repo-finder-mount.h \
$(NULL)
@ -163,9 +164,17 @@ libostree_1_la_SOURCES += \
src/libostree/ostree-bloom.c \
src/libostree/ostree-bloom-private.h \
src/libostree/ostree-repo-finder.c \
src/libostree/ostree-repo-finder-avahi.c \
src/libostree/ostree-repo-finder-config.c \
src/libostree/ostree-repo-finder-mount.c \
$(NULL)
if USE_AVAHI
libostree_1_la_SOURCES += \
src/libostree/ostree-repo-finder-avahi-parser.c \
src/libostree/ostree-repo-finder-avahi-private.h \
$(NULL)
endif # USE_AVAHI
endif
symbol_files = $(top_srcdir)/src/libostree/libostree-released.sym
@ -193,6 +202,13 @@ libostree_1_la_CFLAGS += $(OT_DEP_LIBARCHIVE_CFLAGS)
libostree_1_la_LIBADD += $(OT_DEP_LIBARCHIVE_LIBS)
endif
if ENABLE_EXPERIMENTAL_API
if USE_AVAHI
libostree_1_la_CFLAGS += $(OT_DEP_AVAHI_CFLAGS)
libostree_1_la_LIBADD += $(OT_DEP_AVAHI_LIBS)
endif
endif
if BUILDOPT_LIBSYSTEMD
libostree_1_la_CFLAGS += $(LIBSYSTEMD_CFLAGS)
libostree_1_la_LIBADD += $(LIBSYSTEMD_LIBS)
@ -241,7 +257,7 @@ OSTree_1_0_gir_INCLUDES = Gio-2.0
OSTree_1_0_gir_CFLAGS = $(libostree_1_la_CFLAGS)
OSTree_1_0_gir_LIBS = libostree-1.la
OSTree_1_0_gir_SCANNERFLAGS = --warn-all --identifier-prefix=Ostree --symbol-prefix=ostree
OSTree_1_0_gir_FILES = $(libostreeinclude_HEADERS) $(filter-out %-private.h %/ostree-soup-uri.h %/ostree-repo-finder.h %/ostree-repo-finder-config.h %/ostree-repo-finder-mount.h,$(libostree_1_la_SOURCES))
OSTree_1_0_gir_FILES = $(libostreeinclude_HEADERS) $(filter-out %-private.h %/ostree-soup-uri.h %/ostree-repo-finder.h %/ostree-repo-finder-avahi.h %/ostree-repo-finder-config.h %/ostree-repo-finder-mount.h,$(libostree_1_la_SOURCES))
INTROSPECTION_GIRS += OSTree-1.0.gir
gir_DATA += OSTree-1.0.gir
typelib_DATA += OSTree-1.0.typelib

View File

@ -199,6 +199,10 @@ test_programs += \
tests/test-repo-finder-config \
tests/test-repo-finder-mount \
$(NULL)
if USE_AVAHI
test_programs += tests/test-repo-finder-avahi
endif
endif
# An interactive tool
@ -231,6 +235,12 @@ tests_test_bloom_SOURCES = src/libostree/ostree-bloom.c tests/test-bloom.c
tests_test_bloom_CFLAGS = $(TESTS_CFLAGS)
tests_test_bloom_LDADD = $(TESTS_LDADD)
if USE_AVAHI
tests_test_repo_finder_avahi_SOURCES = src/libostree/ostree-repo-finder-avahi-parser.c tests/test-repo-finder-avahi.c
tests_test_repo_finder_avahi_CFLAGS = $(TESTS_CFLAGS)
tests_test_repo_finder_avahi_LDADD = $(TESTS_LDADD)
endif
tests_test_repo_finder_config_SOURCES = tests/test-repo-finder-config.c
tests_test_repo_finder_config_CFLAGS = $(TESTS_CFLAGS)
tests_test_repo_finder_config_LDADD = $(TESTS_LDADD)

View File

@ -49,6 +49,16 @@ ostree_repo_finder_get_type
ostree_repo_finder_result_get_type
</SECTION>
<SECTION>
<FILE>ostree-repo-finder-avahi</FILE>
OstreeRepoFinderAvahi
ostree_repo_finder_avahi_new
ostree_repo_finder_avahi_start
ostree_repo_finder_avahi_stop
<SUBSECTION Standard>
ostree_repo_finder_avahi_get_type
</SECTION>
<SECTION>
<FILE>ostree-repo-finder-config</FILE>
OstreeRepoFinderConfig

View File

@ -320,6 +320,31 @@ if test x$with_openssl != xno; then OSTREE_FEATURES="$OSTREE_FEATURES openssl";
AM_CONDITIONAL(USE_OPENSSL, test $with_openssl != no)
dnl end openssl
dnl Avahi dependency for finding repos
AVAHI_DEPENDENCY="avahi-client >= 0.6.31 avahi-glib >= 0.6.31"
AC_ARG_WITH(avahi,
AS_HELP_STRING([--without-avahi], [Do not use Avahi]),
:, with_avahi=maybe)
AS_IF([ test x$with_avahi != xno ], [
AC_MSG_CHECKING([for $AVAHI_DEPENDENCY])
PKG_CHECK_EXISTS($AVAHI_DEPENDENCY, have_avahi=yes, have_avahi=no)
AC_MSG_RESULT([$have_avahi])
AS_IF([ test x$have_avahi = xno && test x$with_avahi != xmaybe ], [
AC_MSG_ERROR([Avahi is enabled but could not be found])
])
AS_IF([ test x$have_avahi = xyes], [
AC_DEFINE([HAVE_AVAHI], 1, [Define if we have avahi-client.pc and avahi-glib.pc])
PKG_CHECK_MODULES(OT_DEP_AVAHI, $AVAHI_DEPENDENCY)
with_avahi=yes
], [
with_avahi=no
])
], [ with_avahi=no ])
if test x$with_avahi != xno; then OSTREE_FEATURES="$OSTREE_FEATURES avahi"; fi
AM_CONDITIONAL(USE_AVAHI, test $with_avahi != no)
dnl This is what is in RHEL7.2 right now, picking it arbitrarily
LIBMOUNT_DEPENDENCY="mount >= 2.23.0"

View File

@ -47,6 +47,10 @@ global:
ostree_collection_ref_new;
ostree_repo_find_remotes_async;
ostree_repo_find_remotes_finish;
ostree_repo_finder_avahi_get_type;
ostree_repo_finder_avahi_new;
ostree_repo_finder_avahi_start;
ostree_repo_finder_avahi_stop;
ostree_repo_finder_config_get_type;
ostree_repo_finder_config_new;
ostree_repo_finder_get_type;

View File

@ -64,6 +64,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeCollectionRef, ostree_collection_ref_free)
G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeCollectionRefv, ostree_collection_ref_freev, NULL)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRemote, ostree_remote_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinder, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderAvahi, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderConfig, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderMount, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderResult, ostree_repo_finder_result_free)

View File

@ -188,6 +188,9 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinder, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderResult, ostree_repo_finder_result_free)
G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeRepoFinderResultv, ostree_repo_finder_result_freev, NULL)
#include "ostree-repo-finder-avahi.h"
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderAvahi, g_object_unref)
#include "ostree-repo-finder-config.h"
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderConfig, g_object_unref)

View File

@ -0,0 +1,137 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright © 2016 Kinvolk GmbH
* Copyright © 2017 Endless Mobile, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Authors:
* - Krzesimir Nowak <krzesimir@kinvolk.io>
* - Philip Withnall <withnall@endlessm.com>
*/
#include "config.h"
#include <avahi-common/strlst.h>
#include <glib.h>
#include <glib-object.h>
#include <string.h>
#include "ostree-autocleanups.h"
#include "ostree-repo-finder-avahi.h"
#include "ostree-repo-finder-avahi-private.h"
/* Reference: RFC 6763, §6. */
static gboolean
parse_txt_record (const guint8 *txt,
gsize txt_len,
const gchar **key,
gsize *key_len,
const guint8 **value,
gsize *value_len)
{
gsize i;
g_return_val_if_fail (key != NULL, FALSE);
g_return_val_if_fail (key_len != NULL, FALSE);
g_return_val_if_fail (value != NULL, FALSE);
g_return_val_if_fail (value_len != NULL, FALSE);
/* RFC 6763, §6.1. */
if (txt_len > 8900)
return FALSE;
*key = (const gchar *) txt;
*key_len = 0;
*value = NULL;
*value_len = 0;
for (i = 0; i < txt_len; i++)
{
if (txt[i] >= 0x20 && txt[i] <= 0x7e && txt[i] != '=')
{
/* Key character. */
*key_len = *key_len + 1;
continue;
}
else if (*key_len > 0 && txt[i] == '=')
{
/* Separator. */
*value = txt + (i + 1);
*value_len = txt_len - (i + 1);
return TRUE;
}
else
{
return FALSE;
}
}
/* The entire TXT record is the key; there is no = or value. */
*value = NULL;
*value_len = 0;
return (*key_len > 0);
}
/* TODO: Docs. Return value is only valid as long as @txt is. Reference: RFC 6763, §6. */
GHashTable *
_ostree_txt_records_parse (AvahiStringList *txt)
{
AvahiStringList *l;
g_autoptr(GHashTable) out = NULL;
out = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_bytes_unref);
for (l = txt; l != NULL; l = avahi_string_list_get_next (l))
{
const guint8 *txt;
gsize txt_len;
const gchar *key;
const guint8 *value;
gsize key_len, value_len;
g_autofree gchar *key_allocated = NULL;
g_autoptr(GBytes) value_allocated = NULL;
txt = avahi_string_list_get_text (l);
txt_len = avahi_string_list_get_size (l);
if (!parse_txt_record (txt, txt_len, &key, &key_len, &value, &value_len))
{
g_debug ("Ignoring invalid TXT record of length %" G_GSIZE_FORMAT,
txt_len);
continue;
}
key_allocated = g_ascii_strdown (key, key_len);
if (g_hash_table_lookup_extended (out, key_allocated, NULL, NULL))
{
g_debug ("Ignoring duplicate TXT record %s", key_allocated);
continue;
}
/* Distinguish between the case where the entire record is the key
* (value == NULL) and the case where the record is the key + = and the
* value is empty (value != NULL && value_len == 0). */
if (value != NULL)
value_allocated = g_bytes_new_static (value, value_len);
g_hash_table_insert (out, g_steal_pointer (&key_allocated), g_steal_pointer (&value_allocated));
}
return g_steal_pointer (&out);
}

View File

@ -0,0 +1,35 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright © 2017 Endless Mobile, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Authors:
* - Philip Withnall <withnall@endlessm.com>
*/
#pragma once
#include <avahi-common/strlst.h>
#include <gio/gio.h>
#include <glib.h>
#include <glib-object.h>
G_BEGIN_DECLS
GHashTable *_ostree_txt_records_parse (AvahiStringList *txt);
G_END_DECLS

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,62 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright © 2017 Endless Mobile, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Authors:
* - Philip Withnall <withnall@endlessm.com>
*/
#pragma once
#include <gio/gio.h>
#include <glib.h>
#include <glib-object.h>
#include "ostree-repo-finder.h"
#include "ostree-types.h"
G_BEGIN_DECLS
#define OSTREE_TYPE_REPO_FINDER_AVAHI (ostree_repo_finder_avahi_get_type ())
/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44):
_OSTREE_PUBLIC
G_DECLARE_FINAL_TYPE (OstreeRepoFinderAvahi, ostree_repo_finder_avahi, OSTREE, REPO_FINDER_AVAHI, GObject) */
_OSTREE_PUBLIC
GType ostree_repo_finder_avahi_get_type (void);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
typedef struct _OstreeRepoFinderAvahi OstreeRepoFinderAvahi;
typedef struct { GObjectClass parent_class; } OstreeRepoFinderAvahiClass;
static inline OstreeRepoFinderAvahi *OSTREE_REPO_FINDER_AVAHI (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_repo_finder_avahi_get_type (), OstreeRepoFinderAvahi); }
static inline gboolean OSTREE_IS_REPO_FINDER_AVAHI (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_repo_finder_avahi_get_type ()); }
G_GNUC_END_IGNORE_DEPRECATIONS
_OSTREE_PUBLIC
OstreeRepoFinderAvahi *ostree_repo_finder_avahi_new (GMainContext *context);
_OSTREE_PUBLIC
void ostree_repo_finder_avahi_start (OstreeRepoFinderAvahi *self,
GError **error);
_OSTREE_PUBLIC
void ostree_repo_finder_avahi_stop (OstreeRepoFinderAvahi *self);
G_END_DECLS

View File

@ -43,6 +43,9 @@
#include "ostree-repo-finder.h"
#include "ostree-repo-finder-config.h"
#include "ostree-repo-finder-mount.h"
#ifdef HAVE_AVAHI
#include "ostree-repo-finder-avahi.h"
#endif /* HAVE_AVAHI */
#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */
#include <gio/gunixinputstream.h>
@ -3961,11 +3964,13 @@ typedef struct
OstreeCollectionRef **refs;
GVariant *options;
OstreeAsyncProgress *progress;
OstreeRepoFinder *default_finder_avahi;
} FindRemotesData;
static void
find_remotes_data_free (FindRemotesData *data)
{
g_clear_object (&data->default_finder_avahi);
g_clear_object (&data->progress);
g_clear_pointer (&data->options, g_variant_unref);
ostree_collection_ref_freev (data->refs);
@ -3978,7 +3983,8 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (FindRemotesData, find_remotes_data_free)
static FindRemotesData *
find_remotes_data_new (const OstreeCollectionRef * const *refs,
GVariant *options,
OstreeAsyncProgress *progress)
OstreeAsyncProgress *progress,
OstreeRepoFinder *default_finder_avahi)
{
g_autoptr(FindRemotesData) data = NULL;
@ -3986,6 +3992,7 @@ find_remotes_data_new (const OstreeCollectionRef * const *refs,
data->refs = ostree_collection_ref_dupv (refs);
data->options = (options != NULL) ? g_variant_ref (options) : NULL;
data->progress = (progress != NULL) ? g_object_ref (progress) : NULL;
data->default_finder_avahi = (default_finder_avahi != NULL) ? g_object_ref (default_finder_avahi) : NULL;
return g_steal_pointer (&data);
}
@ -4085,10 +4092,10 @@ ostree_repo_find_remotes_async (OstreeRepo *self,
{
g_autoptr(GTask) task = NULL;
g_autoptr(FindRemotesData) data = NULL;
GMainContext *context;
OstreeRepoFinder *default_finders[4] = { NULL, };
g_autoptr(OstreeRepoFinder) finder_config = NULL;
g_autoptr(OstreeRepoFinder) finder_mount = NULL;
g_autoptr(OstreeRepoFinder) finder_avahi = NULL;
g_return_if_fail (OSTREE_IS_REPO (self));
g_return_if_fail (is_valid_collection_ref_array (refs));
@ -4102,21 +4109,43 @@ ostree_repo_find_remotes_async (OstreeRepo *self,
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, ostree_repo_find_remotes_async);
context = g_main_context_get_thread_default ();
/* Are we using #OstreeRepoFinders provided by the user, or the defaults? */
if (finders == NULL)
{
#ifdef HAVE_AVAHI
GMainContext *context = g_main_context_get_thread_default ();
g_autoptr(GError) local_error = NULL;
#endif /* HAVE_AVAHI */
finder_config = OSTREE_REPO_FINDER (ostree_repo_finder_config_new ());
finder_mount = OSTREE_REPO_FINDER (ostree_repo_finder_mount_new (NULL));
#ifdef HAVE_AVAHI
finder_avahi = OSTREE_REPO_FINDER (ostree_repo_finder_avahi_new (context));
#endif /* HAVE_AVAHI */
default_finders[0] = finder_config;
default_finders[1] = finder_mount;
default_finders[2] = finder_avahi;
finders = default_finders;
#ifdef HAVE_AVAHI
ostree_repo_finder_avahi_start (OSTREE_REPO_FINDER_AVAHI (finder_avahi),
&local_error);
if (local_error != NULL)
{
g_warning ("Avahi finder failed; removing it: %s", local_error->message);
default_finders[2] = NULL;
g_clear_object (&finder_avahi);
}
#endif /* HAVE_AVAHI */
}
data = find_remotes_data_new (refs, options, progress);
/* We need to keep a pointer to the default Avahi finder so we can stop it
* again after the operation, which happens implicitly by dropping the final
* ref. */
data = find_remotes_data_new (refs, options, progress, finder_avahi);
g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) find_remotes_data_free);
/* Asynchronously resolve all possible remotes for the given refs. */

View File

@ -38,6 +38,7 @@
#ifdef OSTREE_ENABLE_EXPERIMENTAL_API
#include <ostree-ref.h>
#include <ostree-repo-finder.h>
#include <ostree-repo-finder-avahi.h>
#include <ostree-repo-finder-config.h>
#include <ostree-repo-finder-mount.h>
#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */

1
tests/.gitignore vendored
View File

@ -15,6 +15,7 @@ test-mutable-tree
test-ot-opt-utils
test-ot-tool-util
test-ot-unix-utils
test-repo-finder-avahi
test-repo-finder-config
test-repo-finder-mount
test-rollsum-cli

View File

@ -0,0 +1,228 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright © 2017 Endless Mobile, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Authors:
* - Philip Withnall <withnall@endlessm.com>
*/
#include "config.h"
#include <gio/gio.h>
#include <glib.h>
#include <glib-object.h>
#include <locale.h>
#include <string.h>
#include "ostree-autocleanups.h"
#include "ostree-repo-finder.h"
#include "ostree-repo-finder-avahi.h"
#include "ostree-repo-finder-avahi-private.h"
/* FIXME: Upstream this */
G_DEFINE_AUTOPTR_CLEANUP_FUNC (AvahiStringList, avahi_string_list_free)
/* Test the object constructor works at a basic level. */
static void
test_repo_finder_avahi_init (void)
{
g_autoptr(OstreeRepoFinderAvahi) finder = NULL;
g_autoptr(GMainContext) context = NULL;
/* Default main context. */
finder = ostree_repo_finder_avahi_new (NULL);
g_clear_object (&finder);
/* Explicit main context. */
context = g_main_context_new ();
finder = ostree_repo_finder_avahi_new (context);
g_clear_object (&finder);
}
/* Test parsing valid and invalid TXT records. */
static void
test_repo_finder_avahi_txt_records_parse (void)
{
struct
{
const guint8 *txt;
gsize txt_len;
const gchar *expected_key; /* (nullable) to indicate parse failure */
const guint8 *expected_value; /* (nullable) to allow for valueless keys */
gsize expected_value_len;
}
vectors[] =
{
{ (const guint8 *) "", 0, NULL, NULL, 0 },
{ (const guint8 *) "\x00", 1, NULL, NULL, 0 },
{ (const guint8 *) "\xff", 1, NULL, NULL, 0 },
{ (const guint8 *) "k\x00", 2, NULL, NULL, 0 },
{ (const guint8 *) "k\xff", 2, NULL, NULL, 0 },
{ (const guint8 *) "=", 1, NULL, NULL, 0 },
{ (const guint8 *) "=value", 6, NULL, NULL, 0 },
{ (const guint8 *) "k=v", 3, "k", (const guint8 *) "v", 1 },
{ (const guint8 *) "key=value", 9, "key", (const guint8 *) "value", 5 },
{ (const guint8 *) "k=v=", 4, "k", (const guint8 *) "v=", 2 },
{ (const guint8 *) "k=", 2, "k", (const guint8 *) "", 0 },
{ (const guint8 *) "k", 1, "k", NULL, 0 },
{ (const guint8 *) "k==", 3, "k", (const guint8 *) "=", 1 },
{ (const guint8 *) "k=\x00\x01\x02", 5, "k", (const guint8 *) "\x00\x01\x02", 3 },
};
gsize i;
for (i = 0; i < G_N_ELEMENTS (vectors); i++)
{
g_autoptr(AvahiStringList) string_list = NULL;
g_autoptr(GHashTable) attributes = NULL;
g_test_message ("Vector %" G_GSIZE_FORMAT, i);
string_list = avahi_string_list_add_arbitrary (NULL, vectors[i].txt, vectors[i].txt_len);
attributes = _ostree_txt_records_parse (string_list);
if (vectors[i].expected_key != NULL)
{
GBytes *value;
g_autoptr(GBytes) expected_value = NULL;
g_assert_true (g_hash_table_lookup_extended (attributes,
vectors[i].expected_key,
NULL,
(gpointer *) &value));
g_assert_cmpuint (g_hash_table_size (attributes), ==, 1);
if (vectors[i].expected_value != NULL)
{
g_assert_nonnull (value);
expected_value = g_bytes_new_static (vectors[i].expected_value, vectors[i].expected_value_len);
g_assert_true (g_bytes_equal (value, expected_value));
}
else
{
g_assert_null (value);
}
}
else
{
g_assert_cmpuint (g_hash_table_size (attributes), ==, 0);
}
}
}
/* Test that the first value for a set of duplicate records is returned.
* See RFC 6763, §6.4. */
static void
test_repo_finder_avahi_txt_records_duplicates (void)
{
g_autoptr(AvahiStringList) string_list = NULL;
g_autoptr(GHashTable) attributes = NULL;
GBytes *value;
g_autoptr(GBytes) expected_value = NULL;
/* Reverse the list before using it, as they are built in reverse order.
* (See the #AvahiStringList documentation.) */
string_list = avahi_string_list_new ("k=value1", "k=value2", "k=value3", NULL);
string_list = avahi_string_list_reverse (string_list);
attributes = _ostree_txt_records_parse (string_list);
g_assert_cmpuint (g_hash_table_size (attributes), ==, 1);
value = g_hash_table_lookup (attributes, "k");
g_assert_nonnull (value);
expected_value = g_bytes_new_static ("value1", strlen ("value1"));
g_assert_true (g_bytes_equal (value, expected_value));
}
/* Test that keys are parsed and looked up case insensitively.
* See RFC 6763, §6.4. */
static void
test_repo_finder_avahi_txt_records_case_sensitivity (void)
{
g_autoptr(AvahiStringList) string_list = NULL;
g_autoptr(GHashTable) attributes = NULL;
GBytes *value1, *value2;
g_autoptr(GBytes) expected_value1 = NULL, expected_value2 = NULL;
/* Reverse the list before using it, as they are built in reverse order.
* (See the #AvahiStringList documentation.) */
string_list = avahi_string_list_new ("k=value1",
"K=value2",
"KeY2=v",
NULL);
string_list = avahi_string_list_reverse (string_list);
attributes = _ostree_txt_records_parse (string_list);
g_assert_cmpuint (g_hash_table_size (attributes), ==, 2);
value1 = g_hash_table_lookup (attributes, "k");
g_assert_nonnull (value1);
expected_value1 = g_bytes_new_static ("value1", strlen ("value1"));
g_assert_true (g_bytes_equal (value1, expected_value1));
g_assert_null (g_hash_table_lookup (attributes, "K"));
value2 = g_hash_table_lookup (attributes, "key2");
g_assert_nonnull (value2);
expected_value2 = g_bytes_new_static ("v", 1);
g_assert_true (g_bytes_equal (value2, expected_value2));
g_assert_null (g_hash_table_lookup (attributes, "KeY2"));
}
/* Test that keys which have an empty value can be distinguished from those
* which have no value. See RFC 6763, §6.4. */
static void
test_repo_finder_avahi_txt_records_empty_and_missing (void)
{
g_autoptr(AvahiStringList) string_list = NULL;
g_autoptr(GHashTable) attributes = NULL;
GBytes *value1, *value2;
g_autoptr(GBytes) expected_value1 = NULL;
string_list = avahi_string_list_new ("empty=",
"missing",
NULL);
attributes = _ostree_txt_records_parse (string_list);
g_assert_cmpuint (g_hash_table_size (attributes), ==, 2);
value1 = g_hash_table_lookup (attributes, "empty");
g_assert_nonnull (value1);
expected_value1 = g_bytes_new_static ("", 0);
g_assert_true (g_bytes_equal (value1, expected_value1));
g_assert_true (g_hash_table_lookup_extended (attributes, "missing", NULL, (gpointer *) &value2));
g_assert_null (value2);
}
int main (int argc, char **argv)
{
setlocale (LC_ALL, "");
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/repo-finder-avahi/init", test_repo_finder_avahi_init);
g_test_add_func ("/repo-finder-avahi/txt-records/parse", test_repo_finder_avahi_txt_records_parse);
g_test_add_func ("/repo-finder-avahi/txt-records/duplicates", test_repo_finder_avahi_txt_records_duplicates);
g_test_add_func ("/repo-finder-avahi/txt-records/case-sensitivity", test_repo_finder_avahi_txt_records_case_sensitivity);
g_test_add_func ("/repo-finder-avahi/txt-records/empty-and-missing", test_repo_finder_avahi_txt_records_empty_and_missing);
/* FIXME: Add tests for service processing, probably by splitting the
* code in OstreeRepoFinderAvahi around found_services. */
return g_test_run();
}