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