#include "xmalloc.h"
#include "log.h"
+/* Don't accept more connection than this */
+#define MAX_CONNECTIONS 10
+
+/* Kick a client if it doesn't authenticate within this time */
+#define AUTH_TIMEOUT 5
+
#define DEFAULT_COOKIE_FILE ".esd_auth"
#define PLAYBACK_BUFFER_SECONDS (.5)
#define SCACHE_PREFIX "esound."
+#define PA_TYPEID_ESOUND PA_TYPEID_MAKE('E', 'S', 'D', 'P')
+
/* This is heavily based on esound's code */
struct connection {
char *name;
struct pa_sample_spec sample_spec;
} scache;
+
+ struct pa_time_event *auth_timeout_event;
};
struct pa_protocol_esound {
if (c->scache.memchunk.memblock)
pa_memblock_unref(c->scache.memchunk.memblock);
pa_xfree(c->scache.name);
+
+ if (c->auth_timeout_event)
+ c->protocol->core->mainloop->time_free(c->auth_timeout_event);
pa_xfree(c);
}
return (uint8_t*) c->write_data+i;
}
-static void format_esd2native(int format, struct pa_sample_spec *ss) {
+static void format_esd2native(int format, int swap_bytes, struct pa_sample_spec *ss) {
assert(ss);
ss->channels = ((format & ESD_MASK_CHAN) == ESD_STEREO) ? 2 : 1;
- ss->format = ((format & ESD_MASK_BITS) == ESD_BITS16) ? PA_SAMPLE_S16NE : PA_SAMPLE_U8;
+ if ((format & ESD_MASK_BITS) == ESD_BITS16)
+ ss->format = swap_bytes ? PA_SAMPLE_S16RE : PA_SAMPLE_S16NE;
+ else
+ ss->format = PA_SAMPLE_U8;
}
static int format_native2esd(struct pa_sample_spec *ss) {
}
c->authorized = 1;
+ if (c->auth_timeout_event) {
+ c->protocol->core->mainloop->time_free(c->auth_timeout_event);
+ c->auth_timeout_event = NULL;
+ }
}
ekey = *(uint32_t*)((uint8_t*) data+ESD_KEY_LEN);
rate = maybe_swap_endian_32(c->swap_byte_order, *((int*)data + 1));
ss.rate = rate;
- format_esd2native(format, &ss);
+ format_esd2native(format, c->swap_byte_order, &ss);
- if (!pa_sample_spec_valid(&ss))
+ if (!pa_sample_spec_valid(&ss)) {
+ pa_log(__FILE__": invalid sample specification\n");
return -1;
+ }
if (!(sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, 1))) {
- pa_log(__FILE__": No output sink\n");
+ pa_log(__FILE__": no such sink\n");
return -1;
}
pa_client_set_name(c->client, name);
- assert(!c->input_memblockq);
+ assert(!c->sink_input && !c->input_memblockq);
+
+ if (!(c->sink_input = pa_sink_input_new(sink, PA_TYPEID_ESOUND, name, &ss, 0, -1))) {
+ pa_log(__FILE__": failed to create sink input.\n");
+ return -1;
+ }
l = (size_t) (pa_bytes_per_second(&ss)*PLAYBACK_BUFFER_SECONDS);
c->input_memblockq = pa_memblockq_new(l, 0, pa_frame_size(&ss), l/2, l/PLAYBACK_BUFFER_FRAGMENTS, c->protocol->core->memblock_stat);
- assert(c->input_memblockq);
pa_iochannel_socket_set_rcvbuf(c->io, l/PLAYBACK_BUFFER_FRAGMENTS*2);
c->playback.fragment_size = l/10;
-
- assert(!c->sink_input);
- c->sink_input = pa_sink_input_new(sink, name, &ss, 0, -1);
- assert(c->sink_input);
c->sink_input->owner = c->protocol->module;
c->sink_input->client = c->client;
rate = maybe_swap_endian_32(c->swap_byte_order, *((int*)data + 1));
ss.rate = rate;
- format_esd2native(format, &ss);
+ format_esd2native(format, c->swap_byte_order, &ss);
- if (!pa_sample_spec_valid(&ss))
+ if (!pa_sample_spec_valid(&ss)) {
+ pa_log(__FILE__": invalid sample specification.\n");
return -1;
+ }
if (request == ESD_PROTO_STREAM_MON) {
struct pa_sink* sink;
- if (!(sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, 1)))
+ if (!(sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, 1))) {
+ pa_log(__FILE__": no such sink.\n");
return -1;
+ }
- if (!(source = sink->monitor_source))
+ if (!(source = sink->monitor_source)) {
+ pa_log(__FILE__": no such monitor source.\n");
return -1;
+ }
} else {
assert(request == ESD_PROTO_STREAM_REC);
- if (!(source = pa_namereg_get(c->protocol->core, c->protocol->source_name, PA_NAMEREG_SOURCE, 1)))
+ if (!(source = pa_namereg_get(c->protocol->core, c->protocol->source_name, PA_NAMEREG_SOURCE, 1))) {
+ pa_log(__FILE__": no such source.\n");
return -1;
+ }
}
strncpy(name, (char*) data + sizeof(int)*2, sizeof(name));
pa_client_set_name(c->client, name);
- assert(!c->output_memblockq);
+ assert(!c->output_memblockq && !c->source_output);
+
+ if (!(c->source_output = pa_source_output_new(source, PA_TYPEID_ESOUND, name, &ss, -1))) {
+ pa_log(__FILE__": failed to create source output\n");
+ return -1;
+ }
l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS);
c->output_memblockq = pa_memblockq_new(l, 0, pa_frame_size(&ss), 0, 0, c->protocol->core->memblock_stat);
- assert(c->output_memblockq);
pa_iochannel_socket_set_sndbuf(c->io, l/RECORD_BUFFER_FRAGMENTS*2);
- assert(!c->source_output);
- c->source_output = pa_source_output_new(source, name, &ss, -1);
- assert(c->source_output);
-
c->source_output->owner = c->protocol->module;
c->source_output->client = c->client;
c->source_output->push = source_output_push_cb;
latency = 0;
else {
double usec = pa_sink_get_latency(sink);
- usec += PLAYBACK_BUFFER_SECONDS*1000000; /* A better estimation would be a good idea! */
latency = (int) ((usec*44100)/1000000);
}
rate = maybe_swap_endian_32(c->swap_byte_order, *((int*)data + 1));
ss.rate = rate;
- format_esd2native(format, &ss);
+ format_esd2native(format, c->swap_byte_order, &ss);
sc_length = (size_t) maybe_swap_endian_32(c->swap_byte_order, (*((int*)data + 2)));
assert(c->read_data_length < sizeof(c->request));
if ((r = pa_iochannel_read(c->io, ((uint8_t*) &c->request) + c->read_data_length, sizeof(c->request) - c->read_data_length)) <= 0) {
- pa_log(__FILE__": read() failed: %s\n", r == 0 ? "EOF" : strerror(errno));
+ if (r != 0)
+ pa_log_warn(__FILE__": read() failed: %s\n", strerror(errno));
return -1;
}
assert(c->read_data && c->read_data_length < handler->data_length);
if ((r = pa_iochannel_read(c->io, (uint8_t*) c->read_data + c->read_data_length, handler->data_length - c->read_data_length)) <= 0) {
- pa_log(__FILE__": read() failed: %s\n", r == 0 ? "EOF" : strerror(errno));
+ if (r != 0)
+ pa_log_warn(__FILE__": read() failed: %s\n", strerror(errno));
return -1;
}
assert(c->scache.memchunk.memblock && c->scache.name && c->scache.memchunk.index < c->scache.memchunk.length);
if ((r = pa_iochannel_read(c->io, (uint8_t*) c->scache.memchunk.memblock->data+c->scache.memchunk.index, c->scache.memchunk.length-c->scache.memchunk.index)) <= 0) {
- pa_log(__FILE__": read() failed: %s\n", r == 0 ? "EOF" : strerror(errno));
+ if (r!= 0)
+ pa_log_warn(__FILE__": read() failed: %s\n", strerror(errno));
return -1;
}
}
if ((r = pa_iochannel_read(c->io, (uint8_t*) c->playback.current_memblock->data+c->playback.memblock_index, l)) <= 0) {
- pa_log(__FILE__": read() failed: %s\n", r == 0 ? "EOF" : strerror(errno));
+ if (r != 0)
+ pa_log(__FILE__": read() failed: %s\n", strerror(errno));
return -1;
}
-
-/* pa_log(__FILE__": read %u\n", r); */
+
+/* pa_log(__FILE__": read %u\n", r); */
chunk.memblock = c->playback.current_memblock;
chunk.index = c->playback.memblock_index;
pa_log(__FILE__": write(): %s\n", strerror(errno));
return -1;
}
-
+
pa_memblockq_drop(c->output_memblockq, &chunk, r);
pa_memblock_unref(chunk.memblock);
}
assert(c->protocol && c->protocol->core && c->protocol->core->mainloop && c->protocol->core->mainloop->defer_enable);
c->protocol->core->mainloop->defer_enable(c->defer_event, 0);
-/* pa_log("DOWORK\n"); */
-
- if (c->dead || !c->io)
- return;
+/* pa_log("DOWORK %i\n", pa_iochannel_is_hungup(c->io)); */
- if (pa_iochannel_is_readable(c->io))
+ if (!c->dead && pa_iochannel_is_readable(c->io))
if (do_read(c) < 0)
goto fail;
-
- if (pa_iochannel_is_writable(c->io))
+
+ if (!c->dead && pa_iochannel_is_writable(c->io))
if (do_write(c) < 0)
goto fail;
+
+ /* In case the line was hungup, make sure to rerun this function
+ as soon as possible, until all data has been read. */
+
+ if (!c->dead && pa_iochannel_is_hungup(c->io))
+ c->protocol->core->mainloop->defer_enable(c->defer_event, 1);
return;
/*** socket server callback ***/
+static void auth_timeout(struct pa_mainloop_api*m, struct pa_time_event *e, const struct timeval *tv, void *userdata) {
+ struct connection *c = userdata;
+ assert(m && tv && c && c->auth_timeout_event == e);
+
+ if (!c->authorized)
+ connection_free(c);
+}
+
static void on_connection(struct pa_socket_server*s, struct pa_iochannel *io, void *userdata) {
struct connection *c;
+ struct pa_protocol_esound *p = userdata;
char cname[256];
- assert(s && io && userdata);
+ assert(s && io && p);
+ if (pa_idxset_ncontents(p->connections)+1 > MAX_CONNECTIONS) {
+ pa_log(__FILE__": Warning! Too many connections (%u), dropping incoming connection.\n", MAX_CONNECTIONS);
+ pa_iochannel_free(io);
+ return;
+ }
+
c = pa_xmalloc(sizeof(struct connection));
- c->protocol = userdata;
+ c->protocol = p;
c->io = io;
pa_iochannel_set_callback(c->io, io_callback, c);
pa_iochannel_socket_peer_to_string(io, cname, sizeof(cname));
- assert(c->protocol->core);
- c->client = pa_client_new(c->protocol->core, "ESOUND", cname);
+ assert(p->core);
+ c->client = pa_client_new(p->core, PA_TYPEID_ESOUND, cname);
assert(c->client);
- c->client->owner = c->protocol->module;
+ c->client->owner = p->module;
c->client->kill = client_kill_cb;
c->client->userdata = c;
- c->authorized = c->protocol->public;
+ c->authorized = p->public;
c->swap_byte_order = 0;
c->dead = 0;
c->scache.memchunk.length = c->scache.memchunk.index = 0;
c->scache.memchunk.memblock = NULL;
c->scache.name = NULL;
+
+ if (!c->authorized) {
+ struct timeval tv;
+ pa_gettimeofday(&tv);
+ tv.tv_sec += AUTH_TIMEOUT;
+ c->auth_timeout_event = p->core->mainloop->time_new(p->core->mainloop, &tv, auth_timeout, c);
+ } else
+ c->auth_timeout_event = NULL;
- c->defer_event = c->protocol->core->mainloop->defer_new(c->protocol->core->mainloop, defer_callback, c);
+ c->defer_event = p->core->mainloop->defer_new(p->core->mainloop, defer_callback, c);
assert(c->defer_event);
- c->protocol->core->mainloop->defer_enable(c->defer_event, 0);
+ p->core->mainloop->defer_enable(c->defer_event, 0);
- pa_idxset_put(c->protocol->connections, c, &c->index);
+ pa_idxset_put(p->connections, c, &c->index);
}
/*** entry points ***/
struct pa_protocol_esound* pa_protocol_esound_new(struct pa_core*core, struct pa_socket_server *server, struct pa_module *m, struct pa_modargs *ma) {
struct pa_protocol_esound *p;
- int public;
+ int public = 0;
assert(core && server && ma);
p = pa_xmalloc(sizeof(struct pa_protocol_esound));