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("%s(): DNSServiceRegister() returned err %d", __func__, err);
        return err;
    }

    TXTRecordDeallocate(&txt);

    return 0;
}
static int publish_main_service(struct userdata *u) {
    AvahiStringList *txt = NULL;
    int r = -1;

    pa_assert(u);

    if (!u->main_entry_group) {
        if (!(u->main_entry_group = avahi_entry_group_new(u->client, main_entry_group_callback, u))) {
            pa_log("avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
            goto fail;
        }
    } else
        avahi_entry_group_reset(u->main_entry_group);

    txt = txt_record_server_data(u->core, txt);

    if (avahi_entry_group_add_service_strlst(
                u->main_entry_group,
                AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
                0,
                u->service_name,
                SERVICE_TYPE_SERVER,
                NULL,
                NULL,
                compute_port(u),
                txt) < 0) {

        pa_log("avahi_entry_group_add_service_strlst() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
        goto fail;
    }

    if (avahi_entry_group_commit(u->main_entry_group) < 0) {
        pa_log("avahi_entry_group_commit() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
        goto fail;
    }

    r = 0;

fail:
    avahi_string_list_free(txt);

    return r;
}
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 Bonjour services for >%s<.", s->service_name);
    return 0;

finish:

    /* Remove this service */
    if (r < 0)
        service_free(s);

    TXTRecordDeallocate(&txt);

    return r;
}
static int publish_service(struct service *s) {
    int r = -1;
    AvahiStringList *txt = NULL;
    const char *name = NULL, *t;
    pa_proplist *proplist = NULL;
    pa_sample_spec ss;
    pa_channel_map map;
    char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
    enum service_subtype subtype;

    const char * const subtype_text[] = {
        [SUBTYPE_HARDWARE] = "hardware",
        [SUBTYPE_VIRTUAL] = "virtual",
        [SUBTYPE_MONITOR] = "monitor"
    };

    pa_assert(s);

    if (!s->userdata->client || avahi_client_get_state(s->userdata->client) != AVAHI_CLIENT_S_RUNNING)
        return 0;

    if (!s->entry_group) {
        if (!(s->entry_group = avahi_entry_group_new(s->userdata->client, service_entry_group_callback, s))) {
            pa_log("avahi_entry_group_new(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
            goto finish;
        }
    } else
        avahi_entry_group_reset(s->entry_group);

    txt = txt_record_server_data(s->userdata->core, txt);

    get_service_data(s, &ss, &map, &name, &proplist, &subtype);
    txt = avahi_string_list_add_pair(txt, "device", name);
    txt = avahi_string_list_add_printf(txt, "rate=%u", ss.rate);
    txt = avahi_string_list_add_printf(txt, "channels=%u", ss.channels);
    txt = avahi_string_list_add_pair(txt, "format", pa_sample_format_to_string(ss.format));
    txt = avahi_string_list_add_pair(txt, "channel_map", pa_channel_map_snprint(cm, sizeof(cm), &map));
    txt = avahi_string_list_add_pair(txt, "subtype", subtype_text[subtype]);

    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_DESCRIPTION)))
        txt = avahi_string_list_add_pair(txt, "description", t);
    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_ICON_NAME)))
        txt = avahi_string_list_add_pair(txt, "icon-name", t);
    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_VENDOR_NAME)))
        txt = avahi_string_list_add_pair(txt, "vendor-name", t);
    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_PRODUCT_NAME)))
        txt = avahi_string_list_add_pair(txt, "product-name", t);
    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_CLASS)))
        txt = avahi_string_list_add_pair(txt, "class", t);
    if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR)))
        txt = avahi_string_list_add_pair(txt, "form-factor", t);

    if (avahi_entry_group_add_service_strlst(
                s->entry_group,
                AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
                0,
                s->service_name,
                pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
                NULL,
                NULL,
                compute_port(s->userdata),
                txt) < 0) {

        pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
        goto finish;
    }

    if (avahi_entry_group_add_service_subtype(
                s->entry_group,
                AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
                0,
                s->service_name,
                pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
                NULL,
                pa_sink_isinstance(s->device) ? (subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SINK_HARDWARE : SERVICE_SUBTYPE_SINK_VIRTUAL) :
                (subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SOURCE_HARDWARE : (subtype == SUBTYPE_VIRTUAL ? SERVICE_SUBTYPE_SOURCE_VIRTUAL : SERVICE_SUBTYPE_SOURCE_MONITOR))) < 0) {

        pa_log("avahi_entry_group_add_service_subtype(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
        goto finish;
    }

    if (pa_source_isinstance(s->device) && subtype != SUBTYPE_MONITOR) {
        if (avahi_entry_group_add_service_subtype(
                    s->entry_group,
                    AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
                    0,
                    s->service_name,
                    SERVICE_TYPE_SOURCE,
                    NULL,
                    SERVICE_SUBTYPE_SOURCE_NON_MONITOR) < 0) {

            pa_log("avahi_entry_group_add_service_subtype(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
            goto finish;
        }
    }

    if (avahi_entry_group_commit(s->entry_group) < 0) {
        pa_log("avahi_entry_group_commit(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
        goto finish;
    }

    r = 0;
    pa_log_debug("Successfully created entry group for %s.", s->service_name);

finish:

    /* Remove this service */
    if (r < 0)
        service_free(s);

    avahi_string_list_free(txt);

    return r;
}