[pulseaudio-discuss] [PATCH v6 16/37] raop: Add the core implementation of RAOP authentication
Hajime Fujita
crisp.fujita at nifty.com
Wed Feb 10 05:57:03 UTC 2016
Arun Raghavan wrote:
> 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.
Oops. Good catch.
Thank you,
Hajime
>
> -- 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
> _______________________________________________
> pulseaudio-discuss mailing list
> pulseaudio-discuss at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
>
More information about the pulseaudio-discuss
mailing list