[MPG123]: DCCP support for mpg123

This adds DCCP support to mpg123, 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
	dccp://[badc0de::1]		# same, but different syntax

DCCP needs buffered I/O since the internals of mpg123 perform read data in chunks
(first the MP3 header, then the frame).
Hence, to allow file-based I/O and stream-based I/O (TCP) as well, the datagram
reads of DCCP are wrapped into standard libc buffered I/O.

As in other uses of streaming, it is good to use the buffer option of mpg123 
(`-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.
		mpg123 -b50 dccp://other_host:9011		      

This patches against mpg123-0.60.

Gerrit Renker
---
 src/Makefile.am |    1 
 src/Makefile.in |    3 -
 src/common.c    |    3 -
 src/dccp.c      |  125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/mpg123.h    |    6 ++
 src/playlist.c  |   16 +++++++
 src/readers.c   |   70 +++++++++++++++----------------
 7 files changed, 186 insertions(+), 38 deletions(-)

--- a/src/mpg123.h
+++ b/src/mpg123.h
@@ -225,7 +225,7 @@ struct reader {
   void (*rewind)(struct reader *);
   off_t filelen;
   off_t filepos;
-  int  filept;
+  FILE *filept;
   int  flags;
   unsigned char id3buf[128];
 };
@@ -254,6 +254,10 @@ extern unsigned long proxyip;
 extern int http_open (char* url, char** content_type);
 extern char *httpauth;
 
+/* ------ Declarations from "dccp.c" ------ */
+/* takes url (IPv4/6 address / hostname + optional port) */
+extern int  dccp_open(const char* url);
+
 /* ------ Declarations from "common.c" ------ */
 
 extern void audio_flush(int, struct audio_info_struct *);
--- /dev/null
+++ b/src/dccp.c
@@ -0,0 +1,125 @@
+/*
+ *	DCCP Plug-In for mpg123
+ *	Works in the same way as the HTTP plugin; it connects to a server serving
+ *	at a specified port, and expects a stream of MP3 bytes coming from that server.
+ *	The only difference is that the HTTP streaming is TCP-based (bytesteam mode),
+ *	while DCCP is record-oriented (datagram mode).
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <stdint.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 <errno.h>
+#include <ctype.h>
+
+#include "mpg123.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"
+
+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 local/remote address information */
+	if ((rc = getaddrinfo(host, port, &hints, &remote))) {
+		fprintf(stderr, "Can not resolve DCCP address %s#%s: %s.\n",
+				host, port, gai_strerror(rc));
+		return -1;
+	}
+
+	if(param.verbose > 1)
+		fprintf(stderr, "Connecting to DCCP host %s, port %s.\n", host, port);
+
+	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) {
+		fprintf(stderr, "Can't create DCCP socket %s#%s: %s.\n",
+				host, port, strerror(errno));
+		return -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)
+		fprintf(stderr, "Can not close write end: %s.\n", strerror(errno));
+	return sockfd;
+}
+
+/*
+ * Handler for URLs 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
+ */
+int dccp_open(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] (RFC 3986) */
+		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:
+	fprintf(stderr, "The URI \"%s\" does not match dccp://host[:port] pattern (see RFC 3986).\n", url);
+	exit(1);
+}
--- a/src/playlist.c
+++ b/src/playlist.c
@@ -210,6 +210,22 @@ int add_next_file (int argc, char *argv[
 					pl.file = fdopen(fd,"r");
 				}
 			}
+			else if (!strncmp(param.listname, "dccp://", 7))
+			{
+				int fd = dccp_open(param.listname);
+
+				if(fd < 0)
+				{
+					param.listname = NULL;
+					pl.file = NULL;
+					fprintf(stderr, "Error: invalid playlist from dccp_open()!\n");
+				}
+				else
+				{
+					pl.entry = 0;
+					pl.file = fdopen(fd,"r");
+				}
+			}
 			else if (!(pl.file = fopen(param.listname, "rb")))
 			{
 				perror (param.listname);
--- a/src/readers.c
+++ b/src/readers.c
@@ -36,8 +36,6 @@ static off_t get_fileinfo(struct reader 
  */
 static ssize_t fullread(struct reader *rds,unsigned char *buf, ssize_t count)
 {
-  ssize_t ret,cnt=0;
-
   /*
    * We check against READER_ID3TAG instead of rds->filelen >= 0 because
    * if we got the ID3 TAG we know we have the end of the file.  If we
@@ -46,28 +44,22 @@ static ssize_t fullread(struct reader *r
    */
   if ((rds->flags & READER_ID3TAG) && rds->filepos + count > rds->filelen)
     count = rds->filelen - rds->filepos;
-  while(cnt < count) {
-    ret = read(rds->filept,buf+cnt,count-cnt);
-    if(ret < 0)
-      return ret;
-    if(ret == 0)
-      break;
-    rds->filepos += ret;
-    cnt += ret;
-  } 
 
-  return cnt;
+  if (fread(buf, 1, count, rds->filept) < count) {
+    if(ferror(rds->filept))
+	  perror("read error");
+    return -1;
+  }
+
+  rds->filepos += count;
+  return count;
 }
 
 static off_t stream_lseek(struct reader *rds, off_t pos, int whence)
 {
-  off_t ret;
-
-  ret = lseek(rds->filept, pos, whence);
-  if (ret >= 0)
-    rds->filepos = ret;
-
-  return ret;
+  if (fseek(rds->filept, pos, whence) == -1)
+	  return -1;
+  return (rds->filepos = ftell(rds->filept));
 }
 
 static int default_init(struct reader *rds)
@@ -91,7 +83,7 @@ static int default_init(struct reader *r
 void stream_close(struct reader *rds)
 {
     if (rds->flags & READER_FD_OPENED)
-        close(rds->filept);
+        fclose(rds->filept);
 }
 
 /**************************************** 
@@ -242,26 +234,25 @@ static void stream_rewind(struct reader 
  * reads the last 128 bytes information into buffer
  * ... that is not totally safe...
  */
-static off_t get_fileinfo(struct reader *rds,char *buf)
+static long get_fileinfo(struct reader *rds,char *buf)
 {
-	off_t len;
+	long len;
 
-        if((len=lseek(rds->filept,0,SEEK_END)) < 0) {
+	/* 1. determine file length */
+        if (fseek(rds->filept, 0, SEEK_END) < 0)
                 return -1;
-        }
-        if(lseek(rds->filept,-128,SEEK_END) < 0)
+	len = ftell(rds->filept);
+
+	/* 2. try to read 128 bytes at the end */
+        if (fseek(rds->filept, -128, SEEK_END) < 0)
                 return -1;
-        if(fullread(rds,(unsigned char *)buf,128) != 128) {
+        if (fullread(rds,(unsigned char *)buf, 128) != 128)
                 return -1;
-        }
-        if(!strncmp(buf,"TAG",3)) {
+        if(!strncmp(buf,"TAG",3))
                 len -= 128;
-        }
-        if(lseek(rds->filept,0,SEEK_SET) < 0)
-                return -1;
-        if(len <= 0)
-                return -1;
-	return len;
+
+        rewind(rds->filept);
+	return len <= 0 ? -1 : len;
 }
 
 
@@ -518,6 +509,11 @@ int open_stream(char *bs_filenam,int fd)
 			if(mime != NULL) free(mime);
 			if(filept < 0) return filept;
 		}
+		else if (!strncmp(bs_filenam, "dccp://", 7))
+		{
+			if((filept = dccp_open(bs_filenam)) < 0)
+				return filept;
+		}
 #ifndef O_BINARY
 #define O_BINARY (0)
 #endif
@@ -529,7 +525,11 @@ int open_stream(char *bs_filenam,int fd)
     rd = NULL;
     for(i=0;;i++) {
       readers[i].filelen = -1;
-      readers[i].filept  = filept;
+      if ((readers[i].filept = fdopen(filept, "r")) == NULL) {
+	perror("cannot fdopen stream");
+	exit(1);
+      }
+
       readers[i].flags = 0;
       if(filept_opened)
         readers[i].flags |= READER_FD_OPENED;
--- a/src/common.c
+++ b/src/common.c
@@ -1058,7 +1058,8 @@ int split_dir_file (const char *path, ch
 			if (lastdir)
 				free (lastdir);
 			lastdir = *dname;
-			return 1;
+			/* http:// or dccp:// contain '/' */
+			return strrchr(path, ':') == NULL;
 		}
 	}
 	else {
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -30,6 +30,7 @@ mpg123_SOURCES = \
 	getbits.h \
 	getlopt.c \
 	getlopt.h \
+	dccp.c \
 	httpget.c \
 	huffman.h \
 	id3.c \
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -158,6 +158,7 @@ mpg123_SOURCES = \
 	getlopt.c \
 	getlopt.h \
 	httpget.c \
+	dccp.c \
 	huffman.h \
 	id3.c \
 	id3.h \
@@ -359,7 +360,7 @@ PROGRAMS = $(bin_PROGRAMS)
 am_mpg123_OBJECTS = audio.$(OBJEXT) buffer.$(OBJEXT) common.$(OBJEXT) \
 	control_generic.$(OBJEXT) decode_2to1.$(OBJEXT) \
 	decode_4to1.$(OBJEXT) decode_ntom.$(OBJEXT) equalizer.$(OBJEXT) \
-	getbits.$(OBJEXT) getlopt.$(OBJEXT) httpget.$(OBJEXT) \
+	getbits.$(OBJEXT) getlopt.$(OBJEXT) httpget.$(OBJEXT) dccp.$(OBJEXT) \
 	id3.$(OBJEXT) layer1.$(OBJEXT) layer2.$(OBJEXT) \
 	layer3.$(OBJEXT) mpg123.$(OBJEXT) playlist.$(OBJEXT) \
 	readers.$(OBJEXT) tabinit.$(OBJEXT) sfifo.$(OBJEXT) \
