diff --git a/src/modules/module-devd-detect.c b/src/modules/module-devd-detect.c index db3c6453b..d298f892f 100644 --- a/src/modules/module-devd-detect.c +++ b/src/modules/module-devd-detect.c @@ -49,7 +49,7 @@ struct userdata { pa_ioline *line; }; -static void line_callback(pa_ioline *line, const char *s, void *userdata) { +static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata) { struct userdata *u = userdata; pa_module *m = NULL; unsigned devnum; diff --git a/src/modules/raop/meson.build b/src/modules/raop/meson.build index 3df07dcba..7bfcb751d 100644 --- a/src/modules/raop/meson.build +++ b/src/modules/raop/meson.build @@ -3,7 +3,6 @@ libraop_sources = [ 'raop-crypto.c', 'raop-packet-buffer.c', 'raop-sink.c', - 'raop-util.c', ] libraop_headers = [ @@ -11,7 +10,7 @@ libraop_headers = [ 'raop-crypto.h', 'raop-packet-buffer.h', 'raop-sink.h', - 'raop-util.h', + 'raop-common.h', ] # FIXME: meson doesn't support multiple RPATH arguments currently diff --git a/src/modules/raop/module-raop-discover.c b/src/modules/raop/module-raop-discover.c index 2bbf46657..dae3a0a4b 100644 --- a/src/modules/raop/module-raop-discover.c +++ b/src/modules/raop/module-raop-discover.c @@ -43,7 +43,7 @@ #include #include -#include "raop-util.h" +#include "raop-common.h" PA_MODULE_AUTHOR("Colin Guthrie"); PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices"); diff --git a/src/modules/raop/raop-client.c b/src/modules/raop/raop-client.c index 2dc3145f2..8e0adcff5 100644 --- a/src/modules/raop/raop-client.c +++ b/src/modules/raop/raop-client.c @@ -58,12 +58,13 @@ #include #include -#include +#include +#include #include "raop-client.h" #include "raop-packet-buffer.h" #include "raop-crypto.h" -#include "raop-util.h" +#include "raop-common.h" #define DEFAULT_RAOP_PORT 5000 @@ -98,8 +99,10 @@ struct pa_raop_client { uint16_t port; pa_rtsp_client *rtsp; char *sci, *sid; - char *password; + const char *password; + bool waiting; bool autoreconnect; + bool has_post; pa_raop_protocol_t protocol; pa_raop_encryption_t encryption; @@ -113,11 +116,13 @@ struct pa_raop_client { int udp_cfd; int udp_tfd; + uint16_t udp_tport; + pa_raop_packet_buffer *pbuf; uint16_t seq; uint32_t rtptime; - bool is_recording; + bool is_streaming; uint32_t ssrc; bool is_first_packet; @@ -193,19 +198,6 @@ static const uint8_t udp_timing_header[8] = { 0x00, 0x00, 0x00, 0x00 }; -/** - * Function to trim a given character at the end of a string (no realloc). - * @param str Pointer to string - * @param rc Character to trim - */ -static inline void rtrim_char(char *str, char rc) { - char *sp = str + strlen(str) - 1; - while (sp >= str && *sp == rc) { - *sp = '\0'; - sp -= 1; - } -} - /** * Function to convert a timeval to ntp timestamp. * @param tv Pointer to the timeval structure @@ -584,7 +576,7 @@ static ssize_t send_udp_sync_packet(pa_raop_client *c, uint32_t stamp) { return written; } -static size_t handle_udp_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) { +static ssize_t handle_udp_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) { uint8_t payload = 0; uint16_t seq, nbp = 0; ssize_t written = 0; @@ -655,11 +647,22 @@ static ssize_t send_udp_timing_packet(pa_raop_client *c, const uint32_t data[6], return written; } -static size_t handle_udp_timing_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) { +static char *get_rtsp_url(pa_raop_client *c) { + pa_assert(c->rtsp); + pa_assert(c->sid); + const char *ip = pa_rtsp_localip(c->rtsp); + if (pa_is_ip6_address(ip)) { + return pa_sprintf_malloc("rtsp://[%s]/%s", ip, c->sid); + } else { + return pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid); + } +} + +static ssize_t handle_udp_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; - size_t written = 0; + ssize_t written = 0; uint64_t rci = 0; /* Timing packets are 32 bytes long: 1 x 8 RTP header (no ssrc) + 3 x 8 NTP timestamps */ @@ -673,6 +676,10 @@ static size_t handle_udp_timing_packet(pa_raop_client *c, const uint8_t packet[] payload = packet[1] ^ 0x80; switch (payload) { case PAYLOAD_TIMING_REQUEST: + if (c->has_post && !c->waiting) { + pa_log_debug("Sending keep-alive"); + pa_rtsp_post(c->rtsp, "/feedback"); + } pa_log_debug("Sending timing packet at %" PRIu64 , rci); written = send_udp_timing_packet(c, data, rci); break; @@ -798,6 +805,10 @@ static int open_bind_udp_socket(pa_raop_client *c, uint16_t *actual_port) { goto fail; } + /* If the socket queue is full, let's drop packets */ + pa_make_udp_socket_low_delay(fd); + pa_make_fd_nonblock(fd); + #ifdef SO_TIMESTAMP if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) { pa_log("setsockopt(SO_TIMESTAMP) failed: %s", pa_cstrerror(errno)); @@ -869,6 +880,69 @@ static void tcp_connection_cb(pa_socket_client *sc, pa_iochannel *io, void *user c->state_callback(PA_RAOP_CONNECTED, c->state_userdata); } +static char *get_sdp(pa_raop_client *c) { + char *key, *iv, *sdp = NULL; + int frames = 0; + const char *ip; + int ipv; + ip = pa_rtsp_localip(c->rtsp); + ipv = pa_is_ip6_address(ip) ? 6 : 4; + + if (c->protocol == PA_RAOP_PROTOCOL_TCP) + frames = FRAMES_PER_TCP_PACKET; + else if (c->protocol == PA_RAOP_PROTOCOL_UDP) + frames = FRAMES_PER_UDP_PACKET; + + switch(c->encryption) { + case PA_RAOP_ENCRYPTION_NONE: { + sdp = pa_sprintf_malloc( + "v=0\r\n" + "o=iTunes %s 0 IN IP%d %s\r\n" + "s=iTunes\r\n" + "c=IN IP%d %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, ipv, ip, ipv, c->host, frames); + + break; + } + + case PA_RAOP_ENCRYPTION_RSA: + case PA_RAOP_ENCRYPTION_FAIRPLAY: + case PA_RAOP_ENCRYPTION_MFISAP: + case PA_RAOP_ENCRYPTION_FAIRPLAY_SAP25: { + key = pa_raop_secret_get_key(c->secret); + if (!key) { + pa_log("pa_raop_secret_get_key() failed."); + break; + } + + iv = pa_raop_secret_get_iv(c->secret); + + sdp = pa_sprintf_malloc( + "v=0\r\n" + "o=iTunes %s 0 IN IP%d %s\r\n" + "s=iTunes\r\n" + "c=IN IP%d %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, ipv, ip, ipv, c->host, frames, key, iv); + + pa_xfree(key); + pa_xfree(iv); + break; + } + } + + return sdp; +} + static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_status_t status, pa_headerlist *headers, void *userdata) { pa_raop_client *c = userdata; @@ -878,122 +952,104 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_ switch (state) { case STATE_CONNECT: { - char *key, *iv, *sdp = NULL; - int frames = 0; - const char *ip; - char *url; - int ipv; + char *url, *sdp; pa_log_debug("RAOP: CONNECTED"); - ip = pa_rtsp_localip(c->rtsp); - if (pa_is_ip6_address(ip)) { - ipv = 6; - url = pa_sprintf_malloc("rtsp://[%s]/%s", ip, c->sid); - } else { - ipv = 4; - url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid); - } + url = get_rtsp_url(c); pa_rtsp_set_url(c->rtsp, url); - - if (c->protocol == PA_RAOP_PROTOCOL_TCP) - frames = FRAMES_PER_TCP_PACKET; - else if (c->protocol == PA_RAOP_PROTOCOL_UDP) - frames = FRAMES_PER_UDP_PACKET; - - switch(c->encryption) { - case PA_RAOP_ENCRYPTION_NONE: { - sdp = pa_sprintf_malloc( - "v=0\r\n" - "o=iTunes %s 0 IN IP%d %s\r\n" - "s=iTunes\r\n" - "c=IN IP%d %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, ipv, ip, ipv, c->host, frames); - - break; - } - - case PA_RAOP_ENCRYPTION_RSA: - case PA_RAOP_ENCRYPTION_FAIRPLAY: - case PA_RAOP_ENCRYPTION_MFISAP: - case PA_RAOP_ENCRYPTION_FAIRPLAY_SAP25: { - key = pa_raop_secret_get_key(c->secret); - if (!key) { - pa_log("pa_raop_secret_get_key() failed."); - pa_rtsp_disconnect(rtsp); - /* FIXME: This is an unrecoverable failure. We should notify - * the pa_raop_client owner so that it could shut itself - * down. */ - goto connect_finish; - } - - iv = pa_raop_secret_get_iv(c->secret); - - sdp = pa_sprintf_malloc( - "v=0\r\n" - "o=iTunes %s 0 IN IP%d %s\r\n" - "s=iTunes\r\n" - "c=IN IP%d %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, ipv, ip, ipv, c->host, frames, key, iv); - - pa_xfree(key); - pa_xfree(iv); - break; - } - } - - pa_rtsp_announce(c->rtsp, sdp); - -connect_finish: - pa_xfree(sdp); pa_xfree(url); + + if (!(sdp = get_sdp(c))) + goto connect_error; + + /* We might need to re-authenticate, so reset this */ + c->waiting = false; + pa_rtsp_announce(c->rtsp, sdp); + pa_xfree(sdp); + + break; + + connect_error: + pa_log_error("Failed to get RTSP SDP"); + pa_raop_client_disconnect(c); + if (c->state_callback) + c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata); break; } case STATE_OPTIONS: { pa_log_debug("RAOP: OPTIONS (stream cb)"); - break; } case STATE_ANNOUNCE: { uint16_t cport = DEFAULT_UDP_CONTROL_PORT; uint16_t tport = DEFAULT_UDP_TIMING_PORT; - char *trs = NULL; + char *sdp, *trs = NULL; pa_log_debug("RAOP: ANNOUNCE"); - if (c->protocol == PA_RAOP_PROTOCOL_TCP) { - trs = pa_sprintf_malloc( - "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record"); - } else if (c->protocol == PA_RAOP_PROTOCOL_UDP) { - c->udp_cfd = open_bind_udp_socket(c, &cport); - c->udp_tfd = open_bind_udp_socket(c, &tport); - if (c->udp_cfd < 0 || c->udp_tfd < 0) - goto annonce_error; - - trs = pa_sprintf_malloc( - "RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;" - "control_port=%d;timing_port=%d", - cport, tport); + /* The authentication might need to be refreshed on a reconnect even + * though we authenticated in OPTIONS previously */ + if (STATUS_UNAUTHORIZED == status) { + if (c->waiting) + goto announce_fail; + if (!(sdp = get_sdp(c))) + goto announce_error; + c->waiting = true; + pa_rtsp_announce(c->rtsp, sdp); + pa_xfree(sdp); + break; } + if (STATUS_OK != status) + goto announce_error; + + switch (c->protocol) { + case PA_RAOP_PROTOCOL_TCP: + trs = pa_sprintf_malloc( + "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record"); + break; + case PA_RAOP_PROTOCOL_UDP: + c->udp_cfd = open_bind_udp_socket(c, &cport); + c->udp_tfd = open_bind_udp_socket(c, &tport); + if (c->udp_cfd < 0 || c->udp_tfd < 0) + goto announce_error; + + trs = pa_sprintf_malloc( + "RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;" + "control_port=%d;timing_port=%d", + cport, tport); + break; + } + + pa_assert(trs); + + /* Don't send a keep-alive until we're ready */ + c->waiting = true; + c->udp_tport = 0; pa_rtsp_setup(c->rtsp, trs); + if (c->protocol == PA_RAOP_PROTOCOL_UDP) { + if (c->state_callback) + c->state_callback(PA_RAOP_CONNECTED, c->state_userdata); + } + pa_xfree(trs); + break; - annonce_error: + announce_fail: + pa_log_error("Aborting RTSP announce, authentication failed"); + pa_raop_client_disconnect(c); + if (c->state_callback) + c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata); + break; + + announce_error: + pa_log_error("Aborting RTSP announce, failed creating required sockets"); + if (c->udp_cfd >= 0) pa_close(c->udp_cfd); c->udp_cfd = -1; @@ -1001,12 +1057,10 @@ connect_finish: pa_close(c->udp_tfd); c->udp_tfd = -1; - pa_rtsp_client_free(c->rtsp); + pa_raop_client_disconnect(c); + if (c->state_callback) + c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata); - pa_log_error("Aborting RTSP announce, failed creating required sockets"); - - c->rtsp = NULL; - pa_xfree(trs); break; } @@ -1014,13 +1068,17 @@ connect_finish: pa_socket_client *sc = NULL; uint32_t sport = DEFAULT_UDP_AUDIO_PORT; uint32_t cport =0, tport = 0; - char *ajs, *token, *pc, *trs; + const char *ajs, *trs; + char *token = NULL, *pc; const char *token_state = NULL; char delimiters[] = ";"; pa_log_debug("RAOP: SETUP"); - ajs = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status")); + if (STATUS_OK != status) + goto setup_error; + + ajs = pa_headerlist_gets(headers, "Audio-Jack-Status"); if (ajs) { c->jack_type = JACK_TYPE_ANALOG; @@ -1037,7 +1095,6 @@ connect_finish: } pa_xfree(token); } - } else { pa_log_warn("\"Audio-Jack-Status\" missing in RTSP setup response"); } @@ -1057,7 +1114,7 @@ connect_finish: pa_socket_client_unref(sc); sc = NULL; } else if (c->protocol == PA_RAOP_PROTOCOL_UDP) { - trs = pa_xstrdup(pa_headerlist_gets(headers, "Transport")); + trs = pa_headerlist_gets(headers, "Transport"); if (trs) { /* Now parse out the server port component of the response. */ @@ -1076,11 +1133,13 @@ connect_finish: } pa_xfree(token); } - pa_xfree(trs); } else { pa_log_warn("\"Transport\" missing in RTSP setup response"); } + if (!tport) + tport = c->udp_tport; + if (cport <= 0 || tport <= 0) goto setup_error; @@ -1093,57 +1152,43 @@ connect_finish: pa_log_debug("Connection established (UDP;control_port=%d;timing_port=%d)", cport, tport); - /* Send an initial UDP packet so a connection tracking firewall - * knows the src_ip:src_port <-> dest_ip:dest_port relation - * and accepts the incoming timing packets. - */ - send_initial_udp_timing_packet(c); - pa_log_debug("Sent initial timing packet to UDP port %d", tport); - - if (c->state_callback) - c->state_callback(PA_RAOP_CONNECTED, c->state_userdata); + if (!c->udp_tport) { + /* Send an initial UDP packet so a connection tracking firewall + * knows the src_ip:src_port <-> dest_ip:dest_port relation + * and accepts the incoming timing packets. + */ + send_initial_udp_timing_packet(c); + pa_log_debug("Sent initial timing packet to UDP port %d", tport); + } } pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime); - pa_xfree(ajs); break; setup_error_parse: pa_log("Failed parsing server port components"); pa_xfree(token); - pa_xfree(trs); /* fall-thru */ setup_error: - if (c->tcp_sfd >= 0) - pa_close(c->tcp_sfd); - c->tcp_sfd = -1; - - if (c->udp_sfd >= 0) - pa_close(c->udp_sfd); - c->udp_sfd = -1; - - c->udp_cfd = c->udp_tfd = -1; - - pa_rtsp_client_free(c->rtsp); - pa_log_error("aborting RTSP setup, failed creating required sockets"); - + pa_raop_client_disconnect(c); if (c->state_callback) c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata); - - c->rtsp = NULL; break; } case STATE_RECORD: { int32_t latency = 0; uint32_t ssrc; - char *alt; + const char *alt; pa_log_debug("RAOP: RECORD"); - alt = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Latency")); + if (STATUS_OK != status) + goto record_error; + + alt = pa_headerlist_gets(headers, "Audio-Latency"); if (alt) { if (pa_atoi(alt, &latency) < 0) pa_log("Failed to parse audio latency"); @@ -1152,15 +1197,23 @@ connect_finish: pa_raop_packet_buffer_reset(c->pbuf, c->seq); pa_random(&ssrc, sizeof(ssrc)); - c->is_first_packet = true; - c->is_recording = true; - c->sync_count = 0; c->ssrc = ssrc; + pa_raop_client_stream(c); + if (c->state_callback) c->state_callback((int) PA_RAOP_RECORDING, c->state_userdata); - pa_xfree(alt); + /* Now the keep-alive can be sent */ + c->waiting = false; + + break; + + record_error: + pa_log_error("aborting RTSP record due to error"); + pa_raop_client_disconnect(c); + if (c->state_callback) + c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata); break; } @@ -1170,6 +1223,12 @@ connect_finish: break; } + case STATE_POST: { + pa_log_debug("RAOP: POST"); + + break; + } + case STATE_FLUSH: { pa_log_debug("RAOP: FLUSHED"); @@ -1179,23 +1238,7 @@ connect_finish: case STATE_TEARDOWN: { pa_log_debug("RAOP: TEARDOWN"); - if (c->tcp_sfd >= 0) - pa_close(c->tcp_sfd); - c->tcp_sfd = -1; - - if (c->udp_sfd >= 0) - pa_close(c->udp_sfd); - c->udp_sfd = -1; - - /* Polling sockets will be closed by sink */ - c->udp_cfd = c->udp_tfd = -1; - c->tcp_sfd = -1; - - pa_rtsp_client_free(c->rtsp); - pa_xfree(c->sid); - c->rtsp = NULL; - c->sid = NULL; - + pa_raop_client_disconnect(c); if (c->state_callback) c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata); @@ -1205,29 +1248,10 @@ connect_finish: case STATE_DISCONNECTED: { pa_log_debug("RAOP: DISCONNECTED"); - c->is_recording = false; - - if (c->tcp_sfd >= 0) - pa_close(c->tcp_sfd); - c->tcp_sfd = -1; - - if (c->udp_sfd >= 0) - pa_close(c->udp_sfd); - c->udp_sfd = -1; - - /* Polling sockets will be closed by sink */ - c->udp_cfd = c->udp_tfd = -1; - c->tcp_sfd = -1; - pa_log_error("RTSP control channel closed (disconnected)"); - - pa_rtsp_client_free(c->rtsp); - pa_xfree(c->sid); - c->rtsp = NULL; - c->sid = NULL; - + pa_raop_client_disconnect(c); if (c->state_callback) - c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata); + c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata); break; } @@ -1257,127 +1281,60 @@ static void rtsp_auth_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_st pa_random(rac, APPLE_CHALLENGE_LENGTH); /* Generate a random Apple-Challenge key */ - pa_raop_base64_encode(rac, APPLE_CHALLENGE_LENGTH, &sac); - rtrim_char(sac, '='); + pa_rtsp_base64_encode(rac, APPLE_CHALLENGE_LENGTH, &sac); + pa_rtsp_rtrim_char(sac, '='); pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac); + /* Reset this for the authentication */ + c->waiting = false; 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 = NULL, *val; - char *realm = NULL, *nonce = NULL, *response = NULL; - char comma[] = ","; - pa_log_debug("RAOP: OPTIONS (auth cb)"); /* 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); + if (c->waiting) goto fail; - } - - if (wath) { - mth = pa_split(wath, space, ¤t); - while ((token = pa_split(wath, comma, ¤t))) { - 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); - } - - pa_xfree(token); - } - } - - if (pa_safe_streq(mth, "Basic") && realm) { - rtrim_char(realm, '\"'); - - pa_raop_basic_response(DEFAULT_USER_NAME, c->password, &response); - ath = pa_sprintf_malloc("Basic %s", - response); - } else if (pa_safe_streq(mth, "Digest") && realm && nonce) { - rtrim_char(realm, '\"'); - rtrim_char(nonce, '\"'); - - pa_raop_digest_response(DEFAULT_USER_NAME, realm, c->password, nonce, "*", &response); - ath = pa_sprintf_malloc("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"*\", response=\"%s\"", - DEFAULT_USER_NAME, realm, nonce, - response); - } else { - pa_log_error("unsupported authentication method: %s", mth); - pa_xfree(realm); - pa_xfree(nonce); - pa_xfree(wath); - pa_xfree(mth); - goto error; - } - - pa_xfree(response); - pa_xfree(realm); - pa_xfree(nonce); - pa_xfree(wath); - pa_xfree(mth); - - pa_rtsp_add_header(c->rtsp, "Authorization", ath); - pa_xfree(ath); - - waiting = true; + c->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 (STATUS_OK != status) + goto error; - if (c->password) - pa_xfree(c->password); - pa_xfree(publ); - c->password = NULL; - } + const char *publ = pa_headerlist_gets(headers, "Public"); + if (publ && strstr(publ, "POST")) + c->has_post = true; + c->sci = pa_xstrdup(pa_rtsp_get_header(c->rtsp, "Client-Instance")); - pa_rtsp_client_free(c->rtsp); - c->rtsp = NULL; - /* Ensure everything is cleaned before calling the callback, otherwise it may raise a crash */ + pa_raop_client_disconnect(c); + /* We call with authenticated, not disconnected */ if (c->state_callback) - c->state_callback((int) PA_RAOP_AUTHENTICATED, c->state_userdata); + c->state_callback(PA_RAOP_AUTHENTICATED, c->state_userdata); - waiting = false; break; fail: - if (c->state_callback) - c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata); - pa_rtsp_client_free(c->rtsp); - c->rtsp = NULL; - pa_log_error("aborting authentication, wrong password"); - - waiting = false; + pa_raop_client_disconnect(c); + if (c->state_callback) + c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata); break; error: - if (c->state_callback) - c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata); - pa_rtsp_client_free(c->rtsp); - c->rtsp = NULL; - pa_log_error("aborting authentication, unexpected failure"); - - waiting = false; + pa_raop_client_disconnect(c); + if (c->state_callback) + c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata); break; } @@ -1389,15 +1346,14 @@ static void rtsp_auth_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_st case STATE_TEARDOWN: case STATE_DISCONNECTED: default: { - if (c->state_callback) - c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata); - pa_rtsp_client_free(c->rtsp); - c->rtsp = NULL; - if (c->sci) pa_xfree(c->sci); c->sci = NULL; + pa_raop_client_disconnect(c); + if (c->state_callback) + c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata); + break; } } @@ -1405,11 +1361,7 @@ static void rtsp_auth_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_st void pa_raop_client_disconnect(pa_raop_client *c) { - c->is_recording = false; - - if (c->tcp_sfd >= 0) - pa_close(c->tcp_sfd); - c->tcp_sfd = -1; + c->is_streaming = false; if (c->udp_sfd >= 0) pa_close(c->udp_sfd); @@ -1419,18 +1371,12 @@ void pa_raop_client_disconnect(pa_raop_client *c) { c->udp_cfd = c->udp_tfd = -1; c->tcp_sfd = -1; - pa_log_error("RTSP control channel closed (disconnected)"); - if (c->rtsp) pa_rtsp_client_free(c->rtsp); if (c->sid) pa_xfree(c->sid); c->rtsp = NULL; c->sid = NULL; - - if (c->state_callback) - c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata); - } @@ -1460,10 +1406,9 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot c->port = a.port; else c->port = DEFAULT_RAOP_PORT; - c->rtsp = NULL; - c->sci = c->sid = NULL; - c->password = NULL; c->autoreconnect = autoreconnect; + c->waiting = false; + c->has_post = false; c->protocol = protocol; c->encryption = encryption; @@ -1475,7 +1420,6 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot c->udp_cfd = -1; c->udp_tfd = -1; - c->secret = NULL; if (c->encryption != PA_RAOP_ENCRYPTION_NONE) c->secret = pa_raop_secret_new(); @@ -1483,7 +1427,7 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot if (c->protocol == PA_RAOP_PROTOCOL_UDP) size = RTX_BUFFERING_SECONDS * ss.rate / FRAMES_PER_UDP_PACKET; - c->is_recording = false; + c->is_streaming = false; c->is_first_packet = true; /* Packet sync interval should be around 1s (UDP only) */ c->sync_interval = ss.rate / FRAMES_PER_UDP_PACKET; @@ -1503,7 +1447,6 @@ void pa_raop_client_free(pa_raop_client *c) { pa_xfree(c->sci); if (c->secret) pa_raop_secret_free(c->secret); - pa_xfree(c->password); c->sci = c->sid = NULL; c->password = NULL; c->secret = NULL; @@ -1516,25 +1459,29 @@ void pa_raop_client_free(pa_raop_client *c) { pa_xfree(c); } -int pa_raop_client_authenticate (pa_raop_client *c, const char *password) { +int pa_raop_client_authenticate(pa_raop_client *c, const char *password) { int rv = 0; pa_assert(c); - if (c->rtsp || c->password) { + if (c->rtsp) { pa_log_debug("Authentication/Connection already in progress..."); return 0; } - c->password = NULL; - if (password) - c->password = pa_xstrdup(password); + c->password = password; c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, DEFAULT_USER_AGENT, c->autoreconnect); pa_assert(c->rtsp); + pa_rtsp_set_credentials(c->rtsp, DEFAULT_USER_NAME, c->password); pa_rtsp_set_callback(c->rtsp, rtsp_auth_cb, c); - rv = pa_rtsp_connect(c->rtsp); + + if ((rv = pa_rtsp_connect(c->rtsp))) { + pa_rtsp_client_free(c->rtsp); + c->rtsp = NULL; + } + return rv; } @@ -1563,23 +1510,27 @@ int pa_raop_client_announce(pa_raop_client *c) { pa_assert(c->rtsp); c->sync_count = 0; - c->is_recording = false; + c->is_streaming = false; c->is_first_packet = true; pa_random(&sid, sizeof(sid)); c->sid = pa_sprintf_malloc("%u", sid); + pa_rtsp_add_header(c->rtsp, "Client-Instance", c->sci); + pa_rtsp_set_credentials(c->rtsp, DEFAULT_USER_NAME, c->password); pa_rtsp_set_callback(c->rtsp, rtsp_stream_cb, c); - rv = pa_rtsp_connect(c->rtsp); + if ((rv = pa_rtsp_connect(c->rtsp))) { + pa_rtsp_client_free(c->rtsp); + c->rtsp = NULL; + } + return rv; } bool pa_raop_client_is_alive(pa_raop_client *c) { pa_assert(c); - if (!c->rtsp || !c->sci) { - pa_log_debug("Not alive, connection not established yet..."); + if (!c->rtsp || !c->sci) return false; - } switch (c->protocol) { case PA_RAOP_PROTOCOL_TCP: @@ -1597,64 +1548,28 @@ bool pa_raop_client_is_alive(pa_raop_client *c) { return false; } -bool pa_raop_client_can_stream(pa_raop_client *c) { +bool pa_raop_client_is_streaming(pa_raop_client *c) { pa_assert(c); - if (!c->rtsp || !c->sci) { - return false; - } - - switch (c->protocol) { - case PA_RAOP_PROTOCOL_TCP: - if (c->tcp_sfd >= 0 && c->is_recording) - return true; - break; - case PA_RAOP_PROTOCOL_UDP: - if (c->udp_sfd >= 0 && c->is_recording) - return true; - break; - default: - break; - } - - return false; -} - -bool pa_raop_client_is_recording(pa_raop_client *c) { - return c->is_recording; + return pa_raop_client_is_alive(c) && c->is_streaming; } int pa_raop_client_stream(pa_raop_client *c) { - int rv = 0; - pa_assert(c); - if (!c->rtsp || !c->sci) { + if (!pa_raop_client_is_alive(c)) { pa_log_debug("Streaming's impossible, connection not established yet..."); + return 1; + } else if (pa_raop_client_is_streaming(c)) { + pa_log_debug("Already streaming..."); return 0; } - switch (c->protocol) { - case PA_RAOP_PROTOCOL_TCP: - if (c->tcp_sfd >= 0 && !c->is_recording) { - c->is_recording = true; - c->is_first_packet = true; - c->sync_count = 0; - } - break; - case PA_RAOP_PROTOCOL_UDP: - if (c->udp_sfd >= 0 && !c->is_recording) { - c->is_recording = true; - c->is_first_packet = true; - c->sync_count = 0; - } - break; - default: - rv = 1; - break; - } + c->is_streaming = true; + c->is_first_packet = true; + c->sync_count = 0; - return rv; + return 0; } int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) { @@ -1664,11 +1579,8 @@ int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) { pa_assert(c); - if (!c->rtsp) { + if (!pa_raop_client_is_alive(c)) { pa_log_debug("Cannot SET_PARAMETER, connection not established yet..."); - return 0; - } else if (!c->sci) { - pa_log_debug("SET_PARAMETER requires a preliminary authentication"); return 1; } @@ -1682,49 +1594,36 @@ int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) { 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); - + rv = pa_rtsp_setparameter(c->rtsp, param); pa_xfree(param); + return rv; } int pa_raop_client_flush(pa_raop_client *c) { - int rv = 0; - pa_assert(c); - if (!c->rtsp || !pa_rtsp_exec_ready(c->rtsp)) { - pa_log_debug("Cannot FLUSH, connection not established yet...)"); - return 0; - } else if (!c->sci) { - pa_log_debug("FLUSH requires a preliminary authentication"); + if (!pa_raop_client_is_alive(c)) { + pa_log_debug("Cannot FLUSH, connection not established yet..."); return 1; } - c->is_recording = false; + c->is_streaming = false; - rv = pa_rtsp_flush(c->rtsp, c->seq, c->rtptime); - return rv; + return pa_rtsp_flush(c->rtsp, c->seq, c->rtptime); } int pa_raop_client_teardown(pa_raop_client *c) { - int rv = 0; - pa_assert(c); - if (!c->rtsp) { + if (!pa_raop_client_is_alive(c)) { pa_log_debug("Cannot TEARDOWN, connection not established yet..."); - return 0; - } else if (!c->sci) { - pa_log_debug("TEARDOWN requires a preliminary authentication"); return 1; } - c->is_recording = false; + c->is_streaming = false; - rv = pa_rtsp_teardown(c->rtsp); - return rv; + return pa_rtsp_teardown(c->rtsp); } void pa_raop_client_get_frames_per_block(pa_raop_client *c, size_t *frames) { @@ -1803,20 +1702,44 @@ pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume) return volume - volume * (minv / maxv) + minv; } -void pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd, const uint8_t packet[], ssize_t size) { +ssize_t pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd) { pa_assert(c); pa_assert(fd >= 0); - pa_assert(packet); if (c->protocol == PA_RAOP_PROTOCOL_UDP) { + uint8_t packet[32]; + ssize_t size; + struct sockaddr_storage sa; + socklen_t salen = sizeof(sa); + + size = recvfrom(fd, packet, sizeof(packet), 0, (struct sockaddr *) &sa, &salen); + + if (size < 0) + return size; + if (fd == c->udp_cfd) { pa_log_debug("Received UDP control packet..."); - handle_udp_control_packet(c, packet, size); + return handle_udp_control_packet(c, packet, size); } else if (fd == c->udp_tfd) { pa_log_debug("Received UDP timing packet..."); - handle_udp_timing_packet(c, packet, size); + if (!c->udp_tport) { + /* Apple TV sends timing packets after the SETUP request + * which we use to get the port. It sends timing_port=0 in the + * SETUP response. + */ + if (sa.ss_family == AF_INET) + c->udp_tport = ntohs(((struct sockaddr_in *) &sa)->sin_port); +#ifdef HAVE_IPV6 + else if (sa.ss_family == AF_INET6) + c->udp_tport = ntohs(((struct sockaddr_in6 *) &sa)->sin6_port); +#endif + c->udp_tfd = connect_udp_socket(c, c->udp_tfd, c->udp_tport); + } + return handle_udp_timing_packet(c, packet, size); } } + + return -1; } ssize_t pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset) { diff --git a/src/modules/raop/raop-client.h b/src/modules/raop/raop-client.h index faec01e65..8bb52d04f 100644 --- a/src/modules/raop/raop-client.h +++ b/src/modules/raop/raop-client.h @@ -65,8 +65,7 @@ bool pa_raop_client_is_authenticated(pa_raop_client *c); int pa_raop_client_announce(pa_raop_client *c); bool pa_raop_client_is_alive(pa_raop_client *c); -bool pa_raop_client_is_recording(pa_raop_client *c); -bool pa_raop_client_can_stream(pa_raop_client *c); +bool pa_raop_client_is_streaming(pa_raop_client *c); int pa_raop_client_stream(pa_raop_client *c); int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume); int pa_raop_client_flush(pa_raop_client *c); @@ -77,7 +76,7 @@ void pa_raop_client_get_frames_per_block(pa_raop_client *c, size_t *size); bool pa_raop_client_register_pollfd(pa_raop_client *c, pa_rtpoll *poll, pa_rtpoll_item **poll_item); bool pa_raop_client_is_timing_fd(pa_raop_client *c, const int fd); pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume); -void pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd, const uint8_t packet[], ssize_t size); +ssize_t pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd); ssize_t pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset); typedef void (*pa_raop_client_state_cb_t)(pa_raop_state_t state, void *userdata); diff --git a/src/modules/raop/raop-common.h b/src/modules/raop/raop-common.h new file mode 100644 index 000000000..7f002f7df --- /dev/null +++ b/src/modules/raop/raop-common.h @@ -0,0 +1,27 @@ +#ifndef fooraopcommonfoo +#define fooraopcommonfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + Copyright Kungliga Tekniska högskolan + Copyright 2013 Martin Blanchard + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see . +***/ + +#define RAOP_DEFAULT_LATENCY 2000 /* msec */ + +#endif diff --git a/src/modules/raop/raop-crypto.c b/src/modules/raop/raop-crypto.c index 710e93c82..dbdbede1f 100644 --- a/src/modules/raop/raop-crypto.c +++ b/src/modules/raop/raop-crypto.c @@ -37,8 +37,9 @@ #include #include +#include + #include "raop-crypto.h" -#include "raop-util.h" #define AES_CHUNK_SIZE 16 @@ -92,7 +93,7 @@ static int rsa_encrypt(uint8_t *data, int len, uint8_t *str) { goto fail; } - size = pa_raop_base64_decode(rsa_modulus, modulus); + size = pa_rtsp_base64_decode(rsa_modulus, modulus); n_bn = BN_bin2bn(modulus, size, NULL); if (!n_bn) { @@ -100,7 +101,7 @@ static int rsa_encrypt(uint8_t *data, int len, uint8_t *str) { goto fail; } - size = pa_raop_base64_decode(rsa_exponent, exponent); + size = pa_rtsp_base64_decode(rsa_exponent, exponent); e_bn = BN_bin2bn(exponent, size, NULL); if (!e_bn) { @@ -165,7 +166,7 @@ char* pa_raop_secret_get_iv(pa_raop_secret *s) { pa_assert(s); - pa_raop_base64_encode(s->iv, AES_CHUNK_SIZE, &base64_iv); + pa_rtsp_base64_encode(s->iv, AES_CHUNK_SIZE, &base64_iv); return base64_iv; } @@ -184,7 +185,7 @@ char* pa_raop_secret_get_key(pa_raop_secret *s) { return NULL; } - pa_raop_base64_encode(rsa_key, size, &base64_key); + pa_rtsp_base64_encode(rsa_key, size, &base64_key); return base64_key; } diff --git a/src/modules/raop/raop-sink.c b/src/modules/raop/raop-sink.c index 4f62b29ad..320b52874 100644 --- a/src/modules/raop/raop-sink.c +++ b/src/modules/raop/raop-sink.c @@ -69,7 +69,7 @@ #include "raop-sink.h" #include "raop-client.h" -#include "raop-util.h" +#include "raop-common.h" #define UDP_TIMING_PACKET_LOSS_MAX (30 * PA_USEC_PER_SEC) #define UDP_TIMING_PACKET_DISCONNECT_CYCLE 3 @@ -79,6 +79,7 @@ struct userdata { pa_module *module; pa_sink *sink; pa_card *card; + pa_subscription *subscription; pa_thread *thread; pa_thread_mq thread_mq; @@ -91,6 +92,7 @@ struct userdata { pa_raop_protocol_t protocol; pa_raop_encryption_t encryption; pa_raop_codec_t codec; + char *password; bool autoreconnect; /* if true, behaves like a null-sink when disconnected */ bool autonull; @@ -115,7 +117,9 @@ struct userdata { enum { PA_SINK_MESSAGE_SET_RAOP_STATE = PA_SINK_MESSAGE_MAX, - PA_SINK_MESSAGE_DISCONNECT_REQUEST + PA_SINK_MESSAGE_CONNECTED, + PA_SINK_MESSAGE_DISCONNECTED, + PA_SINK_MESSAGE_CONNECT_REQUEST }; static void userdata_free(struct userdata *u); @@ -129,7 +133,8 @@ static void raop_state_cb(pa_raop_state_t state, void *userdata) { pa_log_debug("State change received, informing IO thread..."); - pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_SET_RAOP_STATE, PA_INT_TO_PTR(state), 0, NULL, NULL); + pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->sink), + PA_SINK_MESSAGE_SET_RAOP_STATE, PA_INT_TO_PTR(state), 0, NULL); } static int64_t sink_get_latency(const struct userdata *u) { @@ -156,6 +161,44 @@ static int64_t sink_get_latency(const struct userdata *u) { return latency; } +static void disconnect(struct userdata *u) { + unsigned int nbfds = 0; + struct pollfd *pollfd; + unsigned int i; + + if (u->rtpoll_item) { + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, &nbfds); + if (pollfd) { + for (i = 0; i < nbfds; i++) { + if (pollfd->fd >= 0) + pa_close(pollfd->fd); + pollfd++; + } + } + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } + + pa_raop_client_disconnect(u->raop); + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) + pa_rtpoll_set_timer_disabled(u->rtpoll); + + if (u->sink->thread_info.state == PA_SINK_RUNNING) { + if (!u->autonull) + pa_rtpoll_set_timer_disabled(u->rtpoll); + + if (u->autoreconnect) { + if (pa_raop_client_is_authenticated(u->raop)) + pa_raop_client_announce(u->raop); + else + pa_raop_client_authenticate(u->raop, u->password); + } else + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink), + PA_SINK_MESSAGE_DISCONNECTED, NULL, 0, NULL, NULL); + } +} + static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SINK(o)->userdata; @@ -163,23 +206,34 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_assert(u->raop); switch (code) { - /* Exception : for this message, we are in main thread, msg sent from the IO/thread - Done here, as alloc/free of rtsp_client is also done in this thread for other cases */ - case PA_SINK_MESSAGE_DISCONNECT_REQUEST: { - if (u->sink->state == PA_SINK_RUNNING) { - /* Disconnect raop client, and restart the whole chain since - * the authentication token might be outdated */ - pa_raop_client_disconnect(u->raop); - pa_raop_client_authenticate(u->raop, NULL); - } + case PA_SINK_MESSAGE_CONNECTED: { + pa_assert_ctl_context(); + pa_device_port_set_available(u->sink->active_port, PA_AVAILABLE_YES); + return 0; + } + case PA_SINK_MESSAGE_DISCONNECTED: { + pa_assert_ctl_context(); + /* Mark the port as unavailable so a different sink can be used */ + pa_device_port_set_available(u->sink->active_port, PA_AVAILABLE_NO); + return 0; + } + + case PA_SINK_MESSAGE_CONNECT_REQUEST: { + pa_assert_io_context(); + pa_log_debug("Received connect request"); + if (pa_raop_client_is_authenticated(u->raop)) + pa_raop_client_announce(u->raop); + else + pa_raop_client_authenticate(u->raop, u->password); return 0; } case PA_SINK_MESSAGE_GET_LATENCY: { + pa_assert_io_context(); int64_t r = 0; - if (u->autonull || pa_raop_client_can_stream(u->raop)) + if (u->autonull || pa_raop_client_is_streaming(u->raop)) r = sink_get_latency(u); *((int64_t*) data) = r; @@ -188,13 +242,10 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse } case PA_SINK_MESSAGE_SET_RAOP_STATE: { + pa_assert_io_context(); switch ((pa_raop_state_t) PA_PTR_TO_UINT(data)) { case PA_RAOP_AUTHENTICATED: { - if (!pa_raop_client_is_authenticated(u->raop)) { - pa_module_unload_request(u->module, true); - } - - if (u->autoreconnect && u->sink->state == PA_SINK_RUNNING) { + if (u->sink->state == PA_SINK_RUNNING) { pa_usec_t now; now = pa_rtclock_now(); #ifdef USE_SMOOTHER_2 @@ -202,11 +253,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse #else pa_smoother_reset(u->smoother, now, false); #endif - - if (!pa_raop_client_is_alive(u->raop)) { - /* Connecting will trigger a RECORD and start steaming */ - pa_raop_client_announce(u->raop); - } + /* Connecting will trigger a RECORD and start streaming */ + pa_raop_client_announce(u->raop); } return 0; @@ -217,6 +265,9 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse u->oob = pa_raop_client_register_pollfd(u->raop, u->rtpoll, &u->rtpoll_item); + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink), + PA_SINK_MESSAGE_CONNECTED, NULL, 0, NULL, NULL); + return 0; } @@ -244,40 +295,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case PA_RAOP_INVALID_STATE: case PA_RAOP_DISCONNECTED: { - unsigned int nbfds = 0; - struct pollfd *pollfd; - unsigned int i; - - if (u->rtpoll_item) { - pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, &nbfds); - if (pollfd) { - for (i = 0; i < nbfds; i++) { - if (pollfd->fd >= 0) - pa_close(pollfd->fd); - pollfd++; - } - } - pa_rtpoll_item_free(u->rtpoll_item); - u->rtpoll_item = NULL; - } - - if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { - pa_rtpoll_set_timer_disabled(u->rtpoll); - - return 0; - } - - if (u->autoreconnect) { - if (u->sink->thread_info.state != PA_SINK_IDLE) { - if (!u->autonull) - pa_rtpoll_set_timer_disabled(u->rtpoll); - pa_raop_client_authenticate(u->raop, NULL); - } - } else { - if (u->sink->thread_info.state != PA_SINK_IDLE) - pa_module_unload_request(u->module, true); - } - + disconnect(u); return 0; } } @@ -286,6 +304,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse } } + pa_assert_io_context(); + return pa_sink_process_msg(o, code, data, offset, chunk); } @@ -308,9 +328,8 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, pa_assert(PA_SINK_IS_OPENED(s->thread_info.state)); /* Issue a TEARDOWN if we are still connected */ - if (pa_raop_client_is_alive(u->raop)) { + if (pa_raop_client_is_alive(u->raop)) pa_raop_client_teardown(u->raop); - } break; @@ -348,9 +367,12 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, if (!pa_raop_client_is_alive(u->raop)) { /* Connecting will trigger a RECORD and start streaming */ - pa_raop_client_announce(u->raop); - } else if (!pa_raop_client_is_recording(u->raop)) { - /* RECORD alredy sent, simply start streaming */ + if (pa_raop_client_is_authenticated(u->raop)) + pa_raop_client_announce(u->raop); + else + pa_raop_client_authenticate(u->raop, u->password); + } else if (!pa_raop_client_is_streaming(u->raop)) { + /* RECORD already sent, simply start streaming */ pa_raop_client_stream(u->raop); pa_rtpoll_set_timer_absolute(u->rtpoll, now); u->write_count = 0; @@ -445,7 +467,7 @@ static void thread_func(void *userdata) { uint64_t position; size_t index; int ret; - bool canstream, sendstream, on_timeout; + bool is_streaming, on_timeout; #ifndef USE_SMOOTHER_2 pa_usec_t estimated; #endif @@ -478,25 +500,26 @@ static void thread_func(void *userdata) { /* if oob: streaming managed by timing, pollfd for oob sockets */ if (pollfd && u->oob && !on_timeout) { - uint8_t packet[32]; - ssize_t read; - for (i = 0; i < nbfds; i++) { if (pollfd->revents & POLLERR) { - if (u->autoreconnect && pa_raop_client_is_alive(u->raop)) { - pollfd->revents = 0; - pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink), - PA_SINK_MESSAGE_DISCONNECT_REQUEST, 0, 0, NULL, NULL); - continue; - } - - /* one of UDP fds is in faulty state, may have been disconnected, this is fatal */ - goto fail; + disconnect(u); + continue; } if (pollfd->revents & pollfd->events) { pollfd->revents = 0; - read = pa_read(pollfd->fd, packet, sizeof(packet), NULL); - pa_raop_client_handle_oob_packet(u->raop, pollfd->fd, packet, read); + if (pa_raop_client_handle_oob_packet(u->raop, pollfd->fd) < 0) { + if (errno == EINTR) { + pa_log_debug("Failed to handle oob packet (EINTR), ignoring"); + continue; + } else if (errno == EAGAIN) { + pa_log_debug("Failed to handle oob packet (EAGAIN), ignoring"); + continue; + } else { + pa_log("Failed to handle oob packet: %s", pa_cstrerror(errno)); + disconnect(u); + continue; + } + } if (pa_raop_client_is_timing_fd(u->raop, pollfd->fd)) { last_timing = pa_rtclock_now(); check_timing_count = 1; @@ -510,9 +533,8 @@ static void thread_func(void *userdata) { } } - if (u->sink->thread_info.state != PA_SINK_RUNNING) { + if (u->sink->thread_info.state != PA_SINK_RUNNING) continue; - } if (u->first) { last_timing = 0; @@ -521,11 +543,11 @@ static void thread_func(void *userdata) { u->first = false; } - canstream = pa_raop_client_can_stream(u->raop); + is_streaming = pa_raop_client_is_streaming(u->raop); now = pa_rtclock_now(); if (u->oob && u->autoreconnect && on_timeout) { - if (!canstream) { + if (!is_streaming) { last_timing = 0; } else if (last_timing != 0) { pa_usec_t since = now - last_timing; @@ -543,28 +565,23 @@ static void thread_func(void *userdata) { UDP_TIMING_PACKET_DISCONNECT_CYCLE-1, since_in_sec, u->server); check_timing_count++; } else { - /* Limit reached, then request disconnect */ + /* Limit reached, then disconnect */ check_timing_count = 1; last_timing = 0; - if (pa_raop_client_is_alive(u->raop)) { - pa_log_warn("UDP Timing Packets Warn limit reached - Requesting reconnect"); - pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink), - PA_SINK_MESSAGE_DISCONNECT_REQUEST, 0, 0, NULL, NULL); - continue; - } + pa_log_warn("UDP Timing Packets Warn limit reached - disconnecting"); + disconnect(u); + continue; } } } } if (!u->autonull) { - if (!canstream) { - pa_log_debug("Can't stream, connection not established yet..."); + if (!is_streaming) continue; - } /* This assertion is meant to silence a complaint from Coverity about * pollfd being possibly NULL when we access it later. That's a false - * positive, because we check pa_raop_client_can_stream() above, and if + * positive, because we check pa_raop_client_is_streaming() above, and if * that returns true, it means that the connection is up, and when the * connection is up, pollfd will be non-NULL. */ pa_assert(pollfd); @@ -584,36 +601,30 @@ static void thread_func(void *userdata) { if (u->memchunk.length > 0) { index = u->memchunk.index; - sendstream = !u->autonull || (u->autonull && canstream); - if (sendstream && pa_raop_client_send_audio_packet(u->raop, &u->memchunk, offset) < 0) { + if (is_streaming && pa_raop_client_send_audio_packet(u->raop, &u->memchunk, offset) < 0) { if (errno == EINTR) { /* Just try again. */ - pa_log_debug("Failed to write data to FIFO (EINTR), retrying"); - if (u->autoreconnect) { - pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_DISCONNECT_REQUEST, - 0, 0, NULL, NULL); + pa_log_debug("Failed to write audio packet (EINTR), retrying"); + continue; + } else if (errno == EAGAIN) { + if (u->oob) { + /* Just try again. */ + pa_log_debug("Failed to write audio packet (EAGAIN), retrying"); continue; - } else - goto fail; - } else if (errno != EAGAIN && !u->oob) { - /* Buffer is full, wait for POLLOUT. */ - if (!u->oob) { + } else { + /* Buffer is full, wait for POLLOUT. */ pollfd->events = POLLOUT; pollfd->revents = 0; } } else { - pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); - if (u->autoreconnect) { - pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_DISCONNECT_REQUEST, - 0, 0, NULL, NULL); - continue; - } else - goto fail; + pa_log("Failed to write audio packet: %s", pa_cstrerror(errno)); + disconnect(u); + continue; } } else { - if (sendstream) { + if (is_streaming) { u->write_count += (uint64_t) u->memchunk.index - (uint64_t) index; - } else { + } else if (u->autonull) { u->write_count += u->memchunk.length; u->memchunk.length = 0; } @@ -627,7 +638,7 @@ static void thread_func(void *userdata) { pa_smoother_put(u->smoother, now, estimated); #endif - if ((u->autonull && !canstream) || (u->oob && canstream && on_timeout)) { + if ((u->autonull && !is_streaming) || (u->oob && is_streaming && on_timeout)) { /* Sleep until next packet transmission */ intvl = u->start + pa_bytes_to_usec(u->write_count, &u->sink->sample_spec); pa_rtpoll_set_timer_absolute(u->rtpoll, intvl); @@ -660,6 +671,18 @@ static int sink_set_port_cb(pa_sink *s, pa_device_port *p) { return 0; } +static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + struct userdata *u = userdata; + + pa_assert_ctl_context(); + + /* Try to reconnect on server changes */ + if (u->sink->active_port->available == PA_AVAILABLE_NO) { + pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), + PA_SINK_MESSAGE_CONNECT_REQUEST, NULL, 0, NULL, NULL); + } +} + static pa_device_port *raop_create_port(struct userdata *u, const char *server) { pa_device_port_new_data data; pa_device_port *port; @@ -902,6 +925,11 @@ pa_sink* pa_raop_sink_new(pa_module *m, pa_modargs *ma, const char *driver) { goto fail; } + u->subscription = pa_subscription_new( + m->core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, + subscribe_callback, u + ); + u->sink->parent.process_msg = sink_process_msg; u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb; pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb); @@ -939,7 +967,8 @@ pa_sink* pa_raop_sink_new(pa_module *m, pa_modargs *ma, const char *driver) { /* username = pa_modargs_get_value(ma, "username", NULL); */ password = pa_modargs_get_value(ma, "password", NULL); - pa_raop_client_authenticate(u->raop, password ); + if (password) + u->password = pa_xstrdup(password); return u->sink; @@ -969,6 +998,10 @@ static void userdata_free(struct userdata *u) { pa_sink_unref(u->sink); u->sink = NULL; + if (u->subscription) + pa_subscription_free(u->subscription); + u->subscription = NULL; + if (u->rtpoll_item) pa_rtpoll_item_free(u->rtpoll_item); if (u->rtpoll) @@ -995,6 +1028,8 @@ static void userdata_free(struct userdata *u) { pa_card_free(u->card); if (u->server) pa_xfree(u->server); + if (u->password) + pa_xfree(u->password); pa_xfree(u); } diff --git a/src/modules/rtp/meson.build b/src/modules/rtp/meson.build index 119cf08ce..ba6a586b3 100644 --- a/src/modules/rtp/meson.build +++ b/src/modules/rtp/meson.build @@ -2,16 +2,18 @@ librtp_sources = [ 'rtp-common.c', 'sdp.c', 'sap.c', - 'rtsp_client.c', + 'rtsp-client.c', 'headerlist.c', + 'rtsp-util.c' ] librtp_headers = [ 'rtp.h', 'sdp.h', 'sap.h', - 'rtsp_client.h', + 'rtsp-client.h', 'headerlist.h', + 'rtsp-util.h' ] if have_gstreamer @@ -26,7 +28,7 @@ librtp = shared_library('rtp', c_args : [pa_c_args, server_c_args], link_args : [nodelete_link_args], include_directories : [configinc, topinc], - dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libatomic_ops_dep, gst_dep, gstapp_dep, gstrtp_dep, gio_dep], + dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libatomic_ops_dep, gst_dep, gstapp_dep, gstrtp_dep, gio_dep, openssl_dep], install : true, install_rpath : privlibdir, install_dir : modlibexecdir, diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp-client.c similarity index 70% rename from src/modules/rtp/rtsp_client.c rename to src/modules/rtp/rtsp-client.c index 9fd386abe..a27b46930 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp-client.c @@ -40,16 +40,30 @@ #include #include #include +#include #include #include #include #include #include -#include "rtsp_client.h" +#include "rtsp-client.h" +#include "rtsp-util.h" #define RECONNECT_INTERVAL (5 * PA_USEC_PER_SEC) +enum wait_state { + WAIT_NONE, + WAIT_RESPONSE, + WAIT_HEADERS +}; + +enum auth_method { + AUTH_NONE, + AUTH_BASIC, + AUTH_DIGEST +}; + struct pa_rtsp_client { pa_mainloop_api *mainloop; char *hostname; @@ -62,10 +76,16 @@ struct pa_rtsp_client { void *userdata; const char *useragent; + const char *username; + const char *password; + enum auth_method mth; + char *realm, *nonce; pa_rtsp_state_t state; pa_rtsp_status_t status; - uint8_t waiting; + enum wait_state waiting; + pa_mutex *mutex; + int length; pa_headerlist* headers; char *last_header; @@ -100,7 +120,13 @@ pa_rtsp_client* pa_rtsp_client_new(pa_mainloop_api *mainloop, const char *hostna else c->useragent = "PulseAudio RTSP Client"; + c->mth = AUTH_NONE; c->autoreconnect = autoreconnect; + + c->waiting = WAIT_NONE; + c->mutex = pa_mutex_new(false, false); + c->length = 0; + return c; } @@ -128,6 +154,10 @@ void pa_rtsp_client_free(pa_rtsp_client *c) { pa_xfree(c->session); pa_xfree(c->transport); pa_xfree(c->last_header); + pa_xfree(c->realm); + pa_xfree(c->nonce); + pa_mutex_free(c->mutex); + c->mutex = NULL; if (c->header_buffer) pa_strbuf_free(c->header_buffer); if (c->response_headers) @@ -137,14 +167,78 @@ void pa_rtsp_client_free(pa_rtsp_client *c) { pa_xfree(c); } +static void authenticate(pa_rtsp_client *c) { + const char *current = NULL; + const char *wath; + char space[] = " "; + char *token = NULL; + char *val = NULL, *mth = NULL; + char comma[] = ","; + + pa_xfree(c->realm); + pa_xfree(c->nonce); + + c->mth = AUTH_NONE; + c->realm = c->nonce = NULL; + + if (!c->username || !c->password) + return; + + wath = pa_headerlist_gets(c->response_headers, "WWW-Authenticate"); + + if (!wath) + return; + + mth = pa_split(wath, space, ¤t); + + if (pa_safe_streq(mth, "Basic")) + c->mth = AUTH_BASIC; + else if (pa_safe_streq(mth, "Digest")) + c->mth = AUTH_DIGEST; + else + goto done; + + while ((token = pa_split(wath, comma, ¤t))) { + if ((val = strstr(token, "="))) { + if (NULL == c->realm && val > strstr(token, "realm")) { + if (!(c->realm = pa_xstrdup(val + 2))) + goto done; + pa_rtsp_rtrim_char(c->realm, '\"'); + } + else if (NULL == c->nonce && val > strstr(token, "nonce")) { + if (!(c->nonce = pa_xstrdup(val + 2))) + goto done; + pa_rtsp_rtrim_char(c->nonce, '\"'); + } + } + + pa_xfree(token); + token = NULL; + } + +done: + pa_xfree(token); + pa_xfree(mth); +} + static void headers_read(pa_rtsp_client *c) { char delimiters[] = ";"; - char* token; + char* token = NULL; + const char *clength; pa_assert(c); pa_assert(c->response_headers); pa_assert(c->callback); + c->length = 0; + + clength = pa_headerlist_gets(c->response_headers, "Content-Length"); + if (clength && pa_atoi(clength, &c->length) < 0) + pa_log_warn("Unexpected value in content-length: %s", clength); + + if (c->status == STATUS_UNAUTHORIZED) + authenticate(c); + /* Deal with a SETUP response */ if (STATE_SETUP == c->state) { const char* token_state = NULL; @@ -154,7 +248,7 @@ static void headers_read(pa_rtsp_client *c) { if (!c->session || !c->transport) { pa_log("Invalid SETUP response."); - return; + goto done; } /* Now parse out the server port component of the response. */ @@ -165,12 +259,10 @@ static void headers_read(pa_rtsp_client *c) { if (pa_atou(pc + 1, &p) < 0 || p <= 0 || p > 0xffff) { pa_log("Invalid SETUP response (invalid server_port)."); - pa_xfree(token); - return; + goto done; } c->rtp_port = p; - pa_xfree(token); break; } } @@ -179,15 +271,20 @@ static void headers_read(pa_rtsp_client *c) { if (0 == c->rtp_port) { /* Error no server_port in response */ pa_log("Invalid SETUP response (no port number)."); - return; + goto done; } } +done: + pa_xfree(token); + + c->waiting = WAIT_NONE; + /* Call our callback */ c->callback(c, c->state, c->status, c->response_headers, c->userdata); } -static void line_callback(pa_ioline *line, const char *s, void *userdata) { +static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata) { pa_rtsp_client *c = userdata; char *delimpos; char *s2, *s2p; @@ -203,6 +300,25 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { return; } + /* Skip any body from the last response */ + if (c->length) { + if (l > c->length) { + l -= c->length; + s += c->length; + c->length = 0; + } else { + c->length -= l; + return; + } + } + + pa_assert(l); + + if (c->waiting == WAIT_NONE) { + pa_log_warn("Received more data than content length"); + return; + } + s2 = pa_xstrdup(s); /* Trim trailing carriage returns */ s2p = s2 + strlen(s2) - 1; @@ -211,23 +327,27 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { s2p -= 1; } - if (c->waiting && pa_streq(s2, "RTSP/1.0 200 OK")) { + if (c->waiting == WAIT_RESPONSE && 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; + c->waiting = WAIT_HEADERS; goto exit; - } else if (c->waiting && pa_streq(s2, "RTSP/1.0 401 Unauthorized")) { + } else if (c->waiting == WAIT_RESPONSE && 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; + c->waiting = WAIT_HEADERS; goto exit; - } else if (c->waiting) { + } else if (c->waiting == WAIT_RESPONSE) { + if (c->response_headers) + pa_headerlist_free(c->response_headers); + c->response_headers = pa_headerlist_new(); + pa_log_warn("Unexpected/Unhandled response: %s", s2); if (pa_streq(s2, "RTSP/1.0 400 Bad Request")) @@ -236,6 +356,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { c->status = STATUS_INTERNAL_ERROR; else c->status = STATUS_NO_RESPONSE; + c->waiting = WAIT_HEADERS; goto exit; } @@ -252,7 +373,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { c->header_buffer = NULL; } - pa_log_debug("Full response received. Dispatching"); + pa_log_debug("Response received. Dispatching"); headers_read(c); goto exit; } @@ -312,10 +433,10 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { } static void reconnect_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) { - if (userdata) { - pa_rtsp_client *c = userdata; - pa_rtsp_connect(c); - } + pa_rtsp_client *c = userdata; + pa_assert(c); + if (pa_rtsp_connect(c)) + c->callback(c, STATE_DISCONNECTED, STATUS_NO_RESPONSE, NULL, c->userdata); } static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { @@ -333,6 +454,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata pa_assert(c->sc == sc); pa_socket_client_unref(c->sc); c->sc = NULL; + c->waiting = WAIT_NONE; if (!io) { if (c->autoreconnect) { @@ -346,6 +468,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata c->mainloop->time_restart(c->reconnect_event, pa_timeval_rtstore(&tv, pa_rtclock_now() + RECONNECT_INTERVAL, true)); } else { pa_log("Connection to server %s:%d failed: %s", c->hostname, c->port, pa_cstrerror(errno)); + c->callback(c, STATE_DISCONNECTED, STATUS_NO_RESPONSE, NULL, c->userdata); } return; } @@ -389,7 +512,7 @@ int pa_rtsp_connect(pa_rtsp_client *c) { } pa_socket_client_set_callback(c->sc, on_connection, c); - c->waiting = 1; + c->waiting = WAIT_RESPONSE; c->state = STATE_CONNECT; c->status = STATUS_NO_RESPONSE; return 0; @@ -424,18 +547,18 @@ 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); + pa_xfree(c->url); c->url = pa_xstrdup(url); } +void pa_rtsp_set_credentials(pa_rtsp_client *c, const char *username, const char*password) { + c->username = username; + c->password = password; +} + bool pa_rtsp_has_header(pa_rtsp_client *c, const char *key) { pa_assert(c); pa_assert(key); @@ -465,22 +588,63 @@ void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key) { pa_headerlist_remove(c->headers, key); } -static int rtsp_exec(pa_rtsp_client *c, const char *cmd, - const char *content_type, const char *content, - int expect_response, - pa_headerlist *headers) { +static char *get_auth(pa_rtsp_client *c, const char *method, const char *url) { + char *ath = NULL, *response = NULL; + + pa_assert(method); + pa_assert(url); + + if (!c->username || !c->password) + return NULL; + + switch (c->mth) { + case AUTH_NONE: + break; + case AUTH_BASIC: + pa_rtsp_basic_response(c->username, c->password, &response); + ath = pa_sprintf_malloc("Basic %s", response); + break; + case AUTH_DIGEST: + pa_rtsp_digest_response(c->username, c->realm, c->password, c->nonce, method, url, &response); + ath = pa_sprintf_malloc("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", + c->username, c->realm, c->nonce, url, response); + break; + } + + pa_xfree(response); + + return ath; +} + +static int rtsp_exec(pa_rtsp_client *c, const char *cmd, const char *url, + pa_headerlist *headers, const char *content_type, const char *content) { pa_strbuf *buf; char *hdrs; - pa_assert(c); - pa_assert(c->url); pa_assert(cmd); + pa_assert(url); + pa_assert(c); pa_assert(c->ioline); + if (!pa_mutex_try_lock(c->mutex)) { + pa_log_warn("Can't send command (locked): %s", cmd); + return -1; + } + + if (c->waiting != WAIT_NONE) { + pa_log_warn("Can't send command (busy): %s", cmd); + pa_mutex_unlock(c->mutex); + return -1; + } + pa_log_debug("Sending command: %s", cmd); + c->waiting = WAIT_RESPONSE; + + pa_mutex_unlock(c->mutex); + buf = pa_strbuf_new(); - pa_strbuf_printf(buf, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, c->url, ++c->cseq); + pa_strbuf_printf(buf, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, url, ++c->cseq); if (c->session) pa_strbuf_printf(buf, "Session: %s\r\n", c->session); @@ -496,6 +660,13 @@ static int rtsp_exec(pa_rtsp_client *c, const char *cmd, content_type, (int)strlen(content)); } + char *auth = get_auth(c, cmd, url); + + if (auth) { + pa_strbuf_printf(buf, "Authorization: %s\r\n", auth); + pa_xfree(auth); + } + pa_strbuf_printf(buf, "User-Agent: %s\r\n", c->useragent); if (c->headers) { @@ -516,24 +687,18 @@ static int rtsp_exec(pa_rtsp_client *c, const char *cmd, pa_log_debug(hdrs);*/ pa_ioline_puts(c->ioline, hdrs); pa_xfree(hdrs); - /* The command is sent we can configure the rtsp client structure to handle a new answer */ - c->waiting = 1; + return 0; } int pa_rtsp_options(pa_rtsp_client *c) { - char *url; int rv; pa_assert(c); - url = c->url; - c->state = STATE_OPTIONS; + if (!(rv = rtsp_exec(c, "OPTIONS", "*", NULL, NULL, NULL))) + c->state = STATE_OPTIONS; - c->url = (char *)"*"; - rv = rtsp_exec(c, "OPTIONS", NULL, NULL, 0, NULL); - - c->url = url; return rv; } @@ -545,8 +710,8 @@ int pa_rtsp_announce(pa_rtsp_client *c, const char *sdp) { if (!sdp) return -1; - c->state = STATE_ANNOUNCE; - rv = rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL); + if (!(rv = rtsp_exec(c, "ANNOUNCE", c->url, NULL, "application/sdp", sdp))) + c->state = STATE_ANNOUNCE; return rv; } @@ -563,8 +728,8 @@ int pa_rtsp_setup(pa_rtsp_client *c, const char *transport) { else pa_headerlist_puts(headers, "Transport", transport); - c->state = STATE_SETUP; - rv = rtsp_exec(c, "SETUP", NULL, NULL, 1, headers); + if (!(rv = rtsp_exec(c, "SETUP", c->url, headers, NULL, NULL))) + c->state = STATE_SETUP; pa_headerlist_free(headers); return rv; @@ -591,8 +756,8 @@ int pa_rtsp_record(pa_rtsp_client *c, uint16_t *seq, uint32_t *rtptime) { pa_headerlist_puts(headers, "RTP-Info", info); pa_xfree(info); - c->state = STATE_RECORD; - rv = rtsp_exec(c, "RECORD", NULL, NULL, 1, headers); + if (!(rv = rtsp_exec(c, "RECORD", c->url, headers, NULL, NULL))) + c->state = STATE_RECORD; pa_headerlist_free(headers); return rv; @@ -606,8 +771,19 @@ int pa_rtsp_setparameter(pa_rtsp_client *c, const char *param) { if (!param) return -1; - c->state = STATE_SET_PARAMETER; - rv = rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL); + if (!(rv = rtsp_exec(c, "SET_PARAMETER", c->url, NULL, "text/parameters", param))) + c->state = STATE_SET_PARAMETER; + + return rv; +} + +int pa_rtsp_post(pa_rtsp_client *c, const char *url) { + int rv; + + pa_assert(c); + + if (!(rv = rtsp_exec(c, "POST", url, NULL, NULL, NULL))) + c->state = STATE_POST; return rv; } @@ -624,8 +800,8 @@ int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime) { pa_headerlist_puts(headers, "RTP-Info", info); pa_xfree(info); - c->state = STATE_FLUSH; - rv = rtsp_exec(c, "FLUSH", NULL, NULL, 1, headers); + if (!(rv = rtsp_exec(c, "FLUSH", c->url, headers, NULL, NULL))) + c->state = STATE_FLUSH; pa_headerlist_free(headers); return rv; @@ -636,8 +812,8 @@ int pa_rtsp_teardown(pa_rtsp_client *c) { pa_assert(c); - c->state = STATE_TEARDOWN; - rv = rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL); + if (!(rv = rtsp_exec(c, "TEARDOWN", c->url, NULL, NULL, NULL))) + c->state = STATE_TEARDOWN; return rv; } diff --git a/src/modules/rtp/rtsp_client.h b/src/modules/rtp/rtsp-client.h similarity index 94% rename from src/modules/rtp/rtsp_client.h rename to src/modules/rtp/rtsp-client.h index 259308581..84e4ba858 100644 --- a/src/modules/rtp/rtsp_client.h +++ b/src/modules/rtp/rtsp-client.h @@ -39,6 +39,7 @@ typedef enum pa_rtsp_state { STATE_SETUP, STATE_RECORD, STATE_SET_PARAMETER, + STATE_POST, STATE_FLUSH, STATE_TEARDOWN, STATE_DISCONNECTED @@ -63,9 +64,9 @@ 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); +void pa_rtsp_set_credentials(pa_rtsp_client *c, const char *username, const char*password); 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); @@ -77,6 +78,7 @@ 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_setparameter(pa_rtsp_client *c, const char *param); +int pa_rtsp_post(pa_rtsp_client *c, const char *url); int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime); int pa_rtsp_teardown(pa_rtsp_client *c); diff --git a/src/modules/raop/raop-util.c b/src/modules/rtp/rtsp-util.c similarity index 81% rename from src/modules/raop/raop-util.c rename to src/modules/rtp/rtsp-util.c index febc204da..6cf2c56f7 100644 --- a/src/modules/raop/raop-util.c +++ b/src/modules/rtp/rtsp-util.c @@ -39,7 +39,7 @@ #include #include -#include "raop-util.h" +#include "rtsp-util.h" #ifndef MD5_DIGEST_LENGTH #define MD5_DIGEST_LENGTH 16 @@ -94,7 +94,7 @@ static unsigned int token_decode(const char *token) { return (marker << 24) | val; } -int pa_raop_base64_encode(const void *data, int len, char **str) { +int pa_rtsp_base64_encode(const void *data, int len, char **str) { const unsigned char *q; char *p, *s = NULL; int i, c; @@ -130,7 +130,7 @@ int pa_raop_base64_encode(const void *data, int len, char **str) { return strlen(s); } -int pa_raop_base64_decode(const char *str, void *data) { +int pa_rtsp_base64_decode(const char *str, void *data) { const char *p; unsigned char *q; @@ -153,7 +153,7 @@ int pa_raop_base64_decode(const char *str, void *data) { return q - (unsigned char *) data; } -int pa_raop_md5_hash(const char *data, int len, char **str) { +int pa_rtsp_md5_hash(const char *data, int len, char **str) { unsigned char d[MD5_DIGEST_LENGTH]; char *s = NULL; int i; @@ -162,7 +162,7 @@ int pa_raop_md5_hash(const char *data, int len, char **str) { pa_assert(str); MD5((unsigned char*) data, len, d); - s = pa_xnew(char, MD5_HASH_LENGTH); + s = pa_xnew(char, MD5_HASH_LENGTH+1); for (i = 0; i < MD5_DIGEST_LENGTH; i++) sprintf(&s[2*i], "%02x", (unsigned int) d[i]); @@ -171,36 +171,36 @@ int pa_raop_md5_hash(const char *data, int len, char **str) { return strlen(s); } -int pa_raop_basic_response(const char *user, const char *pwd, char **str) { +int pa_rtsp_basic_response(const char *user, const char *pwd, char **str) { char *tmp, *B = NULL; pa_assert(str); tmp = pa_sprintf_malloc("%s:%s", user, pwd); - pa_raop_base64_encode(tmp, strlen(tmp), &B); + pa_rtsp_base64_encode(tmp, strlen(tmp), &B); pa_xfree(tmp); *str = B; return strlen(B); } -int pa_raop_digest_response(const char *user, const char *realm, const char *password, - const char *nonce, const char *uri, char **str) { +int pa_rtsp_digest_response(const char *user, const char *realm, const char *password, + const char *nonce, const char *method, const char *uri, char **str) { char *A1, *HA1, *A2, *HA2; char *tmp, *KD = NULL; pa_assert(str); A1 = pa_sprintf_malloc("%s:%s:%s", user, realm, password); - pa_raop_md5_hash(A1, strlen(A1), &HA1); + pa_rtsp_md5_hash(A1, strlen(A1), &HA1); pa_xfree(A1); - A2 = pa_sprintf_malloc("OPTIONS:%s", uri); - pa_raop_md5_hash(A2, strlen(A2), &HA2); + A2 = pa_sprintf_malloc("%s:%s", method, uri); + pa_rtsp_md5_hash(A2, strlen(A2), &HA2); pa_xfree(A2); tmp = pa_sprintf_malloc("%s:%s:%s", HA1, nonce, HA2); - pa_raop_md5_hash(tmp, strlen(tmp), &KD); + pa_rtsp_md5_hash(tmp, strlen(tmp), &KD); pa_xfree(tmp); pa_xfree(HA1); @@ -209,3 +209,16 @@ int pa_raop_digest_response(const char *user, const char *realm, const char *pas *str = KD; return strlen(KD); } + +/** + * Function to trim a given character at the end of a string (no realloc). + * @param str Pointer to string + * @param rc Character to trim + */ +void pa_rtsp_rtrim_char(char *str, char rc) { + char *sp = str + strlen(str) - 1; + while (sp >= str && *sp == rc) { + *sp = '\0'; + sp -= 1; + } +} diff --git a/src/modules/raop/raop-util.h b/src/modules/rtp/rtsp-util.h similarity index 65% rename from src/modules/raop/raop-util.h rename to src/modules/rtp/rtsp-util.h index 7c25e5cab..d61b60c3e 100644 --- a/src/modules/raop/raop-util.h +++ b/src/modules/rtp/rtsp-util.h @@ -1,5 +1,5 @@ -#ifndef fooraoputilfoo -#define fooraoputilfoo +#ifndef foortsputilfoo +#define foortsputilfoo /*** This file is part of PulseAudio. @@ -27,15 +27,15 @@ Kungliga Tekniska högskolan. ***/ -#define RAOP_DEFAULT_LATENCY 2000 /* msec */ +int pa_rtsp_base64_encode(const void *data, int len, char **str); +int pa_rtsp_base64_decode(const char *str, void *data); -int pa_raop_base64_encode(const void *data, int len, char **str); -int pa_raop_base64_decode(const char *str, void *data); +int pa_rtsp_md5_hash(const char *data, int len, char **str); -int pa_raop_md5_hash(const char *data, int len, char **str); +int pa_rtsp_basic_response(const char *user, const char *pwd, char **str); +int pa_rtsp_digest_response(const char *user, const char *realm, const char *password, + const char *nonce, const char *method, const char *uri, char **str); -int pa_raop_basic_response(const char *user, const char *pwd, char **str); -int pa_raop_digest_response(const char *user, const char *realm, const char *password, - const char *nonce, const char *uri, char **str); +void pa_rtsp_rtrim_char(char *str, char rc); #endif diff --git a/src/pulsecore/cli.c b/src/pulsecore/cli.c index f94262988..ddb9dcfda 100644 --- a/src/pulsecore/cli.c +++ b/src/pulsecore/cli.c @@ -57,7 +57,7 @@ struct pa_cli { char *last_line; }; -static void line_callback(pa_ioline *line, const char *s, void *userdata); +static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata); static void client_kill(pa_client *c); pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m) { @@ -117,7 +117,7 @@ static void client_kill(pa_client *client) { c->eof_callback(c, c->userdata); } -static void line_callback(pa_ioline *line, const char *s, void *userdata) { +static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata) { pa_strbuf *buf; pa_cli *c = userdata; char *p; diff --git a/src/pulsecore/ioline.c b/src/pulsecore/ioline.c index dfc5a7336..92d1e8acc 100644 --- a/src/pulsecore/ioline.c +++ b/src/pulsecore/ioline.c @@ -227,14 +227,14 @@ static void failure(pa_ioline *l, bool process_leftover) { /* Pass the last missing bit to the client */ if (l->callback) { - char *p = pa_xstrndup(l->rbuf+l->rbuf_index, l->rbuf_valid_length); - l->callback(l, p, l->userdata); + char *p = pa_xmemdup(l->rbuf+l->rbuf_index, l->rbuf_valid_length); + l->callback(l, p, l->rbuf_valid_length, l->userdata); pa_xfree(p); } } if (l->callback) { - l->callback(l, NULL, l->userdata); + l->callback(l, NULL, 0, l->userdata); l->callback = NULL; } @@ -256,7 +256,7 @@ static void scan_for_lines(pa_ioline *l, size_t skip) { *e = 0; p = l->rbuf + l->rbuf_index; - m = strlen(p); + m = e - p; l->rbuf_index += m+1; l->rbuf_valid_length -= m+1; @@ -266,7 +266,7 @@ static void scan_for_lines(pa_ioline *l, size_t skip) { l->rbuf_index = 0; if (l->callback) - l->callback(l, pa_strip_nl(p), l->userdata); + l->callback(l, pa_strip_nl(p), m, l->userdata); skip = 0; } diff --git a/src/pulsecore/ioline.h b/src/pulsecore/ioline.h index 7b6dff32f..050df351f 100644 --- a/src/pulsecore/ioline.h +++ b/src/pulsecore/ioline.h @@ -30,7 +30,7 @@ typedef struct pa_ioline pa_ioline; -typedef void (*pa_ioline_cb_t)(pa_ioline*io, const char *s, void *userdata); +typedef void (*pa_ioline_cb_t)(pa_ioline*io, const char *s, size_t l, void *userdata); typedef void (*pa_ioline_drain_cb_t)(pa_ioline *io, void *userdata); pa_ioline* pa_ioline_new(pa_iochannel *io); diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c index e8d22ed37..fa2c6698d 100644 --- a/src/pulsecore/protocol-http.c +++ b/src/pulsecore/protocol-http.c @@ -629,7 +629,7 @@ static void handle_url(struct connection *c) { html_response(c, 404, "Not Found", NULL); } -static void line_callback(pa_ioline *line, const char *s, void *userdata) { +static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata) { struct connection *c = userdata; pa_assert(line); pa_assert(c);