Merge ~ubuntu-audio-dev/pulseaudio:snap-policy-xenial into ~ubuntu-audio-dev/pulseaudio:ubuntu-xenial

Proposed by James Henstridge
Status: Merged
Merged at revision: b4a5a52b182907b5cccf81bb3bf1cde89164c32d
Proposed branch: ~ubuntu-audio-dev/pulseaudio:snap-policy-xenial
Merge into: ~ubuntu-audio-dev/pulseaudio:ubuntu-xenial
Diff against target: 2228 lines (+2148/-2)
8 files modified
debian/changelog (+17/-0)
debian/control (+1/-1)
debian/patches/0450-modules-add-snappy-policy-module.patch (+482/-0)
debian/patches/0451-enable-snap-policy-module.patch (+30/-0)
debian/patches/0805-remove-libjson-c-dependency.patch (+1611/-0)
debian/patches/series (+5/-0)
debian/pulseaudio.install (+1/-0)
debian/rules (+1/-1)
Reviewer Review Type Date Requested Status
Ubuntu Audio Development Team Pending
Review via email: mp+353966@code.launchpad.net

Commit message

Backport the snap policy module changes to Xenial.

Description of the change

This is a backport of the changes in Cosmic's pulseaudio 1:12.2-0ubuntu2 and 1:12.2-0ubuntu3.

To post a comment you must log in.
Revision history for this message
James Henstridge (jamesh) wrote :

Testing on a VM, this isn't working correctly. I got the following trying to record when using a snap AppArmor label:

    I: [pulseaudio] client.c: Created 4 "Native client (UNIX socket client)"
    I: [pulseaudio] protocol-native.c: Got credentials: uid=1000 gid=1000 success=1
    I: [pulseaudio] module-snap-policy.c: Checking access for client 4 (skype)
    W: [snapd-glib] module-snap-policy.c: snapd_client_get_snap failed: Not connected to snapd
    I: [pulseaudio] module-snap-policy.c: Access check for client 4 (skype): 0
    I: [pulseaudio] client.c: Freed 4 "parecord"
    I: [pulseaudio] protocol-native.c: Connection died.

This looks like it is due to version skew of snapd-glib. The old version requires a snapd_client_connect_sync() call.

Revision history for this message
James Henstridge (jamesh) wrote :

Talking to Robert on IRC, this isn't as simple as adding a call to snapd_client_connect_sync().

As Pulse is a long running session service, there is a good chance it will be running across a snapd refresh and get disconnected. So simply adding a _connect_sync() call at module startup could lead to bug reports of microphone access breaking part way through a session.

I think the most reliable option would be to SRU a new snapd-glib together with this Pulse Audio update. That's assuming we want to go ahead with updating Xenial at all.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/debian/changelog b/debian/changelog
2index bf3f7a0..688a73b 100644
3--- a/debian/changelog
4+++ b/debian/changelog
5@@ -1,3 +1,20 @@
6+pulseaudio (1:8.0-0ubuntu3.11) xenial; urgency=medium
7+
8+ * Backport the snap policy module to make access to audio recording
9+ conditional on plugging the "pulseaudio" or "audio-record" interfaces
10+ (LP: #1781428):
11+ - 0450-modules-add-snappy-policy-module.patch: rewrite to query
12+ snapd for the client's plugged interfaces.
13+ - 0451-enable-snap-policy-module.patch: enable the module in the
14+ default configuration.
15+ - Build depend on libsnapd-glib-dev.
16+ * Backport libjson-c dependency removal from Pulse Audio 10. This is
17+ required by the snap policy module due to a symbol name clash with
18+ libjson-glib.
19+ - 0805-remove-libjson-c-dependency.patch: new file.
20+
21+ -- James Henstridge <james.henstridge@canonical.com> Tue, 05 Nov 2019 17:16:22 +0800
22+
23 pulseaudio (1:8.0-0ubuntu3.10) xenial; urgency=medium
24
25 * Revert Cherrypick fixes for checking profile availabilities
26diff --git a/debian/control b/debian/control
27index 66e1ce2..68f6dbe 100644
28--- a/debian/control
29+++ b/debian/control
30@@ -33,11 +33,11 @@ Build-Depends: android-headers-19 (>= 23) [armhf i386 amd64],
31 libgtk-3-dev,
32 libice-dev,
33 libjack-dev,
34- libjson-c-dev (>= 0.11),
35 liblircclient-dev,
36 libltdl-dev (>= 2.2.6a-2),
37 liborc-0.4-dev (>= 1:0.4.11),
38 libsamplerate0-dev,
39+ libsnapd-glib-dev (>= 1.44),
40 libsndfile1-dev (>= 1.0.20),
41 libspeexdsp-dev (>= 1.2~rc1),
42 libssl-dev,
43diff --git a/debian/patches/0450-modules-add-snappy-policy-module.patch b/debian/patches/0450-modules-add-snappy-policy-module.patch
44new file mode 100644
45index 0000000..d4f84a2
46--- /dev/null
47+++ b/debian/patches/0450-modules-add-snappy-policy-module.patch
48@@ -0,0 +1,482 @@
49+From: James Henstridge <james.henstridge@canonical.com>
50+Date: Tue, 7 Aug 2018 12:40:59 +0800
51+Subject: [PATCH] modules: add snap policy module
52+
53+Forwarded: not-needed
54+
55+This patch allows pulseaudio to limit audio recording to snaps with
56+the audio-recording interface connected. We will not pursue upstreaming
57+this patch as the longer term solution will probably use PipeWire.
58+
59+Co-authored-by: Simon Fels <simon.fels@canonical.com>
60+---
61+ configure.ac | 18 ++
62+ src/Makefile.am | 13 ++
63+ src/modules/module-snap-policy.c | 384 +++++++++++++++++++++++++++++++++++++++
64+ 3 files changed, 415 insertions(+)
65+ create mode 100644 src/modules/module-snap-policy.c
66+
67+diff --git a/configure.ac b/configure.ac
68+index 7783026..e308081 100644
69+--- a/configure.ac
70++++ b/configure.ac
71+@@ -1428,6 +1428,22 @@ AS_IF([test "x$enable_trust_store" = "xyes" && test "x$HAVE_TRUST_STORE" = "x0"]
72+
73+ AM_CONDITIONAL([HAVE_TRUST_STORE], [test "x$HAVE_TRUST_STORE" = "x1"])
74+
75++# Snappy support
76++
77++AC_ARG_ENABLE([snap],
78++ AS_HELP_STRING([--enable-snap], [Enable snap support]))
79++
80++have_apparmor=0
81++have_snapd_glib=0
82++AS_IF([test "x$enable_snap" != "xno"],
83++ [PKG_CHECK_MODULES(APPARMOR, [libapparmor], [have_apparmor=1])
84++ PKG_CHECK_MODULES(SNAPD_GLIB, [snapd-glib], [have_snapd_glib=1])])
85++
86++AS_IF([test "x$enable_snap" = "xyes" && test "x$have_apparmor" = "x0" -o "x$have-snapd_glib" = "x0"],
87++ [AC_MSG_ERROR([*** Snap module dependencies missing])])
88++
89++AM_CONDITIONAL([BUILD_SNAP], [test "x$enable_snap" = "xyes"])
90++
91+
92+ ###################################
93+ # Output #
94+@@ -1605,6 +1621,7 @@ AS_IF([test "x$HAVE_ESOUND" = "x1" -a "x$USE_PER_USER_ESOUND_SOCKET" = "x1"], EN
95+ AS_IF([test "x$HAVE_GCOV" = "x1"], ENABLE_GCOV=yes, ENABLE_GCOV=no)
96+ AS_IF([test "x$HAVE_LIBCHECK" = "x1"], ENABLE_TESTS=yes, ENABLE_TESTS=no)
97+ AS_IF([test "x$enable_legacy_database_entry_format" != "xno"], ENABLE_LEGACY_DATABASE_ENTRY_FORMAT=yes, ENABLE_LEGACY_DATABASE_ENTRY_FORMAT=no)
98++AS_IF([test "x$enable_snap" != "xno"], ENABLE_SNAP=yes, ENABLE_SNAP=no)
99+
100+ echo "
101+ ---{ $PACKAGE_NAME $VERSION }---
102+@@ -1662,6 +1679,7 @@ echo "
103+ Enable soxr (resampler): ${ENABLE_SOXR}
104+ Enable WebRTC echo canceller: ${ENABLE_WEBRTC}
105+ Enable Ubuntu trust store: ${ENABLE_TRUST_STORE}
106++ Enable Snap support: ${ENABLE_SNAP}
107+ Enable gcov coverage: ${ENABLE_GCOV}
108+ Enable unit tests: ${ENABLE_TESTS}
109+ Database
110+diff --git a/src/Makefile.am b/src/Makefile.am
111+index d8337a9..4046bca 100644
112+--- a/src/Makefile.am
113++++ b/src/Makefile.am
114+@@ -1216,6 +1216,11 @@ modlibexec_LTLIBRARIES += \
115+ module-esound-sink.la
116+ endif
117+
118++if BUILD_SNAP
119++modlibexec_LTLIBRARIES += \
120++ module-snap-policy.la
121++endif
122++
123+ # See comment at librtp.la above
124+ if !OS_IS_WIN32
125+ modlibexec_LTLIBRARIES += \
126+@@ -2143,6 +2148,14 @@ module_trust_store_la_LIBADD = $(MODULE_LIBADD) libtruststore-util.la
127+ module_trust_store_la_CFLAGS = $(AM_CFLAGS) -DHAVE_TRUST_STORE=1
128+ endif
129+
130++# Snappy
131++if BUILD_SNAP
132++module_snap_policy_la_SOURCES = modules/module-snap-policy.c
133++module_snap_policy_la_LDFLAGS = $(MODULE_LDFLAGS)
134++module_snap_policy_la_LIBADD = $(MODULE_LIBADD) $(APPARMOR_LIBS) $(SNAPD_GLIB_LIBS)
135++module_snap_policy_la_CFLAGS = $(AM_CFLAGS) $(APPARMOR_CFLAGS) $(SNAPD_GLIB_CFLAGS) -DPA_MODULE_NAME=module_snap_policy
136++endif
137++
138+ # RTP modules
139+ module_rtp_send_la_SOURCES = modules/rtp/module-rtp-send.c
140+ module_rtp_send_la_LDFLAGS = $(MODULE_LDFLAGS)
141+diff --git a/src/modules/module-snap-policy.c b/src/modules/module-snap-policy.c
142+new file mode 100644
143+index 0000000..0a1f5f4
144+--- /dev/null
145++++ b/src/modules/module-snap-policy.c
146+@@ -0,0 +1,384 @@
147++/***
148++ This file is part of PulseAudio.
149++
150++ Copyright 2018 Canonical Ltd.
151++ Authors:
152++ Simon Fels <simon.fels@canonical.com>
153++ James Henstridge <james.henstridge@canonical.com>
154++
155++ PulseAudio is free software; you can redistribute it and/or modify
156++ it under the terms of the GNU Lesser General Public License as published
157++ by the Free Software Foundation; either version 2.1 of the License,
158++ or (at your option) any later version.
159++
160++ PulseAudio is distributed in the hope that it will be useful, but
161++ WITHOUT ANY WARRANTY; without even the implied warranty of
162++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
163++ General Public License for more details.
164++
165++ You should have received a copy of the GNU Lesser General Public License
166++ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
167++***/
168++
169++#ifdef HAVE_CONFIG_H
170++#include <config.h>
171++#endif
172++
173++#include <sys/apparmor.h>
174++#include <glib.h>
175++#include <snapd-glib/snapd-glib.h>
176++
177++#include <pulsecore/asyncq.h>
178++#include <pulsecore/core.h>
179++#include <pulsecore/idxset.h>
180++#include <pulsecore/hashmap.h>
181++#include <pulsecore/module.h>
182++#include <pulsecore/mutex.h>
183++#include <pulsecore/thread.h>
184++#include <pulse/mainloop-api.h>
185++#include <pulse/xmalloc.h>
186++
187++#define SNAP_LABEL_PREFIX "snap."
188++#define SNAP_LABEL_PREFIX_LENGTH 5
189++
190++PA_MODULE_AUTHOR("Canonical Ltd");
191++PA_MODULE_DESCRIPTION("Ubuntu Snap policy management");
192++PA_MODULE_VERSION(PACKAGE_VERSION);
193++PA_MODULE_LOAD_ONCE(true);
194++
195++struct per_client {
196++ struct userdata *userdata;
197++ uint32_t index;
198++ char *snap_name;
199++ pa_dynarray *pending_requests; /* of pa_access_data */
200++ bool completed;
201++ bool grant_access;
202++};
203++
204++struct userdata {
205++ pa_io_event *io_event;
206++ pa_hook_slot *connect_hook_slot;
207++
208++ pa_thread *thread;
209++ pa_mutex *mutex;
210++ pa_cond *cond;
211++
212++ pa_hashmap *clients; /* int => struct per_client */
213++ pa_asyncq *results; /* of struct per_client */
214++
215++ /* Data owned by glib thread */
216++ GMainContext *main_context;
217++ GMainLoop *main_loop;
218++ GCancellable *cancellable;
219++ SnapdClient *snapd;
220++};
221++
222++/* ---- Code running in glib thread ---- */
223++
224++static void complete_check_access(struct per_client *pc, bool grant_access)
225++{
226++ struct userdata *u = pc->userdata;
227++
228++ pa_mutex_lock(u->mutex);
229++ pc->grant_access = grant_access;
230++ pc->completed = true;
231++ pa_asyncq_push(u->results, pc, true);
232++ pa_mutex_unlock(u->mutex);
233++}
234++
235++static void get_interfaces_finished(GObject *source_object,
236++ GAsyncResult *result,
237++ gpointer user_data)
238++{
239++ struct per_client *pc = user_data;
240++ struct userdata *u = pc->userdata;
241++ bool grant_access = false;
242++ g_autoptr(GError) error = NULL;
243++ g_autoptr(GPtrArray) plugs = NULL;
244++ unsigned i;
245++
246++ if (!snapd_client_get_interfaces_finish(u->snapd, result, &plugs, NULL, &error)) {
247++ pa_log_warn("snapd_client_get_interfaces failed: %s", error->message);
248++ goto end;
249++ }
250++
251++ /* determine pc->grant_access */
252++ for (i = 0; i < plugs->len; i++) {
253++ SnapdPlug *plug = plugs->pdata[i];
254++ const char *snap_name = snapd_plug_get_snap(plug);
255++ const char *iface = snapd_plug_get_interface(plug);
256++ const bool connected = snapd_plug_get_connections(plug)->len != 0;
257++
258++ /* We are only interested in connected plugs of our snap */
259++ if (!connected || strcmp(snap_name, pc->snap_name) != 0) {
260++ continue;
261++ }
262++ if (!strcmp(iface, "pulseaudio") || !strcmp(iface, "audio-record")) {
263++ grant_access = true;
264++ break;
265++ }
266++ }
267++
268++end:
269++ complete_check_access(pc, grant_access);
270++}
271++
272++static void get_snap_finished(GObject *source_object,
273++ GAsyncResult *result,
274++ gpointer user_data)
275++{
276++ struct per_client *pc = user_data;
277++ struct userdata *u = pc->userdata;
278++ g_autoptr(GError) error = NULL;
279++ g_autoptr(SnapdSnap) snap = NULL;
280++
281++ snap = snapd_client_get_snap_finish(u->snapd, result, &error);
282++ if (!snap) {
283++ pa_log_warn("snapd_client_get_snap failed: %s", error->message);
284++ complete_check_access(pc, false);
285++ return;
286++ }
287++
288++ /* Snaps using classic confinement are granted access */
289++ if (snapd_snap_get_confinement(snap) == SNAPD_CONFINEMENT_CLASSIC) {
290++ complete_check_access(pc, true);
291++ return;
292++ }
293++
294++ /* We have a non-classic snap, we need to check its connected
295++ * interfaces */
296++ snapd_client_get_interfaces_async(u->snapd, u->cancellable,
297++ get_interfaces_finished, pc);
298++}
299++
300++
301++static gboolean check_access(void *data)
302++{
303++ struct per_client *pc = data;
304++ struct userdata *u = pc->userdata;
305++
306++ snapd_client_get_snap_async(u->snapd, pc->snap_name, u->cancellable,
307++ get_snap_finished, pc);
308++ return G_SOURCE_REMOVE;
309++}
310++
311++
312++static void thread_main(void *data) {
313++ struct userdata *u = data;
314++
315++ pa_mutex_lock(u->mutex);
316++
317++ u->main_context = g_main_context_new();
318++ g_main_context_push_thread_default(u->main_context);
319++ u->main_loop = g_main_loop_new(u->main_context, false);
320++ u->cancellable = g_cancellable_new();
321++ u->snapd = snapd_client_new();
322++
323++ /* Signal main thread that we've finished initialisation */
324++ pa_cond_signal(u->cond, false);
325++ pa_mutex_unlock(u->mutex);
326++
327++ pa_log_info("Starting GLib main loop");
328++ g_main_loop_run(u->main_loop);
329++ pa_log_info("GLib main loop exited");
330++
331++ g_cancellable_cancel(u->cancellable);
332++
333++ g_clear_object(&u->snapd);
334++ g_clear_object(&u->cancellable);
335++ g_main_context_pop_thread_default(u->main_context);
336++ g_clear_pointer(&u->main_loop, g_main_loop_unref);
337++ g_clear_pointer(&u->main_context, g_main_context_unref);
338++}
339++
340++static gboolean thread_quit(void *data)
341++{
342++ struct userdata *u = data;
343++
344++ g_main_loop_quit(u->main_loop);
345++ return G_SOURCE_REMOVE;
346++}
347++
348++/* ---- Code running in main thread ---- */
349++
350++static struct per_client *per_client_new(struct userdata *u,
351++ uint32_t client_index,
352++ char *snap_name) {
353++ struct per_client *pc = pa_xnew0(struct per_client, 1);
354++ pc->userdata = u;
355++ pc->index = client_index;
356++ pc->snap_name = snap_name;
357++ pc->pending_requests = pa_dynarray_new(NULL);
358++ pc->completed = false;
359++ pc->grant_access = false;
360++ return pc;
361++}
362++
363++static void per_client_free(struct per_client *pc) {
364++ if (!pc) return;
365++ pa_xfree(pc->snap_name);
366++ pa_dynarray_free(pc->pending_requests);
367++ pa_xfree(pc);
368++}
369++
370++static char *client_get_snap_name(pa_core *core, uint32_t client_index) {
371++ pa_client *client;
372++ char *context = NULL;
373++ char *snap_name = NULL;
374++ char *dot;
375++
376++ client = pa_idxset_get_by_index(core->clients, client_index);
377++ pa_assert(client != NULL);
378++ if (!client->creds_valid) {
379++ pa_log_warn("Client %u has no creds, cannot authenticate", client_index);
380++ goto end;
381++ }
382++
383++ /* If AppArmor is not enabled, then we can't identify the client */
384++ if (!aa_is_enabled()) {
385++ goto end;
386++ }
387++ if (aa_gettaskcon(client->creds.pid, &context, NULL) < 0) {
388++ pa_log_error("AppArmor profile could not be retrieved.");
389++ goto end;
390++ }
391++
392++ /* If the AppArmor context does not begin with "snap.", then this
393++ * is not a snap */
394++ if (strncmp(context, SNAP_LABEL_PREFIX, SNAP_LABEL_PREFIX_LENGTH) != 0) {
395++ goto end;
396++ }
397++
398++ dot = strchr(context+SNAP_LABEL_PREFIX_LENGTH, '.');
399++ if (dot == NULL) {
400++ pa_log_warn("Malformed snapd AppArmor profile name: %s", context);
401++ goto end;
402++ }
403++ snap_name = pa_xstrndup(context+5, dot-context-SNAP_LABEL_PREFIX_LENGTH);
404++
405++end:
406++ free(context);
407++ return snap_name;
408++}
409++
410++static pa_hook_result_t connect_record_hook(pa_core *core, pa_access_data *d,
411++ struct userdata *u) {
412++ pa_hook_result_t result = PA_HOOK_STOP;
413++ struct per_client *pc = NULL;
414++ char *snap_name = NULL;
415++
416++ pa_mutex_lock(u->mutex);
417++ pc = pa_hashmap_get(u->clients, (void *)(size_t)d->client_index);
418++ if (pc) {
419++ if (pc->completed) {
420++ result = pc->grant_access ? PA_HOOK_OK : PA_HOOK_STOP;
421++ } else {
422++ /* A permission check for this snap is currently in progress */
423++ pa_dynarray_append(pc->pending_requests, d);
424++ result = PA_HOOK_CANCEL;
425++ }
426++ goto end;
427++ }
428++
429++ snap_name = client_get_snap_name(core, d->client_index);
430++ if (!snap_name) {
431++ /* Not a snap, so allow access */
432++ result = PA_HOOK_OK;
433++ goto end;
434++ }
435++
436++ /* create new per client struct, and submit to glib thread */
437++ pc = per_client_new(u, d->client_index, snap_name);
438++ pa_dynarray_append(pc->pending_requests, d);
439++ pa_hashmap_put(u->clients, (void *) (size_t) d->client_index, pc);
440++ pa_log_info("Checking access for client %d (%s)", pc->index, pc->snap_name);
441++ g_main_context_invoke(u->main_context, check_access, pc);
442++
443++ result = PA_HOOK_CANCEL;
444++
445++end:
446++ pa_mutex_unlock(u->mutex);
447++ return result;
448++}
449++
450++static void deliver_result(struct userdata *u, struct per_client *pc) {
451++ pa_access_data *ad;
452++ unsigned i;
453++
454++ pa_log_info("Access check for client %u (%s): %d",
455++ pc->index, pc->snap_name, pc->grant_access);
456++
457++ /* Call the hooks without holding the mutex, since this will
458++ * recurse into connect_record_hook. Access to pending_requests
459++ * should be safe here, since connect_record_hook wont alter the
460++ * array when the access check is complete. */
461++ PA_DYNARRAY_FOREACH(ad, pc->pending_requests, i) {
462++ ad->async_finish_cb(ad, pc->grant_access);
463++ }
464++ pa_mutex_lock(u->mutex);
465++ pa_hashmap_remove_and_free(u->clients, (void *) (size_t) pc->index);
466++ pa_mutex_unlock(u->mutex);
467++}
468++
469++static void check_result(pa_mainloop_api *a, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) {
470++ struct userdata *u = userdata;
471++ struct per_client *pc;
472++
473++ pa_asyncq_read_after_poll(u->results);
474++ while ((pc = pa_asyncq_pop(u->results, false)) != NULL) {
475++ deliver_result(u, pc);
476++ }
477++ pa_asyncq_read_before_poll(u->results);
478++}
479++
480++int pa__init(pa_module *m) {
481++ struct userdata *u;
482++
483++ u = pa_xnew0(struct userdata, 1);
484++ m->userdata = u;
485++ u->mutex = pa_mutex_new(false, false);
486++ u->cond = pa_cond_new();
487++
488++ u->clients = pa_hashmap_new_full(pa_idxset_trivial_hash_func,
489++ pa_idxset_trivial_compare_func,
490++ NULL, (pa_free_cb_t) per_client_free);
491++
492++ u->results = pa_asyncq_new(0);
493++ u->io_event = m->core->mainloop->io_new(
494++ m->core->mainloop, pa_asyncq_read_fd(u->results), PA_IO_EVENT_INPUT,
495++ check_result, u);
496++ pa_asyncq_read_before_poll(u->results);
497++
498++ u->connect_hook_slot = pa_hook_connect(
499++ &m->core->access[PA_ACCESS_HOOK_CONNECT_RECORD], PA_HOOK_NORMAL,
500++ (pa_hook_cb_t) connect_record_hook, u);
501++
502++ /* Start glib thread and wait for it to finish initialising. */
503++ pa_mutex_lock(u->mutex);
504++ u->thread = pa_thread_new("snapd-glib", thread_main, u);
505++ pa_cond_wait(u->cond, u->mutex);
506++ pa_mutex_unlock(u->mutex);
507++
508++ return 0;
509++}
510++
511++void pa__done(pa_module *m) {
512++ struct userdata *u = m->userdata;
513++ if (!u) return;
514++
515++ pa_hook_slot_free(u->connect_hook_slot);
516++ m->core->mainloop->io_free(u->io_event);
517++
518++ /* Stop the glib thread and wait for it to exit */
519++ g_main_context_invoke(u->main_context, thread_quit, u);
520++ pa_thread_join(u->thread);
521++ pa_thread_free(u->thread);
522++
523++ pa_asyncq_free(u->results, NULL); /* items in queue owned by u->clients */
524++ g_clear_pointer(&u->clients, pa_hashmap_free);
525++
526++ g_clear_pointer(&u->cond, pa_cond_free);
527++ g_clear_pointer(&u->mutex, pa_mutex_free);
528++
529++ pa_xfree(u);
530++}
531diff --git a/debian/patches/0451-enable-snap-policy-module.patch b/debian/patches/0451-enable-snap-policy-module.patch
532new file mode 100644
533index 0000000..dc13a11
534--- /dev/null
535+++ b/debian/patches/0451-enable-snap-policy-module.patch
536@@ -0,0 +1,30 @@
537+From: James Henstridge <james.henstridge@canonical.com>
538+Date: Tue, 7 Aug 2018 16:54:00 +0800
539+Subject: daemon: enable module-snap-policy module
540+
541+Forwarded: not-needed
542+
543+This patch allows pulseaudio to limit audio recording to snaps with
544+the audio-recording interface connected. We will not pursue upstreaming
545+this patch as the longer term solution will probably use PipeWire.
546+---
547+ src/daemon/default.pa.in | 6 ++++++
548+ 1 file changed, 6 insertions(+)
549+
550+diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in
551+index b770a4d..236b049 100755
552+--- a/src/daemon/default.pa.in
553++++ b/src/daemon/default.pa.in
554+@@ -166,6 +166,12 @@ load-module module-position-event-sounds
555+ ### Cork music/video streams when a phone stream is active
556+ #load-module module-role-cork
557+
558++### Block audio recording for snap confined packages unless they have
559++### the "pulseaudio" or "audio-record" interfaces plugged.
560++.ifexists module-snap-policy@PA_SOEXT@
561++load-module module-snap-policy
562++.endif
563++
564+ ### Modules to allow autoloading of filters (such as echo cancellation)
565+ ### on demand. module-filter-heuristics tries to determine what filters
566+ ### make sense, and module-filter-apply does the heavy-lifting of
567diff --git a/debian/patches/0805-remove-libjson-c-dependency.patch b/debian/patches/0805-remove-libjson-c-dependency.patch
568new file mode 100644
569index 0000000..c7e9461
570--- /dev/null
571+++ b/debian/patches/0805-remove-libjson-c-dependency.patch
572@@ -0,0 +1,1611 @@
573+From: James Henstridge <james.henstridge@canonical.com>
574+Date: Fri, 8 Nov 2019 14:43:46 +0800
575+Subject: src/pulse: Remove libjson-c dependency
576+
577+This comprises the following sequence of upstream commits:
578+
579+e3148f9ac json: Drop refcounting of json objects
580+8f45d83bd json: Add some more negative test cases
581+1879beab8 json: Add a positive test for nested objects
582+0c1dbf5c7 json: Error out for objects and arrays that are nested too deep
583+5b1bd8490 json: Handle error cases while parsing numbers
584+777a5091f json: Add overflow checks for integer and float parsing
585+708b4aac9 json: Correctly handle bad strings with missing closing quotes
586+c692ec3af format: Drop dependency on json-c
587+6741e5ae7 pulse: Add a JSON-parsing library
588+
589+Bug link: https://bugs.freedesktop.org/show_bug.cgi?id=95135
590+---
591+ configure.ac | 4 -
592+ src/.gitignore | 1 +
593+ src/Makefile.am | 15 +-
594+ src/pulse/format.c | 226 +++++++++----------
595+ src/pulse/json.c | 614 ++++++++++++++++++++++++++++++++++++++++++++++++++
596+ src/pulse/json.h | 53 +++++
597+ src/tests/json-test.c | 280 +++++++++++++++++++++++
598+ 7 files changed, 1068 insertions(+), 125 deletions(-)
599+ create mode 100644 src/pulse/json.c
600+ create mode 100644 src/pulse/json.h
601+ create mode 100644 src/tests/json-test.c
602+
603+diff --git a/configure.ac b/configure.ac
604+index b58ae5d..be8fd38 100644
605+--- a/configure.ac
606++++ b/configure.ac
607+@@ -658,10 +658,6 @@ AS_IF([test "x$enable_tests" = "xyes" && test "x$HAVE_LIBCHECK" = "x0"],
608+
609+ AM_CONDITIONAL([HAVE_TESTS], [test "x$HAVE_LIBCHECK" = x1])
610+
611+-#### json parsing ####
612+-
613+-PKG_CHECK_MODULES(LIBJSON, [ json-c >= 0.11 ])
614+-
615+ #### Sound file ####
616+
617+ PKG_CHECK_MODULES(LIBSNDFILE, [ sndfile >= 1.0.20 ])
618+diff --git a/src/.gitignore b/src/.gitignore
619+index 8a5dfb0..a1243dd 100644
620+--- a/src/.gitignore
621++++ b/src/.gitignore
622+@@ -49,6 +49,7 @@ gtk-test
623+ hook-list-test
624+ interpol-test
625+ ipacl-test
626++json-test
627+ lfe-filter-test
628+ lock-autospawn-test
629+ lo-latency-test
630+diff --git a/src/Makefile.am b/src/Makefile.am
631+index e29011d..6de7a0d 100644
632+--- a/src/Makefile.am
633++++ b/src/Makefile.am
634+@@ -247,6 +247,7 @@ TESTS_default = \
635+ thread-mainloop-test \
636+ utf8-test \
637+ format-test \
638++ json-test \
639+ get-binary-name-test \
640+ hook-list-test \
641+ memblock-test \
642+@@ -373,6 +374,11 @@ format_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS)
643+ format_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINOR@.la libpulse.la libpulsecommon-@PA_MAJORMINOR@.la
644+ format_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBCHECK_LIBS)
645+
646++json_test_SOURCES = tests/json-test.c
647++json_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS)
648++json_test_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINOR@.la
649++json_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBCHECK_LIBS)
650++
651+ srbchannel_test_SOURCES = tests/srbchannel-test.c
652+ srbchannel_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS)
653+ srbchannel_test_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINOR@.la
654+@@ -645,6 +651,7 @@ libpulsecommon_@PA_MAJORMINOR@_la_SOURCES = \
655+ pulse/client-conf.c pulse/client-conf.h \
656+ pulse/fork-detect.c pulse/fork-detect.h \
657+ pulse/format.c pulse/format.h \
658++ pulse/json.c pulse/json.h \
659+ pulse/xmalloc.c pulse/xmalloc.h \
660+ pulse/proplist.c pulse/proplist.h \
661+ pulse/utf8.c pulse/utf8.h \
662+@@ -724,9 +731,9 @@ else
663+ libpulsecommon_@PA_MAJORMINOR@_la_SOURCES += pulsecore/poll-posix.c pulsecore/poll.h
664+ endif
665+
666+-libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS) $(LIBSNDFILE_CFLAGS)
667++libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS)
668+ libpulsecommon_@PA_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version
669+-libpulsecommon_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBJSON_LIBS) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS)
670++libpulsecommon_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS)
671+
672+ if HAVE_X11
673+ libpulsecommon_@PA_MAJORMINOR@_la_SOURCES += \
674+@@ -873,8 +880,8 @@ libpulse_la_SOURCES = \
675+ pulse/volume.c pulse/volume.h \
676+ pulse/xmalloc.c pulse/xmalloc.h
677+
678+-libpulse_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS)
679+-libpulse_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBJSON_LIBS) libpulsecommon-@PA_MAJORMINOR@.la
680++libpulse_la_CFLAGS = $(AM_CFLAGS)
681++libpulse_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) $(LTLIBICONV) libpulsecommon-@PA_MAJORMINOR@.la
682+ libpulse_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) $(VERSIONING_LDFLAGS) -version-info $(LIBPULSE_VERSION_INFO)
683+
684+ if HAVE_DBUS
685+diff --git a/src/pulse/format.c b/src/pulse/format.c
686+index c2a1552..8474978 100644
687+--- a/src/pulse/format.c
688++++ b/src/pulse/format.c
689+@@ -23,8 +23,7 @@
690+ #include <config.h>
691+ #endif
692+
693+-#include <json.h>
694+-
695++#include <pulse/json.h>
696+ #include <pulse/internal.h>
697+ #include <pulse/xmalloc.h>
698+
699+@@ -32,6 +31,7 @@
700+ #include <pulsecore/core-util.h>
701+ #include <pulsecore/i18n.h>
702+ #include <pulsecore/macro.h>
703++#include <pulsecore/strbuf.h>
704+
705+ #include "format.h"
706+
707+@@ -236,7 +236,8 @@ int pa_format_info_to_sample_spec(const pa_format_info *f, pa_sample_spec *ss, p
708+
709+ pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char *key) {
710+ const char *str;
711+- json_object *o, *o1;
712++ pa_json_object *o;
713++ const pa_json_object *o1;
714+ pa_prop_type_t type;
715+
716+ pa_assert(f);
717+@@ -246,47 +247,47 @@ pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char
718+ if (!str)
719+ return PA_PROP_TYPE_INVALID;
720+
721+- o = json_tokener_parse(str);
722++ o = pa_json_parse(str);
723+ if (!o)
724+ return PA_PROP_TYPE_INVALID;
725+
726+- switch (json_object_get_type(o)) {
727+- case json_type_int:
728++ switch (pa_json_object_get_type(o)) {
729++ case PA_JSON_TYPE_INT:
730+ type = PA_PROP_TYPE_INT;
731+ break;
732+
733+- case json_type_string:
734++ case PA_JSON_TYPE_STRING:
735+ type = PA_PROP_TYPE_STRING;
736+ break;
737+
738+- case json_type_array:
739+- if (json_object_array_length(o) == 0) {
740++ case PA_JSON_TYPE_ARRAY:
741++ if (pa_json_object_get_array_length(o) == 0) {
742+ /* Unlikely, but let's account for this anyway. We need at
743+ * least one element to figure out the array type. */
744+ type = PA_PROP_TYPE_INVALID;
745+ break;
746+ }
747+
748+- o1 = json_object_array_get_idx(o, 1);
749++ o1 = pa_json_object_get_array_member(o, 0);
750+
751+- if (json_object_get_type(o1) == json_type_int)
752++ if (pa_json_object_get_type(o1) == PA_JSON_TYPE_INT)
753+ type = PA_PROP_TYPE_INT_ARRAY;
754+- else if (json_object_get_type(o1) == json_type_string)
755++ else if (pa_json_object_get_type(o1) == PA_JSON_TYPE_STRING)
756+ type = PA_PROP_TYPE_STRING_ARRAY;
757+ else
758+ type = PA_PROP_TYPE_INVALID;
759+
760+ break;
761+
762+- case json_type_object:
763++ case PA_JSON_TYPE_OBJECT:
764+ /* We actually know at this point that it's a int range, but let's
765+ * confirm. */
766+- if (!json_object_object_get_ex(o, PA_JSON_MIN_KEY, NULL)) {
767++ if (!pa_json_object_get_object_member(o, PA_JSON_MIN_KEY)) {
768+ type = PA_PROP_TYPE_INVALID;
769+ break;
770+ }
771+
772+- if (!json_object_object_get_ex(o, PA_JSON_MAX_KEY, NULL)) {
773++ if (!pa_json_object_get_object_member(o, PA_JSON_MAX_KEY)) {
774+ type = PA_PROP_TYPE_INVALID;
775+ break;
776+ }
777+@@ -299,13 +300,13 @@ pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char
778+ break;
779+ }
780+
781+- json_object_put(o);
782++ pa_json_object_free(o);
783+ return type;
784+ }
785+
786+ int pa_format_info_get_prop_int(const pa_format_info *f, const char *key, int *v) {
787+ const char *str;
788+- json_object *o;
789++ pa_json_object *o;
790+
791+ pa_assert(f);
792+ pa_assert(key);
793+@@ -315,27 +316,28 @@ int pa_format_info_get_prop_int(const pa_format_info *f, const char *key, int *v
794+ if (!str)
795+ return -PA_ERR_NOENTITY;
796+
797+- o = json_tokener_parse(str);
798++ o = pa_json_parse(str);
799+ if (!o) {
800+ pa_log_debug("Failed to parse format info property '%s'.", key);
801+ return -PA_ERR_INVALID;
802+ }
803+
804+- if (json_object_get_type(o) != json_type_int) {
805++ if (pa_json_object_get_type(o) != PA_JSON_TYPE_INT) {
806+ pa_log_debug("Format info property '%s' type is not int.", key);
807+- json_object_put(o);
808++ pa_json_object_free(o);
809+ return -PA_ERR_INVALID;
810+ }
811+
812+- *v = json_object_get_int(o);
813+- json_object_put(o);
814++ *v = pa_json_object_get_int(o);
815++ pa_json_object_free(o);
816+
817+ return 0;
818+ }
819+
820+ int pa_format_info_get_prop_int_range(const pa_format_info *f, const char *key, int *min, int *max) {
821+ const char *str;
822+- json_object *o, *o1;
823++ pa_json_object *o;
824++ const pa_json_object *o1;
825+ int ret = -PA_ERR_INVALID;
826+
827+ pa_assert(f);
828+@@ -347,24 +349,26 @@ int pa_format_info_get_prop_int_range(const pa_format_info *f, const char *key,
829+ if (!str)
830+ return -PA_ERR_NOENTITY;
831+
832+- o = json_tokener_parse(str);
833++ o = pa_json_parse(str);
834+ if (!o) {
835+ pa_log_debug("Failed to parse format info property '%s'.", key);
836+ return -PA_ERR_INVALID;
837+ }
838+
839+- if (json_object_get_type(o) != json_type_object)
840++ if (pa_json_object_get_type(o) != PA_JSON_TYPE_OBJECT)
841+ goto out;
842+
843+- if (!json_object_object_get_ex(o, PA_JSON_MIN_KEY, &o1))
844++ if (!(o1 = pa_json_object_get_object_member(o, PA_JSON_MIN_KEY)) ||
845++ (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT))
846+ goto out;
847+
848+- *min = json_object_get_int(o1);
849++ *min = pa_json_object_get_int(o1);
850+
851+- if (!json_object_object_get_ex(o, PA_JSON_MAX_KEY, &o1))
852++ if (!(o1 = pa_json_object_get_object_member(o, PA_JSON_MAX_KEY)) ||
853++ (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT))
854+ goto out;
855+
856+- *max = json_object_get_int(o1);
857++ *max = pa_json_object_get_int(o1);
858+
859+ ret = 0;
860+
861+@@ -372,13 +376,14 @@ out:
862+ if (ret < 0)
863+ pa_log_debug("Format info property '%s' is not a valid int range.", key);
864+
865+- json_object_put(o);
866++ pa_json_object_free(o);
867+ return ret;
868+ }
869+
870+ int pa_format_info_get_prop_int_array(const pa_format_info *f, const char *key, int **values, int *n_values) {
871+ const char *str;
872+- json_object *o, *o1;
873++ pa_json_object *o;
874++ const pa_json_object *o1;
875+ int i, ret = -PA_ERR_INVALID;
876+
877+ pa_assert(f);
878+@@ -390,26 +395,26 @@ int pa_format_info_get_prop_int_array(const pa_format_info *f, const char *key,
879+ if (!str)
880+ return -PA_ERR_NOENTITY;
881+
882+- o = json_tokener_parse(str);
883++ o = pa_json_parse(str);
884+ if (!o) {
885+ pa_log_debug("Failed to parse format info property '%s'.", key);
886+ return -PA_ERR_INVALID;
887+ }
888+
889+- if (json_object_get_type(o) != json_type_array)
890++ if (pa_json_object_get_type(o) != PA_JSON_TYPE_ARRAY)
891+ goto out;
892+
893+- *n_values = json_object_array_length(o);
894++ *n_values = pa_json_object_get_array_length(o);
895+ *values = pa_xnew(int, *n_values);
896+
897+ for (i = 0; i < *n_values; i++) {
898+- o1 = json_object_array_get_idx(o, i);
899++ o1 = pa_json_object_get_array_member(o, i);
900+
901+- if (json_object_get_type(o1) != json_type_int) {
902++ if (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT) {
903+ goto out;
904+ }
905+
906+- (*values)[i] = json_object_get_int(o1);
907++ (*values)[i] = pa_json_object_get_int(o1);
908+ }
909+
910+ ret = 0;
911+@@ -418,13 +423,13 @@ out:
912+ if (ret < 0)
913+ pa_log_debug("Format info property '%s' is not a valid int array.", key);
914+
915+- json_object_put(o);
916++ pa_json_object_free(o);
917+ return ret;
918+ }
919+
920+ int pa_format_info_get_prop_string(const pa_format_info *f, const char *key, char **v) {
921+ const char *str = NULL;
922+- json_object *o;
923++ pa_json_object *o;
924+
925+ pa_assert(f);
926+ pa_assert(key);
927+@@ -434,27 +439,28 @@ int pa_format_info_get_prop_string(const pa_format_info *f, const char *key, cha
928+ if (!str)
929+ return -PA_ERR_NOENTITY;
930+
931+- o = json_tokener_parse(str);
932++ o = pa_json_parse(str);
933+ if (!o) {
934+ pa_log_debug("Failed to parse format info property '%s'.", key);
935+ return -PA_ERR_INVALID;
936+ }
937+
938+- if (json_object_get_type(o) != json_type_string) {
939++ if (pa_json_object_get_type(o) != PA_JSON_TYPE_STRING) {
940+ pa_log_debug("Format info property '%s' type is not string.", key);
941+- json_object_put(o);
942++ pa_json_object_free(o);
943+ return -PA_ERR_INVALID;
944+ }
945+
946+- *v = pa_xstrdup(json_object_get_string(o));
947+- json_object_put(o);
948++ *v = pa_xstrdup(pa_json_object_get_string(o));
949++ pa_json_object_free(o);
950+
951+ return 0;
952+ }
953+
954+ int pa_format_info_get_prop_string_array(const pa_format_info *f, const char *key, char ***values, int *n_values) {
955+ const char *str;
956+- json_object *o, *o1;
957++ pa_json_object *o;
958++ const pa_json_object *o1;
959+ int i, ret = -PA_ERR_INVALID;
960+
961+ pa_assert(f);
962+@@ -466,26 +472,26 @@ int pa_format_info_get_prop_string_array(const pa_format_info *f, const char *ke
963+ if (!str)
964+ return -PA_ERR_NOENTITY;
965+
966+- o = json_tokener_parse(str);
967++ o = pa_json_parse(str);
968+ if (!o) {
969+ pa_log_debug("Failed to parse format info property '%s'.", key);
970+ return -PA_ERR_INVALID;
971+ }
972+
973+- if (json_object_get_type(o) != json_type_array)
974++ if (pa_json_object_get_type(o) != PA_JSON_TYPE_ARRAY)
975+ goto out;
976+
977+- *n_values = json_object_array_length(o);
978++ *n_values = pa_json_object_get_array_length(o);
979+ *values = pa_xnew(char *, *n_values);
980+
981+ for (i = 0; i < *n_values; i++) {
982+- o1 = json_object_array_get_idx(o, i);
983++ o1 = pa_json_object_get_array_member(o, i);
984+
985+- if (json_object_get_type(o1) != json_type_string) {
986++ if (pa_json_object_get_type(o1) != PA_JSON_TYPE_STRING) {
987+ goto out;
988+ }
989+
990+- (*values)[i] = pa_xstrdup(json_object_get_string(o1));
991++ (*values)[i] = pa_xstrdup(pa_json_object_get_string(o1));
992+ }
993+
994+ ret = 0;
995+@@ -494,7 +500,7 @@ out:
996+ if (ret < 0)
997+ pa_log_debug("Format info property '%s' is not a valid string array.", key);
998+
999+- json_object_put(o);
1000++ pa_json_object_free(o);
1001+ return ret;
1002+ }
1003+
1004+@@ -528,85 +534,76 @@ void pa_format_info_set_channel_map(pa_format_info *f, const pa_channel_map *map
1005+ }
1006+
1007+ void pa_format_info_set_prop_int(pa_format_info *f, const char *key, int value) {
1008+- json_object *o;
1009+-
1010+ pa_assert(f);
1011+ pa_assert(key);
1012+
1013+- o = json_object_new_int(value);
1014+-
1015+- pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
1016+-
1017+- json_object_put(o);
1018++ pa_proplist_setf(f->plist, key, "%d", value);
1019+ }
1020+
1021+ void pa_format_info_set_prop_int_array(pa_format_info *f, const char *key, const int *values, int n_values) {
1022+- json_object *o;
1023++ pa_strbuf *buf;
1024++ char *str;
1025+ int i;
1026+
1027+ pa_assert(f);
1028+ pa_assert(key);
1029++ pa_assert(n_values > 0);
1030+
1031+- o = json_object_new_array();
1032++ buf = pa_strbuf_new();
1033+
1034+- for (i = 0; i < n_values; i++)
1035+- json_object_array_add(o, json_object_new_int(values[i]));
1036++ pa_strbuf_printf(buf, "[ %d", values[0]);
1037+
1038+- pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
1039++ for (i = 1; i < n_values; i++)
1040++ pa_strbuf_printf(buf, ", %d", values[i]);
1041+
1042+- json_object_put(o);
1043++ pa_strbuf_printf(buf, " ]");
1044++ str = pa_strbuf_to_string_free(buf);
1045++
1046++ pa_proplist_sets(f->plist, key, str);
1047++ pa_xfree (str);
1048+ }
1049+
1050+ void pa_format_info_set_prop_int_range(pa_format_info *f, const char *key, int min, int max) {
1051+- json_object *o;
1052+-
1053+ pa_assert(f);
1054+ pa_assert(key);
1055+
1056+- o = json_object_new_object();
1057+-
1058+- json_object_object_add(o, PA_JSON_MIN_KEY, json_object_new_int(min));
1059+- json_object_object_add(o, PA_JSON_MAX_KEY, json_object_new_int(max));
1060+-
1061+- pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
1062+-
1063+- json_object_put(o);
1064++ pa_proplist_setf(f->plist, key, "{ \"" PA_JSON_MIN_KEY "\": %d, \"" PA_JSON_MAX_KEY "\": %d }",
1065++ min, max);
1066+ }
1067+
1068+ void pa_format_info_set_prop_string(pa_format_info *f, const char *key, const char *value) {
1069+- json_object *o;
1070+-
1071+ pa_assert(f);
1072+ pa_assert(key);
1073+
1074+- o = json_object_new_string(value);
1075+-
1076+- pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
1077+-
1078+- json_object_put(o);
1079++ pa_proplist_setf(f->plist, key, "\"%s\"", value);
1080+ }
1081+
1082+ void pa_format_info_set_prop_string_array(pa_format_info *f, const char *key, const char **values, int n_values) {
1083+- json_object *o;
1084++ pa_strbuf *buf;
1085++ char *str;
1086+ int i;
1087+
1088+ pa_assert(f);
1089+ pa_assert(key);
1090+
1091+- o = json_object_new_array();
1092++ buf = pa_strbuf_new();
1093+
1094+- for (i = 0; i < n_values; i++)
1095+- json_object_array_add(o, json_object_new_string(values[i]));
1096++ pa_strbuf_printf(buf, "[ \"%s\"", values[0]);
1097++
1098++ for (i = 1; i < n_values; i++)
1099++ pa_strbuf_printf(buf, ", \"%s\"", values[i]);
1100+
1101+- pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
1102++ pa_strbuf_printf(buf, " ]");
1103++ str = pa_strbuf_to_string_free(buf);
1104+
1105+- json_object_put(o);
1106++ pa_proplist_sets(f->plist, key, str);
1107++ pa_xfree (str);
1108+ }
1109+
1110+-static bool pa_json_is_fixed_type(json_object *o) {
1111+- switch(json_object_get_type(o)) {
1112+- case json_type_object:
1113+- case json_type_array:
1114++static bool pa_json_is_fixed_type(pa_json_object *o) {
1115++ switch(pa_json_object_get_type(o)) {
1116++ case PA_JSON_TYPE_OBJECT:
1117++ case PA_JSON_TYPE_ARRAY:
1118+ return false;
1119+
1120+ default:
1121+@@ -614,20 +611,15 @@ static bool pa_json_is_fixed_type(json_object *o) {
1122+ }
1123+ }
1124+
1125+-static int pa_json_value_equal(json_object *o1, json_object *o2) {
1126+- return (json_object_get_type(o1) == json_object_get_type(o2)) &&
1127+- pa_streq(json_object_to_json_string(o1), json_object_to_json_string(o2));
1128+-}
1129+-
1130+ static int pa_format_info_prop_compatible(const char *one, const char *two) {
1131+- json_object *o1 = NULL, *o2 = NULL;
1132++ pa_json_object *o1 = NULL, *o2 = NULL;
1133+ int i, ret = 0;
1134+
1135+- o1 = json_tokener_parse(one);
1136++ o1 = pa_json_parse(one);
1137+ if (!o1)
1138+ goto out;
1139+
1140+- o2 = json_tokener_parse(two);
1141++ o2 = pa_json_parse(two);
1142+ if (!o2)
1143+ goto out;
1144+
1145+@@ -635,46 +627,46 @@ static int pa_format_info_prop_compatible(const char *one, const char *two) {
1146+ pa_return_val_if_fail(pa_json_is_fixed_type(o1) || pa_json_is_fixed_type(o2), false);
1147+
1148+ if (pa_json_is_fixed_type(o1) && pa_json_is_fixed_type(o2)) {
1149+- ret = pa_json_value_equal(o1, o2);
1150++ ret = pa_json_object_equal(o1, o2);
1151+ goto out;
1152+ }
1153+
1154+ if (pa_json_is_fixed_type(o1)) {
1155+- json_object *tmp = o2;
1156++ pa_json_object *tmp = o2;
1157+ o2 = o1;
1158+ o1 = tmp;
1159+ }
1160+
1161+ /* o2 is now a fixed type, and o1 is not */
1162+
1163+- if (json_object_get_type(o1) == json_type_array) {
1164+- for (i = 0; i < json_object_array_length(o1); i++) {
1165+- if (pa_json_value_equal(json_object_array_get_idx(o1, i), o2)) {
1166++ if (pa_json_object_get_type(o1) == PA_JSON_TYPE_ARRAY) {
1167++ for (i = 0; i < pa_json_object_get_array_length(o1); i++) {
1168++ if (pa_json_object_equal(pa_json_object_get_array_member(o1, i), o2)) {
1169+ ret = 1;
1170+ break;
1171+ }
1172+ }
1173+- } else if (json_object_get_type(o1) == json_type_object) {
1174++ } else if (pa_json_object_get_type(o1) == PA_JSON_TYPE_OBJECT) {
1175+ /* o1 should be a range type */
1176+ int min, max, v;
1177+- json_object *o_min = NULL, *o_max = NULL;
1178++ const pa_json_object *o_min = NULL, *o_max = NULL;
1179+
1180+- if (json_object_get_type(o2) != json_type_int) {
1181++ if (pa_json_object_get_type(o2) != PA_JSON_TYPE_INT) {
1182+ /* We don't support non-integer ranges */
1183+ goto out;
1184+ }
1185+
1186+- if (!json_object_object_get_ex(o1, PA_JSON_MIN_KEY, &o_min) ||
1187+- json_object_get_type(o_min) != json_type_int)
1188++ if (!(o_min = pa_json_object_get_object_member(o1, PA_JSON_MIN_KEY)) ||
1189++ pa_json_object_get_type(o_min) != PA_JSON_TYPE_INT)
1190+ goto out;
1191+
1192+- if (!json_object_object_get_ex(o1, PA_JSON_MAX_KEY, &o_max) ||
1193+- json_object_get_type(o_max) != json_type_int)
1194++ if (!(o_max = pa_json_object_get_object_member(o1, PA_JSON_MAX_KEY)) ||
1195++ pa_json_object_get_type(o_max) != PA_JSON_TYPE_INT)
1196+ goto out;
1197+
1198+- v = json_object_get_int(o2);
1199+- min = json_object_get_int(o_min);
1200+- max = json_object_get_int(o_max);
1201++ v = pa_json_object_get_int(o2);
1202++ min = pa_json_object_get_int(o_min);
1203++ max = pa_json_object_get_int(o_max);
1204+
1205+ ret = v >= min && v <= max;
1206+ } else {
1207+@@ -683,9 +675,9 @@ static int pa_format_info_prop_compatible(const char *one, const char *two) {
1208+
1209+ out:
1210+ if (o1)
1211+- json_object_put(o1);
1212++ pa_json_object_free(o1);
1213+ if (o2)
1214+- json_object_put(o2);
1215++ pa_json_object_free(o2);
1216+
1217+ return ret;
1218+ }
1219+diff --git a/src/pulse/json.c b/src/pulse/json.c
1220+new file mode 100644
1221+index 0000000..d126712
1222+--- /dev/null
1223++++ b/src/pulse/json.c
1224+@@ -0,0 +1,614 @@
1225++/***
1226++ This file is part of PulseAudio.
1227++
1228++ Copyright 2016 Arun Raghavan <mail@arunraghavan.net>
1229++
1230++ PulseAudio is free software; you can redistribute it and/or modify
1231++ it under the terms of the GNU Lesser General Public License as published
1232++ by the Free Software Foundation; either version 2.1 of the License,
1233++ or (at your option) any later version.
1234++
1235++ PulseAudio is distributed in the hope that it will be useful, but
1236++ WITHOUT ANY WARRANTY; without even the implied warranty of
1237++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1238++ General Public License for more details.
1239++
1240++ You should have received a copy of the GNU Lesser General Public License
1241++ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
1242++***/
1243++
1244++#ifdef HAVE_CONFIG_H
1245++#include <config.h>
1246++#endif
1247++
1248++#include <math.h>
1249++
1250++#include <pulse/json.h>
1251++#include <pulse/xmalloc.h>
1252++#include <pulsecore/core-util.h>
1253++#include <pulsecore/hashmap.h>
1254++#include <pulsecore/strbuf.h>
1255++
1256++#define MAX_NESTING_DEPTH 20 /* Arbitrary number to make sure we don't have a stack overflow */
1257++
1258++struct pa_json_object {
1259++ pa_json_type type;
1260++
1261++ union {
1262++ int int_value;
1263++ double double_value;
1264++ bool bool_value;
1265++ char *string_value;
1266++ pa_hashmap *object_values; /* name -> object */
1267++ pa_idxset *array_values; /* objects */
1268++ };
1269++};
1270++
1271++static const char* parse_value(const char *str, const char *end, pa_json_object **obj, unsigned int depth);
1272++
1273++static pa_json_object* json_object_new(void) {
1274++ pa_json_object *obj;
1275++
1276++ obj = pa_xnew0(pa_json_object, 1);
1277++
1278++ return obj;
1279++}
1280++
1281++static bool is_whitespace(char c) {
1282++ return c == '\t' || c == '\n' || c == '\r' || c == ' ';
1283++}
1284++
1285++static bool is_digit(char c) {
1286++ return c >= '0' && c <= '9';
1287++}
1288++
1289++static bool is_end(const char c, const char *end) {
1290++ if (!end)
1291++ return c == '\0';
1292++ else {
1293++ while (*end) {
1294++ if (c == *end)
1295++ return true;
1296++ end++;
1297++ }
1298++ }
1299++
1300++ return false;
1301++}
1302++
1303++static const char* consume_string(const char *str, const char *expect) {
1304++ while (*expect) {
1305++ if (*str != *expect)
1306++ return NULL;
1307++
1308++ str++;
1309++ expect++;
1310++ }
1311++
1312++ return str;
1313++}
1314++
1315++static const char* parse_null(const char *str, pa_json_object *obj) {
1316++ str = consume_string(str, "null");
1317++
1318++ if (str)
1319++ obj->type = PA_JSON_TYPE_NULL;
1320++
1321++ return str;
1322++}
1323++
1324++static const char* parse_boolean(const char *str, pa_json_object *obj) {
1325++ const char *tmp;
1326++
1327++ tmp = consume_string(str, "true");
1328++
1329++ if (tmp) {
1330++ obj->type = PA_JSON_TYPE_BOOL;
1331++ obj->bool_value = true;
1332++ } else {
1333++ tmp = consume_string(str, "false");
1334++
1335++ if (str) {
1336++ obj->type = PA_JSON_TYPE_BOOL;
1337++ obj->bool_value = false;
1338++ }
1339++ }
1340++
1341++ return tmp;
1342++}
1343++
1344++static const char* parse_string(const char *str, pa_json_object *obj) {
1345++ pa_strbuf *buf = pa_strbuf_new();
1346++
1347++ str++; /* Consume leading '"' */
1348++
1349++ while (*str && *str != '"') {
1350++ if (*str != '\\') {
1351++ /* We only accept ASCII printable characters. */
1352++ if (*str < 0x20 || *str > 0x7E) {
1353++ pa_log("Invalid non-ASCII character: 0x%x", (unsigned int) *str);
1354++ goto error;
1355++ }
1356++
1357++ /* Normal character, juts consume */
1358++ pa_strbuf_putc(buf, *str);
1359++ } else {
1360++ /* Need to unescape */
1361++ str++;
1362++
1363++ switch (*str) {
1364++ case '"':
1365++ case '\\':
1366++ case '/':
1367++ pa_strbuf_putc(buf, *str);
1368++ break;
1369++
1370++ case 'b':
1371++ pa_strbuf_putc(buf, '\b' /* backspace */);
1372++ break;
1373++
1374++ case 'f':
1375++ pa_strbuf_putc(buf, '\f' /* form feed */);
1376++ break;
1377++
1378++ case 'n':
1379++ pa_strbuf_putc(buf, '\n' /* new line */);
1380++ break;
1381++
1382++ case 'r':
1383++ pa_strbuf_putc(buf, '\r' /* carriage return */);
1384++ break;
1385++
1386++ case 't':
1387++ pa_strbuf_putc(buf, '\t' /* horizontal tab */);
1388++ break;
1389++
1390++ case 'u':
1391++ pa_log("Unicode code points are currently unsupported");
1392++ goto error;
1393++
1394++ default:
1395++ pa_log("Unexepcted escape value: %c", *str);
1396++ goto error;
1397++ }
1398++ }
1399++
1400++ str++;
1401++ }
1402++
1403++ if (*str != '"') {
1404++ pa_log("Failed to parse remainder of string: %s", str);
1405++ goto error;
1406++ }
1407++
1408++ str++;
1409++
1410++ obj->type = PA_JSON_TYPE_STRING;
1411++ obj->string_value = pa_strbuf_to_string_free(buf);
1412++
1413++ return str;
1414++
1415++error:
1416++ pa_strbuf_free(buf);
1417++ return NULL;
1418++}
1419++
1420++static const char* parse_number(const char *str, pa_json_object *obj) {
1421++ bool negative = false, has_fraction = false, has_exponent = false, valid = false;
1422++ unsigned int integer = 0;
1423++ unsigned int fraction = 0;
1424++ unsigned int fraction_digits = 0;
1425++ int exponent = 0;
1426++
1427++ if (*str == '-') {
1428++ negative = true;
1429++ str++;
1430++ }
1431++
1432++ if (*str == '0') {
1433++ valid = true;
1434++ str++;
1435++ goto fraction;
1436++ }
1437++
1438++ while (is_digit(*str)) {
1439++ valid = true;
1440++
1441++ if (integer > ((negative ? INT_MAX : UINT_MAX) / 10)) {
1442++ pa_log("Integer overflow while parsing number");
1443++ goto error;
1444++ }
1445++
1446++ integer = (integer * 10) + (*str - '0');
1447++ str++;
1448++ }
1449++
1450++fraction:
1451++
1452++ if (!valid) {
1453++ pa_log("Missing digits while parsing number");
1454++ goto error;
1455++ }
1456++
1457++ if (*str == '.') {
1458++ has_fraction = true;
1459++ str++;
1460++ valid = false;
1461++
1462++ while (is_digit(*str)) {
1463++ valid = true;
1464++
1465++ if (fraction > (UINT_MAX / 10)) {
1466++ pa_log("Integer overflow while parsing fractional part of number");
1467++ goto error;
1468++ }
1469++
1470++ fraction = (fraction * 10) + (*str - '0');
1471++ fraction_digits++;
1472++ str++;
1473++ }
1474++
1475++ if (!valid) {
1476++ pa_log("No digit after '.' while parsing fraction");
1477++ goto error;
1478++ }
1479++ }
1480++
1481++ if (*str == 'e' || *str == 'E') {
1482++ bool exponent_negative = false;
1483++
1484++ has_exponent = true;
1485++ str++;
1486++ valid = false;
1487++
1488++ if (*str == '-') {
1489++ exponent_negative = true;
1490++ str++;
1491++ } else if (*str == '+')
1492++ str++;
1493++
1494++ while (is_digit(*str)) {
1495++ valid = true;
1496++
1497++ if (exponent > (INT_MAX / 10)) {
1498++ pa_log("Integer overflow while parsing exponent part of number");
1499++ goto error;
1500++ }
1501++
1502++ exponent = (exponent * 10) + (*str - '0');
1503++ str++;
1504++ }
1505++
1506++ if (!valid) {
1507++ pa_log("No digit in exponent while parsing fraction");
1508++ goto error;
1509++ }
1510++
1511++ if (exponent_negative)
1512++ exponent *= -1;
1513++ }
1514++
1515++ if (has_fraction || has_exponent) {
1516++ obj->type = PA_JSON_TYPE_DOUBLE;
1517++ obj->double_value =
1518++ (negative ? -1.0 : 1.0) * (integer + (double) fraction / pow(10, fraction_digits)) * pow(10, exponent);
1519++ } else {
1520++ obj->type = PA_JSON_TYPE_INT;
1521++ obj->int_value = (negative ? -1 : 1) * integer;
1522++ }
1523++
1524++ return str;
1525++
1526++error:
1527++ return NULL;
1528++}
1529++
1530++static const char *parse_object(const char *str, pa_json_object *obj, unsigned int depth) {
1531++ pa_json_object *name = NULL, *value = NULL;
1532++
1533++ obj->object_values = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
1534++ pa_xfree, (pa_free_cb_t) pa_json_object_free);
1535++
1536++ while (*str != '}') {
1537++ str++; /* Consume leading '{' or ',' */
1538++
1539++ str = parse_value(str, ":", &name, depth + 1);
1540++ if (!str || pa_json_object_get_type(name) != PA_JSON_TYPE_STRING) {
1541++ pa_log("Could not parse key for object");
1542++ goto error;
1543++ }
1544++
1545++ /* Consume the ':' */
1546++ str++;
1547++
1548++ str = parse_value(str, ",}", &value, depth + 1);
1549++ if (!str) {
1550++ pa_log("Could not parse value for object");
1551++ goto error;
1552++ }
1553++
1554++ pa_hashmap_put(obj->object_values, pa_xstrdup(pa_json_object_get_string(name)), value);
1555++ pa_json_object_free(name);
1556++
1557++ name = NULL;
1558++ value = NULL;
1559++ }
1560++
1561++ /* Drop trailing '}' */
1562++ str++;
1563++
1564++ /* We now know the value was correctly parsed */
1565++ obj->type = PA_JSON_TYPE_OBJECT;
1566++
1567++ return str;
1568++
1569++error:
1570++ pa_hashmap_free(obj->object_values);
1571++ obj->object_values = NULL;
1572++
1573++ if (name)
1574++ pa_json_object_free(name);
1575++ if (value)
1576++ pa_json_object_free(value);
1577++
1578++ return NULL;
1579++}
1580++
1581++static const char *parse_array(const char *str, pa_json_object *obj, unsigned int depth) {
1582++ pa_json_object *value;
1583++
1584++ obj->array_values = pa_idxset_new(NULL, NULL);
1585++
1586++ while (*str != ']') {
1587++ str++; /* Consume leading '[' or ',' */
1588++
1589++ /* Need to chew up whitespaces as a special case to deal with the
1590++ * possibility of an empty array */
1591++ while (is_whitespace(*str))
1592++ str++;
1593++
1594++ if (*str == ']')
1595++ break;
1596++
1597++ str = parse_value(str, ",]", &value, depth + 1);
1598++ if (!str) {
1599++ pa_log("Could not parse value for array");
1600++ goto error;
1601++ }
1602++
1603++ pa_idxset_put(obj->array_values, value, NULL);
1604++ }
1605++
1606++ /* Drop trailing ']' */
1607++ str++;
1608++
1609++ /* We now know the value was correctly parsed */
1610++ obj->type = PA_JSON_TYPE_ARRAY;
1611++
1612++ return str;
1613++
1614++error:
1615++ pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_free);
1616++ obj->array_values = NULL;
1617++ return NULL;
1618++}
1619++
1620++typedef enum {
1621++ JSON_PARSER_STATE_INIT,
1622++ JSON_PARSER_STATE_FINISH,
1623++} json_parser_state;
1624++
1625++static const char* parse_value(const char *str, const char *end, pa_json_object **obj, unsigned int depth) {
1626++ json_parser_state state = JSON_PARSER_STATE_INIT;
1627++ pa_json_object *o;
1628++
1629++ pa_assert(str != NULL);
1630++
1631++ o = json_object_new();
1632++
1633++ if (depth > MAX_NESTING_DEPTH) {
1634++ pa_log("Exceeded maximum permitted nesting depth of objects (%u)", MAX_NESTING_DEPTH);
1635++ goto error;
1636++ }
1637++
1638++ while (!is_end(*str, end)) {
1639++ switch (state) {
1640++ case JSON_PARSER_STATE_INIT:
1641++ if (is_whitespace(*str)) {
1642++ str++;
1643++ } else if (*str == 'n') {
1644++ str = parse_null(str, o);
1645++ state = JSON_PARSER_STATE_FINISH;
1646++ } else if (*str == 't' || *str == 'f') {
1647++ str = parse_boolean(str, o);
1648++ state = JSON_PARSER_STATE_FINISH;
1649++ } else if (*str == '"') {
1650++ str = parse_string(str, o);
1651++ state = JSON_PARSER_STATE_FINISH;
1652++ } else if (is_digit(*str) || *str == '-') {
1653++ str = parse_number(str, o);
1654++ state = JSON_PARSER_STATE_FINISH;
1655++ } else if (*str == '{') {
1656++ str = parse_object(str, o, depth);
1657++ state = JSON_PARSER_STATE_FINISH;
1658++ } else if (*str == '[') {
1659++ str = parse_array(str, o, depth);
1660++ state = JSON_PARSER_STATE_FINISH;
1661++ } else {
1662++ pa_log("Invalid JSON string: %s", str);
1663++ goto error;
1664++ }
1665++
1666++ if (!str)
1667++ goto error;
1668++
1669++ break;
1670++
1671++ case JSON_PARSER_STATE_FINISH:
1672++ /* Consume trailing whitespaces */
1673++ if (is_whitespace(*str)) {
1674++ str++;
1675++ } else {
1676++ goto error;
1677++ }
1678++ }
1679++ }
1680++
1681++ if (pa_json_object_get_type(o) == PA_JSON_TYPE_INIT) {
1682++ /* We didn't actually get any data */
1683++ pa_log("No data while parsing json string: '%s' till '%s'", str, pa_strnull(end));
1684++ goto error;
1685++ }
1686++
1687++ *obj = o;
1688++
1689++ return str;
1690++
1691++error:
1692++ pa_json_object_free(o);
1693++ return NULL;
1694++}
1695++
1696++
1697++pa_json_object* pa_json_parse(const char *str) {
1698++ pa_json_object *obj;
1699++
1700++ str = parse_value(str, NULL, &obj, 0);
1701++
1702++ if (!str) {
1703++ pa_log("JSON parsing failed");
1704++ return NULL;
1705++ }
1706++
1707++ if (*str != '\0') {
1708++ pa_log("Unable to parse complete JSON string, remainder is: %s", str);
1709++ pa_json_object_free(obj);
1710++ return NULL;
1711++ }
1712++
1713++ return obj;
1714++}
1715++
1716++pa_json_type pa_json_object_get_type(const pa_json_object *obj) {
1717++ return obj->type;
1718++}
1719++
1720++void pa_json_object_free(pa_json_object *obj) {
1721++
1722++ switch (pa_json_object_get_type(obj)) {
1723++ case PA_JSON_TYPE_INIT:
1724++ case PA_JSON_TYPE_INT:
1725++ case PA_JSON_TYPE_DOUBLE:
1726++ case PA_JSON_TYPE_BOOL:
1727++ case PA_JSON_TYPE_NULL:
1728++ break;
1729++
1730++ case PA_JSON_TYPE_STRING:
1731++ pa_xfree(obj->string_value);
1732++ break;
1733++
1734++ case PA_JSON_TYPE_OBJECT:
1735++ pa_hashmap_free(obj->object_values);
1736++ break;
1737++
1738++ case PA_JSON_TYPE_ARRAY:
1739++ pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_free);
1740++ break;
1741++
1742++ default:
1743++ pa_assert_not_reached();
1744++ }
1745++
1746++ pa_xfree(obj);
1747++}
1748++
1749++int pa_json_object_get_int(const pa_json_object *o) {
1750++ pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_INT);
1751++ return o->int_value;
1752++}
1753++
1754++double pa_json_object_get_double(const pa_json_object *o) {
1755++ pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_DOUBLE);
1756++ return o->double_value;
1757++}
1758++
1759++bool pa_json_object_get_bool(const pa_json_object *o) {
1760++ pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
1761++ return o->bool_value;
1762++}
1763++
1764++const char* pa_json_object_get_string(const pa_json_object *o) {
1765++ pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_STRING);
1766++ return o->string_value;
1767++}
1768++
1769++const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o, const char *name) {
1770++ pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
1771++ return pa_hashmap_get(o->object_values, name);
1772++}
1773++
1774++int pa_json_object_get_array_length(const pa_json_object *o) {
1775++ pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
1776++ return pa_idxset_size(o->array_values);
1777++}
1778++
1779++const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index) {
1780++ pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
1781++ return pa_idxset_get_by_index(o->array_values, index);
1782++}
1783++
1784++bool pa_json_object_equal(const pa_json_object *o1, const pa_json_object *o2) {
1785++ int i;
1786++
1787++ if (pa_json_object_get_type(o1) != pa_json_object_get_type(o2))
1788++ return false;
1789++
1790++ switch (pa_json_object_get_type(o1)) {
1791++ case PA_JSON_TYPE_NULL:
1792++ return true;
1793++
1794++ case PA_JSON_TYPE_BOOL:
1795++ return o1->bool_value == o2->bool_value;
1796++
1797++ case PA_JSON_TYPE_INT:
1798++ return o1->int_value == o2->int_value;
1799++
1800++ case PA_JSON_TYPE_DOUBLE:
1801++ return PA_DOUBLE_IS_EQUAL(o1->double_value, o2->double_value);
1802++
1803++ case PA_JSON_TYPE_STRING:
1804++ return pa_streq(o1->string_value, o2->string_value);
1805++
1806++ case PA_JSON_TYPE_ARRAY:
1807++ if (pa_json_object_get_array_length(o1) != pa_json_object_get_array_length(o2))
1808++ return false;
1809++
1810++ for (i = 0; i < pa_json_object_get_array_length(o1); i++) {
1811++ if (!pa_json_object_equal(pa_json_object_get_array_member(o1, i),
1812++ pa_json_object_get_array_member(o2, i)))
1813++ return false;
1814++ }
1815++
1816++ return true;
1817++
1818++ case PA_JSON_TYPE_OBJECT: {
1819++ void *state;
1820++ const char *key;
1821++ const pa_json_object *v1, *v2;
1822++
1823++ if (pa_hashmap_size(o1->object_values) != pa_hashmap_size(o2->object_values))
1824++ return false;
1825++
1826++ PA_HASHMAP_FOREACH_KV(key, v1, o1->object_values, state) {
1827++ v2 = pa_json_object_get_object_member(o2, key);
1828++ if (!v2 || !pa_json_object_equal(v1, v2))
1829++ return false;
1830++ }
1831++
1832++ return true;
1833++ }
1834++
1835++ default:
1836++ pa_assert_not_reached();
1837++ }
1838++}
1839+diff --git a/src/pulse/json.h b/src/pulse/json.h
1840+new file mode 100644
1841+index 0000000..7759bf2
1842+--- /dev/null
1843++++ b/src/pulse/json.h
1844+@@ -0,0 +1,53 @@
1845++/***
1846++ This file is part of PulseAudio.
1847++
1848++ Copyright 2016 Arun Raghavan <mail@arunraghavan.net>
1849++
1850++ PulseAudio is free software; you can redistribute it and/or modify
1851++ it under the terms of the GNU Lesser General Public License as published
1852++ by the Free Software Foundation; either version 2.1 of the License,
1853++ or (at your option) any later version.
1854++
1855++ PulseAudio is distributed in the hope that it will be useful, but
1856++ WITHOUT ANY WARRANTY; without even the implied warranty of
1857++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1858++ General Public License for more details.
1859++
1860++ You should have received a copy of the GNU Lesser General Public License
1861++ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
1862++***/
1863++
1864++#include <stdbool.h>
1865++
1866++#define PA_DOUBLE_IS_EQUAL(x, y) (((x) - (y)) < 0.000001 && ((x) - (y)) > -0.000001)
1867++
1868++typedef enum {
1869++ PA_JSON_TYPE_INIT = 0,
1870++ PA_JSON_TYPE_NULL,
1871++ PA_JSON_TYPE_INT,
1872++ PA_JSON_TYPE_DOUBLE,
1873++ PA_JSON_TYPE_BOOL,
1874++ PA_JSON_TYPE_STRING,
1875++ PA_JSON_TYPE_ARRAY,
1876++ PA_JSON_TYPE_OBJECT,
1877++} pa_json_type;
1878++
1879++typedef struct pa_json_object pa_json_object;
1880++
1881++pa_json_object* pa_json_parse(const char *str);
1882++pa_json_type pa_json_object_get_type(const pa_json_object *obj);
1883++void pa_json_object_free(pa_json_object *obj);
1884++
1885++/* All pointer members that are returned are valid while the corresponding object is valid */
1886++
1887++int pa_json_object_get_int(const pa_json_object *o);
1888++double pa_json_object_get_double(const pa_json_object *o);
1889++bool pa_json_object_get_bool(const pa_json_object *o);
1890++const char* pa_json_object_get_string(const pa_json_object *o);
1891++
1892++const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o, const char *name);
1893++
1894++int pa_json_object_get_array_length(const pa_json_object *o);
1895++const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index);
1896++
1897++bool pa_json_object_equal(const pa_json_object *o1, const pa_json_object *o2);
1898+diff --git a/src/tests/json-test.c b/src/tests/json-test.c
1899+new file mode 100644
1900+index 0000000..3e956db
1901+--- /dev/null
1902++++ b/src/tests/json-test.c
1903+@@ -0,0 +1,280 @@
1904++/***
1905++ This file is part of PulseAudio.
1906++
1907++ Copyright 2016 Arun Raghavan <mail@arunraghavan.net>
1908++
1909++ PulseAudio is free software; you can redistribute it and/or modify
1910++ it under the terms of the GNU Lesser General Public License as published
1911++ by the Free Software Foundation; either version 2.1 of the License,
1912++ or (at your option) any later version.
1913++
1914++ PulseAudio is distributed in the hope that it will be useful, but
1915++ WITHOUT ANY WARRANTY; without even the implied warranty of
1916++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1917++ General Public License for more details.
1918++
1919++ You should have received a copy of the GNU Lesser General Public License
1920++ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
1921++***/
1922++
1923++#ifdef HAVE_CONFIG_H
1924++#include <config.h>
1925++#endif
1926++
1927++#include <check.h>
1928++
1929++#include <pulse/json.h>
1930++#include <pulsecore/core-util.h>
1931++
1932++START_TEST (string_test) {
1933++ pa_json_object *o;
1934++ unsigned int i;
1935++ const char *strings_parse[] = {
1936++ "\"\"", "\"test\"", "\"test123\"", "\"123\"", "\"newline\\n\"", "\" spaces \"",
1937++ " \"lots of spaces\" ", "\"esc\\nape\"", "\"escape a \\\" quote\"",
1938++ };
1939++ const char *strings_compare[] = {
1940++ "", "test", "test123", "123", "newline\n", " spaces ",
1941++ "lots of spaces", "esc\nape", "escape a \" quote",
1942++ };
1943++
1944++ for (i = 0; i < PA_ELEMENTSOF(strings_parse); i++) {
1945++ o = pa_json_parse(strings_parse[i]);
1946++
1947++ fail_unless(o != NULL);
1948++ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_STRING);
1949++ fail_unless(pa_streq(pa_json_object_get_string(o), strings_compare[i]));
1950++
1951++ pa_json_object_free(o);
1952++ }
1953++}
1954++END_TEST
1955++
1956++START_TEST(int_test) {
1957++ pa_json_object *o;
1958++ unsigned int i;
1959++ const char *ints_parse[] = { "1", "-1", "1234", "0" };
1960++ const int ints_compare[] = { 1, -1, 1234, 0 };
1961++
1962++ for (i = 0; i < PA_ELEMENTSOF(ints_parse); i++) {
1963++ o = pa_json_parse(ints_parse[i]);
1964++
1965++ fail_unless(o != NULL);
1966++ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_INT);
1967++ fail_unless(pa_json_object_get_int(o) == ints_compare[i]);
1968++
1969++ pa_json_object_free(o);
1970++ }
1971++}
1972++END_TEST
1973++
1974++START_TEST(double_test) {
1975++ pa_json_object *o;
1976++ unsigned int i;
1977++ const char *doubles_parse[] = {
1978++ "1.0", "-1.1", "1234e2", "1234e0", "0.1234", "-0.1234", "1234e-1", "1234.5e-1", "1234.5e+2",
1979++ };
1980++ const double doubles_compare[] = {
1981++ 1.0, -1.1, 123400.0, 1234.0, 0.1234, -0.1234, 123.4, 123.45, 123450.0,
1982++ };
1983++
1984++ for (i = 0; i < PA_ELEMENTSOF(doubles_parse); i++) {
1985++ o = pa_json_parse(doubles_parse[i]);
1986++
1987++ fail_unless(o != NULL);
1988++ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_DOUBLE);
1989++ fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(o), doubles_compare[i]));
1990++
1991++ pa_json_object_free(o);
1992++ }
1993++}
1994++END_TEST
1995++
1996++START_TEST(null_test) {
1997++ pa_json_object *o;
1998++
1999++ o = pa_json_parse("null");
2000++
2001++ fail_unless(o != NULL);
2002++ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_NULL);
2003++
2004++ pa_json_object_free(o);
2005++}
2006++END_TEST
2007++
2008++START_TEST(bool_test) {
2009++ pa_json_object *o;
2010++
2011++ o = pa_json_parse("true");
2012++
2013++ fail_unless(o != NULL);
2014++ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
2015++ fail_unless(pa_json_object_get_bool(o) == true);
2016++
2017++ pa_json_object_free(o);
2018++
2019++ o = pa_json_parse("false");
2020++
2021++ fail_unless(o != NULL);
2022++ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
2023++ fail_unless(pa_json_object_get_bool(o) == false);
2024++
2025++ pa_json_object_free(o);
2026++}
2027++END_TEST
2028++
2029++START_TEST(object_test) {
2030++ pa_json_object *o;
2031++ const pa_json_object *v;
2032++
2033++ o = pa_json_parse(" { \"name\" : \"A Person\" } ");
2034++
2035++ fail_unless(o != NULL);
2036++ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
2037++
2038++ v = pa_json_object_get_object_member(o, "name");
2039++ fail_unless(v != NULL);
2040++ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
2041++ fail_unless(pa_streq(pa_json_object_get_string(v), "A Person"));
2042++
2043++ pa_json_object_free(o);
2044++
2045++ o = pa_json_parse(" { \"age\" : -45.3e-0 } ");
2046++
2047++ fail_unless(o != NULL);
2048++ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
2049++
2050++ v = pa_json_object_get_object_member(o, "age");
2051++ fail_unless(v != NULL);
2052++ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE);
2053++ fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(v), -45.3));
2054++
2055++ pa_json_object_free(o);
2056++
2057++ o = pa_json_parse("{\"person\":true}");
2058++
2059++ fail_unless(o != NULL);
2060++ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
2061++
2062++ v = pa_json_object_get_object_member(o, "person");
2063++ fail_unless(v != NULL);
2064++ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL);
2065++ fail_unless(pa_json_object_get_bool(v) == true);
2066++
2067++ pa_json_object_free(o);
2068++
2069++ o = pa_json_parse("{ \"parent\": { \"child\": false } }");
2070++ fail_unless(o != NULL);
2071++ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
2072++
2073++ v = pa_json_object_get_object_member(o, "parent");
2074++ fail_unless(v != NULL);
2075++ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_OBJECT);
2076++ v = pa_json_object_get_object_member(v, "child");
2077++ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL);
2078++ fail_unless(pa_json_object_get_bool(v) == false);
2079++
2080++ pa_json_object_free(o);
2081++}
2082++END_TEST
2083++
2084++START_TEST(array_test) {
2085++ pa_json_object *o;
2086++ const pa_json_object *v, *v2;
2087++
2088++ o = pa_json_parse(" [ ] ");
2089++
2090++ fail_unless(o != NULL);
2091++ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
2092++ fail_unless(pa_json_object_get_array_length(o) == 0);
2093++
2094++ pa_json_object_free(o);
2095++
2096++ o = pa_json_parse("[\"a member\"]");
2097++
2098++ fail_unless(o != NULL);
2099++ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
2100++ fail_unless(pa_json_object_get_array_length(o) == 1);
2101++
2102++ v = pa_json_object_get_array_member(o, 0);
2103++ fail_unless(v != NULL);
2104++ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
2105++ fail_unless(pa_streq(pa_json_object_get_string(v), "a member"));
2106++
2107++ pa_json_object_free(o);
2108++
2109++ o = pa_json_parse("[\"a member\", 1234.5, { \"another\": true } ]");
2110++
2111++ fail_unless(o != NULL);
2112++ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
2113++ fail_unless(pa_json_object_get_array_length(o) == 3);
2114++
2115++ v = pa_json_object_get_array_member(o, 0);
2116++ fail_unless(v != NULL);
2117++ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
2118++ fail_unless(pa_streq(pa_json_object_get_string(v), "a member"));
2119++ v = pa_json_object_get_array_member(o, 1);
2120++ fail_unless(v != NULL);
2121++ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE);
2122++ fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(v), 1234.5));
2123++ v = pa_json_object_get_array_member(o, 2);
2124++ fail_unless(v != NULL);
2125++ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_OBJECT);
2126++ v2 =pa_json_object_get_object_member(v, "another");
2127++ fail_unless(v2 != NULL);
2128++ fail_unless(pa_json_object_get_type(v2) == PA_JSON_TYPE_BOOL);
2129++ fail_unless(pa_json_object_get_bool(v2) == true);
2130++
2131++ pa_json_object_free(o);
2132++}
2133++END_TEST
2134++
2135++START_TEST(bad_test) {
2136++ unsigned int i;
2137++ const char *bad_parse[] = {
2138++ "\"" /* Quote not closed */,
2139++ "123456789012345678901234567890" /* Overflow */,
2140++ "0.123456789012345678901234567890" /* Overflow */,
2141++ "1e123456789012345678901234567890" /* Overflow */,
2142++ "1e" /* Bad number string */,
2143++ "1." /* Bad number string */,
2144++ "1.e3" /* Bad number string */,
2145++ "-" /* Bad number string */,
2146++ "{ \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { } } } } } } } } } } } } } } } } } } } } } }" /* Nested too deep */,
2147++ "[ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ { \"a\": \"b\" } ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ]" /* Nested too deep */,
2148++ "asdf" /* Unquoted string */,
2149++ "{ a: true }" /* Unquoted key in object */,
2150++ "\" \a\"" /* Alarm is not a valid character */
2151++ };
2152++
2153++ for (i = 0; i < PA_ELEMENTSOF(bad_parse); i++) {
2154++ fail_unless(pa_json_parse(bad_parse[i]) == NULL);
2155++ }
2156++}
2157++END_TEST
2158++
2159++int main(int argc, char *argv[]) {
2160++ int failed = 0;
2161++ Suite *s;
2162++ TCase *tc;
2163++ SRunner *sr;
2164++
2165++ s = suite_create("JSON");
2166++ tc = tcase_create("json");
2167++ tcase_add_test(tc, string_test);
2168++ tcase_add_test(tc, int_test);
2169++ tcase_add_test(tc, double_test);
2170++ tcase_add_test(tc, null_test);
2171++ tcase_add_test(tc, bool_test);
2172++ tcase_add_test(tc, object_test);
2173++ tcase_add_test(tc, array_test);
2174++ tcase_add_test(tc, bad_test);
2175++ suite_add_tcase(s, tc);
2176++
2177++ sr = srunner_create(s);
2178++ srunner_run_all(sr, CK_NORMAL);
2179++ failed = srunner_ntests_failed(sr);
2180++ srunner_free(sr);
2181++
2182++ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
2183++}
2184diff --git a/debian/patches/series b/debian/patches/series
2185index ec479b1..b7351ad 100644
2186--- a/debian/patches/series
2187+++ b/debian/patches/series
2188@@ -33,6 +33,10 @@
2189 0410-Add-thread-to-activate-trust-store-interface.patch
2190 0417-increase-timeout-check-apparmor.patch
2191
2192+# Ubuntu Snap policy support
2193+0450-modules-add-snappy-policy-module.patch
2194+0451-enable-snap-policy-module.patch
2195+
2196 # Ubuntu touch: enable bluez5 HFP over ofono support
2197 0501-bluetooth-bluez5-ofono-add-support-for-HFP-gateway-r.patch
2198 0502-bluetooth-bluez5-bring-back-SCO-over-PCM-support.patch
2199@@ -60,3 +64,4 @@
2200 0802-alsa-mixer-Add-support-for-usb-audio-in-the-Dell-doc.patch
2201 0803-droid-fix-crash-on-module-load.patch
2202 0804-build-sys-add-the-Dell-dock-TB16-configuration.patch
2203+0805-remove-libjson-c-dependency.patch
2204diff --git a/debian/pulseaudio.install b/debian/pulseaudio.install
2205index b137241..78f7a78 100755
2206--- a/debian/pulseaudio.install
2207+++ b/debian/pulseaudio.install
2208@@ -77,6 +77,7 @@ usr/lib/pulse-*/modules/module-virtual-sink.so
2209 usr/lib/pulse-*/modules/module-virtual-source.so
2210 usr/lib/pulse-*/modules/module-switch-on-port-available.so
2211 usr/lib/pulse-*/modules/module-virtual-surround-sink.so
2212+usr/lib/pulse-*/modules/module-snap-policy.so
2213 [linux-any] usr/lib/pulse-*/modules/module-systemd-login.so
2214 [linux-any] usr/lib/systemd/user/pulseaudio.*
2215 usr/share/bash-completion/completions/*
2216diff --git a/debian/rules b/debian/rules
2217index 258494f..bcb18c1 100755
2218--- a/debian/rules
2219+++ b/debian/rules
2220@@ -37,7 +37,7 @@ DEB_CONFIGURE_EXTRA_FLAGS = --enable-x11 --disable-hal-compat \
2221 --with-zsh-completion-dir=\$${datadir}/zsh/vendor-completions \
2222 --with-bash-completion-dir=\$${datadir}/bash-completion/completions \
2223 --with-systemduserunitdir=\$${prefix}/lib/systemd/user \
2224- --disable-bluez4
2225+ --disable-bluez4 --enable-snap
2226
2227 ifeq ($(words $(sort $(filter stage1,$(DEB_BUILD_PROFILES)))),1)
2228 DEB_CONFIGURE_EXTRA_FLAGS += --disable-bluez

Subscribers

People subscribed via source and target branches