Add module-tizenaudio-discover / module-tizenaudio-publish for remote audio feature 25/229025/17
authorSeungbae Shin <seungbae.shin@samsung.com>
Mon, 23 Mar 2020 11:22:54 +0000 (20:22 +0900)
committerSeungbae Shin <seungbae.shin@samsung.com>
Thu, 13 Aug 2020 06:20:28 +0000 (15:20 +0900)
module-tizenaudio-discover : based on module-zeroconf-discover with mDNSResponder porting
module-tizenaudio-publish  : based on module-bonjour-publish with minor changes

[Version] 13.0.21
[Issue Type] New feature

Change-Id: Id47dad038bfae487bbe57a09aaece674b30f9008

Makefile.am
configure.ac
packaging/pulseaudio-modules-tizen.spec
src/module-tizenaudio-discover.c [new file with mode: 0644]
src/module-tizenaudio-publish.c [new file with mode: 0644]

index 9b59496..07985dc 100644 (file)
@@ -36,6 +36,8 @@ pulsemodlibexec_LTLIBRARIES = \
                module-tizenaudio-sink.la \
                module-tizenaudio-source.la \
                module-tizenaudio-policy.la \
+               module-tizenaudio-discover.la \
+               module-tizenaudio-publish.la \
                module-sound-player.la \
                module-tone-player.la \
                module-poweroff.la
@@ -115,6 +117,16 @@ module_acm_sink_la_LIBADD = $(MODULE_LIBADD)
 module_acm_sink_la_CFLAGS = $(MODULE_CFLAGS)
 endif
 
+module_tizenaudio_discover_la_SOURCES = src/module-tizenaudio-discover.c
+module_tizenaudio_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_tizenaudio_discover_la_LIBADD = $(MODULE_LIBADD) $(DNSSD_LIBS)
+module_tizenaudio_discover_la_CFLAGS = $(MODULE_CFLAGS) $(DNSSD_CFLAGS)
+
+module_tizenaudio_publish_la_SOURCES = src/module-tizenaudio-publish.c
+module_tizenaudio_publish_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_tizenaudio_publish_la_LIBADD = $(MODULE_LIBADD) $(DNSSD_LIBS)
+module_tizenaudio_publish_la_CFLAGS = $(AM_CFLAGS) $(DNSSD_CFLAGS)
+
 if ENABLE_VCONF_HELPER
 pulsemodlibexec_LTLIBRARIES += module-vconf.la
 
index f38d675..8557992 100644 (file)
@@ -361,6 +361,10 @@ PKG_CHECK_MODULES(VCONF, vconf)
 AC_SUBST(VCONF_CFLAGS)
 AC_SUBST(VCONF_LIBS)
 
+PKG_CHECK_MODULES(DNSSD, dns_sd)
+AC_SUBST(DNSSD_CFLAGS)
+AC_SUBST(DNSSD_LIBS)
+
 dnl use hal tc ------------------------------------------------------------
 AC_ARG_ENABLE(haltc, AC_HELP_STRING([--enable-haltc], [using haltc]),
 [
index 3944878..c3749d4 100644 (file)
@@ -1,6 +1,6 @@
 Name:             pulseaudio-modules-tizen
 Summary:          Pulseaudio modules for Tizen
-Version:          13.0.20
+Version:          13.0.21
 Release:          0
 Group:            Multimedia/Audio
 License:          LGPL-2.1+
@@ -20,6 +20,7 @@ BuildRequires:    mm-hal-interface-devel
 BuildRequires:    pkgconfig(libpulse)
 BuildRequires:    pkgconfig(pulsecore)
 BuildRequires:    pkgconfig(libsystemd)
+BuildRequires:    pkgconfig(dns_sd)
 BuildRequires:    pulseaudio
 BuildRequires:    m4
 Requires(post):   /sbin/ldconfig
@@ -74,6 +75,8 @@ install -m 0644 %SOURCE1 %{buildroot}%{_tmpfilesdir}/pulseaudio.conf
 %{_libdir}/pulse-13.0/modules/module-tizenaudio-policy.so
 %{_libdir}/pulse-13.0/modules/module-tizenaudio-sink.so
 %{_libdir}/pulse-13.0/modules/module-tizenaudio-source.so
+%{_libdir}/pulse-13.0/modules/module-tizenaudio-discover.so
+%{_libdir}/pulse-13.0/modules/module-tizenaudio-publish.so
 %{_libdir}/pulse-13.0/modules/libhal-interface.so
 %{_libdir}/pulse-13.0/modules/libcommunicator.so
 %{_tmpfilesdir}/pulseaudio.conf
diff --git a/src/module-tizenaudio-discover.c b/src/module-tizenaudio-discover.c
new file mode 100644 (file)
index 0000000..ac88209
--- /dev/null
@@ -0,0 +1,733 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2020 Seungbae Shin
+  based on module-zeroconf-discover.c
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <dns_sd.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/namereg.h>
+
+PA_MODULE_AUTHOR("Seungbae Shin");
+PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(true);
+
+#define SERVICE_TYPE_SINK "_pulse-sink._tcp"
+#define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
+#define MAX_IP_ADDR 32
+#define MAX_RECORD_LEN 256
+
+static const char * const valid_modargs[] = {
+    NULL
+};
+
+typedef struct dns_request_data {
+    DNSServiceRef sd_ref;
+    pa_io_event *io;
+} dns_request_data_t;
+
+typedef struct service_data {
+    struct userdata *u;
+
+    int protocol;
+    dns_request_data_t resolve;
+    dns_request_data_t addr;
+
+    /* browse */
+    int flags;
+    unsigned int interface;
+    char *service_name;
+    char *service_type;
+    char *domain;
+
+    /* resolve */
+    char *host_name;
+    unsigned short port;
+
+    char *device;
+    pa_sample_spec ss;
+    pa_channel_map cm;
+    bool channel_map_set;
+
+    /* address */
+    char *ip_address;
+} service_data_t;
+
+struct tunnel {
+    unsigned int interface;
+    int protocol;
+    char *name, *type, *domain;
+    uint32_t module_index;
+};
+
+struct userdata {
+    pa_core *core;
+    pa_module *module;
+
+    dns_request_data_t browse_sink;
+    dns_request_data_t browse_source;
+
+    pa_hashmap *tunnels;
+};
+
+static unsigned tunnel_hash(const void *p) {
+    const struct tunnel *t = p;
+
+    return
+        (unsigned) t->interface +
+        (unsigned) t->protocol +
+        pa_idxset_string_hash_func(t->name) +
+        pa_idxset_string_hash_func(t->type) +
+        pa_idxset_string_hash_func(t->domain);
+}
+
+static int tunnel_compare(const void *a, const void *b) {
+    const struct tunnel *ta = a, *tb = b;
+    int r;
+
+    if (ta->interface != tb->interface)
+        return 1;
+    if (ta->protocol != tb->protocol)
+        return 1;
+    if ((r = strcmp(ta->name, tb->name)))
+        return r;
+    if ((r = strcmp(ta->type, tb->type)))
+        return r;
+    if ((r = strcmp(ta->domain, tb->domain)))
+        return r;
+
+    return 0;
+}
+
+static struct tunnel *tunnel_new(
+        unsigned int interface, int protocol,
+        const char *name, const char *type, const char *domain) {
+    struct tunnel *t;
+    t = pa_xnew0(struct tunnel, 1);
+    t->interface = interface;
+    t->protocol = protocol;
+    t->name = pa_xstrdup(name);
+    t->type = pa_xstrdup(type);
+    t->domain = pa_xstrdup(domain);
+    t->module_index = PA_IDXSET_INVALID;
+    return t;
+}
+
+static void tunnel_free(struct tunnel *t) {
+    pa_assert(t);
+    pa_xfree(t->name);
+    pa_xfree(t->type);
+    pa_xfree(t->domain);
+    pa_xfree(t);
+}
+
+static service_data_t *service_data_new() {
+    return pa_xnew0(service_data_t, 1);
+}
+
+static void service_data_free(service_data_t *data) {
+    pa_assert(data);
+
+    pa_xfree(data->service_name);
+    pa_xfree(data->service_type);
+    pa_xfree(data->domain);
+    pa_xfree(data->host_name);
+    pa_xfree(data->ip_address);
+    pa_xfree(data->device);
+
+    pa_xfree(data);
+}
+
+
+static int request_set_io_callback(pa_mainloop_api *m_api, dns_request_data_t *request, pa_io_event_cb_t cb, void *user_data) {
+    int fd = -1;
+
+    pa_assert(request);
+    pa_assert(request->sd_ref);
+
+    fd = DNSServiceRefSockFD(request->sd_ref);
+
+    if (fd < 0) {
+        pa_log_error("DNSServiceRefSockFD(%p) error", request->sd_ref);
+        return -1;
+    }
+
+    request->io = m_api->io_new(m_api, fd,
+                                PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP,
+                                cb, user_data);
+
+    pa_log_info("io(%p) with cb(%p) added for fd(%d)", request->io, cb, fd);
+
+    return 0;
+}
+
+static void request_clear(pa_mainloop_api *m_api, dns_request_data_t *request) {
+    if (request->sd_ref) {
+        DNSServiceRefDeallocate(request->sd_ref);
+        request->sd_ref = NULL;
+    }
+
+    if (request->io) {
+        m_api->io_free(request->io);
+        request->io = NULL;
+    }
+}
+
+static char *get_ip_str(const struct sockaddr *sa) {
+    char addr_str[MAX_IP_ADDR] = { 0, };
+
+    switch(sa->sa_family) {
+    case AF_INET:
+        inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr), addr_str, MAX_IP_ADDR);
+        break;
+
+    case AF_INET6:
+        inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr), addr_str, MAX_IP_ADDR);
+        break;
+
+    default:
+        pa_strlcpy(addr_str, "Unknown AF", MAX_IP_ADDR);
+        break;
+    }
+
+    return pa_xstrdup(addr_str);
+}
+
+static void parse_txt_record(service_data_t *data,
+                            const unsigned char *txt_record,
+                            unsigned short txt_len) {
+    char record_str[MAX_RECORD_LEN] = { 0, };
+    int record_len;
+    const char *key_str = NULL;
+    const char *val_str = NULL;
+    const char *state = NULL;
+    size_t val_len;
+    const char *delimiter = "=";
+    const unsigned char *cur = txt_record;
+
+    pa_assert(data);
+    pa_assert(txt_record);
+    pa_assert(txt_len > 0);
+
+    while (cur < txt_record + txt_len) {
+        record_len = *cur;
+        state = NULL;
+
+        pa_snprintf(record_str, record_len + 1, "%s", ++cur);
+
+        key_str = pa_split_in_place(record_str, delimiter, &val_len, &state);
+        if (pa_strneq(key_str, "device", val_len)) {
+            data->device = pa_split(record_str, delimiter, &state);
+        } else if (pa_strneq(key_str, "rate", val_len)) {
+            data->ss.rate = (uint32_t)atoi(pa_split_in_place(record_str, delimiter, &val_len, &state));
+        } else if (pa_strneq(key_str, "channels", val_len)) {
+            data->ss.channels = (uint8_t)atoi(pa_split_in_place(record_str, delimiter, &val_len, &state));
+        } else if (pa_strneq(key_str, "format", val_len)) {
+            val_str = pa_split_in_place(record_str, delimiter, &val_len, &state);
+            data->ss.format = pa_parse_sample_format(val_str);
+        } else if (pa_strneq(key_str, "channel_map", val_len)) {
+            val_str = pa_split_in_place(record_str, delimiter, &val_len, &state);
+            pa_channel_map_parse(&data->cm, val_str);
+        }
+
+        cur += record_len;
+    }
+    pa_log_info("-----------------------------------");
+    pa_log_info("device      : [%s]", data->device);
+    pa_log_info("rate        : [%d]", data->ss.rate);
+    pa_log_info("channels    : [%d]", data->ss.channels);
+    pa_log_info("format      : [%s]", pa_sample_format_to_string(data->ss.format));
+    pa_log_info("channel_map : [%s]", pa_channel_map_to_pretty_name(&data->cm));
+    pa_log_info("-----------------------------------");
+}
+
+static void tunnel_add(service_data_t *data) {
+    struct tunnel *tnl;
+    char *dname, *module_name, *args;
+    const char *t;
+    char cmt[PA_CHANNEL_MAP_SNPRINT_MAX];
+    pa_module *m;
+
+    pa_assert(data);
+    pa_assert(data->u);
+
+    tnl = tunnel_new(data->interface, data->protocol, data->service_name, data->service_type, data->domain);
+
+    data->ss = data->u->core->default_sample_spec;
+    data->cm = data->u->core->default_channel_map;
+
+    if (!data->channel_map_set && data->cm.channels != data->ss.channels)
+        pa_channel_map_init_extend(&data->cm, data->ss.channels, PA_CHANNEL_MAP_DEFAULT);
+
+    if (!pa_sample_spec_valid(&data->ss)) {
+        pa_log("Service '%s' contains an invalid sample specification.", data->service_name);
+        goto finish;
+    }
+
+    if (!pa_channel_map_valid(&data->cm) || data->cm.channels != data->ss.channels) {
+        pa_log("Service '%s' contains an invalid channel map.", data->service_name);
+        goto finish;
+    }
+
+    if (data->device)
+        dname = pa_sprintf_malloc("tunnel.%s.%s", data->host_name, data->device);
+    else
+        dname = pa_sprintf_malloc("tunnel.%s", data->host_name);
+
+    if (!pa_namereg_is_valid_name(dname)) {
+        pa_log("Cannot construct valid device name from credentials of service '%s'.", dname);
+        pa_xfree(dname);
+        goto finish;
+    }
+
+    t = strstr(data->service_type, "sink") ? "sink" : "source";
+
+    module_name = pa_sprintf_malloc("module-tunnel-%s", t);
+    args = pa_sprintf_malloc("server=[%s]:%u "
+                             "%s=%s "
+                             "format=%s "
+                             "channels=%u "
+                             "rate=%u "
+                             "%s_name=%s "
+                             "channel_map=%s",
+                             data->ip_address,
+                             data->port,
+                             t, data->device,
+                             pa_sample_format_to_string(data->ss.format),
+                             data->ss.channels,
+                             data->ss.rate,
+                             t, dname,
+                             pa_channel_map_snprint(cmt, sizeof(cmt), &data->cm));
+
+    pa_log_debug("Loading %s with arguments '%s'", module_name, args);
+
+    if (pa_module_load(&m, data->u->core, module_name, args) >= 0) {
+        tnl->module_index = m->index;
+        pa_hashmap_put(data->u->tunnels, tnl, tnl);
+        pa_log_info("Module(%u) loaded, Tunnel(%p) added success", m->index, tnl);
+        tnl = NULL;
+    } else {
+        pa_log_error("Failed to load Module(%s)", module_name);
+    }
+
+    pa_xfree(module_name);
+    pa_xfree(dname);
+    pa_xfree(args);
+
+finish:
+
+    if (tnl)
+        tunnel_free(tnl);
+}
+
+
+static void dnssd_getaddrinfo_reply_cb(DNSServiceRef sd_ref,
+                                       unsigned int flags, unsigned int if_index,
+                                       DNSServiceErrorType error_code, const char *host_name,
+                                       const struct sockaddr *address, unsigned int ttl,
+                                       void *user_data) {
+    service_data_t *data = (service_data_t *)user_data;
+
+    pa_assert(data);
+    pa_assert(data->u);
+
+    if (flags & kDNSServiceFlagsMoreComing) {
+        pa_log_warn("More results are queued, No need to send callback to "
+                    "application at this stage");
+        return;
+    }
+
+    if (data->interface != if_index)
+        pa_log_warn("diff interface %d <=> %d", data->interface, if_index);
+    if (!pa_safe_streq(data->host_name, host_name))
+        pa_log_warn("diff host_name %s <=> %s", data->host_name, host_name);
+
+    data->ip_address = get_ip_str(address);
+
+    pa_log_info("-----------------------------------");
+    pa_log_info("Address Reply     : ref(%p), userdata(%p), error(%d), flags(0x%x)",
+            sd_ref, user_data, error_code, flags);
+    pa_log_info("  Interface Index : %d", if_index);
+    pa_log_info("  Host Name       : %s", data->host_name);
+    pa_log_info("  IP Address      : %s", data->ip_address);
+    pa_log_info("  TTL             : %d", ttl);
+
+    pa_log_info("-----------------------------------");
+
+    tunnel_add(data);
+
+    /*  cleanup addr */
+    request_clear(data->u->module->core->mainloop, &data->addr);
+
+    /* cleanup data */
+    service_data_free(data);
+}
+
+static void io_addr_event_callback(pa_mainloop_api *io, pa_io_event *e,
+                                   int fd, pa_io_event_flags_t events,
+                                   void *user_data) {
+    service_data_t *data = (service_data_t *)user_data;
+    DNSServiceErrorType err;
+
+    pa_assert(io);
+    pa_assert(data);
+    pa_assert(data->u);
+
+    if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
+        pa_log_warn("Lost connection fd : %d", fd);
+        goto fail;
+    }
+
+    if (events & PA_IO_EVENT_INPUT) {
+        err = DNSServiceProcessResult(data->addr.sd_ref);
+        if (err == 0)
+            pa_log_info("DNSServiceProcessResult(%p, fd:%d) success", data->addr.sd_ref, fd);
+        else
+            pa_log_info("DNSServiceProcessResult(%p, fd:%d) error(%d)", data->addr.sd_ref, fd, err);
+    }
+
+    return;
+
+fail:
+    request_clear(data->u->module->core->mainloop, &data->addr);
+}
+
+static int dnssd_getaddrinfo(service_data_t *data) {
+    int ret;
+
+    ret = DNSServiceGetAddrInfo(&(data->addr.sd_ref), data->flags, data->interface,
+                                data->protocol, data->host_name,
+                                dnssd_getaddrinfo_reply_cb, data);
+    if (ret != kDNSServiceErr_NoError) {
+        pa_log_error("DNSServiceGetAddrInfo() error(%d)", ret);
+        return -1;
+    }
+
+    pa_log_info("DNSServiceGetAddrInfo(%p) success", data->addr.sd_ref);
+
+    return request_set_io_callback(data->u->module->core->mainloop, &data->addr, io_addr_event_callback, data);
+}
+
+static void dnssd_resolve_reply_cb(DNSServiceRef sd_ref, unsigned int flags,
+                                   unsigned int if_index, DNSServiceErrorType error_code,
+                                   const char *fullname, const char *host_name,
+                                   unsigned short port, unsigned short txt_len,
+                                   const unsigned char *txt_record, void *user_data) {
+    int ret;
+    service_data_t *data = (service_data_t *)user_data;
+
+    pa_assert(data);
+    pa_assert(data->u);
+
+    if (flags & kDNSServiceFlagsMoreComing) {
+        pa_log_warn("More results are queued");
+        return;
+    }
+
+    if (data->interface != if_index)
+        pa_log_warn("diff interface %d <=> %d", data->interface, if_index);
+
+    data->flags = flags;
+    data->host_name = pa_xstrdup(host_name);
+    data->port = ntohs(port);
+
+    pa_log_info("-----------------------------------");
+    pa_log_info("Resolve Reply     : ref(%p), userdata(%p), error(%d), flags(0x%x)",
+            sd_ref, user_data, error_code, flags);
+    pa_log_info("  Interface Index : %d", if_index);
+    pa_log_info("  Full Name       : %s", fullname);
+    pa_log_info("  Host Name       : %s", data->host_name);
+    pa_log_info("  Port            : %d", data->port);
+    pa_log_info("  Text Record     : [%p][%d]", txt_record, txt_len);
+    pa_log_info("-----------------------------------");
+
+    parse_txt_record(data, txt_record, txt_len);
+
+    /*  cleanup resolve */
+    request_clear(data->u->module->core->mainloop, &data->resolve);
+
+    /* Get Address Info details and send browse callback */
+    ret = dnssd_getaddrinfo(data);
+    if (ret < 0) {
+        pa_log_error("failed to get address info!");
+        service_data_free(data);
+    }
+}
+
+
+static void io_resolve_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd,
+                                      pa_io_event_flags_t events, void *user_data) {
+    service_data_t *data = (service_data_t *)user_data;
+    DNSServiceErrorType err;
+
+    pa_assert(io);
+    pa_assert(data);
+    pa_assert(data->u);
+
+    if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
+        pa_log_warn("Lost connection fd : %d", fd);
+        goto fail;
+    }
+
+    if (events & PA_IO_EVENT_INPUT) {
+        err = DNSServiceProcessResult(data->resolve.sd_ref);
+        if (err == 0)
+            pa_log_info("DNSServiceProcessResult(%p, fd:%d) success", data->resolve.sd_ref, fd);
+        else
+            pa_log_info("DNSServiceProcessResult(%p, fd:%d) error(%d)", data->resolve.sd_ref, fd, err);
+    }
+
+    return;
+
+fail:
+    request_clear(data->u->module->core->mainloop, &data->resolve);
+}
+
+
+static int dnssd_resolve_dns_service(struct userdata *u,
+                                     unsigned int flags, unsigned int if_index, int protocol,
+                                     const char *service_name, const char *service_type,
+                                     const char *domain) {
+    int ret;
+
+    service_data_t *data = service_data_new();
+    data->u = u;
+    data->flags = flags;
+    data->interface = if_index;
+    data->protocol = protocol;
+    data->service_name = pa_xstrdup(service_name);
+    data->service_type = pa_xstrdup(service_type);
+    data->domain = pa_xstrdup(domain);
+
+    ret = DNSServiceResolve(&(data->resolve.sd_ref),
+                            flags, if_index,
+                            service_name, service_type,
+                            domain,
+                            dnssd_resolve_reply_cb,
+                            data);
+    if (ret != kDNSServiceErr_NoError) {
+        pa_log_error("DNSServiceResolve() error(%d)", ret);
+        goto fail;
+    }
+
+    pa_log_info("DNSServiceResolve(%p) success", data->resolve.sd_ref);
+
+    ret = request_set_io_callback(u->module->core->mainloop, &data->resolve, io_resolve_event_callback, data);
+    if (ret < 0)
+        goto fail;
+
+    return 0;
+
+fail:
+    service_data_free(data);
+    return -1;
+}
+
+static void dnssd_browse_reply_cb(DNSServiceRef sd_ref, unsigned int flags,
+                                  unsigned int if_index, DNSServiceErrorType error_code,
+                                  const char *service_name, const char *service_type,
+                                  const char *domain, void *user_data) {
+    struct userdata *u = user_data;
+    struct tunnel *t;
+    int protocol = kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6;
+
+    pa_log_info("-----------------------------------");
+    pa_log_info("Browse Reply      : ref(%p), userdata(%p), error(%d), flags(0x%x)",
+            sd_ref, user_data, error_code, flags);
+    pa_log_info("  Interface Index : %d", if_index);
+    pa_log_info("  Service Name    : %s", service_name);
+    pa_log_info("  Service Type    : %s", service_type);
+    pa_log_info("  Domain          : %s", domain);
+    pa_log_info("-----------------------------------");
+
+    t = tunnel_new(if_index, protocol, service_name, service_type, domain);
+
+    if (flags & kDNSServiceFlagsAdd) {
+        if (!pa_hashmap_get(u->tunnels, t))
+            dnssd_resolve_dns_service(u, flags, if_index, protocol,
+                                    service_name, service_type, domain);
+        else
+            pa_log_warn("already exists %p", t);
+    } else {
+        struct tunnel *t2;
+
+        if ((t2 = pa_hashmap_get(u->tunnels, t))) {
+            pa_module_unload_request_by_index(u->core, t2->module_index, true);
+            pa_hashmap_remove(u->tunnels, t2);
+            pa_log_info("Module(%u) Unloaded, Tunnel(%p) removed success", t2->module_index, t2);
+            tunnel_free(t2);
+        } else {
+            pa_log_warn("Not found %p", t);
+        }
+    }
+}
+
+static void io_browse_handle_event(pa_mainloop_api *io, pa_io_event *e, int fd,
+                                          pa_io_event_flags_t events, struct userdata *u, dns_request_data_t *request) {
+    DNSServiceErrorType err;
+
+    pa_assert(io);
+    pa_assert(u);
+
+    if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
+        pa_log_warn("Lost connection fd : %d", fd);
+        goto fail;
+    }
+
+    if (events & PA_IO_EVENT_INPUT) {
+        err = DNSServiceProcessResult(request->sd_ref);
+        if (err == 0)
+            pa_log_info("DNSServiceProcessResult(%p, fd:%d) success", request->sd_ref, fd);
+        else
+            pa_log_info("DNSServiceProcessResult(%p, fd:%d) error(%d)", request->sd_ref, fd, err);
+    }
+
+    return;
+
+fail:
+    request_clear(u->module->core->mainloop, request);
+    pa_module_unload_request(u->module, true);
+}
+
+static void io_browse_sink_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd,
+                              pa_io_event_flags_t events, void *user_data) {
+    struct userdata *u = user_data;
+
+    io_browse_handle_event(io, e, fd, events, u, &u->browse_sink);
+}
+
+static void io_browse_source_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd,
+                                     pa_io_event_flags_t events, void *user_data) {
+    struct userdata *u = user_data;
+
+    io_browse_handle_event(io, e, fd, events, u, &u->browse_source);
+}
+
+static int dnssd_browse_dns_service(struct userdata *u, const char *service_type) {
+    int ret;
+    dns_request_data_t *request;
+    pa_io_event_cb_t cb;
+
+    if (pa_safe_streq(service_type, SERVICE_TYPE_SINK)) {
+        request = &u->browse_sink;
+        cb = io_browse_sink_event_callback;
+    } else if (pa_safe_streq(service_type, SERVICE_TYPE_SOURCE)) {
+        request = &u->browse_source;
+        cb = io_browse_source_event_callback;
+    } else {
+        pa_log_error("invalid service type %s", pa_strnull(service_type));
+        return -1;
+    }
+
+    ret = DNSServiceBrowse(&request->sd_ref, 0,
+                           kDNSServiceInterfaceIndexAny, service_type,
+                           NULL, dnssd_browse_reply_cb,
+                           u);
+    if (ret != kDNSServiceErr_NoError) {
+        pa_log_error("DNSServiceBrowse() with %s error(%d)", service_type, ret);
+        return -1;
+    }
+
+    pa_log_info("DNSServiceBrowse(%p) success", request->sd_ref);
+
+    ret = request_set_io_callback(u->module->core->mainloop, request, cb, u);
+    if (ret < 0) {
+        request_clear(u->module->core->mainloop, request);
+        return -1;
+    }
+
+    return 0;
+}
+
+void pa__done(pa_module *m) {
+    struct userdata *u;
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    if (u->tunnels) {
+        struct tunnel *t;
+
+        while ((t = pa_hashmap_steal_first(u->tunnels))) {
+            pa_module_unload_request_by_index(u->core, t->module_index, true);
+            tunnel_free(t);
+        }
+
+        pa_hashmap_free(u->tunnels);
+    }
+
+    request_clear(u->module->core->mainloop, &u->browse_sink);
+    request_clear(u->module->core->mainloop, &u->browse_source);
+
+    pa_xfree(u);
+}
+
+int pa__init(pa_module *m) {
+
+    struct userdata *u;
+    pa_modargs *ma = NULL;
+    int ret;
+
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log("Failed to parse module arguments.");
+        goto fail;
+    }
+
+    m->userdata = u = pa_xnew(struct userdata, 1);
+    u->core = m->core;
+    u->module = m;
+    u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare);
+
+    ret = dnssd_browse_dns_service(u, SERVICE_TYPE_SINK);
+    if (ret < 0)
+        goto fail;
+
+    ret = dnssd_browse_dns_service(u, SERVICE_TYPE_SOURCE);
+    if (ret < 0)
+        goto fail;
+
+    pa_modargs_free(ma);
+
+    return 0;
+
+fail:
+    pa__done(m);
+
+    if (ma)
+        pa_modargs_free(ma);
+
+    return -1;
+}
diff --git a/src/module-tizenaudio-publish.c b/src/module-tizenaudio-publish.c
new file mode 100644 (file)
index 0000000..480b98f
--- /dev/null
@@ -0,0 +1,513 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2020 Seungbae Shin
+  based on module-bonjour-publish.c
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <dns_sd.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/parseaddr.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/native-common.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/dynarray.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/protocol-native.h>
+
+PA_MODULE_AUTHOR("Seungbae Shin");
+PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(true);
+
+#define SERVICE_TYPE_SINK "_pulse-sink._tcp"
+#define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
+#define SERVICE_TYPE_SERVER "_pulse-server._tcp"
+
+static const char* const valid_modargs[] = {
+    NULL
+};
+
+enum service_subtype {
+    SUBTYPE_HARDWARE,
+    SUBTYPE_VIRTUAL,
+    SUBTYPE_MONITOR
+};
+
+struct service {
+    struct userdata *userdata;
+    DNSServiceRef service;
+    DNSRecordRef rec, rec2;
+    char *service_name;
+    pa_object *device;
+    enum service_subtype subtype;
+};
+
+struct userdata {
+    pa_core *core;
+    pa_module *module;
+
+    pa_hashmap *services;
+    char *service_name;
+
+    pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot;
+
+    pa_native_protocol *native;
+    DNSServiceRef main_service;
+};
+
+static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, pa_proplist **ret_proplist, enum service_subtype *ret_subtype) {
+    pa_assert(s);
+    pa_assert(ret_ss);
+    pa_assert(ret_proplist);
+    pa_assert(ret_subtype);
+
+    if (pa_sink_isinstance(s->device)) {
+        pa_sink *sink = PA_SINK(s->device);
+
+        *ret_ss = sink->sample_spec;
+        *ret_map = sink->channel_map;
+        *ret_name = sink->name;
+        *ret_proplist = sink->proplist;
+        *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;
+
+    } else if (pa_source_isinstance(s->device)) {
+        pa_source *source = PA_SOURCE(s->device);
+
+        *ret_ss = source->sample_spec;
+        *ret_map = source->channel_map;
+        *ret_name = source->name;
+        *ret_proplist = source->proplist;
+        *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL);
+
+    } else {
+        pa_assert_not_reached();
+    }
+}
+
+static void txt_record_server_data(pa_core *c, TXTRecordRef *txt) {
+    char s[128];
+    char *t;
+
+    pa_assert(c);
+
+    TXTRecordSetValue(txt, "server-version", strlen(PACKAGE_NAME" "PACKAGE_VERSION), PACKAGE_NAME" "PACKAGE_VERSION);
+
+    t = pa_get_user_name_malloc();
+    TXTRecordSetValue(txt, "user-name", strlen(t), t);
+    pa_xfree(t);
+
+    t = pa_machine_id();
+    TXTRecordSetValue(txt, "machine-id", strlen(t), t);
+    pa_xfree(t);
+
+    t = pa_uname_string();
+    TXTRecordSetValue(txt, "uname", strlen(t), t);
+    pa_xfree(t);
+
+    t = pa_get_fqdn(s, sizeof(s));
+    TXTRecordSetValue(txt, "fqdn", strlen(t), t);
+
+    snprintf(s, sizeof(s), "0x%08x", c->cookie);
+    TXTRecordSetValue(txt, "cookie", strlen(s), s);
+}
+
+static void service_free(struct service *s);
+
+static void dns_service_register_reply(DNSServiceRef sdRef,
+                                       DNSServiceFlags flags,
+                                       DNSServiceErrorType errorCode,
+                                       const char *name,
+                                       const char *regtype,
+                                       const char *domain,
+                                       void *context) {
+    struct service *s = context;
+
+    pa_assert(s);
+
+    switch (errorCode) {
+    case kDNSServiceErr_NameConflict:
+        pa_log("DNS service reported kDNSServiceErr_NameConflict\n");
+        service_free(s);
+        break;
+
+    case kDNSServiceErr_NoError:
+        /* fall through */
+    default:
+        break;
+    }
+}
+
+static uint16_t compute_port(struct userdata *u) {
+    pa_strlist *i;
+
+    pa_assert(u);
+
+    for (i = pa_native_protocol_servers(u->native); i; i = pa_strlist_next(i)) {
+        pa_parsed_address a;
+
+        if (pa_parse_address(pa_strlist_data(i), &a) >= 0 &&
+            (a.type == PA_PARSED_ADDRESS_TCP4 ||
+             a.type == PA_PARSED_ADDRESS_TCP6 ||
+             a.type == PA_PARSED_ADDRESS_TCP_AUTO) &&
+            a.port > 0) {
+
+            pa_xfree(a.path_or_host);
+            return htons(a.port);
+        }
+
+        pa_xfree(a.path_or_host);
+    }
+
+    return htons(PA_NATIVE_DEFAULT_PORT);
+}
+
+static int publish_service(struct service *s) {
+    int r = -1;
+    TXTRecordRef txt;
+    DNSServiceErrorType err;
+    const char *name = NULL, *t;
+    pa_proplist *proplist = NULL;
+    pa_sample_spec ss;
+    pa_channel_map map;
+    char cm[PA_CHANNEL_MAP_SNPRINT_MAX], tmp[64];
+    enum service_subtype subtype;
+
+    const char * const subtype_text[] = {
+        [SUBTYPE_HARDWARE] = "hardware",
+        [SUBTYPE_VIRTUAL] = "virtual",
+        [SUBTYPE_MONITOR] = "monitor"
+    };
+
+    pa_assert(s);
+
+    if (s->service) {
+        DNSServiceRefDeallocate(s->service);
+        s->service = NULL;
+    }
+
+    TXTRecordCreate(&txt, 0, NULL);
+
+    txt_record_server_data(s->userdata->core, &txt);
+
+    get_service_data(s, &ss, &map, &name, &proplist, &subtype);
+    TXTRecordSetValue(&txt, "device", strlen(name), name);
+
+    snprintf(tmp, sizeof(tmp), "%u", ss.rate);
+    TXTRecordSetValue(&txt, "rate", strlen(tmp), tmp);
+
+    snprintf(tmp, sizeof(tmp), "%u", ss.channels);
+    TXTRecordSetValue(&txt, "channels", strlen(tmp), tmp);
+
+    t = pa_sample_format_to_string(ss.format);
+    TXTRecordSetValue(&txt, "format", strlen(t), t);
+
+    t = pa_channel_map_snprint(cm, sizeof(cm), &map);
+    TXTRecordSetValue(&txt, "channel_map", strlen(t), t);
+
+    t = subtype_text[subtype];
+    TXTRecordSetValue(&txt, "subtype", strlen(t), t);
+
+    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_DESCRIPTION)))
+        TXTRecordSetValue(&txt, "description", strlen(t), t);
+    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_ICON_NAME)))
+        TXTRecordSetValue(&txt, "icon-name", strlen(t), t);
+    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_VENDOR_NAME)))
+        TXTRecordSetValue(&txt, "vendor-name", strlen(t), t);
+    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_PRODUCT_NAME)))
+        TXTRecordSetValue(&txt, "product-name", strlen(t), t);
+    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_CLASS)))
+        TXTRecordSetValue(&txt, "class", strlen(t), t);
+    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR)))
+        TXTRecordSetValue(&txt, "form-factor", strlen(t), t);
+
+    err = DNSServiceRegister(&s->service,
+                             0,         /* flags */
+                             kDNSServiceInterfaceIndexAny,
+                             s->service_name,
+                             pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
+                             NULL,      /* domain */
+                             NULL,      /* host */
+                             compute_port(s->userdata),
+                             TXTRecordGetLength(&txt),
+                             TXTRecordGetBytesPtr(&txt),
+                             dns_service_register_reply, s);
+
+    if (err != kDNSServiceErr_NoError) {
+        pa_log("DNSServiceRegister() returned err %d", err);
+        goto finish;
+    }
+
+    pa_log_debug("Successfully registered mDNS services for >%s<.", s->service_name);
+    return 0;
+
+finish:
+
+    /* Remove this service */
+    if (r < 0)
+        service_free(s);
+
+    TXTRecordDeallocate(&txt);
+
+    return r;
+}
+
+static struct service *get_service(struct userdata *u, pa_object *device) {
+    struct service *s;
+    char *hn, *un;
+    const char *n;
+
+    pa_assert(u);
+    pa_object_assert_ref(device);
+
+    if ((s = pa_hashmap_get(u->services, device)))
+        return s;
+
+    s = pa_xnew0(struct service, 1);
+    s->userdata = u;
+    s->device = device;
+
+    if (pa_sink_isinstance(device)) {
+        if (!(n = pa_proplist_gets(PA_SINK(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
+            n = PA_SINK(device)->name;
+    } else {
+        if (!(n = pa_proplist_gets(PA_SOURCE(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
+            n = PA_SOURCE(device)->name;
+    }
+
+    hn = pa_get_host_name_malloc();
+    un = pa_get_user_name_malloc();
+
+    s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un, hn, n), kDNSServiceMaxDomainName-1);
+
+    pa_xfree(un);
+    pa_xfree(hn);
+
+    pa_hashmap_put(u->services, s->device, s);
+
+    return s;
+}
+
+static void service_free(struct service *s) {
+    pa_assert(s);
+
+    pa_hashmap_remove(s->userdata->services, s->device);
+
+    if (s->service)
+        DNSServiceRefDeallocate(s->service);
+
+    pa_xfree(s->service_name);
+    pa_xfree(s);
+}
+
+static bool shall_ignore(pa_object *o) {
+    pa_object_assert_ref(o);
+
+    if (pa_sink_isinstance(o))
+        return !!(PA_SINK(o)->flags & PA_SINK_NETWORK);
+
+    if (pa_source_isinstance(o))
+        return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK);
+
+    pa_assert_not_reached();
+}
+
+static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) {
+    pa_assert(c);
+    pa_object_assert_ref(o);
+
+    if (!shall_ignore(o))
+        publish_service(get_service(u, o));
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) {
+    struct service *s;
+
+    pa_assert(c);
+    pa_object_assert_ref(o);
+
+    if ((s = pa_hashmap_get(u->services, o)))
+        service_free(s);
+
+    return PA_HOOK_OK;
+}
+
+static int publish_main_service(struct userdata *u) {
+    DNSServiceErrorType err;
+    TXTRecordRef txt;
+
+    pa_assert(u);
+
+    if (u->main_service) {
+        DNSServiceRefDeallocate(u->main_service);
+        u->main_service = NULL;
+    }
+
+    TXTRecordCreate(&txt, 0, NULL);
+    txt_record_server_data(u->core, &txt);
+
+    err = DNSServiceRegister(&u->main_service,
+                             0, /* flags */
+                             kDNSServiceInterfaceIndexAny,
+                             u->service_name,
+                             SERVICE_TYPE_SERVER,
+                             NULL, /* domain */
+                             NULL, /* host */
+                             compute_port(u),
+                             TXTRecordGetLength(&txt),
+                             TXTRecordGetBytesPtr(&txt),
+                             NULL, NULL);
+
+    if (err != kDNSServiceErr_NoError) {
+        pa_log("DNSServiceRegister() returned err %d", err);
+        return err;
+    }
+
+    TXTRecordDeallocate(&txt);
+
+    return 0;
+}
+
+static int publish_all_services(struct userdata *u) {
+    pa_sink *sink;
+    pa_source *source;
+    uint32_t idx;
+
+    pa_assert(u);
+
+    pa_log_debug("Publishing services in mDNS");
+
+    for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx)))
+        if (!shall_ignore(PA_OBJECT(sink)))
+            publish_service(get_service(u, PA_OBJECT(sink)));
+
+    for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx)))
+        if (!shall_ignore(PA_OBJECT(source)))
+            publish_service(get_service(u, PA_OBJECT(source)));
+
+    return publish_main_service(u);
+}
+
+static void unpublish_all_services(struct userdata *u) {
+    void *state = NULL;
+    struct service *s;
+
+    pa_assert(u);
+
+    pa_log_debug("Unpublishing services in mDNS");
+
+    while ((s = pa_hashmap_iterate(u->services, &state, NULL)))
+        service_free(s);
+
+    if (u->main_service)
+        DNSServiceRefDeallocate(u->main_service);
+}
+
+void pa__done(pa_module *m) {
+    struct userdata *u;
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    unpublish_all_services(u);
+
+    if (u->services)
+        pa_hashmap_free(u->services);
+
+    if (u->sink_new_slot)
+        pa_hook_slot_free(u->sink_new_slot);
+    if (u->source_new_slot)
+        pa_hook_slot_free(u->source_new_slot);
+    if (u->sink_changed_slot)
+        pa_hook_slot_free(u->sink_changed_slot);
+    if (u->source_changed_slot)
+        pa_hook_slot_free(u->source_changed_slot);
+    if (u->sink_unlink_slot)
+        pa_hook_slot_free(u->sink_unlink_slot);
+    if (u->source_unlink_slot)
+        pa_hook_slot_free(u->source_unlink_slot);
+
+    if (u->native)
+        pa_native_protocol_unref(u->native);
+
+    pa_xfree(u->service_name);
+    pa_xfree(u);
+}
+
+int pa__init(pa_module *m) {
+    struct userdata *u;
+    pa_modargs *ma = NULL;
+    char *hn, *un;
+
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log("Failed to parse module arguments.");
+        goto fail;
+    }
+
+    m->userdata = u = pa_xnew0(struct userdata, 1);
+    u->core = m->core;
+    u->module = m;
+    u->native = pa_native_protocol_get(u->core);
+
+    u->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
+    u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u);
+    u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u);
+
+    un = pa_get_user_name_malloc();
+    hn = pa_get_host_name_malloc();
+    u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", un, hn), kDNSServiceMaxDomainName-1);
+    pa_xfree(un);
+    pa_xfree(hn);
+
+    if (publish_all_services(u) != 0)
+        goto fail;
+
+    pa_modargs_free(ma);
+
+    return 0;
+
+fail:
+    pa__done(m);
+
+    if (ma)
+        pa_modargs_free(ma);
+
+    return -1;
+}