On Sun, 2016-01-31 at 22:16 -0600, Hajime Fujita wrote: > From: Martin Blanchard <tchaik at gmx.com> > > RAOP authentication (password) is based on BA and DA HTTP authentication > schemes. This patch adds the RSTP client the ability to specify the caller > of server response status. Tracking the '401 Unauthorized' status allow > the RAOP client to respond the server challenge authentication request. > This patch adds the core implementation but does not introduce the > authentication scheme in the RAOP connection process yet. > --- >  src/modules/raop/raop_client.c      |  245 +++++- >  src/modules/raop/raop_client.c.orig | 1495 +++++++++++++++++++++++++++++++++++ Please remove this file from this commit rather than adding it and removing it later. -- Arun >  src/modules/raop/raop_client.h      |    4 + >  src/modules/rtp/rtsp_client.c       |   83 +- >  src/modules/rtp/rtsp_client.h       |   26 +- >  5 files changed, 1810 insertions(+), 43 deletions(-) >  create mode 100644 src/modules/raop/raop_client.c.orig > > diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c > index a2ec91e..1323cd0 100644 > --- a/src/modules/raop/raop_client.c > +++ b/src/modules/raop/raop_client.c > @@ -65,6 +65,9 @@ >  #define VOLUME_MIN -144 >  #define VOLUME_MAX 0 >  > +#define USER_AGENT "iTunes/11.0.4 (Windows; N)" > +#define USER_NAME "iTunes" > + >  #define DEFAULT_RAOP_PORT 5000 >  #define UDP_DEFAULT_AUDIO_PORT 6000 >  #define UDP_DEFAULT_CONTROL_PORT 6001 > @@ -85,13 +88,15 @@ struct pa_raop_client { >      pa_core *core; >      char *host; >      uint16_t port; > -    char *sid; >      pa_rtsp_client *rtsp; > -    pa_raop_protocol_t protocol; > +    char *sci, *sid; > +    char *pwd; >  >      uint8_t jack_type; >      uint8_t jack_status; >  > +    pa_raop_protocol_t protocol; > + >      int encryption; /* Enable encryption? */ >      pa_raop_secret *aes; >  > @@ -125,6 +130,9 @@ struct pa_raop_client { >      uint32_t udp_sync_interval; >      uint32_t udp_sync_count; >  > +    pa_raop_client_auth_cb_t udp_auth_callback; > +    void *udp_auth_userdata; > + >      pa_raop_client_setup_cb_t udp_setup_callback; >      void *udp_setup_userdata; >  > @@ -576,7 +584,7 @@ static void do_rtsp_announce(pa_raop_client *c) { >      pa_xfree(sdp); >  } >  > -static void tcp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) { > +static void tcp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_rtsp_status status, pa_headerlist* headers, void *userdata) { >      pa_raop_client* c = userdata; >      pa_assert(c); >      pa_assert(rtsp); > @@ -675,12 +683,13 @@ static void tcp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist >      } >  } >  > -static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist *headers, void *userdata) { > +static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_rtsp_status status, pa_headerlist *headers, void *userdata) { >      pa_raop_client *c = userdata; >  >      pa_assert(c); >      pa_assert(rtsp); >      pa_assert(rtsp == c->rtsp); > +    pa_assert(STATUS_OK == status); >  >      switch (state) { >          case STATE_CONNECT: { > @@ -982,6 +991,178 @@ static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist >      } >  } >  > +static void rtsp_authentication_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_rtsp_status status, pa_headerlist *headers, void *userdata) { > +    pa_raop_client *c = userdata; > + > +    pa_assert(c); > +    pa_assert(rtsp); > +    pa_assert(rtsp == c->rtsp); > + > +    switch (state) { > +        case STATE_CONNECT: { > +            char *sci = NULL, *sac = NULL; > +            uint16_t rac; > +            struct { > +                uint32_t ci1; > +                uint32_t ci2; > +            } rci; > + > +            pa_random(&rci, sizeof(rci)); > +            /* Generate a random Client-Instance number */ > +            sci = pa_sprintf_malloc("%08x%08x",rci.ci1, rci.ci2); > +            pa_rtsp_add_header(c->rtsp, "Client-Instance", sci); > + > +            pa_random(&rac, sizeof(rac)); > +            /* Generate a random Apple-Challenge key */ > +            pa_raop_base64_encode(&rac, 8*sizeof(rac), &sac); > +            pa_log_debug ("APPLECHALLENGE >> %s | %d", sac, (int) sizeof(rac)); > +            rtrimchar(sac, '='); > +            pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac); > + > +            pa_rtsp_options(c->rtsp); > + > +            pa_xfree(sac); > +            pa_xfree(sci); > +            break; > +        } > + > +        case STATE_OPTIONS: { > +            static bool waiting = false; > +            const char *current = NULL; > +            char space[] = " "; > +            char *token,*ath = NULL; > +            char *publ, *wath, *mth, *val; > +            char *realm = NULL, *nonce = NULL, *response = NULL; > +            char comma[] = ","; > + > +            pa_log_debug("RAOP: OPTIONS"); > +            /* We do not consider the Apple-Response */ > +            pa_rtsp_remove_header(c->rtsp, "Apple-Challenge"); > + > +            if (STATUS_UNAUTHORIZED == status) { > +                wath = pa_xstrdup(pa_headerlist_gets(headers, "WWW-Authenticate")); > +                if (true == waiting) { > +                    pa_xfree(wath); > +                    goto failure; > +                } > + > +                if (wath) > +                    mth = pa_split(wath, space, ¤t); > +                while ((token = pa_split(wath, comma, ¤t))) { > +                    val = NULL; > +                    if ((val = strstr(token, "="))) { > +                        if (NULL == realm && val > strstr(token, "realm")) > +                            realm = pa_xstrdup(val + 2); > +                        else if (NULL == nonce && val > strstr(token, "nonce")) > +                            nonce = pa_xstrdup(val + 2); > +                        val = NULL; > +                    } > + > +                    pa_xfree(token); > +                } > + > +                if (pa_safe_streq(mth, "Basic")) { > +                    rtrimchar(realm, '\"'); > + > +                    pa_raop_basic_response(USER_NAME, c->pwd, &response); > +                    ath = pa_sprintf_malloc("Basic %s", > +                        response); > + > +                    pa_xfree(response); > +                    pa_xfree(realm); > +                } else if (pa_safe_streq(mth, "Digest")) { > +                    rtrimchar(realm, '\"'); > +                    rtrimchar(nonce, '\"'); > + > +                    pa_raop_digest_response(USER_NAME, realm, c->pwd, nonce, "*", &response); > +                    ath = pa_sprintf_malloc("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"*\", response=\"%s\"", > +                        USER_NAME, realm, nonce, > +                        response); > + > +                    pa_xfree(response); > +                    pa_xfree(realm); > +                    pa_xfree(nonce); > +                } else { > +                    pa_log_error("unsupported authentication method: %s", mth); > +                    pa_xfree(wath); > +                    pa_xfree(mth); > +                    goto error; > +                } > + > +                pa_xfree(wath); > +                pa_xfree(mth); > + > +                pa_rtsp_add_header(c->rtsp, "Authorization", ath); > +                pa_xfree(ath); > + > +                waiting = true; > +                pa_rtsp_options(c->rtsp); > +                break; > +            } > + > +            if (STATUS_OK == status) { > +                publ = pa_xstrdup(pa_headerlist_gets(headers, "Public")); > +                c->sci = pa_xstrdup(pa_rtsp_get_header(c->rtsp, "Client-Instance")); > + > +                if (c->pwd) > +                    pa_xfree(c->pwd); > +                pa_xfree(publ); > +                c->pwd = NULL; > +            } > + > +            if (c->udp_auth_callback) > +                c->udp_auth_callback((int) status, c->udp_auth_userdata); > +            pa_rtsp_client_free(c->rtsp); > +            c->rtsp = NULL; > + > +            waiting = false; > +            break; > + > +        failure: > +            if (c->udp_auth_callback) > +                c->udp_auth_callback((int) STATUS_UNAUTHORIZED, c->udp_auth_userdata); > +            pa_rtsp_client_free(c->rtsp); > +            c->rtsp = NULL; > + > +            pa_log_error("aborting RTSP authentication, wrong password"); > + > +            waiting = false; > +            break; > + > +        error: > +            if (c->udp_auth_callback) > +                c->udp_auth_callback((int) status, c->udp_auth_userdata); > +            pa_rtsp_client_free(c->rtsp); > +            c->rtsp = NULL; > + > +            pa_log_error("aborting RTSP authentication, unexpected failure"); > + > +            waiting = false; > +            break; > +        } > + > +        case STATE_ANNOUNCE: > +        case STATE_SETUP: > +        case STATE_RECORD: > +        case STATE_SET_PARAMETER: > +        case STATE_FLUSH: > +        case STATE_TEARDOWN: > +        case STATE_DISCONNECTED: > +        default: { > +            if (c->udp_auth_callback) > +                c->udp_auth_callback((int) STATUS_BAD_REQUEST, c->udp_auth_userdata); > +            pa_rtsp_client_free(c->rtsp); > +            c->rtsp = NULL; > + > +            if (c->sci) > +                pa_xfree(c->sci); > +            c->sci = NULL; > + > +            break; > +        } > +    } > +} > + >  pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol) { >      pa_raop_client* c; >      pa_parsed_address a; > @@ -1047,15 +1228,40 @@ void pa_raop_client_free(pa_raop_client *c) { >  >      if (c->sid) >          pa_xfree(c->sid); > +    if (c->sci) > +        pa_xfree(c->sci); > +    c->sci = c->sid = NULL; > + >      if (c->aes) >          pa_raop_secret_free(c->aes); >      if (c->rtsp) >          pa_rtsp_client_free(c->rtsp); > -    pa_xfree(c->host); > +    c->rtsp = NULL; >  > +    pa_xfree(c->host); >      pa_xfree(c); >  } >  > +int pa_raop_client_authenticate (pa_raop_client *c, const char *password) { > + > +    pa_assert(c); > + > +    if (c->rtsp || c->pwd) { > +        pa_log_debug("Connection already in progress"); > +        return 0; > +    } > + > +    c->pwd = NULL; > +    if (password) > +        c->pwd = pa_xstrdup(password); > +    c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, USER_AGENT); > + > +    pa_assert(c->rtsp); > + > +    pa_rtsp_set_callback(c->rtsp, rtsp_authentication_cb, c); > +    return pa_rtsp_connect(c->rtsp); > +} > + >  int pa_raop_client_connect(pa_raop_client *c) { >      char *sci; >      struct { > @@ -1074,7 +1280,7 @@ int pa_raop_client_connect(pa_raop_client *c) { >      if (c->protocol == RAOP_TCP) >          c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); >      else > -        c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, "iTunes/7.6.2 (Windows; N;)"); > +        c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, USER_AGENT); >  >      /* Generate random instance id. */ >      pa_random(&rand_data, sizeof(rand_data)); > @@ -1116,6 +1322,17 @@ int pa_raop_client_teardown(pa_raop_client *c) { >      return rv; >  } >  > +int pa_raop_client_udp_is_authenticated(pa_raop_client *c) { > +    int rv = 0; > + > +    pa_assert(c); > + > +    if (c->sci != NULL) > +        rv = 1; > + > +    return rv; > +} > + >  int pa_raop_client_udp_is_alive(pa_raop_client *c) { >      int rv = 0; >  > @@ -1147,7 +1364,6 @@ int pa_raop_client_udp_stream(pa_raop_client *c) { >          if (!c->is_recording) { >              c->udp_first_packet = true; >              c->udp_sync_count = 0; > - >              c->is_recording = true; >           } >  > @@ -1326,6 +1542,14 @@ ssize_t pa_raop_client_udp_send_audio_packet(pa_raop_client *c, pa_memchunk *blo >      return len; >  } >  > +void pa_raop_client_set_encryption(pa_raop_client *c, int encryption) { > +    pa_assert(c); > + > +    c->encryption = encryption; > +    if (c->encryption) > +        c->aes = pa_raop_secret_new(); > +} > + >  /* Adjust volume so that it fits into VOLUME_DEF <= v <= 0 dB */ >  pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume) { >      double minv, maxv; > @@ -1469,12 +1693,11 @@ void pa_raop_client_tcp_set_closed_callback(pa_raop_client *c, pa_raop_client_cl >      c->tcp_closed_userdata = userdata; >  } >  > -void pa_raop_client_set_encryption(pa_raop_client *c, int encryption) { > +void pa_raop_client_udp_set_auth_callback(pa_raop_client *c, pa_raop_client_auth_cb_t callback, void *userdata) { >      pa_assert(c); >  > -    c->encryption = encryption; > -    if (c->encryption) > -        c->aes = pa_raop_secret_new(); > +    c->udp_auth_callback = callback; > +    c->udp_auth_userdata = userdata; >  } >  >  void pa_raop_client_udp_set_setup_callback(pa_raop_client *c, pa_raop_client_setup_cb_t callback, void *userdata) { > diff --git a/src/modules/raop/raop_client.c.orig b/src/modules/raop/raop_client.c.orig > new file mode 100644 > index 0000000..56f4ccf > --- /dev/null > +++ b/src/modules/raop/raop_client.c.orig > @@ -0,0 +1,1495 @@ > +/*** > +  This file is part of PulseAudio. > + > +  Copyright 2008 Colin Guthrie > + > +  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, write to the Free Software > +  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 > +  USA. > +***/ > + > +#ifdef HAVE_CONFIG_H > +#include > +#endif > + > +#include > +#include > +#include > +#include > +#include > +#include > + > +#ifdef HAVE_SYS_FILIO_H > +#include > +#endif > + > +#include > +#include > +#include > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "raop_client.h" > + > +#include "rtsp_client.h" > +#include "raop_packet_buffer.h" > +#include "raop_crypto.h" > +#include "raop_util.h" > + > +#define JACK_STATUS_DISCONNECTED 0 > +#define JACK_STATUS_CONNECTED 1 > + > +#define JACK_TYPE_ANALOG 0 > +#define JACK_TYPE_DIGITAL 1 > + > +#define VOLUME_DEF -30 > +#define VOLUME_MIN -144 > +#define VOLUME_MAX 0 > + > +#define DEFAULT_RAOP_PORT 5000 > +#define UDP_DEFAULT_AUDIO_PORT 6000 > +#define UDP_DEFAULT_CONTROL_PORT 6001 > +#define UDP_DEFAULT_TIMING_PORT 6002 > + > +#define UDP_DEFAULT_PKT_BUF_SIZE 1000 > + > +typedef enum { > +    UDP_PAYLOAD_TIMING_REQUEST = 0x52, > +    UDP_PAYLOAD_TIMING_RESPONSE = 0x53, > +    UDP_PAYLOAD_SYNCHRONIZATION = 0x54, > +    UDP_PAYLOAD_RETRANSMIT_REQUEST = 0x55, > +    UDP_PAYLOAD_RETRANSMIT_REPLY = 0x56, > +    UDP_PAYLOAD_AUDIO_DATA = 0x60 > +} pa_raop_udp_payload_type; > + > +struct pa_raop_client { > +    pa_core *core; > +    char *host; > +    uint16_t port; > +    char *sid; > +    pa_rtsp_client *rtsp; > +    pa_raop_protocol_t protocol; > + > +    uint8_t jack_type; > +    uint8_t jack_status; > + > +    int encryption; /* Enable encryption? */ > +    pa_raop_secret *aes; > + > +    uint16_t seq; > +    uint32_t rtptime; > + > +    /* Members only for the TCP protocol */ > +    pa_socket_client *tcp_sc; > +    int tcp_fd; > + > +    pa_raop_client_cb_t tcp_callback; > +    void *tcp_userdata; > +    pa_raop_client_closed_cb_t tcp_closed_callback; > +    void *tcp_closed_userdata; > + > +    /* Members only for the UDP protocol */ > +    uint16_t udp_my_control_port; > +    uint16_t udp_my_timing_port; > +    uint16_t udp_server_control_port; > +    uint16_t udp_server_timing_port; > + > +    int udp_stream_fd; > +    int udp_control_fd; > +    int udp_timing_fd; > + > +    uint32_t udp_ssrc; > + > +    bool is_recording; > + > +    bool udp_first_packet; > +    uint32_t udp_sync_interval; > +    uint32_t udp_sync_count; > + > +    pa_raop_client_setup_cb_t udp_setup_callback; > +    void *udp_setup_userdata; > + > +    pa_raop_client_record_cb_t udp_record_callback; > +    void *udp_record_userdata; > + > +    pa_raop_client_disconnected_cb_t udp_disconnected_callback; > +    void *udp_disconnected_userdata; > + > +    pa_raop_packet_buffer *packet_buffer; > +}; > + > +/* Timming packet header (8x8): > + *  [0]   RTP v2: 0x80, > + *  [1]   Payload type: 0x53 | marker bit: 0x80, > + *  [2,3] Sequence number: 0x0007, > + *  [4,7] Timestamp: 0x00000000 (unused). */ > +static const uint8_t udp_timming_header[8] = { > +    0x80, 0xd3, 0x00, 0x07, > +    0x00, 0x00, 0x00, 0x00 > +}; > + > +/* Sync packet header (8x8): > + *  [0]   RTP v2: 0x80, > + *  [1]   Payload type: 0x54 | marker bit: 0x80, > + *  [2,3] Sequence number: 0x0007, > + *  [4,7] Timestamp: 0x00000000 (to be set). */ > +static const uint8_t udp_sync_header[8] = { > +    0x80, 0xd4, 0x00, 0x07, > +    0x00, 0x00, 0x00, 0x00 > +}; > + > +static const uint8_t tcp_audio_header[16] = { > +    0x24, 0x00, 0x00, 0x00, > +    0xF0, 0xFF, 0x00, 0x00, > +    0x00, 0x00, 0x00, 0x00, > +    0x00, 0x00, 0x00, 0x00, > +}; > + > +/* Audio packet header (12x8): > + *  [0]    RTP v2: 0x80, > + *  [1]    Payload type: 0x60, > + *  [2,3]  Sequence number: 0x0000 (to be set), > + *  [4,7]  Timestamp: 0x00000000 (to be set), > + *  [8,12] SSRC: 0x00000000 (to be set).*/ > +static const uint8_t udp_audio_header[12] = { > +    0x80, 0x60, 0x00, 0x00, > +    0x00, 0x00, 0x00, 0x00, > +    0x00, 0x00, 0x00, 0x00 > +}; > + > +/** > + * Function to write bits into a buffer. > + * @param buffer Handle to the buffer. It will be incremented if new data requires it. > + * @param bit_pos A pointer to a position buffer to keep track the current write location (0 for MSB, 7 for LSB) > + * @param size A pointer to the byte size currently written. This allows the calling function to do simple buffer overflow checks > + * @param data The data to write > + * @param data_bit_len The number of bits from data to write > + */ > +static inline void bit_writer(uint8_t **buffer, uint8_t *bit_pos, int *size, uint8_t data, uint8_t data_bit_len) { > +    int bits_left, bit_overflow; > +    uint8_t bit_data; > + > +    if (!data_bit_len) > +        return; > + > +    /* If bit pos is zero, we will definately use at least one bit from the current byte so size increments. */ > +    if (!*bit_pos) > +        *size += 1; > + > +    /* Calc the number of bits left in the current byte of buffer. */ > +    bits_left = 7 - *bit_pos  + 1; > +    /* Calc the overflow of bits in relation to how much space we have left... */ > +    bit_overflow = bits_left - data_bit_len; > +    if (bit_overflow >= 0) { > +        /* We can fit the new data in our current byte. > +         * As we write from MSB->LSB we need to left shift by the overflow amount. */ > +        bit_data = data << bit_overflow; > +        if (*bit_pos) > +            **buffer |= bit_data; > +        else > +            **buffer = bit_data; > +        /* If our data fits exactly into the current byte, we need to increment our pointer. */ > +        if (0 == bit_overflow) { > +            /* Do not increment size as it will be incremented on next call as bit_pos is zero. */ > +            *buffer += 1; > +            *bit_pos = 0; > +        } else { > +            *bit_pos += data_bit_len; > +        } > +    } else { > +        /* bit_overflow is negative, there for we will need a new byte from our buffer > +         * Firstly fill up what's left in the current byte. */ > +        bit_data = data >> -bit_overflow; > +        **buffer |= bit_data; > +        /* Increment our buffer pointer and size counter. */ > +        *buffer += 1; > +        *size += 1; > +        **buffer = data << (8 + bit_overflow); > +        *bit_pos = -bit_overflow; > +    } > +} > + > +static inline void rtrimchar(char *str, char rc) { > +    char *sp = str + strlen(str) - 1; > +    while (sp >= str && *sp == rc) { > +        *sp = '\0'; > +        sp -= 1; > +    } > +} > + > +static void tcp_on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { > +    pa_raop_client *c = userdata; > + > +    pa_assert(sc); > +    pa_assert(c); > +    pa_assert(c->tcp_sc == sc); > +    pa_assert(c->tcp_fd < 0); > +    pa_assert(c->tcp_callback); > + > +    pa_socket_client_unref(c->tcp_sc); > +    c->tcp_sc = NULL; > + > +    if (!io) { > +        pa_log("Connection failed: %s", pa_cstrerror(errno)); > +        return; > +    } > + > +    c->tcp_fd = pa_iochannel_get_send_fd(io); > + > +    pa_iochannel_set_noclose(io, true); > +    pa_iochannel_free(io); > + > +    pa_make_tcp_socket_low_delay(c->tcp_fd); > + > +    pa_log_debug("Connection established"); > +    c->tcp_callback(c->tcp_fd, c->tcp_userdata); > +} > + > +static inline uint64_t timeval_to_ntp(struct timeval *tv) { > +    uint64_t ntp = 0; > + > +    /* Converting micro seconds to a fraction. */ > +    ntp = (uint64_t) tv->tv_usec * UINT32_MAX / PA_USEC_PER_SEC; > +    /* Moving reference from  1 Jan 1970 to 1 Jan 1900 (seconds). */ > +    ntp |= (uint64_t) (tv->tv_sec + 0x83aa7e80) << 32; > + > +    return ntp; > +} > + > +static int connect_udp_socket(pa_raop_client *c, int fd, uint16_t port) { > +    struct sockaddr_in sa4; > +#ifdef HAVE_IPV6 > +    struct sockaddr_in6 sa6; > +#endif > +    struct sockaddr *sa; > +    socklen_t salen; > +    sa_family_t af; > + > +    pa_zero(sa4); > +#ifdef HAVE_IPV6 > +    pa_zero(sa6); > +#endif > +    if (inet_pton(AF_INET, c->host, &sa4.sin_addr) > 0) { > +        sa4.sin_family = af = AF_INET; > +        sa4.sin_port = htons(port); > +        sa = (struct sockaddr *) &sa4; > +        salen = sizeof(sa4); > +#ifdef HAVE_IPV6 > +    } else if (inet_pton(AF_INET6, c->host, &sa6.sin6_addr) > 0) { > +        sa6.sin6_family = af = AF_INET6; > +        sa6.sin6_port = htons(port); > +        sa = (struct sockaddr *) &sa6; > +        salen = sizeof(sa6); > +#endif > +    } else { > +        pa_log("Invalid destination '%s'", c->host); > +        goto fail; > +    } > + > +    if (fd < 0 && (fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) { > +        pa_log("socket() failed: %s", pa_cstrerror(errno)); > +        goto fail; > +    } > + > +    /* If the socket queue is full, let's drop packets */ > +    pa_make_udp_socket_low_delay(fd); > +    pa_make_fd_nonblock(fd); > + > +    if (connect(fd, sa, salen) < 0) { > +        pa_log("connect() failed: %s", pa_cstrerror(errno)); > +        goto fail; > +    } > + > +    pa_log_debug("Connected to %s on port %d (SOCK_DGRAM)", c->host, port); > +    return fd; > + > +fail: > +    if (fd >= 0) > +        pa_close(fd); > + > +    return -1; > +} > + > +static int open_bind_udp_socket(pa_raop_client *c, uint16_t *actual_port) { > +    int fd = -1; > +    uint16_t port; > +    struct sockaddr_in sa4; > +#ifdef HAVE_IPV6 > +    struct sockaddr_in6 sa6; > +#endif > +    struct sockaddr *sa; > +    uint16_t *sa_port; > +    socklen_t salen; > +    sa_family_t af; > +    int one = 1; > + > +    pa_assert(actual_port); > + > +    port = *actual_port; > + > +    pa_zero(sa4); > +#ifdef HAVE_IPV6 > +    pa_zero(sa6); > +#endif > +    if (inet_pton(AF_INET, pa_rtsp_localip(c->rtsp), &sa4.sin_addr) > 0) { > +        sa4.sin_family = af = AF_INET; > +        sa4.sin_port = htons(port); > +        sa = (struct sockaddr *) &sa4; > +        salen = sizeof(sa4); > +        sa_port = &sa4.sin_port; > +#ifdef HAVE_IPV6 > +    } else if (inet_pton(AF_INET6, pa_rtsp_localip(c->rtsp), &sa6.sin6_addr) > 0) { > +        sa6.sin6_family = af = AF_INET6; > +        sa6.sin6_port = htons(port); > +        sa = (struct sockaddr *) &sa6; > +        salen = sizeof(sa6); > +        sa_port = &sa6.sin6_port; > +#endif > +    } else { > +        pa_log("Could not determine which address family to use"); > +        goto fail; > +    } > + > +    pa_zero(sa4); > +#ifdef HAVE_IPV6 > +    pa_zero(sa6); > +#endif > + > +    if ((fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) { > +        pa_log("socket() failed: %s", pa_cstrerror(errno)); > +        goto fail; > +    } > + > +#ifdef SO_TIMESTAMP > +    if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) { > +        pa_log("setsockopt(SO_TIMESTAMP) failed: %s", pa_cstrerror(errno)); > +        goto fail; > +    } > +#else > +    pa_log("SO_TIMESTAMP unsupported on this platform"); > +    goto fail; > +#endif > + > +    one = 1; > +    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { > +        pa_log("setsockopt(SO_REUSEADDR) failed: %s", pa_cstrerror(errno)); > +        goto fail; > +    } > + > +    do { > +        *sa_port = htons(port); > + > +        if (bind(fd, sa, salen) < 0 && errno != EADDRINUSE) { > +            pa_log("bind_socket() failed: %s", pa_cstrerror(errno)); > +            goto fail; > +        } > +        break; > +    } while (++port > 0); > + > +    pa_log_debug("Socket bound to port %d (SOCK_DGRAM)", port); > +    *actual_port = port; > + > +    return fd; > + > +fail: > +    if (fd >= 0) > +        pa_close(fd); > + > +    return -1; > +} > + > +static int udp_send_timing_packet(pa_raop_client *c, const uint32_t data[6], uint64_t received) { > +    uint32_t packet[8]; > +    struct timeval tv; > +    ssize_t written = 0; > +    uint64_t trs = 0; > +    int rv = 1; > + > +    memcpy(packet, udp_timming_header, sizeof(udp_timming_header)); > +    /* Copying originate timestamp from the incoming request packet. */ > +    packet[2] = data[4]; > +    packet[3] = data[5]; > +    /* Set the receive timestamp to reception time. */ > +    packet[4] = htonl(received >> 32); > +    packet[5] = htonl(received & 0xffffffff); > +    /* Set the transmit timestamp to current time. */ > +    trs = timeval_to_ntp(pa_rtclock_get(&tv)); > +    packet[6] = htonl(trs >> 32); > +    packet[7] = htonl(trs & 0xffffffff); > + > +    written = pa_loop_write(c->udp_timing_fd, packet, sizeof(packet), NULL); > +    if (written == sizeof(packet)) > +        rv = 0; > + > +    return rv; > +} > + > +static int udp_send_sync_packet(pa_raop_client *c, uint32_t stamp) { > +    const uint32_t delay = 88200; > +    uint32_t packet[5]; > +    struct timeval tv; > +    ssize_t written = 0; > +    uint64_t trs = 0; > +    int rv = 1; > + > +    memcpy(packet, udp_sync_header, sizeof(udp_sync_header)); > +    if (c->udp_first_packet) > +        packet[0] |= 0x10; > +    stamp -= delay; > +    packet[1] = htonl(stamp); > +    /* Set the transmited timestamp to current time. */ > +    trs = timeval_to_ntp(pa_rtclock_get(&tv)); > +    packet[2] = htonl(trs >> 32); > +    packet[3] = htonl(trs & 0xffffffff); > +    stamp += delay; > +    packet[4] = htonl(stamp); > + > +    written = pa_loop_write(c->udp_control_fd, packet, sizeof(packet), NULL); > +    if (written == sizeof(packet)) > +        rv = 0; > + > +    return rv; > +} > + > +static void udp_build_audio_header(pa_raop_client *c, uint32_t *buffer, size_t size) { > +    pa_assert(size >= sizeof(udp_audio_header)); > + > +    memcpy(buffer, udp_audio_header, sizeof(udp_audio_header)); > +    if (c->udp_first_packet) > +        buffer[0] |= htonl((uint32_t) 0x80 << 16); > +    buffer[0] |= htonl((uint32_t) c->seq); > +    buffer[1] = htonl(c->rtptime); > +    buffer[2] = htonl(c->udp_ssrc); > +} > + > +/* Audio retransmission header: > + * [0]    RTP v2: 0x80 > + * [1]    Payload type: 0x56 + 0x80 (marker == on) > + * [2]    Unknown; seems always 0x01 > + * [3]    Unknown; seems some random number around 0x20~0x40 > + * [4,5]  Original RTP header htons(0x8060) > + * [6,7]  Packet sequence number to be retransmitted > + * [8,11] Original RTP timestamp on the lost packet */ > +static void udp_build_retrans_header(uint32_t *buffer, size_t size, uint16_t seq_num) { > +    uint8_t x = 0x30; /* FIXME: what's this?? */ > + > +    pa_assert(size >= sizeof(uint32_t) * 2); > + > +    buffer[0] = htonl((uint32_t) 0x80000000 > +                      | ((uint32_t) UDP_PAYLOAD_RETRANSMIT_REPLY | 0x80) << 16 > +                      | 0x0100 > +                      | x); > +    buffer[1] = htonl((uint32_t) 0x80600000 | seq_num); > +} > + > +static ssize_t udp_send_audio_packet(pa_raop_client *c, bool retrans, uint8_t *buffer, size_t size) { > +    ssize_t length; > +    int fd = retrans ? c->udp_control_fd : c->udp_stream_fd; > + > +    length = pa_write(fd, buffer, size, NULL); > +    if (length < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { > +        pa_log_debug("Discarding audio packet %d due to EAGAIN", c->seq); > +        length = size; > +    } > +    return length; > +} > + > +static void do_rtsp_announce(pa_raop_client *c) { > +    char *key, *iv, *sac = NULL, *sdp; > +    uint16_t rand_data; > +    const char *ip; > +    char *url; > + > +    ip = pa_rtsp_localip(c->rtsp); > +    /* First of all set the url properly. */ > +    url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid); > +    pa_rtsp_set_url(c->rtsp, url); > +    pa_xfree(url); > + > +    /* UDP protocol does not need "Apple-Challenge" at announce. */ > +    if (c->protocol == RAOP_TCP) { > +        pa_random(&rand_data, sizeof(rand_data)); > +        pa_raop_base64_encode(&rand_data, sizeof(rand_data), &sac); > +        rtrimchar(sac, '='); > +        pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac); > +    } > + > +    if (c->encryption) { > +        iv = pa_raop_secret_get_iv(c->aes); > +        rtrimchar(iv, '='); > +        key = pa_raop_secret_get_key(c->aes); > +        rtrimchar(key, '='); > + > +        sdp = pa_sprintf_malloc( > +            "v=0\r\n" > +            "o=iTunes %s 0 IN IP4 %s\r\n" > +            "s=iTunes\r\n" > +            "c=IN IP4 %s\r\n" > +            "t=0 0\r\n" > +            "m=audio 0 RTP/AVP 96\r\n" > +            "a=rtpmap:96 AppleLossless\r\n" > +            "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n" > +            "a=rsaaeskey:%s\r\n" > +            "a=aesiv:%s\r\n", > +            c->sid, ip, c->host, > +            c->protocol == RAOP_TCP ? 4096 : UDP_FRAMES_PER_PACKET, > +            key, iv); > + > +        pa_xfree(iv); > +        pa_xfree(key); > +    } else { > +        sdp = pa_sprintf_malloc( > +            "v=0\r\n" > +            "o=iTunes %s 0 IN IP4 %s\r\n" > +            "s=iTunes\r\n" > +            "c=IN IP4 %s\r\n" > +            "t=0 0\r\n" > +            "m=audio 0 RTP/AVP 96\r\n" > +            "a=rtpmap:96 AppleLossless\r\n" > +            "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n", > +            c->sid, ip, c->host, > +            c->protocol == RAOP_TCP ? 4096 : UDP_FRAMES_PER_PACKET); > +    } > + > +    pa_rtsp_announce(c->rtsp, sdp); > + > +    pa_xfree(sac); > +    pa_xfree(sdp); > +} > + > +static void tcp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) { > +    pa_raop_client* c = userdata; > +    pa_assert(c); > +    pa_assert(rtsp); > +    pa_assert(rtsp == c->rtsp); > + > +    switch (state) { > +        case STATE_CONNECT: { > +            pa_log_debug("RAOP: CONNECTED"); > +            do_rtsp_announce(c); > +            break; > +        } > + > +        case STATE_OPTIONS: > +            pa_log_debug("RAOP: OPTIONS"); > +            break; > + > +        case STATE_ANNOUNCE: > +            pa_log_debug("RAOP: ANNOUNCED"); > +            pa_rtsp_remove_header(c->rtsp, "Apple-Challenge"); > +            pa_rtsp_setup(c->rtsp, NULL); > +            break; > + > +        case STATE_SETUP: { > +            char *aj = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status")); > +            pa_log_debug("RAOP: SETUP"); > +            if (aj) { > +                char *token, *pc; > +                char delimiters[] = ";"; > +                const char* token_state = NULL; > +                c->jack_type = JACK_TYPE_ANALOG; > +                c->jack_status = JACK_STATUS_DISCONNECTED; > + > +                while ((token = pa_split(aj, delimiters, &token_state))) { > +                    if ((pc = strstr(token, "="))) { > +                      *pc = 0; > +                      if (pa_streq(token, "type") && pa_streq(pc+1, "digital")) { > +                          c->jack_type = JACK_TYPE_DIGITAL; > +                      } > +                    } else { > +                        if (pa_streq(token, "connected")) > +                            c->jack_status = JACK_STATUS_CONNECTED; > +                    } > +                    pa_xfree(token); > +                } > +                pa_xfree(aj); > +            } else { > +                pa_log_warn("Audio Jack Status missing"); > +            } > +            pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime); > +            break; > +        } > + > +        case STATE_RECORD: { > +            uint32_t port = pa_rtsp_serverport(c->rtsp); > +            pa_log_debug("RAOP: RECORDED"); > + > +            if (!(c->tcp_sc = pa_socket_client_new_string(c->core->mainloop, true, c->host, port))) { > +                pa_log("failed to connect to server '%s:%d'", c->host, port); > +                return; > +            } > +            pa_socket_client_set_callback(c->tcp_sc, tcp_on_connection, c); > +            break; > +        } > + > +        case STATE_FLUSH: > +            pa_log_debug("RAOP: FLUSHED"); > +            break; > + > +        case STATE_TEARDOWN: > +            pa_log_debug("RAOP: TEARDOWN"); > +            break; > + > +        case STATE_SET_PARAMETER: > +            pa_log_debug("RAOP: SET_PARAMETER"); > +            break; > + > +        case STATE_DISCONNECTED: > +            pa_assert(c->tcp_closed_callback); > +            pa_assert(c->rtsp); > + > +            pa_log_debug("RTSP control channel closed"); > +            pa_rtsp_client_free(c->rtsp); > +            c->rtsp = NULL; > +            if (c->tcp_fd > 0) { > +                /* We do not close the fd, we leave it to the closed callback to do that */ > +                c->tcp_fd = -1; > +            } > +            if (c->tcp_sc) { > +                pa_socket_client_unref(c->tcp_sc); > +                c->tcp_sc = NULL; > +            } > +            pa_xfree(c->sid); > +            c->sid = NULL; > +            c->tcp_closed_callback(c->tcp_closed_userdata); > +            break; > +    } > +} > + > +static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist *headers, void *userdata) { > +    pa_raop_client *c = userdata; > + > +    pa_assert(c); > +    pa_assert(rtsp); > +    pa_assert(rtsp == c->rtsp); > + > +    switch (state) { > +        case STATE_CONNECT: { > +            uint16_t rand; > +            char *sac; > + > +            /* Set the Apple-Challenge key */ > +            pa_random(&rand, sizeof(rand)); > +            pa_raop_base64_encode(&rand, sizeof(rand), &sac); > +            rtrimchar(sac, '='); > +            pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac); > + > +            pa_rtsp_options(c->rtsp); > + > +            pa_xfree(sac); > +            break; > +        } > + > +        case STATE_OPTIONS: { > +            pa_log_debug("RAOP: OPTIONS"); > + > +            pa_rtsp_remove_header(c->rtsp, "Apple-Challenge"); > +            do_rtsp_announce(c); > +            break; > +        } > + > +        case STATE_ANNOUNCE: { > +            char *trs; > + > +            pa_assert(c->udp_control_fd < 0); > +            pa_assert(c->udp_timing_fd < 0); > + > +            c->udp_control_fd = open_bind_udp_socket(c, &c->udp_my_control_port); > +            if (c->udp_control_fd < 0) > +                goto error_announce; > +            c->udp_timing_fd  = open_bind_udp_socket(c, &c->udp_my_timing_port); > +            if (c->udp_timing_fd < 0) > +                goto error_announce; > + > +            trs = pa_sprintf_malloc("RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;control_port=%d;timing_port=%d", > +                c->udp_my_control_port, > +                c->udp_my_timing_port); > + > +            pa_rtsp_setup(c->rtsp, trs); > + > +            pa_xfree(trs); > +            break; > + > +        error_announce: > +            if (c->udp_control_fd > 0) { > +                pa_close(c->udp_control_fd); > +                c->udp_control_fd = -1; > +            } > +            if (c->udp_timing_fd > 0) { > +                pa_close(c->udp_timing_fd); > +                c->udp_timing_fd = -1; > +            } > + > +            pa_rtsp_client_free(c->rtsp); > +            c->rtsp = NULL; > + > +            c->udp_my_control_port     = UDP_DEFAULT_CONTROL_PORT; > +            c->udp_server_control_port = UDP_DEFAULT_CONTROL_PORT; > +            c->udp_my_timing_port      = UDP_DEFAULT_TIMING_PORT; > +            c->udp_server_timing_port  = UDP_DEFAULT_TIMING_PORT; > + > +            pa_log_error("aborting RTSP announce, failed creating required sockets"); > +        } > + > +        case STATE_SETUP: { > +            uint32_t stream_port = UDP_DEFAULT_AUDIO_PORT; > +            char *ajs, *trs, *token, *pc; > +            char delimiters[] = ";"; > +            const char *token_state = NULL; > +            uint32_t port = 0; > +            int ret; > + > +            pa_log_debug("RAOP: SETUP"); > + > +            ajs = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status")); > +            trs = pa_xstrdup(pa_headerlist_gets(headers, "Transport")); > + > +            if (ajs) { > +                c->jack_type = JACK_TYPE_ANALOG; > +                c->jack_status = JACK_STATUS_DISCONNECTED; > + > +                while ((token = pa_split(ajs, delimiters, &token_state))) { > +                    if ((pc = strstr(token, "="))) { > +                      *pc = 0; > +                      if (pa_streq(token, "type") && pa_streq(pc + 1, "digital")) > +                          c->jack_type = JACK_TYPE_DIGITAL; > +                    } else { > +                        if (pa_streq(token, "connected")) > +                            c->jack_status = JACK_STATUS_CONNECTED; > +                    } > +                    pa_xfree(token); > +                } > + > +            } else { > +                pa_log_warn("Audio-Jack-Status missing"); > +            } > + > +            token_state = NULL; > + > +            if (trs) { > +                /* Now parse out the server port component of the response. */ > +                while ((token = pa_split(trs, delimiters, &token_state))) { > +                    if ((pc = strstr(token, "="))) { > +                        *pc = 0; > +                        if (pa_streq(token, "control_port")) { > +                            port = 0; > +                            pa_atou(pc + 1, &port); > +                            c->udp_server_control_port = port; > +                        } > +                        if (pa_streq(token, "timing_port")) { > +                            port = 0; > +                            pa_atou(pc + 1, &port); > +                            c->udp_server_timing_port = port; > +                        } > +                        *pc = '='; > +                    } > +                    pa_xfree(token); > +                } > +            } else { > +                pa_log_warn("Transport missing"); > +            } > + > +            pa_xfree(ajs); > +            pa_xfree(trs); > + > +            stream_port = pa_rtsp_serverport(c->rtsp); > +            if (stream_port == 0) > +                goto error; > +            if (c->udp_server_control_port == 0 || c->udp_server_timing_port == 0) > +                goto error; > + > +            pa_log_debug("Using server_port=%d, control_port=%d & timing_port=%d", > +                stream_port, > +                c->udp_server_control_port, > +                c->udp_server_timing_port); > + > +            pa_assert(c->udp_stream_fd < 0); > +            pa_assert(c->udp_control_fd >= 0); > +            pa_assert(c->udp_timing_fd >= 0); > + > +            c->udp_stream_fd = connect_udp_socket(c, -1, stream_port); > +            if (c->udp_stream_fd <= 0) > +                goto error; > +            ret = connect_udp_socket(c, c->udp_control_fd, > +                                     c->udp_server_control_port); > +            if (ret < 0) > +                goto error; > +            ret = connect_udp_socket(c, c->udp_timing_fd, > +                                     c->udp_server_timing_port); > +            if (ret < 0) > +                goto error; > + > +            c->udp_setup_callback(c->udp_control_fd, c->udp_timing_fd, c->udp_setup_userdata); > +            pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime); > + > +            break; > + > +        error: > +            if (c->udp_stream_fd > 0) { > +                pa_close(c->udp_stream_fd); > +                c->udp_stream_fd = -1; > +            } > +            if (c->udp_control_fd > 0) { > +                pa_close(c->udp_control_fd); > +                c->udp_control_fd = -1; > +            } > +            if (c->udp_timing_fd > 0) { > +                pa_close(c->udp_timing_fd); > +                c->udp_timing_fd = -1; > +            } > + > +            pa_rtsp_client_free(c->rtsp); > +            c->rtsp = NULL; > + > +            c->udp_my_control_port     = UDP_DEFAULT_CONTROL_PORT; > +            c->udp_server_control_port = UDP_DEFAULT_CONTROL_PORT; > +            c->udp_my_timing_port      = UDP_DEFAULT_TIMING_PORT; > +            c->udp_server_timing_port  = UDP_DEFAULT_TIMING_PORT; > + > +            pa_log_error("aborting RTSP setup, failed creating required sockets"); > + > +            break; > +        } > + > +        case STATE_RECORD: { > +            int32_t latency = 0; > +            uint32_t rand; > +            char *alt; > + > +            pa_log_debug("RAOP: RECORD"); > + > +            alt = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Latency")); > +            /* Generate a random synchronization source identifier from this session. */ > +            pa_random(&rand, sizeof(rand)); > +            c->udp_ssrc = rand; > + > +            if (alt) > +                pa_atoi(alt, &latency); > + > +            c->udp_first_packet = true; > +            c->udp_sync_count = 0; > + > +            c->is_recording = true; > + > +            c->udp_record_callback(c->udp_setup_userdata); > + > +            pa_xfree(alt); > +            break; > +        } > + > +        case STATE_SET_PARAMETER: { > +            pa_log_debug("RAOP: SET_PARAMETER"); > + > +            break; > +        } > + > +        case STATE_FLUSH: { > +            pa_log_debug("RAOP: FLUSHED"); > + > +            c->is_recording = false; > + > +            break; > +        } > + > +        case STATE_TEARDOWN: { > +            pa_log_debug("RAOP: TEARDOWN"); > +            pa_assert(c->udp_disconnected_callback); > +            pa_assert(c->rtsp); > + > +            c->is_recording = false; > + > +            pa_rtsp_disconnect(c->rtsp); > + > +            if (c->udp_stream_fd > 0) { > +                pa_close(c->udp_stream_fd); > +                c->udp_stream_fd = -1; > +            } > + > +            pa_log_debug("RTSP control channel closed (teardown)"); > + > +            pa_raop_pb_clear(c->packet_buffer); > + > +            pa_rtsp_client_free(c->rtsp); > +            pa_xfree(c->sid); > +            c->rtsp = NULL; > +            c->sid = NULL; > + > +            /* > +              Callback for cleanup -- e.g. pollfd > + > +              Share the disconnected callback since TEARDOWN event > +              is essentially equivalent to DISCONNECTED. > +              In case some special treatment turns out to be required > +              for TEARDOWN in future, a new callback function may be > +              defined and used. > +            */ > +            c->udp_disconnected_callback(c->udp_disconnected_userdata); > + > +            /* Control and timing fds are closed by udp_sink_process_msg, > +               after it disables poll */ > +            c->udp_control_fd = -1; > +            c->udp_timing_fd = -1; > + > +            break; > +        } > + > +        case STATE_DISCONNECTED: { > +            pa_log_debug("RAOP: DISCONNECTED"); > +            pa_assert(c->udp_disconnected_callback); > +            pa_assert(c->rtsp); > + > +            if (c->udp_stream_fd > 0) { > +                pa_close(c->udp_stream_fd); > +                c->udp_stream_fd = -1; > +            } > + > +            pa_log_debug("RTSP control channel closed (disconnected)"); > + > +            pa_raop_pb_clear(c->packet_buffer); > + > +            pa_rtsp_client_free(c->rtsp); > +            pa_xfree(c->sid); > +            c->rtsp = NULL; > +            c->sid = NULL; > + > +            c->udp_disconnected_callback(c->udp_disconnected_userdata); > +            /* Control and timing fds are closed by udp_sink_process_msg, > +               after it disables poll */ > +            c->udp_control_fd = -1; > +            c->udp_timing_fd = -1; > + > +            break; > +        } > +    } > +} > + > +pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol) { > +    pa_raop_client* c; > +    pa_parsed_address a; > +    pa_sample_spec ss; > + > +    pa_assert(core); > +    pa_assert(host); > + > +    if (pa_parse_address(host, &a) < 0 || a.type == PA_PARSED_ADDRESS_UNIX) > +        return NULL; > + > +    c = pa_xnew0(pa_raop_client, 1); > +    c->core = core; > +    c->tcp_fd = -1; > +    c->protocol = protocol; > +    c->udp_stream_fd = -1; > +    c->udp_control_fd = -1; > +    c->udp_timing_fd = -1; > + > +    c->encryption = 0; > +    c->aes = NULL; > + > +    c->udp_my_control_port     = UDP_DEFAULT_CONTROL_PORT; > +    c->udp_server_control_port = UDP_DEFAULT_CONTROL_PORT; > +    c->udp_my_timing_port      = UDP_DEFAULT_TIMING_PORT; > +    c->udp_server_timing_port  = UDP_DEFAULT_TIMING_PORT; > + > +    c->host = pa_xstrdup(a.path_or_host); > +    if (a.port) > +        c->port = a.port; > +    else > +        c->port = DEFAULT_RAOP_PORT; > + > +    c->is_recording = false; > +    c->udp_first_packet = true; > + > +    ss = core->default_sample_spec; > +    /* Packet sync interval should be around 1s. */ > +    c->udp_sync_interval = ss.rate / UDP_FRAMES_PER_PACKET; > +    c->udp_sync_count = 0; > + > +    if (c->protocol == RAOP_TCP) { > +        if (pa_raop_client_connect(c)) { > +            pa_raop_client_free(c); > +            return NULL; > +        } > +    } else > +        c->packet_buffer = pa_raop_pb_new(UDP_DEFAULT_PKT_BUF_SIZE); > + > +    return c; > +} > + > +void pa_raop_client_free(pa_raop_client *c) { > +    pa_assert(c); > + > +    pa_raop_pb_delete(c->packet_buffer); > + > +    if (c->sid) > +        pa_xfree(c->sid); > +    if (c->aes) > +        pa_raop_secret_free(c->aes); > +    if (c->rtsp) > +        pa_rtsp_client_free(c->rtsp); > +    pa_xfree(c->host); > + > +    pa_xfree(c); > +} > + > +int pa_raop_client_connect(pa_raop_client *c) { > +    char *sci; > +    struct { > +        uint32_t a; > +        uint32_t b; > +        uint32_t c; > +    } rand_data; > + > +    pa_assert(c); > + > +    if (c->rtsp) { > +        pa_log_debug("Connection already in progress"); > +        return 0; > +    } > + > +    if (c->protocol == RAOP_TCP) > +        c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); > +    else > +        c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, "iTunes/7.6.2 (Windows; N;)"); > + > +    /* Generate random instance id. */ > +    pa_random(&rand_data, sizeof(rand_data)); > +    c->sid = pa_sprintf_malloc("%u", rand_data.a); > +    sci = pa_sprintf_malloc("%08x%08x",rand_data.b, rand_data.c); > +    pa_rtsp_add_header(c->rtsp, "Client-Instance", sci); > +    pa_xfree(sci); > +    if (c->protocol == RAOP_TCP) > +        pa_rtsp_set_callback(c->rtsp, tcp_rtsp_cb, c); > +    else > +        pa_rtsp_set_callback(c->rtsp, udp_rtsp_cb, c); > + > +    c->is_recording = false; > + > +    return pa_rtsp_connect(c->rtsp); > +} > + > +int pa_raop_client_flush(pa_raop_client *c) { > +    int rv = 0; > + > +    pa_assert(c); > + > +    if (c->rtsp != NULL) { > +        rv = pa_rtsp_flush(c->rtsp, c->seq, c->rtptime); > +        c->udp_sync_count = 0; > +    } > + > +    return rv; > +} > + > +int pa_raop_client_teardown(pa_raop_client *c) { > +    int rv = 0; > + > +    pa_assert(c); > + > +    if (c->rtsp != NULL) > +        rv = pa_rtsp_teardown(c->rtsp); > + > +    return rv; > +} > + > +int pa_raop_client_udp_is_alive(pa_raop_client *c) { > +    int rv = 0; > + > +    pa_assert(c); > + > +    if (c->udp_stream_fd > 0) > +        rv = 1; > + > +    return rv; > +} > + > +int pa_raop_client_udp_can_stream(pa_raop_client *c) { > +    int rv = 0; > + > +    pa_assert(c); > + > +    if (c->is_recording && c->udp_stream_fd > 0) > +        rv = 1; > + > +    return rv; > +} > + > +int pa_raop_client_udp_stream(pa_raop_client *c) { > +    int rv = 0; > + > +    pa_assert(c); > + > +    if (c->rtsp != NULL && c->udp_stream_fd > 0) { > +        if (!c->is_recording) { > +            c->udp_first_packet = true; > +            c->udp_sync_count = 0; > + > +            c->is_recording = true; > +         } > + > +        rv = 1; > +    } > + > +    return rv; > +} > + > +int pa_raop_client_udp_handle_timing_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) { > +    const uint32_t * data = NULL; > +    uint8_t payload = 0; > +    struct timeval tv; > +    uint64_t rci = 0; > +    int rv = 0; > + > +    pa_assert(c); > +    pa_assert(packet); > + > +    /* Timing packets are 32 bytes long: 1 x 8 RTP header (no ssrc) + 3 x 8 NTP timestamps. */ > +    if (size != 32 || packet[0] != 0x80) > +    { > +        pa_log_debug("Received an invalid timing packet."); > +        return 1; > +    } > + > +    data = (uint32_t *) (packet + sizeof(udp_timming_header)); > +    rci = timeval_to_ntp(pa_rtclock_get(&tv)); > +    /* The market bit is always set (see rfc3550 for packet structure) ! */ > +    payload = packet[1] ^ 0x80; > +    switch (payload) { > +        case UDP_PAYLOAD_TIMING_REQUEST: > +            rv = udp_send_timing_packet(c, data, rci); > +            break; > +        case UDP_PAYLOAD_TIMING_RESPONSE: > +        default: > +            pa_log_debug("Got an unexpected payload type on timing channel !"); > +            return 1; > +    } > + > +    return rv; > +} > + > +static int udp_resend_packets(pa_raop_client *c, uint16_t seq_num, uint16_t num_packets) { > +    int rv = -1; > +    uint8_t *data = NULL; > +    ssize_t len = 0; > +    int i = 0; > + > +    pa_assert(c); > +    pa_assert(num_packets > 0); > +    pa_assert(c->packet_buffer); > + > +    for (i = seq_num; i < seq_num + num_packets; i++) { > +        len = pa_raop_pb_read_packet(c->packet_buffer, i, (uint8_t **) &data); > + > +        if (len > 0) { > +            ssize_t r; > + > +            /* Obtained buffer has a header room for retransmission > +               header */ > +            udp_build_retrans_header((uint32_t *) data, len, seq_num); > +            r = udp_send_audio_packet(c, true /* retrans */, data, len); > +            if (r == len) > +                rv = 0; > +            else > +                rv = -1; > +        } else > +            pa_log_debug("Packet not found in retrans buffer: %u", i); > +    } > + > +    return rv; > +} > + > +int pa_raop_client_udp_handle_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) { > +    uint8_t payload = 0; > +    int rv = 0; > + > +    uint16_t seq_num; > +    uint16_t num_packets; > + > +    pa_assert(c); > +    pa_assert(packet); > + > +    if ((size != 20 && size != 8) || packet[0] != 0x80) > +    { > +        pa_log_debug("Received an invalid control packet."); > +        return 1; > +    } > + > +    /* The market bit is always set (see rfc3550 for packet structure) ! */ > + > +    payload = packet[1] ^ 0x80; > +    switch (payload) { > +        case UDP_PAYLOAD_RETRANSMIT_REQUEST: > +            pa_assert(size == 8); > + > +            /* Requested start sequence number */ > +            seq_num = ((uint16_t) packet[4]) << 8; > +            seq_num |= (uint16_t) packet[5]; > +            /* Number of requested packets starting at requested seq. number */ > +            num_packets = (uint16_t) packet[6] << 8; > +            num_packets |= (uint16_t) packet[7]; > +            pa_log_debug("Resending %d packets starting at %d", num_packets, seq_num); > +            rv = udp_resend_packets(c, seq_num, num_packets); > +            break; > + > +        case UDP_PAYLOAD_RETRANSMIT_REPLY: > +            pa_log_debug("Received a retransmit reply packet on control port (this should never happen)"); > +            break; > + > +        default: > +            pa_log_debug("Got an unexpected payload type on control channel: %u !", payload); > +            return 1; > +    } > + > +    return rv; > +} > + > +int pa_raop_client_udp_get_blocks_size(pa_raop_client *c, size_t *size) { > +    int rv = 0; > + > +    pa_assert(c); > +    pa_assert(size); > + > +    *size = UDP_FRAMES_PER_PACKET; > + > +    return rv; > +} > + > +ssize_t pa_raop_client_udp_send_audio_packet(pa_raop_client *c, pa_memchunk *block) { > +    uint8_t *buf = NULL; > +    ssize_t len; > + > +    pa_assert(c); > +    pa_assert(block); > + > +    /* Sync RTP & NTP timestamp if required. */ > +    if (c->udp_first_packet || c->udp_sync_count >= c->udp_sync_interval) { > +        udp_send_sync_packet(c, c->rtptime); > +        c->udp_sync_count = 0; > +    } else { > +        c->udp_sync_count++; > +    } > + > +    buf = pa_memblock_acquire(block->memblock); > +    pa_assert(buf); > +    pa_assert(block->length > 0); > +    udp_build_audio_header(c, (uint32_t *) (buf + block->index), block->length); > +    len = udp_send_audio_packet(c, false, buf + block->index, block->length); > + > +    /* Store packet for resending in the packet buffer */ > +    pa_raop_pb_write_packet(c->packet_buffer, c->seq, buf + block->index, > +                            block->length); > + > +    c->seq++; > + > +    pa_memblock_release(block->memblock); > + > +    if (len > 0) { > +        pa_assert((size_t) len <= block->length); > +        /* UDP packet has to be sent at once, so it is meaningless to > +           preseve the partial data > +           FIXME: This won't happen at least in *NIX systems?? */ > +        if (block->length > (size_t) len) { > +            pa_log_warn("Tried to send %zu bytes but managed to send %zu bytes", block->length, len); > +            len = block->length; > +        } > +        block->index += block->length; > +        block->length = 0; > +    } > + > +    if (c->udp_first_packet) > +        c->udp_first_packet = false; > + > +    return len; > +} > + > +/* Adjust volume so that it fits into VOLUME_DEF <= v <= 0 dB */ > +pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume) { > +    double minv, maxv; > + > +    if (c->protocol != RAOP_UDP) > +        return volume; > + > +    maxv = pa_sw_volume_from_dB(0.0); > +    minv = maxv * pow(10.0, (double) VOLUME_DEF / 60.0); > + > +    return volume - volume * (minv / maxv) + minv; > +} > + > +int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) { > +    int rv = 0; > +    double db; > +    char *param; > + > +    pa_assert(c); > + > +    db = pa_sw_volume_to_dB(volume); > +    if (db < VOLUME_MIN) > +        db = VOLUME_MIN; > +    else if (db > VOLUME_MAX) > +        db = VOLUME_MAX; > + > +    pa_log_debug("volume=%u db=%.6f", volume, db); > + > +    param = pa_sprintf_malloc("volume: %0.6f\r\n",  db); > + > +    /* We just hit and hope, cannot wait for the callback. */ > +    if (c->rtsp != NULL && pa_rtsp_exec_ready(c->rtsp)) > +        rv = pa_rtsp_setparameter(c->rtsp, param); > +    pa_xfree(param); > + > +    return rv; > +} > + > +int pa_raop_client_encode_sample(pa_raop_client *c, pa_memchunk *raw, pa_memchunk *encoded) { > +    uint16_t len; > +    size_t bufmax; > +    uint8_t *bp, bpos; > +    uint8_t *ibp, *maxibp; > +    int size; > +    uint8_t *b, *p; > +    uint32_t bsize; > +    size_t length; > +    const uint8_t *header; > +    int header_size; > + > +    pa_assert(c); > +    pa_assert(raw); > +    pa_assert(raw->memblock); > +    pa_assert(raw->length > 0); > +    pa_assert(encoded); > + > +    if (c->protocol == RAOP_TCP) { > +        header = tcp_audio_header; > +        header_size = sizeof(tcp_audio_header); > +    } else { > +        header = udp_audio_header; > +        header_size = sizeof(udp_audio_header); > +    } > + > +    /* We have to send 4 byte chunks */ > +    bsize = (int)(raw->length / 4); > +    length = bsize * 4; > + > +    /* Leave 16 bytes extra to allow for the ALAC header which is about 55 bits. */ > +    bufmax = length + header_size + 16; > +    pa_memchunk_reset(encoded); > +    encoded->memblock = pa_memblock_new(c->core->mempool, bufmax); > +    b = pa_memblock_acquire(encoded->memblock); > +    memcpy(b, header, header_size); > + > +    /* Now write the actual samples. */ > +    bp = b + header_size; > +    size = bpos = 0; > +    bit_writer(&bp,&bpos,&size,1,3); /* channel=1, stereo */ > +    bit_writer(&bp,&bpos,&size,0,4); /* Unknown */ > +    bit_writer(&bp,&bpos,&size,0,8); /* Unknown */ > +    bit_writer(&bp,&bpos,&size,0,4); /* Unknown */ > +    bit_writer(&bp,&bpos,&size,1,1); /* Hassize */ > +    bit_writer(&bp,&bpos,&size,0,2); /* Unused */ > +    bit_writer(&bp,&bpos,&size,1,1); /* Is-not-compressed */ > + > +    /* Size of data, integer, big endian. */ > +    bit_writer(&bp,&bpos,&size,(bsize>>24)&0xff,8); > +    bit_writer(&bp,&bpos,&size,(bsize>>16)&0xff,8); > +    bit_writer(&bp,&bpos,&size,(bsize>>8)&0xff,8); > +    bit_writer(&bp,&bpos,&size,(bsize)&0xff,8); > + > +    p = pa_memblock_acquire(raw->memblock); > +    p += raw->index; > +    ibp = p; > +    maxibp = p + raw->length - 4; > +    while (ibp <= maxibp) { > +        /* Byte swap stereo data. */ > +        bit_writer(&bp,&bpos,&size,*(ibp+1),8); > +        bit_writer(&bp,&bpos,&size,*(ibp+0),8); > +        bit_writer(&bp,&bpos,&size,*(ibp+3),8); > +        bit_writer(&bp,&bpos,&size,*(ibp+2),8); > +        ibp += 4; > +        raw->index += 4; > +        raw->length -= 4; > +    } > +    if (c->protocol == RAOP_UDP) > +        c->rtptime += (ibp - p) / 4; > +    pa_memblock_release(raw->memblock); > +    encoded->length = header_size + size; > + > +    if (c->protocol == RAOP_TCP) { > +        /* Store the length (endian swapped: make this better). */ > +        len = size + header_size - 4; > +        *(b + 2) = len >> 8; > +        *(b + 3) = len & 0xff; > +    } > + > +    if (c->encryption) { > +        /* Encrypt our data. */ > +        pa_raop_aes_encrypt(c->aes, (b + header_size), size); > +    } > + > +    /* We're done with the chunk. */ > +    pa_memblock_release(encoded->memblock); > + > +    return 0; > +} > + > +void pa_raop_client_tcp_set_callback(pa_raop_client *c, pa_raop_client_cb_t callback, void *userdata) { > +    pa_assert(c); > + > +    c->tcp_callback = callback; > +    c->tcp_userdata = userdata; > +} > + > +void pa_raop_client_tcp_set_closed_callback(pa_raop_client *c, pa_raop_client_closed_cb_t callback, void *userdata) { > +    pa_assert(c); > + > +    c->tcp_closed_callback = callback; > +    c->tcp_closed_userdata = userdata; > +} > + > +void pa_raop_client_set_encryption(pa_raop_client *c, int encryption) { > +    pa_assert(c); > + > +    c->encryption = encryption; > +    if (c->encryption) > +        c->aes = pa_raop_secret_new(); > +} > + > +void pa_raop_client_udp_set_setup_callback(pa_raop_client *c, pa_raop_client_setup_cb_t callback, void *userdata) { > +    pa_assert(c); > + > +    c->udp_setup_callback = callback; > +    c->udp_setup_userdata = userdata; > +} > + > +void pa_raop_client_udp_set_record_callback(pa_raop_client *c, pa_raop_client_record_cb_t callback, void *userdata) { > +    pa_assert(c); > + > +    c->udp_record_callback = callback; > +    c->udp_record_userdata = userdata; > +} > + > +void pa_raop_client_udp_set_disconnected_callback(pa_raop_client *c, pa_raop_client_disconnected_cb_t callback, void *userdata) { > +    pa_assert(c); > + > +    c->udp_disconnected_callback = callback; > +    c->udp_disconnected_userdata = userdata; > +} > diff --git a/src/modules/raop/raop_client.h b/src/modules/raop/raop_client.h > index 5d0bb14..e208349 100644 > --- a/src/modules/raop/raop_client.h > +++ b/src/modules/raop/raop_client.h > @@ -37,10 +37,12 @@ typedef struct pa_raop_client pa_raop_client; >  pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol); >  void pa_raop_client_free(pa_raop_client *c); >  > +int pa_raop_client_authenticate (pa_raop_client *c, const char *password); >  int pa_raop_client_connect(pa_raop_client *c); >  int pa_raop_client_flush(pa_raop_client *c); >  int pa_raop_client_teardown(pa_raop_client *c); >  > +int pa_raop_client_udp_is_authenticated(pa_raop_client *c); >  int pa_raop_client_udp_is_alive(pa_raop_client *c); >  int pa_raop_client_udp_can_stream(pa_raop_client *c); >  int pa_raop_client_udp_stream(pa_raop_client *c); > @@ -61,6 +63,8 @@ void pa_raop_client_tcp_set_callback(pa_raop_client *c, pa_raop_client_cb_t call >  typedef void (*pa_raop_client_closed_cb_t)(void *userdata); >  void pa_raop_client_tcp_set_closed_callback(pa_raop_client *c, pa_raop_client_closed_cb_t callback, void *userdata); >  > +typedef void (*pa_raop_client_auth_cb_t)(int status, void *userdata); > +void pa_raop_client_udp_set_auth_callback(pa_raop_client *c, pa_raop_client_auth_cb_t callback, void *userdata); >  >  typedef void (*pa_raop_client_setup_cb_t)(int control_fd, int timing_fd, void *userdata); >  void pa_raop_client_udp_set_setup_callback(pa_raop_client *c, pa_raop_client_setup_cb_t callback, void *userdata); > diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c > index 198417e..c6d9ac6 100644 > --- a/src/modules/rtp/rtsp_client.c > +++ b/src/modules/rtp/rtsp_client.c > @@ -59,6 +59,7 @@ struct pa_rtsp_client { >      const char *useragent; >  >      pa_rtsp_state state; > +    pa_rtsp_status status; >      uint8_t waiting; >  >      pa_headerlist* headers; > @@ -119,8 +120,8 @@ void pa_rtsp_client_free(pa_rtsp_client *c) { >  } >  >  static void headers_read(pa_rtsp_client *c) { > -    char* token; >      char delimiters[] = ";"; > +    char* token; >  >      pa_assert(c); >      pa_assert(c->response_headers); > @@ -165,14 +166,14 @@ static void headers_read(pa_rtsp_client *c) { >      } >  >      /* Call our callback */ > -    c->callback(c, c->state, c->response_headers, c->userdata); > +    c->callback(c, c->state, c->status, c->response_headers, c->userdata); >  } >  >  static void line_callback(pa_ioline *line, const char *s, void *userdata) { > +    pa_rtsp_client *c = userdata; >      char *delimpos; >      char *s2, *s2p; >  > -    pa_rtsp_client *c = userdata; >      pa_assert(line); >      pa_assert(c); >      pa_assert(c->callback); > @@ -180,7 +181,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { >      if (!s) { >          /* Keep the ioline/iochannel open as they will be freed automatically */ >          c->ioline = NULL; > -        c->callback(c, STATE_DISCONNECTED, NULL, c->userdata); > +        c->callback(c, STATE_DISCONNECTED, STATUS_NO_RESPONSE, NULL, c->userdata); >          return; >      } >  > @@ -191,17 +192,35 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { >          *s2p = '\0'; >          s2p -= 1; >      } > + >      if (c->waiting && pa_streq(s2, "RTSP/1.0 200 OK")) { > +        if (c->response_headers) > +            pa_headerlist_free(c->response_headers); > +        c->response_headers = pa_headerlist_new(); > + > +        c->status = STATUS_OK; >          c->waiting = 0; > +        goto exit; > +    } else if (c->waiting && pa_streq(s2, "RTSP/1.0 401 Unauthorized")) { >          if (c->response_headers) >              pa_headerlist_free(c->response_headers); >          c->response_headers = pa_headerlist_new(); > + > +        c->status = STATUS_UNAUTHORIZED; > +        c->waiting = 0; >          goto exit; > -    } > -    if (c->waiting) { > -        pa_log_warn("Unexpected response: %s", s2); > +    } else if (c->waiting) { > +        pa_log_warn("Unexpected/Unhandled response: %s", s2); > + > +        if (pa_streq(s2, "RTSP/1.0 400 Bad Request")) > +            c->status = STATUS_BAD_REQUEST; > +        else if (pa_streq(s2, "RTSP/1.0 500 Internal Server Error")) > +            c->status = STATUS_INTERNAL_ERROR; > +        else > +            c->status = STATUS_NO_RESPONSE; >          goto exit; >      } > + >      if (!strlen(s2)) { >          /* End of headers */ >          /* We will have a header left from our looping iteration, so add it in :) */ > @@ -228,7 +247,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { >      if (c->last_header && ' ' == s2[0]) { >          pa_assert(c->header_buffer); >  > -        /* Add this line to the buffer (sans the space. */ > +        /* Add this line to the buffer (sans the space) */ >          pa_strbuf_puts(c->header_buffer, &(s2[1])); >          goto exit; >      } > @@ -270,6 +289,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { >  >      /* Save the header name */ >      c->last_header = pa_xstrdup(s2); > + >    exit: >      pa_xfree(s2); >  } > @@ -317,7 +337,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata >      pa_log_debug("Established RTSP connection from local ip %s", c->localip); >  >      if (c->callback) > -        c->callback(c, c->state, NULL, c->userdata); > +        c->callback(c, c->state, STATUS_OK, NULL, c->userdata); >  } >  >  int pa_rtsp_connect(pa_rtsp_client *c) { > @@ -336,6 +356,7 @@ int pa_rtsp_connect(pa_rtsp_client *c) { >      pa_socket_client_set_callback(c->sc, on_connection, c); >      c->waiting = 1; >      c->state = STATE_CONNECT; > +    c->status = STATUS_NO_RESPONSE; >      return 0; >  } >  > @@ -368,12 +389,25 @@ uint32_t pa_rtsp_serverport(pa_rtsp_client *c) { >      return c->rtp_port; >  } >  > +bool pa_rtsp_exec_ready(const pa_rtsp_client *c) { > +    pa_assert(c); > + > +    return c->url != NULL && c->ioline != NULL; > +} > + >  void pa_rtsp_set_url(pa_rtsp_client *c, const char *url) { >      pa_assert(c); >  >      c->url = pa_xstrdup(url); >  } >  > +bool pa_rtsp_has_header(pa_rtsp_client *c, const char *key) { > +    pa_assert(c); > +    pa_assert(key); > + > +    return pa_headerlist_contains(c->headers, key); > +} > + >  void pa_rtsp_add_header(pa_rtsp_client *c, const char *key, const char *value) { >      pa_assert(c); >      pa_assert(key); > @@ -382,17 +416,18 @@ void pa_rtsp_add_header(pa_rtsp_client *c, const char *key, const char *value) { >      pa_headerlist_puts(c->headers, key, value); >  } >  > -void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key) { > +const char* pa_rtsp_get_header(pa_rtsp_client *c, const char *key) { >      pa_assert(c); >      pa_assert(key); >  > -    pa_headerlist_remove(c->headers, key); > +    return pa_headerlist_gets(c->headers, key); >  } >  > -bool pa_rtsp_exec_ready(const pa_rtsp_client *c) { > +void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key) { >      pa_assert(c); > +    pa_assert(key); >  > -    return c->url != NULL && c->ioline != NULL; > +    pa_headerlist_remove(c->headers, key); >  } >  >  static int rtsp_exec(pa_rtsp_client *c, const char *cmd, > @@ -527,17 +562,6 @@ int pa_rtsp_record(pa_rtsp_client *c, uint16_t *seq, uint32_t *rtptime) { >      return rv; >  } >  > -int pa_rtsp_teardown(pa_rtsp_client *c) { > -    int rv; > - > -    pa_assert(c); > - > -    c->state = STATE_TEARDOWN; > -    rv = rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL); > - > -    return rv; > -} > - >  int pa_rtsp_setparameter(pa_rtsp_client *c, const char *param) { >      int rv; >  > @@ -570,3 +594,14 @@ int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime) { >      pa_headerlist_free(headers); >      return rv; >  } > + > +int pa_rtsp_teardown(pa_rtsp_client *c) { > +    int rv; > + > +    pa_assert(c); > + > +    c->state = STATE_TEARDOWN; > +    rv = rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL); > + > +    return rv; > +} > diff --git a/src/modules/rtp/rtsp_client.h b/src/modules/rtp/rtsp_client.h > index abc60ee..4a35851 100644 > --- a/src/modules/rtp/rtsp_client.h > +++ b/src/modules/rtp/rtsp_client.h > @@ -31,43 +31,53 @@ >  #include "headerlist.h" >  >  typedef struct pa_rtsp_client pa_rtsp_client; > + >  typedef enum { >    STATE_CONNECT, >    STATE_OPTIONS, >    STATE_ANNOUNCE, >    STATE_SETUP, >    STATE_RECORD, > +  STATE_SET_PARAMETER, >    STATE_FLUSH, >    STATE_TEARDOWN, > -  STATE_SET_PARAMETER, >    STATE_DISCONNECTED >  } pa_rtsp_state; > -typedef void (*pa_rtsp_cb_t)(pa_rtsp_client *c, pa_rtsp_state state, pa_headerlist *hl, void *userdata); > + > +typedef enum { > +  STATUS_OK             = 200, > +  STATUS_BAD_REQUEST    = 400, > +  STATUS_UNAUTHORIZED   = 401, > +  STATUS_NO_RESPONSE    = 444, > +  STATUS_INTERNAL_ERROR = 500 > +} pa_rtsp_status; > + > +typedef void (*pa_rtsp_cb_t)(pa_rtsp_client *c, pa_rtsp_state state, pa_rtsp_status code, pa_headerlist *headers, void *userdata); >  >  pa_rtsp_client* pa_rtsp_client_new(pa_mainloop_api *mainloop, const char *hostname, uint16_t port, const char *useragent); >  void pa_rtsp_client_free(pa_rtsp_client *c); >  >  int pa_rtsp_connect(pa_rtsp_client *c); >  void pa_rtsp_set_callback(pa_rtsp_client *c, pa_rtsp_cb_t callback, void *userdata); > - >  void pa_rtsp_disconnect(pa_rtsp_client *c); >  >  const char* pa_rtsp_localip(pa_rtsp_client *c); >  uint32_t pa_rtsp_serverport(pa_rtsp_client *c); > +bool pa_rtsp_exec_ready(const pa_rtsp_client *c); > + >  void pa_rtsp_set_url(pa_rtsp_client *c, const char *url); > + > +bool pa_rtsp_has_header(pa_rtsp_client *c, const char *key); >  void pa_rtsp_add_header(pa_rtsp_client *c, const char *key, const char *value); > +const char* pa_rtsp_get_header(pa_rtsp_client *c, const char *key); >  void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key); >  > -bool pa_rtsp_exec_ready(const pa_rtsp_client *c); > - >  int pa_rtsp_options(pa_rtsp_client *c); >  int pa_rtsp_announce(pa_rtsp_client *c, const char *sdp); > - >  int pa_rtsp_setup(pa_rtsp_client *c, const char *transport); >  int pa_rtsp_record(pa_rtsp_client *c, uint16_t *seq, uint32_t *rtptime); > -int pa_rtsp_teardown(pa_rtsp_client *c); > - >  int pa_rtsp_setparameter(pa_rtsp_client *c, const char *param); >  int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime); > +int pa_rtsp_teardown(pa_rtsp_client *c); >  >  #endif