Merge ~ubuntu-audio-dev/pulseaudio:snap-policy-xenial into ~ubuntu-audio-dev/pulseaudio:ubuntu-xenial
- Git
- lp:~ubuntu-audio-dev/pulseaudio
- snap-policy-xenial
- Merge into 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) |
||||
Related bugs: |
|
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 : | # |
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_
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
1 | diff --git a/debian/changelog b/debian/changelog |
2 | index 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 |
26 | diff --git a/debian/control b/debian/control |
27 | index 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, |
43 | diff --git a/debian/patches/0450-modules-add-snappy-policy-module.patch b/debian/patches/0450-modules-add-snappy-policy-module.patch |
44 | new file mode 100644 |
45 | index 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 | ++} |
531 | diff --git a/debian/patches/0451-enable-snap-policy-module.patch b/debian/patches/0451-enable-snap-policy-module.patch |
532 | new file mode 100644 |
533 | index 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 |
567 | diff --git a/debian/patches/0805-remove-libjson-c-dependency.patch b/debian/patches/0805-remove-libjson-c-dependency.patch |
568 | new file mode 100644 |
569 | index 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 | ++} |
2184 | diff --git a/debian/patches/series b/debian/patches/series |
2185 | index 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 |
2204 | diff --git a/debian/pulseaudio.install b/debian/pulseaudio.install |
2205 | index 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/* |
2216 | diff --git a/debian/rules b/debian/rules |
2217 | index 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 |
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)" snap-policy. c: Checking access for client 4 (skype) snap-policy. c: snapd_client_ get_snap failed: Not connected to snapd snap-policy. c: Access check for client 4 (skype): 0
I: [pulseaudio] protocol-native.c: Got credentials: uid=1000 gid=1000 success=1
I: [pulseaudio] module-
W: [snapd-glib] module-
I: [pulseaudio] module-
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.