Merge branch 'fix-raop' into 'master'

raop: fix auth, reconnect, apple tv compat

See merge request pulseaudio/pulseaudio!820
This commit is contained in:
Mohamed Akram 2025-10-13 19:27:36 +04:00
commit 16203c5733
17 changed files with 780 additions and 603 deletions

View file

@ -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;

View file

@ -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

View file

@ -43,7 +43,7 @@
#include <pulsecore/namereg.h>
#include <pulsecore/avahi-wrap.h>
#include "raop-util.h"
#include "raop-common.h"
PA_MODULE_AUTHOR("Colin Guthrie");
PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices");

File diff suppressed because it is too large Load diff

View file

@ -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);

View file

@ -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 <http://www.gnu.org/licenses/>.
***/
#define RAOP_DEFAULT_LATENCY 2000 /* msec */
#endif

View file

@ -37,8 +37,9 @@
#include <pulsecore/macro.h>
#include <pulsecore/random.h>
#include <modules/rtp/rtsp-util.h>
#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;
}

View file

@ -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);
}

View file

@ -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,

View file

@ -40,16 +40,30 @@
#include <pulsecore/core-util.h>
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
#include <pulsecore/mutex.h>
#include <pulsecore/strbuf.h>
#include <pulsecore/ioline.h>
#include <pulsecore/arpa-inet.h>
#include <pulsecore/random.h>
#include <pulsecore/core-rtclock.h>
#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, &current);
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, &current))) {
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;
}

View file

@ -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);

View file

@ -39,7 +39,7 @@
#include <pulsecore/core-util.h>
#include <pulsecore/macro.h>
#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;
}
}

View file

@ -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

View file

@ -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;

View file

@ -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;
}

View file

@ -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);

View file

@ -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);