Remove `const` qualifier from the argument of `free()` and `realloc()`,
[helenos.git] / uspace / lib / hound / src / protocol.c
blob7588c22664365236ac06522adc0f650ef3a9e4ba
1 /*
2 * Copyright (c) 2012 Jan Vesely
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 /** @addtogroup libhound
30 * @addtogroup audio
31 * @{
33 /** @file
34 * Common USB functions.
36 #include <adt/list.h>
37 #include <errno.h>
38 #include <loc.h>
39 #include <macros.h>
40 #include <str.h>
41 #include <stdlib.h>
42 #include <stdio.h>
43 #include <types/common.h>
45 #include "protocol.h"
46 #include "client.h"
47 #include "server.h"
49 enum ipc_methods {
50 /** Create new context representation on the server side */
51 IPC_M_HOUND_CONTEXT_REGISTER = IPC_FIRST_USER_METHOD,
52 /** Release existing context representation on the server side */
53 IPC_M_HOUND_CONTEXT_UNREGISTER,
54 /** Request list of objects */
55 IPC_M_HOUND_GET_LIST,
56 /** Create new connection */
57 IPC_M_HOUND_CONNECT,
58 /** Destroy connection */
59 IPC_M_HOUND_DISCONNECT,
60 /** Switch IPC pipe to stream mode */
61 IPC_M_HOUND_STREAM_ENTER,
62 /** Switch IPC pipe back to general mode */
63 IPC_M_HOUND_STREAM_EXIT,
64 /** Wait until there is no data in the stream */
65 IPC_M_HOUND_STREAM_DRAIN,
69 /** PCM format conversion helper structure */
70 typedef union {
71 struct {
72 uint16_t rate;
73 uint8_t channels;
74 uint8_t format;
75 } f __attribute__((packed));
76 sysarg_t arg;
77 } format_convert_t;
80 /****
81 * CLIENT
82 ****/
84 /** Well defined service name */
85 const char *HOUND_SERVICE = "audio/hound";
87 /**
88 * Start a new audio session.
89 * @param service Named service typically 'HOUND_SERVICE' constant.
90 * @return Valid session on success, NULL on failure.
92 hound_sess_t *hound_service_connect(const char *service)
94 service_id_t id = 0;
95 const errno_t ret =
96 loc_service_get_id(service, &id, IPC_FLAG_BLOCKING);
97 if (ret != EOK)
98 return NULL;
99 return loc_service_connect(id, INTERFACE_HOUND, IPC_FLAG_BLOCKING);
103 * End an existing audio session.
104 * @param sess The session.
106 void hound_service_disconnect(hound_sess_t *sess)
108 if (sess)
109 async_hangup(sess);
113 * Register a named application context to the audio server.
114 * @param sess Valid audio session.
115 * @param name Valid string identifier
116 * @param record True if the application context wishes to receive data.
118 * @param[out] id Return context ID.
120 * @return EOK on success, Error code on failure.
122 errno_t hound_service_register_context(hound_sess_t *sess,
123 const char *name, bool record, hound_context_id_t *id)
125 assert(sess);
126 assert(name);
127 ipc_call_t call;
128 async_exch_t *exch = async_exchange_begin(sess);
129 aid_t mid =
130 async_send_1(exch, IPC_M_HOUND_CONTEXT_REGISTER, record, &call);
131 errno_t ret = mid ? EOK : EPARTY;
133 if (ret == EOK)
134 ret = async_data_write_start(exch, name, str_size(name));
135 else
136 async_forget(mid);
138 if (ret == EOK)
139 async_wait_for(mid, &ret);
141 async_exchange_end(exch);
142 if (ret == EOK) {
143 *id = (hound_context_id_t)IPC_GET_ARG1(call);
146 return ret;
150 * Remove application context from the server's list.
151 * @param sess Valid audio session.
152 * @param id Valid context id.
153 * @return Error code.
155 errno_t hound_service_unregister_context(hound_sess_t *sess, hound_context_id_t id)
157 assert(sess);
158 async_exch_t *exch = async_exchange_begin(sess);
159 const errno_t ret =
160 async_req_1_0(exch, IPC_M_HOUND_CONTEXT_UNREGISTER, id);
161 async_exchange_end(exch);
162 return ret;
166 * Retrieve a list of server side actors.
167 * @param[in] sess Valid audio session.
168 * @param[out] ids list of string identifiers.
169 * @param[out] count Number of elements int the @p ids list.
170 * @param[in] flags list requirements.
171 * @param[in] connection name of target actor. Used only if the list should
172 * contain connected actors.
173 * @retval Error code.
175 errno_t hound_service_get_list(hound_sess_t *sess, char ***ids, size_t *count,
176 int flags, const char *connection)
178 assert(sess);
179 assert(ids);
180 assert(count);
182 if (connection && !(flags & HOUND_CONNECTED))
183 return EINVAL;
185 async_exch_t *exch = async_exchange_begin(sess);
186 if (!exch)
187 return ENOMEM;
189 ipc_call_t res_call;
190 aid_t mid = async_send_3(exch, IPC_M_HOUND_GET_LIST, flags, *count,
191 connection != NULL, &res_call);
193 errno_t ret = EOK;
194 if (mid && connection)
195 ret = async_data_write_start(exch, connection,
196 str_size(connection));
198 if (ret == EOK)
199 async_wait_for(mid, &ret);
201 if (ret != EOK) {
202 async_exchange_end(exch);
203 return ret;
205 unsigned name_count = IPC_GET_ARG1(res_call);
207 /* Start receiving names */
208 char **names = NULL;
209 if (name_count) {
210 size_t *sizes = calloc(name_count, sizeof(size_t));
211 names = calloc(name_count, sizeof(char *));
212 if (!names || !sizes)
213 ret = ENOMEM;
215 if (ret == EOK)
216 ret = async_data_read_start(exch, sizes,
217 name_count * sizeof(size_t));
218 for (unsigned i = 0; i < name_count && ret == EOK; ++i) {
219 char *name = malloc(sizes[i] + 1);
220 if (name) {
221 memset(name, 0, sizes[i] + 1);
222 ret = async_data_read_start(exch, name, sizes[i]);
223 names[i] = name;
224 } else {
225 ret = ENOMEM;
228 free(sizes);
230 async_exchange_end(exch);
231 if (ret != EOK) {
232 for (unsigned i = 0; i < name_count; ++i)
233 free(names[i]);
234 free(names);
235 } else {
236 *ids = names;
237 *count = name_count;
239 return ret;
243 * Create a new connection between a source and a sink.
244 * @param sess Valid audio session.
245 * @param source Source name, valid string.
246 * @param sink Sink name, valid string.
247 * @return Error code.
249 errno_t hound_service_connect_source_sink(hound_sess_t *sess, const char *source,
250 const char *sink)
252 assert(sess);
253 assert(source);
254 assert(sink);
256 async_exch_t *exch = async_exchange_begin(sess);
257 if (!exch)
258 return ENOMEM;
259 ipc_call_t call;
260 aid_t id = async_send_0(exch, IPC_M_HOUND_CONNECT, &call);
261 errno_t ret = id ? EOK : EPARTY;
262 if (ret == EOK)
263 ret = async_data_write_start(exch, source, str_size(source));
264 if (ret == EOK)
265 ret = async_data_write_start(exch, sink, str_size(sink));
266 async_wait_for(id, &ret);
267 async_exchange_end(exch);
268 return ret;
272 * Destroy an existing connection between a source and a sink.
273 * @param sess Valid audio session.
274 * @param source Source name, valid string.
275 * @param sink Sink name, valid string.
276 * @return Error code.
278 errno_t hound_service_disconnect_source_sink(hound_sess_t *sess, const char *source,
279 const char *sink)
281 assert(sess);
282 async_exch_t *exch = async_exchange_begin(sess);
283 if (!exch)
284 return ENOMEM;
285 ipc_call_t call;
286 aid_t id = async_send_0(exch, IPC_M_HOUND_DISCONNECT, &call);
287 errno_t ret = id ? EOK : EPARTY;
288 if (ret == EOK)
289 ret = async_data_write_start(exch, source, str_size(source));
290 if (ret == EOK)
291 ret = async_data_write_start(exch, sink, str_size(sink));
292 async_wait_for(id, &ret);
293 async_exchange_end(exch);
294 return ENOTSUP;
298 * Switch IPC exchange to a STREAM mode.
299 * @param exch IPC exchange.
300 * @param id context id this stream should be associated with
301 * @param flags set stream properties
302 * @param format format of the new stream.
303 * @param bsize size of the server side buffer.
304 * @return Error code.
306 errno_t hound_service_stream_enter(async_exch_t *exch, hound_context_id_t id,
307 int flags, pcm_format_t format, size_t bsize)
309 const format_convert_t c = { .f = {
310 .channels = format.channels,
311 .rate = format.sampling_rate / 100,
312 .format = format.sample_format,
314 return async_req_4_0(exch, IPC_M_HOUND_STREAM_ENTER, id, flags,
315 c.arg, bsize);
319 * Destroy existing stream and return IPC exchange to general mode.
320 * @param exch IPC exchange.
321 * @return Error code.
323 errno_t hound_service_stream_exit(async_exch_t *exch)
325 return async_req_0_0(exch, IPC_M_HOUND_STREAM_EXIT);
329 * Wait until the server side buffer is empty.
330 * @param exch IPC exchange.
331 * @return Error code.
333 errno_t hound_service_stream_drain(async_exch_t *exch)
335 return async_req_0_0(exch, IPC_M_HOUND_STREAM_DRAIN);
339 * Write audio data to a stream.
340 * @param exch IPC exchange in STREAM MODE.
341 * @param data Audio data buffer.
342 * @size size of the buffer
343 * @return Error code.
345 errno_t hound_service_stream_write(async_exch_t *exch, const void *data, size_t size)
347 return async_data_write_start(exch, data, size);
351 * Read data from a stream.
352 * @param exch IPC exchange in STREAM MODE.
353 * @param data Audio data buffer.
354 * @size size of the buffer
355 * @return Error code.
357 errno_t hound_service_stream_read(async_exch_t *exch, void *data, size_t size)
359 return async_data_read_start(exch, data, size);
362 /****
363 * SERVER
364 ****/
366 static void hound_server_read_data(void *stream);
367 static void hound_server_write_data(void *stream);
368 static const hound_server_iface_t *server_iface;
371 * Set hound server interface implementation.
372 * @param iface Initialized Hound server interface.
374 void hound_service_set_server_iface(const hound_server_iface_t *iface)
376 server_iface = iface;
380 * Server side implementation of the hound protocol. IPC connection handler.
381 * @param iid initial call id
382 * @param icall pointer to initial call structure.
383 * @param arg (unused)
385 void hound_connection_handler(ipc_callid_t iid, ipc_call_t *icall, void *arg)
387 /* Accept connection if there is a valid iface*/
388 if (server_iface) {
389 async_answer_0(iid, EOK);
390 } else {
391 async_answer_0(iid, ENOTSUP);
392 return;
395 while (1) {
396 ipc_call_t call;
397 ipc_callid_t callid = async_get_call(&call);
398 switch (IPC_GET_IMETHOD(call)) {
399 case IPC_M_HOUND_CONTEXT_REGISTER: {
400 /* check interface functions */
401 if (!server_iface || !server_iface->add_context) {
402 async_answer_0(callid, ENOTSUP);
403 break;
405 bool record = IPC_GET_ARG1(call);
406 void *name;
408 /* Get context name */
409 errno_t ret =
410 async_data_write_accept(&name, true, 0, 0, 0, 0);
411 if (ret != EOK) {
412 async_answer_0(callid, ret);
413 break;
415 hound_context_id_t id = 0;
416 ret = server_iface->add_context(server_iface->server,
417 &id, name, record);
418 /** new context should create a copy */
419 free(name);
420 if (ret != EOK) {
421 async_answer_0(callid, ret);
422 } else {
423 async_answer_1(callid, EOK, id);
425 break;
427 case IPC_M_HOUND_CONTEXT_UNREGISTER: {
428 /* check interface functions */
429 if (!server_iface || !server_iface->rem_context) {
430 async_answer_0(callid, ENOTSUP);
431 break;
434 /* get id, 1st param */
435 hound_context_id_t id = IPC_GET_ARG1(call);
436 const errno_t ret =
437 server_iface->rem_context(server_iface->server, id);
438 async_answer_0(callid, ret);
439 break;
441 case IPC_M_HOUND_GET_LIST: {
442 /* check interface functions */
443 if (!server_iface || !server_iface->get_list) {
444 async_answer_0(callid, ENOTSUP);
445 break;
448 char **list = NULL;
449 const int flags = IPC_GET_ARG1(call);
450 size_t count = IPC_GET_ARG2(call);
451 const bool conn = IPC_GET_ARG3(call);
452 char *conn_name = NULL;
453 errno_t ret = EOK;
455 /* get connected actor name if provided */
456 if (conn)
457 ret = async_data_write_accept(
458 (void**)&conn_name, true, 0, 0, 0, 0);
460 if (ret == EOK)
461 ret = server_iface->get_list(
462 server_iface->server, &list, &count,
463 conn_name, flags);
464 free(conn_name);
466 /* Alloc string sizes array */
467 size_t *sizes = NULL;
468 if (count)
469 sizes = calloc(count, sizeof(size_t));
470 if (count && !sizes)
471 ret = ENOMEM;
472 async_answer_1(callid, ret, count);
474 /* We are done */
475 if (count == 0 || ret != EOK)
476 break;
478 /* Prepare sizes table */
479 for (unsigned i = 0; i < count; ++i)
480 sizes[i] = str_size(list[i]);
482 /* Send sizes table */
483 ipc_callid_t id;
484 if (async_data_read_receive(&id, NULL)) {
485 ret = async_data_read_finalize(id, sizes,
486 count * sizeof(size_t));
488 free(sizes);
490 /* Proceed to send names */
491 for (unsigned i = 0; i < count; ++i) {
492 size_t size = str_size(list[i]);
493 ipc_callid_t id;
494 if (ret == EOK &&
495 async_data_read_receive(&id, NULL)) {
496 ret = async_data_read_finalize(id,
497 list[i], size);
499 free(list[i]);
501 free(list);
502 break;
504 case IPC_M_HOUND_CONNECT: {
505 /* check interface functions */
506 if (!server_iface || !server_iface->connect) {
507 async_answer_0(callid, ENOTSUP);
508 break;
511 void *source = NULL;
512 void *sink = NULL;
514 /* read source name */
515 errno_t ret =
516 async_data_write_accept(&source, true, 0, 0, 0, 0);
517 /* read sink name */
518 if (ret == EOK)
519 ret = async_data_write_accept(&sink,
520 true, 0, 0, 0, 0);
522 if (ret == EOK)
523 ret = server_iface->connect(
524 server_iface->server, source, sink);
525 free(source);
526 free(sink);
527 async_answer_0(callid, ret);
528 break;
530 case IPC_M_HOUND_DISCONNECT: {
531 /* check interface functions */
532 if (!server_iface || !server_iface->disconnect) {
533 async_answer_0(callid, ENOTSUP);
534 break;
537 void *source = NULL;
538 void *sink = NULL;
540 /* read source name */
541 errno_t ret =
542 async_data_write_accept(&source, true, 0, 0, 0, 0);
543 /*read sink name */
544 if (ret == EOK)
545 ret = async_data_write_accept(&sink,
546 true, 0, 0, 0, 0);
547 if (ret == EOK)
548 ret = server_iface->connect(
549 server_iface->server, source, sink);
550 free(source);
551 free(sink);
552 async_answer_0(callid, ret);
553 break;
555 case IPC_M_HOUND_STREAM_ENTER: {
556 /* check interface functions */
557 if (!server_iface || !server_iface->is_record_context
558 || !server_iface->add_stream
559 || !server_iface->rem_stream) {
560 async_answer_0(callid, ENOTSUP);
561 break;
564 /* get parameters */
565 hound_context_id_t id = IPC_GET_ARG1(call);
566 const int flags = IPC_GET_ARG2(call);
567 const format_convert_t c = {.arg = IPC_GET_ARG3(call)};
568 const pcm_format_t f = {
569 .sampling_rate = c.f.rate * 100,
570 .channels = c.f.channels,
571 .sample_format = c.f.format,
573 size_t bsize = IPC_GET_ARG4(call);
575 void *stream;
576 errno_t ret = server_iface->add_stream(server_iface->server,
577 id, flags, f, bsize, &stream);
578 if (ret != EOK) {
579 async_answer_0(callid, ret);
580 break;
582 const bool rec = server_iface->is_record_context(
583 server_iface->server, id);
584 if (rec) {
585 if(server_iface->stream_data_read) {
586 async_answer_0(callid, EOK);
587 /* start answering read calls */
588 hound_server_write_data(stream);
589 server_iface->rem_stream(
590 server_iface->server, stream);
591 } else {
592 async_answer_0(callid, ENOTSUP);
594 } else {
595 if (server_iface->stream_data_write) {
596 async_answer_0(callid, EOK);
597 /* accept write calls */
598 hound_server_read_data(stream);
599 server_iface->rem_stream(
600 server_iface->server, stream);
601 } else {
602 async_answer_0(callid, ENOTSUP);
605 break;
607 case IPC_M_HOUND_STREAM_EXIT:
608 case IPC_M_HOUND_STREAM_DRAIN:
609 /* Stream exit/drain is only allowed in stream context*/
610 async_answer_0(callid, EINVAL);
611 break;
612 default:
613 async_answer_0(callid, ENOTSUP);
614 return;
620 * Read data and push it to the stream.
621 * @param stream target stream, will push data there.
623 static void hound_server_read_data(void *stream)
625 ipc_callid_t callid;
626 ipc_call_t call;
627 size_t size = 0;
628 errno_t ret_answer = EOK;
629 /* accept data write or drain */
630 while (async_data_write_receive_call(&callid, &call, &size)
631 || (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN)) {
632 /* check drain first */
633 if (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN) {
634 errno_t ret = ENOTSUP;
635 if (server_iface->drain_stream)
636 ret = server_iface->drain_stream(stream);
637 async_answer_0(callid, ret);
638 continue;
641 /* there was an error last time */
642 if (ret_answer != EOK) {
643 async_answer_0(callid, ret_answer);
644 continue;
647 char *buffer = malloc(size);
648 if (!buffer) {
649 async_answer_0(callid, ENOMEM);
650 continue;
652 const errno_t ret = async_data_write_finalize(callid, buffer, size);
653 if (ret == EOK) {
654 /* push data to stream */
655 ret_answer = server_iface->stream_data_write(
656 stream, buffer, size);
659 const errno_t ret = IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_EXIT
660 ? EOK : EINVAL;
662 async_answer_0(callid, ret);
666 * Accept reads and pull data from the stream.
667 * @param stream target stream, will pull data from there.
669 static void hound_server_write_data(void *stream)
672 ipc_callid_t callid;
673 ipc_call_t call;
674 size_t size = 0;
675 errno_t ret_answer = EOK;
676 /* accept data read and drain */
677 while (async_data_read_receive_call(&callid, &call, &size)
678 || (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN)) {
679 /* drain does not make much sense but it is allowed */
680 if (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN) {
681 errno_t ret = ENOTSUP;
682 if (server_iface->drain_stream)
683 ret = server_iface->drain_stream(stream);
684 async_answer_0(callid, ret);
685 continue;
687 /* there was an error last time */
688 if (ret_answer != EOK) {
689 async_answer_0(callid, ret_answer);
690 continue;
692 char *buffer = malloc(size);
693 if (!buffer) {
694 async_answer_0(callid, ENOMEM);
695 continue;
697 errno_t ret = server_iface->stream_data_read(stream, buffer, size);
698 if (ret == EOK) {
699 ret_answer =
700 async_data_read_finalize(callid, buffer, size);
703 const errno_t ret = IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_EXIT
704 ? EOK : EINVAL;
706 async_answer_0(callid, ret);
710 /***
711 * SERVER SIDE
712 ***/
715 * Register new hound service to the location service.
716 * @param[in] name server name
717 * @param[out] id assigned service id.
718 * @return Error code.
720 errno_t hound_server_register(const char *name, service_id_t *id)
722 if (!name || !id)
723 return EINVAL;
725 errno_t ret = loc_server_register(name);
726 if (ret != EOK)
727 return ret;
729 return loc_service_register(HOUND_SERVICE, id);
733 * Unregister server from the location service.
734 * @param id previously assigned service id.
736 void hound_server_unregister(service_id_t id)
738 loc_service_unregister(id);
742 * Set callback on device category change event.
743 * @param cb Callback function.
744 * @return Error code.
746 errno_t hound_server_set_device_change_callback(dev_change_callback_t cb)
748 return loc_register_cat_change_cb(cb);
752 * Walk through all device in the audio-pcm category.
753 * @param callback Function to call on every device.
754 * @return Error code.
756 errno_t hound_server_devices_iterate(device_callback_t callback)
758 if (!callback)
759 return EINVAL;
760 static bool resolved = false;
761 static category_id_t cat_id = 0;
763 if (!resolved) {
764 const errno_t ret = loc_category_get_id("audio-pcm", &cat_id,
765 IPC_FLAG_BLOCKING);
766 if (ret != EOK)
767 return ret;
768 resolved = true;
771 service_id_t *svcs = NULL;
772 size_t count = 0;
773 const errno_t ret = loc_category_get_svcs(cat_id, &svcs, &count);
774 if (ret != EOK)
775 return ret;
777 for (unsigned i = 0; i < count; ++i) {
778 char *name = NULL;
779 loc_service_get_name(svcs[i], &name);
780 callback(svcs[i], name);
781 free(name);
783 free(svcs);
784 return EOK;
787 * @}