[OGG123]: DCCP support for ogg123

This adds DCCP support to ogg123, which is enabled via pseudo-URIs of the type
	dccp://10.10.1.2:8000		# host:port
	dccp://10.10.1.2		# host with default port 8000
	dccp://localhost		# hostname (for IPv4 or IPv6 host)
	dccp://localhost:8001		# hostname with port
	dccp://[::1]:8000 		# same, but different syntax
	dccp://[badc0de::1]		# IPv6 host with default port 8000

This version patches against vorbis-tools-1.2.0

As in other uses of streaming, it is good to use the buffer option of ogg123
(`-b' argument which takes a size in kilobytes): in experimental test runs it
was found that values greater than 40 .. 50 are usually sufficient to allow
smooth streaming. E.g.

		ogg123  -b50  dccp://other_host:8000

Gerrit Renker
---
 ogg123/Makefile.am      |    2 
 ogg123/Makefile.in      |    8 -
 ogg123/dccp_transport.c |  324 ++++++++++++++++++++++++++++++++++++++++++++++++
 ogg123/transport.c      |    2 
 4 files changed, 331 insertions(+), 5 deletions(-)
--- /dev/null
+++ b/ogg123/dccp_transport.c
@@ -0,0 +1,324 @@
+/********************************************************************
+ *                                                                  *
+ * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE.   *
+ * USE, DISTRIBUTION AND REPRODUCTION OF THIS SOURCE IS GOVERNED BY *
+ * THE GNU PUBLIC LICENSE 2, WHICH IS INCLUDED WITH THIS SOURCE.    *
+ * PLEASE READ THESE TERMS BEFORE DISTRIBUTING.                     *
+ *                                                                  *
+ * DCCP Plugin for ogg123, Gerrit Renker 2008                       *
+ ********************************************************************/
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <pthread.h>
+
+#include <netdb.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "ogg123.h"
+#include "transport.h"
+#include "buffer.h"
+#include "status.h"
+#include "i18n.h"
+
+/* DCCP-specific defines which may not appear in all header files */
+#ifndef IPPROTO_DCCP
+#define IPPROTO_DCCP		33	/* IANA assigned value */
+#define SOCK_DCCP		6	/* Linux socket type */
+#define SOL_DCCP		269	/* Linux socket level */
+#endif
+
+/* The fallback port if none is given */
+#define DCCP_DEFAULT_PORT	"8000"
+
+extern signal_request_t sig_request;	/* Need access to global cancel flag */
+transport_t		dccp_transport;	/* Forward declaration */
+
+typedef struct dccp_private_t {
+  int cancel_flag;
+
+  buf_t *buf;
+  int	sockfd;
+
+  pthread_t datagram_thread;
+
+  data_source_t *data_source;
+  data_source_stats_t stats;
+} dccp_private_t;
+
+
+/* -------------------------- Private functions --------------------- */
+void *dccp_thread_func(void *arg)
+{
+  dccp_private_t *myarg = (dccp_private_t *) arg;
+  /* Buffer needs to hold one datagram size (approx. MPS value) */
+  char	buf[1500];
+  ssize_t len;
+  sigset_t set;
+
+  /* Block signals to this thread */
+  sigfillset(&set);
+  sigaddset(&set, SIGINT);
+  sigaddset(&set, SIGTSTP);
+  sigaddset(&set, SIGCONT);
+  if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0)
+    status_error(_("Error: Could not set signal mask."));
+
+  do  {
+     len = read(myarg->sockfd, buf, sizeof(buf));
+     if (len <= 0) {
+       buffer_mark_eos(myarg->buf);
+       if (len != 0)
+          status_error("DCCP transfer failed.");
+     } else if (myarg->cancel_flag || sig_request.cancel) {
+       buffer_abort_write(myarg->buf);
+       len = 0;
+     } else {
+       buffer_submit_data(myarg->buf, buf, len);
+     }
+  } while (len > 0);
+
+  close(myarg->sockfd);
+  myarg->sockfd = -1;
+
+  return (void *)len;
+}
+
+/* Resolve `host' (IPv4/6 address or DNS hostname) and connect on DCCP `port' */
+static int dccp_connect(const char *host, const char *port)
+{
+	struct addrinfo *remote = NULL, *dst, hints;
+	int rc, sockfd = -1;
+
+	/* Set up address hint structure */
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	/* getaddrinfo does not really work well with SOCK_DCCP - ignore ai_socktype */
+
+	/* only use addresses available on the host */
+	/* use v4-mapped-v6 if no v6 addresses found */
+	hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED | AI_ALL;
+
+	/* Obtain remote address information */
+	if ((rc = getaddrinfo(host, port, &hints, &remote))) {
+		status_error("Can not resolve DCCP address %s#%s: %s.",
+				host, port, gai_strerror(rc));
+		exit(1);
+	}
+
+	for (dst = remote; dst != NULL; dst = dst->ai_next) {
+		sockfd = socket(dst->ai_family, SOCK_DCCP, IPPROTO_DCCP);
+		if (sockfd < 0)
+			continue;
+		if (connect(sockfd, dst->ai_addr, dst->ai_addrlen) == 0)
+			break;
+		close(sockfd);
+	}
+	freeaddrinfo(remote);
+
+	if (dst == NULL) {
+		status_error("Can not create DCCP socket: %s.", strerror(errno));
+		exit(1);
+	}
+	/*
+	 * Optimisation: we know we won't be writing any data to the server. We thus
+	 * shut down the write end, which causes the kernel module for the client-server
+	 * half-connection to be shut down. This reduces internal processing costs.
+	 */
+	if (shutdown(sockfd, SHUT_WR) < 0)
+		status_error("Can not close DCCP write end.");
+	return sockfd;
+}
+
+/*
+ * Handles pseudo-URIs of the type
+ *	dccp://x.x.x.x[:port]		// IPv4 address with optional port
+ *	dccp://hostname[:port]		// hostname plus optional port
+ *	dccp://[x:x:x:...:x][:port]	// IPv6 address with optional port
+ * The syntax is inspired by RFC 3986, in particular with regard to IPv6 addresses.
+ */
+static int dccp_init(const char* url)
+{
+	char *host, *port, *copy_url, *ptr;
+	int  sockfd;
+
+	/* string needs to start with dccp://host-part and host-part must not be empty */
+	if (strncmp(url, "dccp://", 7) || strlen(url) <= 7)
+		goto no_match;
+	host = ptr = copy_url = strdup(url + 7);
+
+	if (*host == '[') {			/* dccp://[IPv6-address] */
+		ptr = strrchr(host++, ']');
+		if (ptr == NULL)		/* missing right bracket */
+			goto no_match;
+		*ptr++ = '\0';
+	}
+
+	/* the last ':' in the string separates host:port */
+	port = strrchr(ptr, ':');
+	if (port == NULL)
+		port = DCCP_DEFAULT_PORT;
+	else if (port == host || strlen(port) < 2 || strchr(ptr, ':') != port)
+		/* empty hostname/port or IPv6 ambiguity (e.g. dccp://abcd::1) */
+		goto no_match;
+	else	/* non-empty port specified */
+		*port++ = '\0';
+
+	sockfd = dccp_connect(host, port);
+	free(copy_url);
+	return sockfd;
+
+no_match:
+	status_error("The URI \"%s\" does not match dccp://host[:port] pattern (see RFC 3986).", url);
+	exit(1);
+}
+
+/* -------------------------- Public interface -------------------------- */
+
+int dccp_can_transport(char *source_string)
+{
+  return !strncmp(source_string, "dccp://", 7);
+}
+
+
+data_source_t* dccp_open(char *source_string, ogg123_options_t *ogg123_opts)
+{
+  data_source_t  *source  = malloc(sizeof(data_source_t));
+  dccp_private_t *private = malloc(sizeof(dccp_private_t));
+
+  if (source != NULL && private != NULL) {
+    source->source_string = strdup(source_string);
+    source->transport = &dccp_transport;
+    source->private = private;
+
+    private->buf = buffer_create(ogg123_opts->input_buffer_size,
+				 ogg123_opts->input_buffer_size *
+				 ogg123_opts->input_prebuffer / 100.0,
+				 NULL, NULL,  /* No write callback, using
+					         buffer in pull mode. */
+				 0 /* Irrelevant */);
+    if (private->buf == NULL) {
+      status_error(_("Error: Unable to create input buffer.\n"));
+      exit(1);
+    }
+
+    private->data_source = source;
+    private->stats.transfer_rate = 0;
+    private->stats.bytes_read = 0;
+    private->stats.input_buffer_used = 0;
+    private->cancel_flag = 0;
+
+  } else {
+    status_error(_("Error: Out of memory.\n"));
+    exit(1);
+  }
+
+  /* Open URL */
+  private->sockfd = dccp_init(source_string);
+  if (private->sockfd < 0)
+    goto fail;
+
+  /* Start thread */
+  if (pthread_create(&private->datagram_thread, NULL, dccp_thread_func,
+		     private) != 0)
+    goto fail;
+
+  return source;
+
+fail:
+  free(source->source_string);
+  free(private);
+  free(source);
+
+  return NULL;
+}
+
+
+int dccp_peek(data_source_t *source, void *ptr, size_t size, size_t nmemb)
+{
+  return 0;
+}
+
+
+int dccp_read(data_source_t *source, void *ptr, size_t size, size_t nmemb)
+{
+  dccp_private_t *private = source->private;
+  int bytes_read;
+
+  if (private->cancel_flag || sig_request.cancel)
+    return 0;
+
+  bytes_read = buffer_get_data(private->buf, ptr, size * nmemb);
+
+  private->stats.bytes_read += bytes_read;
+
+  return bytes_read;
+}
+
+
+int dccp_seek(data_source_t *source, long offset, int whence)
+{
+  return -1;
+}
+
+
+data_source_stats_t *dccp_statistics(data_source_t *source)
+{
+  dccp_private_t *private = source->private;
+  data_source_stats_t *data_source_stats;
+  buffer_stats_t *buffer_stats;
+
+  data_source_stats = malloc_data_source_stats(&private->stats);
+  data_source_stats->input_buffer_used = 1;
+  data_source_stats->transfer_rate = 0;
+
+  buffer_stats = buffer_statistics(private->buf);
+  data_source_stats->input_buffer = *buffer_stats;
+  free(buffer_stats);
+
+  return data_source_stats;
+}
+
+
+long dccp_tell(data_source_t *source)
+{
+  return 0;
+}
+
+
+void dccp_close(data_source_t *source)
+{
+  dccp_private_t *private = source->private;
+
+  private->cancel_flag = 1;
+  buffer_abort_write(private->buf);
+  pthread_join(private->datagram_thread, NULL);
+
+  buffer_destroy(private->buf);
+  private->buf = NULL;
+
+  free(source->source_string);
+  free(source->private);
+  free(source);
+}
+
+
+transport_t dccp_transport = {
+  "dccp",
+  &dccp_can_transport,
+  &dccp_open,
+  &dccp_peek,
+  &dccp_read,
+  &dccp_seek,
+  &dccp_statistics,
+  &dccp_tell,
+  &dccp_close
+};
--- a/ogg123/transport.c
+++ b/ogg123/transport.c
@@ -29,8 +29,10 @@ extern transport_t file_transport;
 #ifdef HAVE_CURL
 extern transport_t http_transport;
 #endif
+extern transport_t dccp_transport;
 
 transport_t *transports[] = {
+  &dccp_transport,
 #ifdef HAVE_CURL
   &http_transport,
 #endif
--- a/ogg123/Makefile.am
+++ b/ogg123/Makefile.am
@@ -30,7 +30,7 @@ ogg123_LDADD = @SHARE_LIBS@ \
 ogg123_DEPENDENCIES = @SHARE_LIBS@
 ogg123_SOURCES = audio.c buffer.c callbacks.c \
                 cfgfile_options.c cmdline_options.c \
-                file_transport.c format.c http_transport.c \
+                file_transport.c format.c http_transport.c dccp_transport.c \
                 ogg123.c oggvorbis_format.c playlist.c \
                 status.c remote.c transport.c vorbis_comments.c \
                 audio.h buffer.h callbacks.h compat.h \
--- a/ogg123/Makefile.in
+++ b/ogg123/Makefile.in
@@ -51,7 +51,7 @@ binPROGRAMS_INSTALL = $(INSTALL_PROGRAM)
 PROGRAMS = $(bin_PROGRAMS)
 am__ogg123_SOURCES_DIST = audio.c buffer.c callbacks.c \
 	cfgfile_options.c cmdline_options.c file_transport.c format.c \
-	http_transport.c ogg123.c oggvorbis_format.c playlist.c \
+	http_transport.c dccp_transport.c ogg123.c oggvorbis_format.c playlist.c \
 	status.c remote.c transport.c vorbis_comments.c audio.h \
 	buffer.h callbacks.h compat.h cfgfile_options.h \
 	cmdline_options.h format.h ogg123.h playlist.h status.h \
@@ -62,8 +62,8 @@ am__ogg123_SOURCES_DIST = audio.c buffer
 @HAVE_LIBSPEEX_TRUE@am__objects_2 = speex_format.$(OBJEXT)
 am_ogg123_OBJECTS = audio.$(OBJEXT) buffer.$(OBJEXT) \
 	callbacks.$(OBJEXT) cfgfile_options.$(OBJEXT) \
-	cmdline_options.$(OBJEXT) file_transport.$(OBJEXT) \
-	format.$(OBJEXT) http_transport.$(OBJEXT) ogg123.$(OBJEXT) \
+	cmdline_options.$(OBJEXT) format.$(OBJEXT) ogg123.$(OBJEXT) \
+	file_transport.$(OBJEXT) http_transport.$(OBJEXT) dccp_transport.$(OBJEXT) \
 	oggvorbis_format.$(OBJEXT) playlist.$(OBJEXT) status.$(OBJEXT) \
 	remote.$(OBJEXT) transport.$(OBJEXT) vorbis_comments.$(OBJEXT) \
 	$(am__objects_1) $(am__objects_2)
@@ -287,7 +287,7 @@ ogg123_LDADD = @SHARE_LIBS@ \
 ogg123_DEPENDENCIES = @SHARE_LIBS@
 ogg123_SOURCES = audio.c buffer.c callbacks.c \
                 cfgfile_options.c cmdline_options.c \
-                file_transport.c format.c http_transport.c \
+                file_transport.c format.c http_transport.c dccp_transport.c \
                 ogg123.c oggvorbis_format.c playlist.c \
                 status.c remote.c transport.c vorbis_comments.c \
                 audio.h buffer.h callbacks.h compat.h \
