1 /*
2 Copyright (c) 2008-2017
3 	Lars-Dominik Braun <lars@6xq.net>
4 
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to deal
7 in the Software without restriction, including without limitation the rights
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11 
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14 
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 THE SOFTWARE.
22 */
23 
24 #include "../config.h"
25 
26 #include <json.h>
27 #include <string.h>
28 #include <assert.h>
29 #include <time.h>
30 #include <stdlib.h>
31 
32 #include "piano.h"
33 #include "piano_private.h"
34 #include "crypt.h"
35 
PianoJsonStrdup(json_object * j,const char * key)36 static char *PianoJsonStrdup (json_object *j, const char *key) {
37 	assert (j != NULL);
38 	assert (key != NULL);
39 
40 	json_object *v;
41 	if (json_object_object_get_ex (j, key, &v)) {
42 		return strdup (json_object_get_string (v));
43 	} else {
44 		return NULL;
45 	}
46 }
47 
getBoolDefault(json_object * const j,const char * const key,const bool def)48 static bool getBoolDefault (json_object * const j, const char * const key, const bool def) {
49 	assert (j != NULL);
50 	assert (key != NULL);
51 
52 	json_object *v;
53 	if (json_object_object_get_ex (j, key, &v)) {
54 		return json_object_get_boolean (v);
55 	} else {
56 		return def;
57 	}
58 }
59 
PianoJsonParseStation(json_object * j,PianoStation_t * s)60 static void PianoJsonParseStation (json_object *j, PianoStation_t *s) {
61 	s->name = PianoJsonStrdup (j, "stationName");
62 	s->id = PianoJsonStrdup (j, "stationToken");
63 	s->isCreator = !getBoolDefault (j, "isShared", !false);
64 	s->isQuickMix = getBoolDefault (j, "isQuickMix", false);
65 }
66 
67 /*	concat strings
68  *	@param destination
69  *	@param source string
70  *	@param destination size
71  */
PianoStrpcat(char * restrict dest,const char * restrict src,size_t len)72 static void PianoStrpcat (char * restrict dest, const char * restrict src,
73 		size_t len) {
74 	/* skip to end of string */
75 	while (*dest != '\0' && len > 1) {
76 		++dest;
77 		--len;
78 	}
79 
80 	/* append until source exhausted or destination full */
81 	while (*src != '\0' && len > 1) {
82 		*dest = *src;
83 		++dest;
84 		++src;
85 		--len;
86 	}
87 
88 	*dest = '\0';
89 }
90 
91 /*	parse xml response and update data structures/return new data structure
92  *	@param piano handle
93  *	@param initialized request (expects responseData to be a NUL-terminated
94  *			string)
95  */
PianoResponse(PianoHandle_t * ph,PianoRequest_t * req)96 PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
97 	PianoReturn_t ret = PIANO_RET_OK;
98 
99 	assert (ph != NULL);
100 	assert (req != NULL);
101 
102 	json_object * const j = json_tokener_parse (req->responseData);
103 
104 	json_object *status;
105 	if (!json_object_object_get_ex (j, "stat", &status)) {
106 		ret = PIANO_RET_INVALID_RESPONSE;
107 		goto cleanup;
108 	}
109 
110 	/* error handling */
111 	if (strcmp (json_object_get_string (status), "ok") != 0) {
112 		json_object *code;
113 		if (!json_object_object_get_ex (j, "code", &code)) {
114 			ret = PIANO_RET_INVALID_RESPONSE;
115 		} else {
116 			ret = json_object_get_int (code)+PIANO_RET_OFFSET;
117 
118 			if (ret == PIANO_RET_P_INVALID_PARTNER_LOGIN &&
119 					req->type == PIANO_REQUEST_LOGIN) {
120 				PianoRequestDataLogin_t *reqData = req->data;
121 				if (reqData->step == 1) {
122 					/* return value is ambiguous, as both, partnerLogin and
123 					 * userLogin return INVALID_PARTNER_LOGIN. Fix that to provide
124 					 * better error messages. */
125 					ret = PIANO_RET_INVALID_LOGIN;
126 				}
127 			}
128 		}
129 
130 		goto cleanup;
131 	}
132 
133 	json_object *result = NULL;
134 	/* missing for some request types */
135 	json_object_object_get_ex (j, "result", &result);
136 
137 	switch (req->type) {
138 		case PIANO_REQUEST_LOGIN: {
139 			/* authenticate user */
140 			PianoRequestDataLogin_t *reqData = req->data;
141 
142 			assert (req->responseData != NULL);
143 			assert (reqData != NULL);
144 
145 			switch (reqData->step) {
146 				case 0: {
147 					/* decrypt timestamp */
148 					json_object *jsonTimestamp;
149 					if (!json_object_object_get_ex (result, "syncTime", &jsonTimestamp)) {
150 						ret = PIANO_RET_INVALID_RESPONSE;
151 						break;
152 					}
153 					assert (jsonTimestamp != NULL);
154 					const char * const cryptedTimestamp = json_object_get_string (jsonTimestamp);
155 					assert (cryptedTimestamp != NULL);
156 					const time_t realTimestamp = time (NULL);
157 					char *decryptedTimestamp = NULL;
158 					size_t decryptedSize;
159 
160 					ret = PIANO_RET_ERR;
161 					if ((decryptedTimestamp = PianoDecryptString (ph->partner.in,
162 							cryptedTimestamp, &decryptedSize)) != NULL &&
163 							decryptedSize > 4) {
164 						/* skip four bytes garbage(?) at beginning */
165 						const unsigned long timestamp = strtoul (
166 								decryptedTimestamp+4, NULL, 0);
167 						ph->timeOffset = (long int) realTimestamp -
168 								(long int) timestamp;
169 						ret = PIANO_RET_CONTINUE_REQUEST;
170 					}
171 					free (decryptedTimestamp);
172 					/* get auth token */
173 					ph->partner.authToken = PianoJsonStrdup (result,
174 							"partnerAuthToken");
175 					json_object *partnerId;
176 					if (!json_object_object_get_ex (result, "partnerId", &partnerId)) {
177 						ret = PIANO_RET_INVALID_RESPONSE;
178 						break;
179 					}
180 					ph->partner.id = json_object_get_int (partnerId);
181 					++reqData->step;
182 					break;
183 				}
184 
185 				case 1:
186 					/* information exists when reauthenticating, destroy to
187 					 * avoid memleak */
188 					if (ph->user.listenerId != NULL) {
189 						PianoDestroyUserInfo (&ph->user);
190 					}
191 					ph->user.listenerId = PianoJsonStrdup (result, "userId");
192 					ph->user.authToken = PianoJsonStrdup (result,
193 							"userAuthToken");
194 					break;
195 			}
196 			break;
197 		}
198 
199 		case PIANO_REQUEST_GET_STATIONS: {
200 			/* get stations */
201 			assert (req->responseData != NULL);
202 
203 			json_object *stations, *mix = NULL;
204 
205 			if (!json_object_object_get_ex (result, "stations", &stations)) {
206 				break;
207 			}
208 
209 			for (int i = 0; i < json_object_array_length (stations); i++) {
210 				PianoStation_t *tmpStation;
211 				json_object *s = json_object_array_get_idx (stations, i);
212 
213 				if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) {
214 					return PIANO_RET_OUT_OF_MEMORY;
215 				}
216 
217 				PianoJsonParseStation (s, tmpStation);
218 
219 				if (tmpStation->isQuickMix) {
220 					/* fix flags on other stations later */
221 					json_object_object_get_ex (s, "quickMixStationIds", &mix);
222 				}
223 
224 				/* start new linked list or append */
225 				ph->stations = PianoListAppendP (ph->stations, tmpStation);
226 			}
227 
228 			/* fix quickmix flags */
229 			if (mix != NULL) {
230 				PianoStation_t *curStation = ph->stations;
231 				PianoListForeachP (curStation) {
232 					for (int i = 0; i < json_object_array_length (mix); i++) {
233 						json_object *id = json_object_array_get_idx (mix, i);
234 						if (strcmp (json_object_get_string (id),
235 								curStation->id) == 0) {
236 							curStation->useQuickMix = true;
237 						}
238 					}
239 				}
240 			}
241 			break;
242 		}
243 
244 		case PIANO_REQUEST_GET_PLAYLIST: {
245 			/* get playlist, usually four songs */
246 			PianoRequestDataGetPlaylist_t *reqData = req->data;
247 			PianoSong_t *playlist = NULL;
248 
249 			assert (req->responseData != NULL);
250 			assert (reqData != NULL);
251 			assert (reqData->quality != PIANO_AQ_UNKNOWN);
252 
253 			json_object *items = NULL;
254 			if (!json_object_object_get_ex (result, "items", &items)) {
255 				break;
256 			}
257 			assert (items != NULL);
258 
259 			for (int i = 0; i < json_object_array_length (items); i++) {
260 				json_object *s = json_object_array_get_idx (items, i);
261 				PianoSong_t *song;
262 
263 				if ((song = calloc (1, sizeof (*song))) == NULL) {
264 					return PIANO_RET_OUT_OF_MEMORY;
265 				}
266 
267 				if (!json_object_object_get_ex (s, "artistName", NULL)) {
268 					free (song);
269 					continue;
270 				}
271 
272 				/* get audio url based on selected quality */
273 				static const char *qualityMap[] = {"", "lowQuality", "mediumQuality",
274 						"highQuality"};
275 				assert (reqData->quality < sizeof (qualityMap)/sizeof (*qualityMap));
276 				static const char *formatMap[] = {"", "aacplus", "mp3"};
277 
278 				json_object *umap;
279 				if (json_object_object_get_ex (s, "audioUrlMap", &umap)) {
280 					assert (umap != NULL);
281 					json_object *jsonEncoding, *qmap;
282 					if (json_object_object_get_ex (umap, qualityMap[reqData->quality], &qmap) &&
283 							json_object_object_get_ex (qmap, "encoding", &jsonEncoding)) {
284 						assert (qmap != NULL);
285 						const char *encoding = json_object_get_string (jsonEncoding);
286 						assert (encoding != NULL);
287 						for (size_t k = 0; k < sizeof (formatMap)/sizeof (*formatMap); k++) {
288 							if (strcmp (formatMap[k], encoding) == 0) {
289 								song->audioFormat = k;
290 								break;
291 							}
292 						}
293 						song->audioUrl = PianoJsonStrdup (qmap, "audioUrl");
294 					} else {
295 						/* requested quality is not available */
296 						ret = PIANO_RET_QUALITY_UNAVAILABLE;
297 						free (song);
298 						PianoDestroyPlaylist (playlist);
299 						goto cleanup;
300 					}
301 				}
302 
303 				json_object *v;
304 				song->artist = PianoJsonStrdup (s, "artistName");
305 				song->album = PianoJsonStrdup (s, "albumName");
306 				song->title = PianoJsonStrdup (s, "songName");
307 				song->trackToken = PianoJsonStrdup (s, "trackToken");
308 				song->stationId = PianoJsonStrdup (s, "stationId");
309 				song->coverArt = PianoJsonStrdup (s, "albumArtUrl");
310 				song->detailUrl = PianoJsonStrdup (s, "songDetailUrl");
311 				song->fileGain = json_object_object_get_ex (s, "trackGain", &v) ?
312 						json_object_get_double (v) : 0.0;
313 				song->length = json_object_object_get_ex (s, "trackLength", &v) ?
314 						json_object_get_int (v) : 0;
315 				switch (json_object_object_get_ex (s, "songRating", &v) ?
316 						json_object_get_int (v) : 0) {
317 					case 1:
318 						song->rating = PIANO_RATE_LOVE;
319 						break;
320 				}
321 
322 				playlist = PianoListAppendP (playlist, song);
323 			}
324 
325 			reqData->retPlaylist = playlist;
326 			break;
327 		}
328 
329 		case PIANO_REQUEST_RATE_SONG: {
330 			/* love/ban song */
331 			PianoRequestDataRateSong_t *reqData = req->data;
332 			reqData->song->rating = reqData->rating;
333 			break;
334 		}
335 
336 		case PIANO_REQUEST_ADD_FEEDBACK:
337 			/* never ever use this directly, low-level call */
338 			assert (0);
339 			break;
340 
341 		case PIANO_REQUEST_RENAME_STATION: {
342 			/* rename station and update PianoStation_t structure */
343 			PianoRequestDataRenameStation_t *reqData = req->data;
344 
345 			assert (reqData != NULL);
346 			assert (reqData->station != NULL);
347 			assert (reqData->newName != NULL);
348 
349 			free (reqData->station->name);
350 			reqData->station->name = strdup (reqData->newName);
351 			break;
352 		}
353 
354 		case PIANO_REQUEST_DELETE_STATION: {
355 			/* delete station from server and station list */
356 			PianoStation_t *station = req->data;
357 
358 			assert (station != NULL);
359 
360 			ph->stations = PianoListDeleteP (ph->stations, station);
361 			PianoDestroyStation (station);
362 			free (station);
363 			break;
364 		}
365 
366 		case PIANO_REQUEST_SEARCH: {
367 			/* search artist/song */
368 			PianoRequestDataSearch_t *reqData = req->data;
369 			PianoSearchResult_t *searchResult;
370 
371 			assert (req->responseData != NULL);
372 			assert (reqData != NULL);
373 
374 			searchResult = &reqData->searchResult;
375 			memset (searchResult, 0, sizeof (*searchResult));
376 
377 			/* get artists */
378 			json_object *artists;
379 			if (json_object_object_get_ex (result, "artists", &artists)) {
380 				for (int i = 0; i < json_object_array_length (artists); i++) {
381 					json_object *a = json_object_array_get_idx (artists, i);
382 					PianoArtist_t *artist;
383 
384 					if ((artist = calloc (1, sizeof (*artist))) == NULL) {
385 						return PIANO_RET_OUT_OF_MEMORY;
386 					}
387 
388 					artist->name = PianoJsonStrdup (a, "artistName");
389 					artist->musicId = PianoJsonStrdup (a, "musicToken");
390 
391 					searchResult->artists =
392 							PianoListAppendP (searchResult->artists, artist);
393 				}
394 			}
395 
396 			/* get songs */
397 			json_object *songs;
398 			if (json_object_object_get_ex (result, "songs", &songs)) {
399 				for (int i = 0; i < json_object_array_length (songs); i++) {
400 					json_object *s = json_object_array_get_idx (songs, i);
401 					PianoSong_t *song;
402 
403 					if ((song = calloc (1, sizeof (*song))) == NULL) {
404 						return PIANO_RET_OUT_OF_MEMORY;
405 					}
406 
407 					song->title = PianoJsonStrdup (s, "songName");
408 					song->artist = PianoJsonStrdup (s, "artistName");
409 					song->musicId = PianoJsonStrdup (s, "musicToken");
410 
411 					searchResult->songs =
412 							PianoListAppendP (searchResult->songs, song);
413 				}
414 			}
415 			break;
416 		}
417 
418 		case PIANO_REQUEST_CREATE_STATION: {
419 			/* create station, insert new station into station list on success */
420 			PianoStation_t *tmpStation;
421 
422 			if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) {
423 				return PIANO_RET_OUT_OF_MEMORY;
424 			}
425 
426 			PianoJsonParseStation (result, tmpStation);
427 
428 			PianoStation_t *search = PianoFindStationById (ph->stations,
429 					tmpStation->id);
430 			if (search != NULL) {
431 				ph->stations = PianoListDeleteP (ph->stations, search);
432 				PianoDestroyStation (search);
433 				free (search);
434 			}
435 			ph->stations = PianoListAppendP (ph->stations, tmpStation);
436 			break;
437 		}
438 
439 		case PIANO_REQUEST_ADD_TIRED_SONG: {
440 			PianoSong_t * const song = req->data;
441 			song->rating = PIANO_RATE_TIRED;
442 			break;
443 		}
444 
445 		case PIANO_REQUEST_ADD_SEED:
446 		case PIANO_REQUEST_SET_QUICKMIX:
447 		case PIANO_REQUEST_BOOKMARK_SONG:
448 		case PIANO_REQUEST_BOOKMARK_ARTIST:
449 		case PIANO_REQUEST_DELETE_FEEDBACK:
450 		case PIANO_REQUEST_DELETE_SEED:
451 		case PIANO_REQUEST_CHANGE_SETTINGS:
452 			/* response unused */
453 			break;
454 
455 		case PIANO_REQUEST_GET_GENRE_STATIONS: {
456 			/* get genre stations */
457 			json_object *categories;
458 			if (json_object_object_get_ex (result, "categories", &categories)) {
459 				for (int i = 0; i < json_object_array_length (categories); i++) {
460 					json_object *c = json_object_array_get_idx (categories, i);
461 					PianoGenreCategory_t *tmpGenreCategory;
462 
463 					if ((tmpGenreCategory = calloc (1,
464 							sizeof (*tmpGenreCategory))) == NULL) {
465 						return PIANO_RET_OUT_OF_MEMORY;
466 					}
467 
468 					tmpGenreCategory->name = PianoJsonStrdup (c,
469 							"categoryName");
470 
471 					/* get genre subnodes */
472 					json_object *stations;
473 					if (json_object_object_get_ex (c, "stations", &stations)) {
474 						for (int k = 0;
475 								k < json_object_array_length (stations); k++) {
476 							json_object *s =
477 									json_object_array_get_idx (stations, k);
478 							PianoGenre_t *tmpGenre;
479 
480 							if ((tmpGenre = calloc (1,
481 									sizeof (*tmpGenre))) == NULL) {
482 								return PIANO_RET_OUT_OF_MEMORY;
483 							}
484 
485 							/* get genre attributes */
486 							tmpGenre->name = PianoJsonStrdup (s,
487 									"stationName");
488 							tmpGenre->musicId = PianoJsonStrdup (s,
489 									"stationToken");
490 
491 							tmpGenreCategory->genres =
492 									PianoListAppendP (tmpGenreCategory->genres,
493 									tmpGenre);
494 						}
495 					}
496 
497 					ph->genreStations = PianoListAppendP (ph->genreStations,
498 							tmpGenreCategory);
499 				}
500 			}
501 			break;
502 		}
503 
504 		case PIANO_REQUEST_TRANSFORM_STATION: {
505 			/* transform shared station into private and update isCreator flag */
506 			PianoStation_t *station = req->data;
507 
508 			assert (req->responseData != NULL);
509 			assert (station != NULL);
510 
511 			station->isCreator = 1;
512 			break;
513 		}
514 
515 		case PIANO_REQUEST_EXPLAIN: {
516 			/* explain why song was selected */
517 			PianoRequestDataExplain_t *reqData = req->data;
518 			const size_t strSize = 768;
519 
520 			assert (reqData != NULL);
521 
522 			json_object *explanations;
523 			if (json_object_object_get_ex (result, "explanations", &explanations)) {
524 				reqData->retExplain = malloc (strSize *
525 						sizeof (*reqData->retExplain));
526 				strncpy (reqData->retExplain, "We're playing this track "
527 						"because it features ", strSize);
528 				for (int i = 0; i < json_object_array_length (explanations); i++) {
529 					json_object *e = json_object_array_get_idx (explanations,
530 							i);
531 					json_object *f;
532 					if (!json_object_object_get_ex (e, "focusTraitName", &f)) {
533 						continue;
534 					}
535 					const char *s = json_object_get_string (f);
536 					PianoStrpcat (reqData->retExplain, s, strSize);
537 					if (i < json_object_array_length (explanations)-2) {
538 						PianoStrpcat (reqData->retExplain, ", ", strSize);
539 					} else if (i == json_object_array_length (explanations)-2) {
540 						PianoStrpcat (reqData->retExplain, " and ", strSize);
541 					} else {
542 						PianoStrpcat (reqData->retExplain, ".", strSize);
543 					}
544 				}
545 			}
546 			break;
547 		}
548 
549 		case PIANO_REQUEST_GET_SETTINGS: {
550 			PianoSettings_t * const settings = req->data;
551 
552 			assert (settings != NULL);
553 
554 			settings->explicitContentFilter = getBoolDefault (result,
555 					"isExplicitContentFilterEnabled", false);
556 			settings->username = PianoJsonStrdup (result, "username");
557 			break;
558 		}
559 
560 		case PIANO_REQUEST_GET_STATION_INFO: {
561 			/* get station information (seeds and feedback) */
562 			PianoRequestDataGetStationInfo_t *reqData = req->data;
563 			PianoStationInfo_t *info;
564 
565 			assert (reqData != NULL);
566 
567 			info = &reqData->info;
568 			assert (info != NULL);
569 
570 			/* parse music seeds */
571 			json_object *music;
572 			if (json_object_object_get_ex (result, "music", &music)) {
573 				/* songs */
574 				json_object *songs;
575 				if (json_object_object_get_ex (music, "songs", &songs)) {
576 					for (int i = 0; i < json_object_array_length (songs); i++) {
577 						json_object *s = json_object_array_get_idx (songs, i);
578 						PianoSong_t *seedSong;
579 
580 						seedSong = calloc (1, sizeof (*seedSong));
581 						if (seedSong == NULL) {
582 							return PIANO_RET_OUT_OF_MEMORY;
583 						}
584 
585 						seedSong->title = PianoJsonStrdup (s, "songName");
586 						seedSong->artist = PianoJsonStrdup (s, "artistName");
587 						seedSong->seedId = PianoJsonStrdup (s, "seedId");
588 
589 						info->songSeeds = PianoListAppendP (info->songSeeds,
590 								seedSong);
591 					}
592 				}
593 
594 				/* artists */
595 				json_object *artists;
596 				if (json_object_object_get_ex (music, "artists", &artists)) {
597 					for (int i = 0; i < json_object_array_length (artists); i++) {
598 						json_object *a = json_object_array_get_idx (artists, i);
599 						PianoArtist_t *seedArtist;
600 
601 						seedArtist = calloc (1, sizeof (*seedArtist));
602 						if (seedArtist == NULL) {
603 							return PIANO_RET_OUT_OF_MEMORY;
604 						}
605 
606 						seedArtist->name = PianoJsonStrdup (a, "artistName");
607 						seedArtist->seedId = PianoJsonStrdup (a, "seedId");
608 
609 						info->artistSeeds =
610 								PianoListAppendP (info->artistSeeds, seedArtist);
611 					}
612 				}
613 			}
614 
615 			/* parse feedback */
616 			json_object *feedback;
617 			if (json_object_object_get_ex (result, "feedback", &feedback)) {
618 				static const char * const keys[] = {"thumbsUp", "thumbsDown"};
619 				for (size_t i = 0; i < sizeof (keys)/sizeof (*keys); i++) {
620 					json_object *val;
621 					if (!json_object_object_get_ex (feedback, keys[i], &val)) {
622 						continue;
623 					}
624 					assert (json_object_is_type (val, json_type_array));
625 					for (int i = 0; i < json_object_array_length (val); i++) {
626 						json_object *s = json_object_array_get_idx (val, i);
627 						PianoSong_t *feedbackSong;
628 
629 						feedbackSong = calloc (1, sizeof (*feedbackSong));
630 						if (feedbackSong == NULL) {
631 							return PIANO_RET_OUT_OF_MEMORY;
632 						}
633 
634 						feedbackSong->title = PianoJsonStrdup (s, "songName");
635 						feedbackSong->artist = PianoJsonStrdup (s,
636 								"artistName");
637 						feedbackSong->feedbackId = PianoJsonStrdup (s,
638 								"feedbackId");
639 						feedbackSong->rating = getBoolDefault (s, "isPositive",
640 								false) ?  PIANO_RATE_LOVE : PIANO_RATE_BAN;
641 
642 						json_object *v;
643 						feedbackSong->length =
644 								json_object_object_get_ex (s, "trackLength", &v) ?
645 								json_object_get_int (v) : 0;
646 
647 						info->feedback = PianoListAppendP (info->feedback,
648 								feedbackSong);
649 					}
650 				}
651 			}
652 			break;
653 		}
654 
655 		case PIANO_REQUEST_GET_STATION_MODES: {
656 			PianoRequestDataGetStationModes_t *reqData = req->data;
657 			assert (reqData != NULL);
658 
659 			int active = -1;
660 
661 			json_object *activeMode;
662 			if (json_object_object_get_ex (result, "currentModeId", &activeMode)) {
663 				active = json_object_get_int (activeMode);
664 			}
665 
666 			json_object *availableModes;
667 			if (json_object_object_get_ex (result, "availableModes", &availableModes)) {
668 				for (int i = 0; i < json_object_array_length (availableModes); i++) {
669 					json_object *val = json_object_array_get_idx (availableModes, i);
670 
671 					assert (json_object_is_type (val, json_type_object));
672 
673 					PianoStationMode_t *mode;
674 					if ((mode = calloc (1, sizeof (*mode))) == NULL) {
675 						return PIANO_RET_OUT_OF_MEMORY;
676 					}
677 
678 					json_object *modeId;
679 					if (json_object_object_get_ex (val, "modeId", &modeId)) {
680 						mode->id = json_object_get_int (modeId);
681 						mode->name = PianoJsonStrdup (val, "modeName");
682 						mode->description = PianoJsonStrdup (val, "modeDescription");
683 						mode->isAlgorithmic = getBoolDefault (val, "isAlgorithmicMode",
684 								false);
685 						mode->isTakeover = getBoolDefault (val, "isTakeoverMode",
686 								false);
687 						mode->active = active == mode->id;
688 					}
689 
690 					reqData->retModes = PianoListAppendP (reqData->retModes,
691 							mode);
692 				}
693 			}
694 			break;
695 		}
696 
697 		case PIANO_REQUEST_SET_STATION_MODE: {
698 			PianoRequestDataSetStationMode_t *reqData = req->data;
699 			assert (reqData != NULL);
700 
701 			int active = -1;
702 
703 			json_object *activeMode;
704 			if (json_object_object_get_ex (result, "currentModeId", &activeMode)) {
705 				active = json_object_get_int (activeMode);
706 			}
707 
708 			if (active != reqData->id) {
709 				/* this did not work */
710 				return PIANO_RET_ERR;
711 			}
712 			break;
713 		}
714 	}
715 
716 cleanup:
717 	json_object_put (j);
718 
719 	return ret;
720 }
721 
722