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

Subscribers

People subscribed via source and target branches