/*
   (c) Copyright 2001-2008  The world wide DirectFB Open Source Community (directfb.org)
   (c) Copyright 2000-2004  Convergence (integrated media) GmbH

   All rights reserved.

   Written by Denis Oliver Kropp <dok@directfb.org>,
              Andreas Hundt <andi@fischlustig.de>,
              Sven Neumann <neo@directfb.org>,
              Ville Syrjälä <syrjala@sci.fi> and
              Claudio Ciccani <klan@users.sf.net>.

   This library 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 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the
   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/

#include <config.h>

#include <sys/stat.h>

#include <direct/build.h>

#include <direct/filesystem.h>
#include <direct/mem.h>
#include <direct/memcpy.h>
#include <direct/messages.h>
#include <direct/debug.h>
#include <direct/util.h>

#include <direct/stream.h>


D_LOG_DOMAIN( Direct_Stream, "Direct/Stream", "Stream wrapper" );


#if DIRECT_BUILD_NETWORK
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#endif


struct __D_DirectStream {
     int                   magic;
     int                   ref;

     int                   fd;
     DirectFile            file;

     off_t                 offset;
     ssize_t               length;

     char                 *mime;

     /* cache for piped streams */
     void                 *cache;
     unsigned int          cache_size;

#if DIRECT_BUILD_NETWORK
     /* remote streams data */
     struct {
          int              sd;

          char            *host;
          int              port;
          struct addrinfo *addr;

          char            *user;
          char            *pass;
          char            *auth;

          char            *path;

          int              redirects;

          void            *data;

          bool             real_rtsp;
          bool             real_pack;
     } remote;
#endif

     DirectResult  (*wait)  ( DirectStream   *stream,
                              unsigned int    length,
                              struct timeval *tv );
     DirectResult  (*peek)  ( DirectStream *stream,
                              unsigned int  length,
                              int           offset,
                              void         *buf,
                              unsigned int *read_out );
     DirectResult  (*read)  ( DirectStream *stream,
                              unsigned int  length,
                              void         *buf,
                              unsigned int *read_out );
     DirectResult  (*seek)  ( DirectStream *stream,
                              unsigned int  offset );
};


static void direct_stream_close( DirectStream *stream );


/************************** Begin Network Support ***************************/

#if DIRECT_BUILD_NETWORK

#define NET_TIMEOUT         15
#define HTTP_PORT           80
#define FTP_PORT            21
#define RTSP_PORT           554
#define HTTP_MAX_REDIRECTS  15

static DirectResult http_open( DirectStream *stream, const char *filename );
static DirectResult ftp_open ( DirectStream *stream, const char *filename );
static DirectResult rtsp_open( DirectStream *stream, const char *filename );


static inline char* trim( char *s )
{
     char *e;

#define space( c ) ((c) == ' '  || (c) == '\t' || \
                    (c) == '\r' || (c) == '\n' || \
                    (c) == '"'  || (c) == '\'')

     for (; space(*s); s++);

     e = s + strlen(s) - 1;
     for (; e > s && space(*e); *e-- = '\0');

#undef space

     return s;
}

static void
parse_url( const char *url, char **ret_host, int *ret_port,
           char **ret_user, char **ret_pass, char **ret_path )
{
     char *host = NULL;
     int   port = 0;
     char *user = NULL;
     char *pass = NULL;
     char *path;
     char *tmp;

     tmp = strchr( url, '/' );
     if (tmp) {
          host = alloca( tmp - url + 1 );
          memcpy( host, url, tmp - url );
          host[tmp-url] = '\0';
          path = tmp;
     } else {
          host = alloca( strlen( url ) + 1 );
          memcpy( host, url, strlen( url ) + 1 );
          path = "/";
     }

     tmp = strrchr( host, '@' );
     if (tmp) {
          *tmp = '\0';
          pass = strchr( host, ':' );
          if (pass) {
               *pass = '\0';
               pass++;
          }
          user = host;
          host = tmp + 1;
     }

     tmp = strchr( host, ':' );
     if (tmp) {
          port = strtol( tmp+1, NULL, 10 );
          *tmp = '\0';
     }

     /* IPv6 variant (host within brackets) */
     if (*host == '[') {
          host++;
          tmp = strchr( host, ']' );
          if (tmp)
               *tmp = '\0';
     }

     if (ret_host)
          *ret_host = D_STRDUP( host );

     if (ret_port && port)
          *ret_port = port;

     if (ret_user && user)
          *ret_user = D_STRDUP( user );

     if (ret_pass && pass)
          *ret_pass = D_STRDUP( pass );

     if (ret_path)
          *ret_path = D_STRDUP( path );
}

/*****************************************************************************/

static int
net_response( DirectStream *stream, char *buf, size_t size )
{
     fd_set         set;
     struct timeval tv;
     int            i;

     FD_ZERO( &set );
     FD_SET( stream->remote.sd, &set );

     for (i = 0; i < size-1; i++) {
          tv.tv_sec  = NET_TIMEOUT;
          tv.tv_usec = 0;
          select( stream->remote.sd+1, &set, NULL, NULL, &tv );

          if (recv( stream->remote.sd, buf+i, 1, 0 ) != 1)
               break;

          if (buf[i] == '\n') {
               if (i > 0 && buf[i-1] == '\r')
                    i--;
               break;
          }
     }

     buf[i] = '\0';

     D_DEBUG_AT( Direct_Stream, "got [%s].\n", buf );

     return i;
}

static int
net_command( DirectStream *stream, char *buf, size_t size )
{
     fd_set         s;
     struct timeval t;
     int            status;
     int            version;
     char           space;

     FD_ZERO( &s );
     FD_SET( stream->remote.sd, &s );

     t.tv_sec  = NET_TIMEOUT;
     t.tv_usec = 0;

     switch (select( stream->remote.sd+1, NULL, &s, NULL, &t )) {
          case 0:
               D_DEBUG_AT( Direct_Stream, "Timeout!\n" );
          case -1:
               return -1;
     }

     send( stream->remote.sd, buf, strlen(buf), 0 );
     send( stream->remote.sd, "\r\n", 2, 0 );

     D_DEBUG_AT( Direct_Stream, "sent [%s].\n", buf );

     while (net_response( stream, buf, size ) > 0) {
          status = 0;
          if (sscanf( buf, "HTTP/1.%d %3d", &version, &status ) == 2 ||
              sscanf( buf, "RTSP/1.%d %3d", &version, &status ) == 2 ||
              sscanf( buf, "ICY %3d", &status )                 == 1 ||
              sscanf( buf, "%3d%[ ]", &status, &space )         == 2)
               return status;
     }

     return 0;
}

static DirectResult
net_wait( DirectStream   *stream,
          unsigned int    length,
          struct timeval *tv )
{
     fd_set s;

     if (stream->cache_size >= length)
          return DR_OK;

     if (stream->fd == -1)
          return DR_EOF;

     FD_ZERO( &s );
     FD_SET( stream->fd, &s );

     switch (select( stream->fd+1, &s, NULL, NULL, tv )) {
          case 0:
               if (!tv && !stream->cache_size)
                    return DR_EOF;
               return DR_TIMEOUT;
          case -1:
               return errno2result( errno );
     }

     return DR_OK;
}

static DirectResult
net_peek( DirectStream *stream,
          unsigned int  length,
          int           offset,
          void         *buf,
          unsigned int *read_out )
{
     char *tmp;
     int   size;

     if (offset < 0)
          return DR_UNSUPPORTED;

     tmp = alloca( length + offset );

     size = recv( stream->fd, tmp, length+offset, MSG_PEEK );
     switch (size) {
          case 0:
               return DR_EOF;
          case -1:
               if (errno == EAGAIN || errno == EWOULDBLOCK)
                    return DR_BUFFEREMPTY;
               return errno2result( errno );
          default:
               if (size < offset)
                    return DR_BUFFEREMPTY;
               size -= offset;
               break;
     }

     direct_memcpy( buf, tmp+offset, size );

     if (read_out)
          *read_out = size;

     return DR_OK;
}

static DirectResult
net_read( DirectStream *stream,
          unsigned int  length,
          void         *buf,
          unsigned int *read_out )
{
     int size;

     size = recv( stream->fd, buf, length, 0 );
     switch (size) {
          case 0:
               return DR_EOF;
          case -1:
               if (errno == EAGAIN || errno == EWOULDBLOCK)
                    return DR_BUFFEREMPTY;
               return errno2result( errno );
     }

     stream->offset += size;

     if (read_out)
          *read_out = size;

     return DR_OK;
}

static DirectResult
net_connect( struct addrinfo *addr, int sock, int proto, int *ret_fd )
{
     DirectResult     ret = DR_OK;
     int              fd  = -1;
     struct addrinfo *tmp;

     D_ASSERT( addr != NULL );
     D_ASSERT( ret_fd != NULL );

     for (tmp = addr; tmp; tmp = tmp->ai_next) {
          int err;

          fd = socket( tmp->ai_family, sock, proto );
          if (fd < 0) {
               ret = errno2result( errno );
               D_DEBUG_AT( Direct_Stream,
                           "failed to create socket!\n\t->%s",
                           strerror(errno) );
               continue;
          }

          fcntl( fd, F_SETFL, fcntl( fd, F_GETFL ) | O_NONBLOCK );

          D_DEBUG_AT( Direct_Stream,
                      "connecting to %s...\n", tmp->ai_canonname );

          if (proto == IPPROTO_UDP)
               err = bind( fd, tmp->ai_addr, tmp->ai_addrlen );
          else
               err = connect( fd, tmp->ai_addr, tmp->ai_addrlen );

          if (err == 0 || errno == EINPROGRESS) {
               struct timeval t = { NET_TIMEOUT, 0 };
               fd_set         s;

               /* Join multicast group? */
               if (tmp->ai_addr->sa_family == AF_INET) {
                    struct sockaddr_in *saddr = (struct sockaddr_in *) tmp->ai_addr;

                    if (IN_MULTICAST( ntohl(saddr->sin_addr.s_addr) )) {
                         struct ip_mreq req;

                         D_DEBUG_AT( Direct_Stream,
                                     "joining multicast group (%u.%u.%u.%u)...\n",
                                     (u8)tmp->ai_addr->sa_data[2], (u8)tmp->ai_addr->sa_data[3],
                                     (u8)tmp->ai_addr->sa_data[4], (u8)tmp->ai_addr->sa_data[5] );

                         req.imr_multiaddr.s_addr = saddr->sin_addr.s_addr;
                         req.imr_interface.s_addr = 0;

                         err = setsockopt( fd, SOL_IP, IP_ADD_MEMBERSHIP, &req, sizeof(req) );
                         if (err < 0) {
                              ret = errno2result( errno );
                              D_PERROR( "Direct/Stream: Could not join multicast group (%u.%u.%u.%u)!\n",
                                        (u8)tmp->ai_addr->sa_data[2], (u8)tmp->ai_addr->sa_data[3],
                                        (u8)tmp->ai_addr->sa_data[4], (u8)tmp->ai_addr->sa_data[5] );
                              close( fd );
                              continue;
                         }

                         setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, saddr, sizeof(*saddr) );
                    }
               }

               FD_ZERO( &s );
               FD_SET( fd, &s );

               err = select( fd+1, NULL, &s, NULL, &t );
               if (err < 1) {
                    D_DEBUG_AT( Direct_Stream, "...connection failed.\n" );

                    close( fd );
                    fd = -1;

                    if (err == 0) {
                         ret = DR_TIMEOUT;
                         continue;
                    } else {
                         ret = errno2result( errno );
                         break;
                    }
               }

               D_DEBUG_AT( Direct_Stream, "...connected.\n" );

               ret = DR_OK;
               break;
          }
     }

     *ret_fd = fd;

     return ret;
}

static DirectResult
net_open( DirectStream *stream, const char *filename, int proto )
{
     DirectResult    ret  = DR_OK;
     int             sock = (proto == IPPROTO_TCP) ? SOCK_STREAM : SOCK_DGRAM;
     struct addrinfo hints;
     char            port[16];

     parse_url( filename,
                &stream->remote.host,
                &stream->remote.port,
                &stream->remote.user,
                &stream->remote.pass,
                &stream->remote.path );

     direct_snprintf( port, sizeof(port), "%d", stream->remote.port );

     memset( &hints, 0, sizeof(hints) );
     hints.ai_flags    = AI_CANONNAME;
     hints.ai_socktype = sock;
     hints.ai_family   = PF_UNSPEC;

     if (getaddrinfo( stream->remote.host, port,
                      &hints, &stream->remote.addr )) {
          D_ERROR( "Direct/Stream: "
                   "failed to resolve host '%s'!\n", stream->remote.host );
          return DR_FAILURE;
     }

     ret = net_connect( stream->remote.addr, sock, proto, &stream->remote.sd );
     if (ret)
          return ret;

     stream->fd     = stream->remote.sd;
     stream->length = -1;
     stream->wait   = net_wait;
     stream->peek   = net_peek;
     stream->read   = net_read;

     return ret;
}

/*****************************************************************************/

static DirectResult
http_seek( DirectStream *stream, unsigned int offset )
{
     DirectResult ret;
     char         buf[1280];
     int          status, len;

     close( stream->remote.sd );
     stream->remote.sd = -1;

     ret = net_connect( stream->remote.addr,
                        SOCK_STREAM, IPPROTO_TCP, &stream->remote.sd );
     if (ret)
          return ret;

     stream->fd = stream->remote.sd;

     len = direct_snprintf( buf, sizeof(buf),
                     "GET %s HTTP/1.0\r\n"
                     "Host: %s:%d\r\n",
                     stream->remote.path,
                     stream->remote.host,
                     stream->remote.port );
     if (stream->remote.auth) {
          len += direct_snprintf( buf+len, sizeof(buf)-len,
                           "Authorization: Basic %s\r\n",
                           stream->remote.auth );
     }
     direct_snprintf( buf+len, sizeof(buf)-len,
               "User-Agent: DirectFB/%s\r\n"
               "Accept: */*\r\n"
               "Range: bytes=%d-\r\n"
               "Connection: Close\r\n",
               DIRECTFB_VERSION, offset );

     status = net_command( stream, buf, sizeof(buf) );
     switch (status) {
          case 200 ... 299:
               stream->offset = offset;
               break;
          default:
               if (status)
                    D_DEBUG_AT( Direct_Stream,
                                "server returned status %d.\n", status );
               return DR_FAILURE;
     }

     /* discard remaining headers */
     while (net_response( stream, buf, sizeof(buf) ) > 0);

     return DR_OK;
}

static DirectResult
http_open( DirectStream *stream, const char *filename )
{
     DirectResult ret;
     char         buf[1280];
     int          status, len;

     stream->remote.port = HTTP_PORT;

     ret = net_open( stream, filename, IPPROTO_TCP );
     if (ret)
          return ret;

     if (stream->remote.user) {
          char *tmp;

          if (stream->remote.pass) {
               tmp = alloca( strlen( stream->remote.user ) +
                             strlen( stream->remote.pass ) + 2 );
               len = sprintf( tmp, "%s:%s",
                              stream->remote.user, stream->remote.pass );
          } else {
               tmp = alloca( strlen( stream->remote.user ) + 2 );
               len = sprintf( tmp, "%s:", stream->remote.user );
          }

          stream->remote.auth = direct_base64_encode( tmp, len );
     }

     len = direct_snprintf( buf, sizeof(buf),
                     "GET %s HTTP/1.0\r\n"
                     "Host: %s:%d\r\n",
                     stream->remote.path,
                     stream->remote.host,
                     stream->remote.port );
     if (stream->remote.auth) {
          len += direct_snprintf( buf+len, sizeof(buf)-len,
                           "Authorization: Basic %s\r\n",
                           stream->remote.auth );
     }
     direct_snprintf( buf+len, sizeof(buf)-len,
               "User-Agent: DirectFB/%s\r\n"
               "Accept: */*\r\n"
               "Connection: Close\r\n",
               DIRECTFB_VERSION );

     status = net_command( stream, buf, sizeof(buf) );

     while (net_response( stream, buf, sizeof(buf) ) > 0) {
          if (!strncasecmp( buf, "Accept-Ranges:", 14 )) {
               if (strcmp( trim( buf+14 ), "none" ))
                    stream->seek = http_seek;
          }
          else if (!strncasecmp( buf, "Content-Type:", 13 )) {
               char *mime = trim( buf+13 );
               char *tmp  = strchr( mime, ';' );
               if (tmp)
                    *tmp = '\0';
               if (stream->mime)
                    D_FREE( stream->mime );
               stream->mime = D_STRDUP( mime );
          }
          else if (!strncasecmp( buf, "Content-Length:", 15 )) {
               char *tmp = trim( buf+15 );
               if (sscanf( tmp, "%zd", &stream->length ) < 1)
                    sscanf( tmp, "bytes=%zd", &stream->length );
          }
          else if (!strncasecmp( buf, "Location:", 9 )) {
               direct_stream_close( stream );
               stream->seek = NULL;

               if (++stream->remote.redirects > HTTP_MAX_REDIRECTS) {
                    D_ERROR( "Direct/Stream: "
                             "reached maximum number of redirects (%d).\n",
                             HTTP_MAX_REDIRECTS );
                    return DR_LIMITEXCEEDED;
               }

               filename = trim( buf+9 );
               if (!strncmp( filename, "http://", 7 ))
                    return http_open( stream, filename+7 );
               if (!strncmp( filename, "ftp://", 6 ))
                    return ftp_open( stream, filename+6 );
               if (!strncmp( filename, "rtsp://", 7 ))
                    return rtsp_open( stream, filename+7 );

               return DR_UNSUPPORTED;
          }
     }

     switch (status) {
          case 200 ... 299:
               break;
          default:
               if (status)
                    D_DEBUG_AT( Direct_Stream,
                                "server returned status %d.\n", status );
               return (status == 404) ? DR_FILENOTFOUND : DR_FAILURE;
     }

     return DR_OK;
}

/*****************************************************************************/

static DirectResult
ftp_open_pasv( DirectStream *stream, char *buf, size_t size )
{
     DirectResult ret;
     int          i, len;

     direct_snprintf( buf, size, "PASV" );
     if (net_command( stream, buf, size ) != 227)
          return DR_FAILURE;

     /* parse IP and port for passive mode */
     for (i = 4; buf[i]; i++) {
          unsigned int d[6];

          if (sscanf( &buf[i], "%u,%u,%u,%u,%u,%u",
                      &d[0], &d[1], &d[2], &d[3], &d[4], &d[5] ) == 6)
          {
               struct addrinfo hints, *addr;

               /* address */
               len = direct_snprintf( buf, size,
                               "%u.%u.%u.%u",
                               d[0], d[1], d[2], d[3] );
               /* port */
               direct_snprintf( buf+len+1, size-len-1,
                         "%u", ((d[4] & 0xff) << 8) | (d[5] & 0xff) );

               memset( &hints, 0, sizeof(hints) );
               hints.ai_flags    = AI_CANONNAME;
               hints.ai_socktype = SOCK_STREAM;
               hints.ai_family   = PF_UNSPEC;

               if (getaddrinfo( buf, buf+len+1, &hints, &addr )) {
                    D_DEBUG_AT( Direct_Stream,
                                "failed to resolve host '%s'.\n", buf );
                    return DR_FAILURE;
               }

               ret = net_connect( addr, SOCK_STREAM, IPPROTO_TCP, &stream->fd );

               freeaddrinfo( addr );

               return ret;
          }
     }

     return DR_FAILURE;
}

static DirectResult
ftp_seek( DirectStream *stream, unsigned int offset )
{
     DirectResult ret = DR_OK;
     char         buf[512];

     if (stream->fd > 0) {
          int status;

          close( stream->fd );
          stream->fd = -1;

          /* ignore response */
          while (net_response( stream, buf, sizeof(buf) ) > 0) {
               if (sscanf( buf, "%3d%[ ]", &status, buf ) == 2)
                    break;
          }
     }

     ret = ftp_open_pasv( stream, buf, sizeof(buf) );
     if (ret)
          return ret;

     direct_snprintf( buf, sizeof(buf), "REST %d", offset );
     if (net_command( stream, buf, sizeof(buf) ) != 350)
          goto error;

     direct_snprintf( buf, sizeof(buf), "RETR %s", stream->remote.path );
     switch (net_command( stream, buf, sizeof(buf) )) {
          case 150:
          case 125:
               break;
          default:
               goto error;
     }

     stream->offset = offset;

     return DR_OK;

error:
     close( stream->fd );
     stream->fd = -1;

     return DR_FAILURE;
}

static DirectResult
ftp_open( DirectStream *stream, const char *filename )
{
     DirectResult ret;
     int          status = 0;
     char         buf[512];

     stream->remote.port = FTP_PORT;

     ret = net_open( stream, filename, IPPROTO_TCP );
     if (ret)
          return ret;

     while (net_response( stream, buf, sizeof(buf) ) > 0) {
          if (sscanf( buf, "%3d%[ ]", &status, buf ) == 2)
               break;
     }
     if (status != 220)
          return DR_FAILURE;

     /* login */
     direct_snprintf( buf, sizeof(buf), "USER %s", stream->remote.user ? : "anonymous" );
     switch (net_command( stream, buf, sizeof(buf) )) {
          case 230:
          case 331:
               break;
          default:
               return DR_FAILURE;
     }

     if (stream->remote.pass) {
          direct_snprintf( buf, sizeof(buf), "PASS %s", stream->remote.pass );
          if (net_command( stream, buf, sizeof(buf) ) != 230)
               return DR_FAILURE;
     }

     /* enter binary mode */
     direct_snprintf( buf, sizeof(buf), "TYPE I" );
     if (net_command( stream, buf, sizeof(buf) ) != 200)
          return DR_FAILURE;

     /* get file size */
     direct_snprintf( buf, sizeof(buf), "SIZE %s", stream->remote.path );
     if (net_command( stream, buf, sizeof(buf) ) == 213)
          stream->length = strtol( buf+4, NULL, 10 );

     /* enter passive mode by default */
     ret = ftp_open_pasv( stream, buf, sizeof(buf) );
     if (ret)
          return ret;

     /* retrieve file */
     direct_snprintf( buf, sizeof(buf), "RETR %s", stream->remote.path );
     switch (net_command( stream, buf, sizeof(buf) )) {
          case 125:
          case 150:
               break;
          default:
               return DR_FAILURE;
     }

     stream->seek = ftp_seek;

     return DR_OK;
}

/*****************************************************************************/

typedef struct {
     s8          pt;   // payload type (-1: dymanic)
     u8          type; // codec type
     const char *name;
     const char *mime;
     u32         fcc;
} RTPPayload;

typedef struct {
     char              *control;

     int                pt;
     const RTPPayload  *payload;

     int                dur; // duration
     int                abr; // avg bitrate
     int                mbr; // max bitrate
     int                aps; // avg packet size
     int                mps; // max packet size
     int                str; // start time
     int                prl; // preroll

     char              *mime;
     int                mime_size;

     void              *data;
     int                data_size;
} SDPStreamDesc;

#define PAYLOAD_VIDEO 1
#define PAYLOAD_AUDIO 2

#define FCC( a, b, c, d ) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24))

static const RTPPayload payloads[] = {
     { 31, PAYLOAD_VIDEO, "H261",          "video/h261",   FCC('H','2','6','1') },
     { 34, PAYLOAD_VIDEO, "H263",          "video/h263",   FCC('H','2','6','3') },
   //{ -1, PAYLOAD_VIDEO, "H264",          "video/h264",   FCC('H','2','6','4') },
     { 32, PAYLOAD_VIDEO, "MPV",           "video/mpeg",   FCC('M','P','E','G') },
     { 33,             0, "MP2T",          "video/mpegts", 0 },
     { -1, PAYLOAD_VIDEO, "MP4V-ES",       "video/mpeg4",  FCC('M','P','4','S') },
     { 14, PAYLOAD_AUDIO, "MPA",           "audio/mpeg",   0x0055 },
   //{ -1, PAYLOAD_AUDIO, "mpeg4-generic", "audio/aac",    0x00ff },
};

#define NUM_PAYLOADS D_ARRAY_SIZE( payloads )

static DirectResult
sdp_parse( DirectStream *stream, int length, SDPStreamDesc **ret_streams, int *ret_num )
{
     char          *buf, *tmp;
     SDPStreamDesc *desc = NULL;
     int            num  = 0;
     fd_set         set;
     struct timeval tv;
     int            i;

     buf = D_CALLOC( 1, length+1 );
     if (!buf)
          return D_OOM();

     FD_ZERO( &set );
     FD_SET( stream->remote.sd, &set );

     for (tmp = buf; length;) {
          int size;

          tv.tv_sec  = NET_TIMEOUT;
          tv.tv_usec = 0;
          select( stream->remote.sd+1, &set, NULL, NULL, &tv );

          size = recv( stream->remote.sd, tmp, length, MSG_WAITALL );
          if (size < 1)
               break;
          tmp += size;
          length -= size;
     }

     for (tmp = buf; tmp && *tmp;) {
          char *end;

          end = strchr( tmp, '\n' );
          if (end) {
               if (end > tmp && *(end-1) == '\r')
                    *(end-1) = '\0';
               *end = '\0';
          }

          D_DEBUG_AT( Direct_Stream, "SDP [%s]\n", tmp );

          switch (*tmp) {
               case 'm':
                    /* media */
                    if (*(tmp+1) == '=') {
                         desc = D_REALLOC( desc, ++num*sizeof(SDPStreamDesc) );
                         memset( &desc[num-1], 0, sizeof(SDPStreamDesc) );

                         tmp += 2;
                         if (sscanf( tmp, "audio %d RTP/AVP %d", &i, &desc[num-1].pt ) == 2 ||
                             sscanf( tmp, "video %d RTP/AVP %d", &i, &desc[num-1].pt ) == 2) {
                              for (i = 0; i < NUM_PAYLOADS; i++) {
                                   if (desc[num-1].pt == payloads[i].pt) {
                                        desc[num-1].payload = &payloads[i];
                                        desc[num-1].mime = D_STRDUP( payloads[i].mime );
                                        desc[num-1].mime_size = strlen( payloads[i].mime );
                                        break;
                                   }
                              }
                         }
                    }
                    break;
               case 'a':
                    /* attribute */
                    if (*(tmp+1) == '=' && num) {
                         tmp += 2;
                         if (!strncmp( tmp, "control:", 8 )) {
                              desc[num-1].control = D_STRDUP( trim( tmp+8 ) );
                         }
                         else if (!strncmp( tmp, "rtpmap:", 7 )) {
                              if (!desc[num-1].payload && desc[num-1].pt) {
                                   char *sep;

                                   tmp = strchr( trim( tmp+7 ), ' ' );
                                   if (!tmp) break;
                                   sep = strchr( ++tmp, '/' );
                                   if (sep) *sep = '\0';

                                   for (i = 0; i < NUM_PAYLOADS; i++) {
                                        if (strcmp( tmp, payloads[i].name ))
                                             continue;
                                        desc[num-1].payload = &payloads[i];
                                        desc[num-1].mime = D_STRDUP( payloads[i].mime );
                                        desc[num-1].mime_size = strlen( payloads[i].mime );
                                        break;
                                   }
                              }
                         }
                         else if (!strncmp( tmp, "length:npt=", 11 )) {
                              double val = atof( tmp+11 );
                              desc[num-1].dur = val * 1000.0;
                         }
                         else if (!strncmp( tmp, "mimetype:string;", 16 )) {
                              if (desc[num-1].mime)
                                   D_FREE( desc[num-1].mime );
                              desc[num-1].mime = D_STRDUP( trim( tmp+16 ) );
                              desc[num-1].mime_size = strlen( desc[num-1].mime );
                         }
                         else if (!strncmp( tmp, "AvgBitRate:", 11 )) {
                              sscanf( tmp+11, "integer;%d", &desc[num-1].abr );
                         }
                         else if (!strncmp( tmp, "MaxBitRate:", 11 )) {
                              sscanf( tmp+11, "integer;%d", &desc[num-1].mbr );
                         }
                         else if (!strncmp( tmp, "AvgPacketSize:", 14 )) {
                              sscanf( tmp+14, "integer;%d", &desc[num-1].aps );
                         }
                         else if (!strncmp( tmp, "MaxPacketSize:", 14 )) {
                              sscanf( tmp+14, "integer;%d", &desc[num-1].mps );
                         }
                         else if (!strncmp( tmp, "StartTime:", 10 )) {
                              sscanf( tmp+10, "integer;%d", &desc[num-1].str );
                         }
                         else if (!strncmp( tmp, "Preroll:", 8 )) {
                              sscanf( tmp+8, "integer;%d", &desc[num-1].prl );
                         }
                         else if (!strncmp( tmp, "OpaqueData:buffer;", 18 )) {
                              desc[num-1].data =
                                   direct_base64_decode( trim( tmp+18 ),
                                                         &desc[num-1].data_size );
                         }
                    }
                    break;
               default:
                    break;
          }

          tmp = end;
          if (tmp) tmp++;
     }

     D_FREE( buf );

     *ret_streams = desc;
     *ret_num     = num;

     return desc ? DR_OK : DR_EOF;
}

static void
sdp_free( SDPStreamDesc *streams, int num )
{
     int i;

     for (i = 0; i < num; i++) {
          if (streams[i].control)
               D_FREE( streams[i].control );
          if (streams[i].mime)
               D_FREE( streams[i].mime );
          if (streams[i].data)
               D_FREE( streams[i].data );
     }
     D_FREE( streams );
}

static DirectResult
rmf_write_header( SDPStreamDesc *streams, int n_streams, void **ret_buf, unsigned int *ret_size )
{
     unsigned char *dst, *tmp;
     int            abr = 0;
     int            mbr = 0;
     int            aps = 0;
     int            mps = 0;
     int            str = 0;
     int            prl = 0;
     int            dur = 0;
     int            i, len;

     len = 86 + n_streams*46;
     for (i = 0; i < n_streams; i++) {
          abr += streams[i].abr;
          aps += streams[i].aps;
          if (mbr < streams[i].mbr)
               mbr = streams[i].mbr;
          if (mps < streams[i].mps)
               mps = streams[i].mps;
          if (dur < streams[i].dur)
               dur = streams[i].dur;
          if (streams[i].mime)
               len += streams[i].mime_size;
          if (streams[i].data)
               len += streams[i].data_size;
          else if (streams[i].payload)
               len += 74;
     }

     *ret_buf = dst = D_MALLOC( len );
     if (!dst)
          return D_OOM();
     *ret_size = len;

     /* RMF */
     dst[0] = '.'; dst[1] = 'R', dst[2] = 'M'; dst[3] = 'F';
     dst[4] = dst[5] = dst[6] = 0; dst[7] = 18;              // size
     dst[8] = dst[9] = 0;                                    // version
     dst[10] = dst[11] = dst[12] = dst[13] = 0;              // ??
     dst[14] = dst[15] = dst[16] = 0; dst[17] = 4+n_streams; // num streams
     dst += 18;

     /* PROP */
     dst[0] = 'P'; dst[1] = 'R'; dst[2] = 'O'; dst[3] = 'P';
     dst[4] = dst[5] = dst[6] = 0; dst[7] = 50;
     dst[8] = dst[9] = 0;
     dst[10] = mbr>>24; dst[11] = mbr>>16; dst[12] = mbr>>8; dst[13] = mbr;
     dst[14] = abr>>24; dst[15] = abr>>16; dst[16] = abr>>8; dst[17] = abr;
     dst[18] = mps>>24; dst[19] = mps>>16; dst[20] = mps>>8; dst[21] = mps;
     dst[22] = aps>>24; dst[23] = aps>>16; dst[24] = aps>>8; dst[25] = aps;
     dst[26] = dst[27] = dst[28] = dst[29] = 0; // num packets
     dst[30] = dur>>24; dst[31] = dur>>16; dst[32] = dur>>8; dst[33] = dur;
     dst[34] = dst[35] = dst[36] = dst[37] = 0; // preroll
     dst[38] = dst[39] = dst[40] = dst[41] = 0; // index offset
     dst[42] = len>>24; dst[43] = len>>16; dst[44] = len>>8; dst[45] = len;
     dst[46] = 0; dst[47] = n_streams;          // num streams
     dst[48] = 0; dst[49] = 7;                  // flags
     dst += 50;

     for (i = 0; i < n_streams; i++) {
          len = 46 + streams[i].mime_size;
          if (streams[i].data)
               len += streams[i].data_size;
          else if (streams[i].payload)
               len += 74;

          abr = streams[i].abr;
          mbr = streams[i].mbr;
          aps = streams[i].aps;
          mps = streams[i].mps;
          str = streams[i].str;
          prl = streams[i].prl;
          dur = streams[i].dur;

          /* MDPR */
          dst[0] = 'M'; dst[1] = 'D'; dst[2] = 'P'; dst[3] = 'R';
          dst[4] = len>>24; dst[5] = len>>16; dst[6] = len>>8; dst[7] = len;
          dst[8] = dst[9] = 0;
          dst[10] = 0; dst[11] = i;
          dst[12] = mbr>>24; dst[13] = mbr>>16; dst[14] = mbr>>8; dst[15] = mbr;
          dst[16] = abr>>24; dst[17] = abr>>16; dst[18] = abr>>8; dst[19] = abr;
          dst[20] = mps>>24; dst[21] = mps>>16; dst[22] = mps>>8; dst[23] = mps;
          dst[24] = aps>>24; dst[25] = aps>>16; dst[26] = aps>>8; dst[27] = aps;
          dst[28] = str>>24; dst[29] = str>>16; dst[30] = str>>8; dst[31] = str;
          dst[32] = prl>>24; dst[33] = prl>>16; dst[34] = prl>>8; dst[35] = prl;
          dst[36] = dur>>24; dst[37] = dur>>16; dst[38] = dur>>8; dst[39] = dur;
          dst += 40;

          /* description */
          *dst++ = 0;
          /* mimetype */
          *dst++ = streams[i].mime_size;
          for (tmp = (unsigned char*)streams[i].mime; tmp && *tmp;)
               *dst++ = *tmp++;

          /* codec data */
          if (streams[i].data) {
               len = streams[i].data_size;
               dst[0] = len>>24; dst[1] = len>>16; dst[2] = len>>8; dst[3] = len;
               direct_memcpy( dst+4, streams[i].data, streams[i].data_size );
               dst += len+4;
          }
          else if (streams[i].payload) {
               u32 fcc = streams[i].payload->fcc;

               dst[0] = dst[1] = dst[2] = 0; dst[3] = 74;
               dst += 4;
               memset( dst, 0, 74 );

               if (streams[i].payload->type == PAYLOAD_AUDIO) {
                    dst[0] = '.'; dst[1] = 'r'; dst[2] = 'a'; dst[3] = 0xfd;
                    dst[4] = 0; dst[5] = 5; // version
                    dst[66] = fcc; dst[67] = fcc>>8; dst[68] = fcc>>16; dst[69] = fcc>>24;
               }
               else {
                    dst[0] = dst[1] = dst[2] = 0; dst[3] = 34;
                    dst[4] = 'V'; dst[5] = 'I'; dst[6] = 'D'; dst[7] = 'O';
                    dst[8] = fcc; dst[9] = fcc>>8; dst[10] = fcc>>16; dst[11] = fcc>>24;
                    dst[30] = 0x10;
               }
               dst += 74;
          }
          else {
               dst[0] = dst[1] = dst[2] = dst[3] = 0;
               dst += 4;
          }
     }

     /* DATA */
     dst[0] = 'D'; dst[1] = 'A'; dst[2] = 'T'; dst[3] = 'A';
     dst[4] = dst[5] = dst[6] = 0; dst[7] = 18; // size
     dst[8] = dst[9] = 0;                       // version
     dst[10] = dst[11] = dst[12] = dst[13] = 0; // num packets
     dst[14] = dst[15] = dst[16] = dst[17] = 0; // next data

     return DR_OK;
}

static int
rmf_write_pheader( unsigned char *dst, int id, int sz, unsigned int ts )
{
     /* version */
     dst[0] = dst[1] = 0;
     /* length */
     dst[2] = (sz+12)>>8; dst[3] = sz+12;
     /* indentifier */
     dst[4] = id>>8; dst[5] = id;
     /* timestamp */
     dst[6] = ts>>24; dst[7] = ts>>16; dst[8] = ts>>8; dst[9] = ts;
     /* reserved */
     dst[10] = 0;
     /* flags */
     dst[11] = 0;

     return 12;
}

static void
real_calc_challenge2( char response[64], char checksum[32], char *challenge )
{
     const unsigned char xor_table[37] = {
          0x05, 0x18, 0x74, 0xd0, 0x0d, 0x09, 0x02, 0x53,
          0xc0, 0x01, 0x05, 0x05, 0x67, 0x03, 0x19, 0x70,
          0x08, 0x27, 0x66, 0x10, 0x10, 0x72, 0x08, 0x09,
          0x63, 0x11, 0x03, 0x71, 0x08, 0x08, 0x70, 0x02,
          0x10, 0x57, 0x05, 0x18, 0x54
     };
     char buf[128];
     char md5[16];
     int  len;
     int  i;

     memset( response, 0, 64 );
     memset( checksum, 0, 32 );

     buf[0] = 0xa1; buf[1] = 0xe9; buf[2] = 0x14; buf[3] = 0x9d;
     buf[4] = 0x0e; buf[5] = 0x6b; buf[6] = 0x3b; buf[7] = 0x59;
     memset( buf+8, 0, 120 );

     len = strlen( challenge );
     if (len == 40) {
          challenge[32] = '\0';
          len = 32;
     }
     memcpy( buf+8, challenge, MAX(len,56) );

     for (i = 0; i < 37; i++)
          buf[8+i] ^= xor_table[i];

     /* compute response */
     direct_md5_sum( md5, buf, 64 );
     /* convert to ascii */
     for (i = 0; i < 16; i++) {
          char a, b;
          a = (md5[i] >> 4) & 15;
          b =  md5[i]       & 15;
          response[i*2+0] = ((a < 10) ? (a + 48) : (a + 87)) & 255;
          response[i*2+1] = ((b < 10) ? (b + 48) : (b + 87)) & 255;
     }
     /* tail */
     len = strlen( response );
     direct_snputs( &response[len], "01d0a8e3", 64-len );

     /* compute checksum */
     for (i = 0; i < len/4; i++)
          checksum[i] = response[i*4];
}

static DirectResult
rtsp_session_open( DirectStream *stream )
{
     DirectResult   ret;
     int            status;
     int            cseq        = 0;
     SDPStreamDesc *streams     = NULL;
     int            n_streams   = 0;
     char           session[32] = {0, };
     char           challen[64] = {0, };
     char           buf[1280];
     int            i, len;

     direct_snprintf( buf, sizeof(buf),
               "OPTIONS rtsp://%s:%d RTSP/1.0\r\n"
               "CSeq: %d\r\n"
               "User-Agent: DirectFB/%s\r\n"
               "ClientChallenge: 9e26d33f2984236010ef6253fb1887f7\r\n"
               "PlayerStarttime: [28/03/2003:22:50:23 00:00]\r\n"
               "CompanyID: KnKV4M4I/B2FjJ1TToLycw==\r\n"
               "GUID: 00000000-0000-0000-0000-000000000000\r\n"
               "RegionData: 0\r\n",
               stream->remote.host,
               stream->remote.port,
               ++cseq, DIRECTFB_VERSION );

     if (net_command( stream, buf, sizeof(buf) ) != 200)
          return DR_FAILURE;

     while (net_response( stream, buf, sizeof(buf) ) > 0) {
          if (!strncmp( buf, "RealChallenge1:", 15 )) {
               direct_snprintf( challen, sizeof(challen), "%s", trim( buf+15 ) );
               stream->remote.real_rtsp = true;
          }
     }

     len = direct_snprintf( buf, sizeof(buf),
                     "DESCRIBE rtsp://%s:%d%s RTSP/1.0\r\n"
                     "CSeq: %d\r\n"
                     "Accept: application/sdp\r\n"
                     "Bandwidth: 10485800\r\n",
                     stream->remote.host,
                     stream->remote.port,
                     stream->remote.path,
                     ++cseq );
     if (stream->remote.real_rtsp) {
          direct_snprintf( buf+len, sizeof(buf)-len,
                    "GUID: 00000000-0000-0000-0000-000000000000\r\n"
                    "RegionData: 0\r\n"
                    "ClientID: Linux_2.4_6.0.9.1235_play32_RN01_EN_586\r\n"
                    "SupportsMaximumASMBandwidth: 1\r\n"
                    "Require: com.real.retain-entity-for-setup\r\n" );
     }

     status = net_command( stream, buf, sizeof(buf) );
     if (status != 200)
          return (status == 404) ? DR_FILENOTFOUND : DR_FAILURE;

     len = 0;
     while (net_response( stream, buf, sizeof(buf) ) > 0) {
          if (!strncasecmp( buf, "ETag:", 5 )) {
               direct_snprintf( session, sizeof(session), "%s", trim( buf+5 ) );
          }
          else if (!strncasecmp( buf, "Content-Length:", 15 )) {
               char *tmp = trim( buf+15 );
               if (sscanf( tmp, "%d", &len ) != 1)
                    sscanf( tmp, "bytes=%d", &len );
          }
     }

     if (!len) {
          D_DEBUG_AT( Direct_Stream, "Couldn't get sdp length!\n" );
          return DR_FAILURE;
     }

     ret = sdp_parse( stream, len, &streams, &n_streams );
     if (ret)
          return ret;

     for (i = 0; i < n_streams; i++) {
          /* skip unhandled payload types */
          if (!stream->remote.real_rtsp && !streams[i].payload)
               continue;

          len = direct_snprintf( buf, sizeof(buf),
                         "SETUP rtsp://%s:%d%s/%s RTSP/1.0\r\n"
                         "CSeq: %d\r\n",
                         stream->remote.host,
                         stream->remote.port,
                         stream->remote.path,
                         streams[i].control, ++cseq );
          if (*session) {
               if (*challen) {
                    char response[64];
                    char checksum[32];

                    real_calc_challenge2( response, checksum, challen );
                    len += direct_snprintf( buf+len, sizeof(buf)-len,
                                     "RealChallenge2: %s, sd=%s\r\n",
                                     response, checksum );
                    *challen = '\0';
               }
               len += direct_snprintf( buf+len, sizeof(buf)-len,
                                "%s: %s\r\n",
                                i ? "Session" : "If-Match", session );
          }
          direct_snprintf( buf+len, sizeof(buf)-len,
                    "Transport: %s\r\n",
                    stream->remote.real_rtsp
                    ? "x-pn-tng/tcp;mode=play,rtp/avp/tcp;unicast;mode=play"
                    : "RTP/AVP/TCP;unicast" );

          if (net_command( stream, buf, sizeof(buf) ) != 200) {
               sdp_free( streams, n_streams );
               return DR_FAILURE;
          }

          while (net_response( stream, buf, sizeof(buf) ) > 0) {
               if (!strncmp( buf, "Session:", 8 ))
                    direct_snprintf( session, sizeof(session), "%s", trim( buf+8 ) );
          }
     }

     len = direct_snprintf( buf, sizeof(buf),
                    "PLAY rtsp://%s:%d%s RTSP/1.0\r\n"
                    "CSeq: %d\r\n",
                    stream->remote.host,
                    stream->remote.port,
                    stream->remote.path,
                    ++cseq );
     if (*session) {
          len += direct_snprintf( buf+len, sizeof(buf)-len,
                           "Session: %s\r\n", session );
     }
     direct_snprintf( buf+len, sizeof(buf)-len,
               "Range: npt=0-\r\n"
               "Connection: Close\r\n" );

     if (net_command( stream, buf, sizeof(buf) ) != 200) {
          sdp_free( streams, n_streams );
          return DR_FAILURE;
     }

     /* discard remaining headers */
     while (net_response( stream, buf, sizeof(buf) ) > 0);

     /* revert to blocking mode */
     fcntl( stream->fd, F_SETFL,
            fcntl( stream->fd, F_GETFL ) & ~O_NONBLOCK );

     if (!stream->remote.real_rtsp) {
          RTPPayload *p;

          stream->remote.data = D_CALLOC( 1, (n_streams+1)*sizeof(RTPPayload) );
          if (!stream->remote.data) {
               sdp_free( streams, n_streams );
               return D_OOM();
          }

          p = (RTPPayload*) stream->remote.data;
          for (i = 0; i < n_streams; i++) {
               if (streams[i].payload) {
                    *p = *(streams[i].payload);
                    p->pt = streams[i].pt;
                    p++;
               }
          }
     }

     stream->remote.real_pack = true;
     if (n_streams == 1 && streams[0].mime) {
          if (!strcmp( streams[0].mime, "audio/mpeg" ) ||
              !strcmp( streams[0].mime, "audio/aac" )  ||
              !strcmp( streams[0].mime, "video/mpeg" ) ||
              !strcmp( streams[0].mime, "video/mpegts" ))
          {
               stream->mime = D_STRDUP( streams[0].mime );
               stream->remote.real_pack = false;
          }
     }
     if (stream->remote.real_pack) {
          ret = rmf_write_header( streams, n_streams,
                                 &stream->cache, &stream->cache_size );
          if (ret) {
               sdp_free( streams, n_streams );
               return ret;
          }
          stream->mime = D_STRDUP( "application/vnd.rn-realmedia" );
     }

     sdp_free( streams, n_streams );

     return DR_OK;
}

static DirectResult
rvp_read_packet( DirectStream *stream )
{
     unsigned char  buf[9];
     int            size;
     int            len;
     unsigned char  id;
     unsigned int   ts;

     while (1) {
          do {
               size = recv( stream->fd, buf, 1, MSG_WAITALL );
               if (size < 1)
                    return DR_EOF;
          } while (buf[0] != '$');

          size = recv( stream->fd, buf, 7, MSG_WAITALL );
          if (size < 7)
               return DR_EOF;

          len = (buf[0] << 16) + (buf[1] << 8) + buf[2];
          id = buf[3];
          if (id != 0x40 && id != 0x42) {
               if (buf[5] == 0x06) // EOS
                    return DR_EOF;
               size = recv( stream->fd, buf, 9, MSG_WAITALL );
               if (size < 9)
                    return DR_EOF;
               id = buf[5];
               len -= 9;
          }
          id = (id >> 1) & 1;

          size = recv( stream->fd, buf, 6, MSG_WAITALL );
          if (size < 6)
               return DR_EOF;
          ts = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];

          len -= 10;
          if (len > 0) {
               unsigned char *dst;

               size = len + 12;
               stream->cache = D_REALLOC( stream->cache, stream->cache_size+size );
               if (!stream->cache)
                    return D_OOM();
               dst = stream->cache+stream->cache_size;
               stream->cache_size += size;

               dst += rmf_write_pheader( dst, id, len, ts );

               while (len) {
                    size = recv( stream->fd, dst, len, MSG_WAITALL );
                    if (size < 1)
                         return DR_EOF;
                    dst += size;
                    len -= size;
               }
               break;
          }
     }

     return DR_OK;
}

static DirectResult
rtp_read_packet( DirectStream *stream )
{
     RTPPayload    *payloads = (RTPPayload*)stream->remote.data;
     unsigned char  buf[12];
     int            size;
     int            len;
     unsigned char  id;
     unsigned short seq;
     unsigned int   ts;
     int            skip;

     while (1) {
          RTPPayload *p;

          do {
               size = recv( stream->fd, buf, 1, MSG_WAITALL );
               if (size < 1)
                    return DR_EOF;
          } while (buf[0] != '$');

          size = recv( stream->fd, buf, 3, MSG_WAITALL );
          if (size < 3)
               return DR_EOF;

          id = buf[0];
          len = (buf[1] << 8) | buf[2];
          if (len < 12)
               continue;

          size = recv( stream->fd, buf, 12, MSG_WAITALL );
          if (size < 12)
               return DR_EOF;
          len -= 12;

          if ((buf[0] & 0xc0) != (2 << 6))
               D_DEBUG_AT( Direct_Stream, "Bad RTP version %d!\n", buf[0] );
          seq = (buf[2] <<  8) |  buf[3];
          ts  = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];

          for (p = payloads; p->pt; p++) {
               if (p->pt == (buf[1] & 0x7f))
                    break;
          }

          switch (p->pt) {
               case  0: // Unhandled
                    skip = len;
                    break;
               case 14: // MPEG Audio
                    skip = 4;
                    break;
               case 32: // MPEG Video
                    size = recv( stream->fd, buf, 1, MSG_WAITALL );
                    if (size < 1)
                         return DR_EOF;
                    len--;
                    skip = 3;
                    if (buf[0] & (1 << 2))
                         skip += 4;
                    break;
               case 34: // H263
                    size = recv( stream->fd, buf, 1, MSG_WAITALL );
                    if (size < 1)
                         return DR_EOF;
                    len--;
                    skip = 3;
                    if (buf[0] & (1 << 7))
                         skip += 4;
                    if (buf[0] & (1 << 6))
                         skip += 4;
                    break;
               default:
                    skip = 0;
                    break;
          }

          while (skip) {
               size = recv( stream->fd, buf, MIN(skip,12), MSG_WAITALL );
               if (size < 1)
                    return DR_EOF;
               len -= size;
               skip -= size;
          }

          if (len > 0) {
               unsigned char *dst;

               size = len;
               if (stream->remote.real_pack) {
                    size += 12;
                    if (p->type == PAYLOAD_VIDEO)
                         size += 7;
               }

               stream->cache = D_REALLOC( stream->cache, stream->cache_size+size );
               if (!stream->cache)
                    return D_OOM();
               dst = stream->cache+stream->cache_size;
               stream->cache_size += size;

               if (stream->remote.real_pack) {
                    if (p->type == PAYLOAD_VIDEO) {
                         dst += rmf_write_pheader( dst, id, len+7, ts );
                         dst[0] = 0x81;
                         dst[1] = 0x01;
                         dst[2] = (len | 0x4000)>>8; dst[3] = len;
                         dst[4] = (len | 0x4000)>>8; dst[5] = len;
                         dst[6] = seq;
                         dst += 7;
                    } else {
                         dst += rmf_write_pheader( dst, id, len, ts );
                    }
               }

               while (len) {
                    size = recv( stream->fd, dst, len, MSG_WAITALL );
                    if (size < 1)
                         return DR_EOF;
                    dst += size;
                    len -= size;
               }
               break;
          }
     }

     return DR_OK;
}

static DirectResult
rtsp_peek( DirectStream *stream,
           unsigned int  length,
           int           offset,
           void         *buf,
           unsigned int *read_out )
{
     DirectResult ret;
     unsigned int len;

     if (offset < 0)
          return DR_UNSUPPORTED;

     len = length + offset;
     while (len > stream->cache_size) {
          ret = (stream->remote.real_rtsp)
                ? rvp_read_packet( stream )
                : rtp_read_packet( stream );
          if (ret) {
               if (stream->cache_size < offset)
                    return ret;
               break;
          }
     }

     len = MIN( stream->cache_size-offset, length );
     direct_memcpy( buf, stream->cache+offset, len );

     if (read_out)
          *read_out = len;

     return DR_OK;
}

static DirectResult
rtsp_read( DirectStream *stream,
           unsigned int  length,
           void         *buf,
           unsigned int *read_out )
{
     DirectResult ret;
     unsigned int size = 0;

     while (size < length) {
          if (stream->cache_size) {
               unsigned int len = MIN( stream->cache_size, length-size );

               direct_memcpy( buf+size, stream->cache, len );
               size += len;
               stream->cache_size -= len;

               if (stream->cache_size) {
                    direct_memcpy( stream->cache,
                                   stream->cache+len, stream->cache_size );
               } else {
                    D_FREE( stream->cache );
                    stream->cache = NULL;
               }
          }

          if (size < length) {
               ret = (stream->remote.real_rtsp)
                     ? rvp_read_packet( stream )
                     : rtp_read_packet( stream );
               if (ret) {
                    if (!size)
                         return ret;
                    break;
               }
          }
     }

     stream->offset += size;

     if (read_out)
          *read_out = size;

     return DR_OK;
}

static DirectResult
rtsp_open( DirectStream *stream, const char *filename )
{
     DirectResult ret;

     stream->remote.port = RTSP_PORT;

     ret = net_open( stream, filename, IPPROTO_TCP );
     if (ret)
          return ret;

     ret = rtsp_session_open( stream );
     if (ret) {
          close( stream->remote.sd );
          return ret;
     }

     stream->peek = rtsp_peek;
     stream->read = rtsp_read;

     return DR_OK;
}

#endif /* DIRECT_BUILD_NETWORK */

/************************** End of Network Support ***************************/

#ifndef WIN32
static DirectResult
pipe_wait( DirectStream   *stream,
           unsigned int    length,
           struct timeval *tv )
{
     fd_set s;

     if (stream->cache_size >= length)
          return DR_OK;

     FD_ZERO( &s );
     FD_SET( stream->fd, &s );

     switch (select( stream->fd+1, &s, NULL, NULL, tv )) {
          case 0:
               if (!tv && !stream->cache_size)
                    return DR_EOF;
               return DR_TIMEOUT;
          case -1:
               return errno2result( errno );
     }

     return DR_OK;
}

static DirectResult
pipe_peek( DirectStream *stream,
           unsigned int  length,
           int           offset,
           void         *buf,
           unsigned int *read_out )
{
     unsigned int size = length;
     int          len;

     if (offset < 0)
          return DR_UNSUPPORTED;

     len = length + offset;
     if (len > stream->cache_size) {
          ssize_t s;

          stream->cache = D_REALLOC( stream->cache, len );
          if (!stream->cache) {
               stream->cache_size = 0;
               return D_OOM();
          }

          s = read( stream->fd,
                    stream->cache + stream->cache_size,
                    len - stream->cache_size );
          if (s < 0) {
               if (errno != EAGAIN || stream->cache_size == 0)
                    return errno2result( errno );
               s = 0;
          }

          stream->cache_size += s;
          if (stream->cache_size <= offset)
               return DR_BUFFEREMPTY;

          size = stream->cache_size - offset;
     }

     direct_memcpy( buf, stream->cache+offset, size );

     if (read_out)
          *read_out = size;

     return DR_OK;
}

static DirectResult
pipe_read( DirectStream *stream,
           unsigned int  length,
           void         *buf,
           unsigned int *read_out )
{
     unsigned int size = 0;

     if (stream->cache_size) {
          size = MIN( stream->cache_size, length );

          direct_memcpy( buf, stream->cache, size );

          length -= size;
          stream->cache_size -= size;

          if (stream->cache_size) {
               direct_memcpy( stream->cache,
                              stream->cache+size, stream->cache_size );
          } else {
               D_FREE( stream->cache );
               stream->cache = NULL;
          }
     }

     if (length) {
          ssize_t s;

          s = read( stream->fd, buf+size, length-size );
          switch (s) {
               case 0:
                    if (!size)
                         return DR_EOF;
                    break;
               case -1:
                    if (!size) {
                         return (errno == EAGAIN)
                                ? DR_BUFFEREMPTY
                                : errno2result( errno );
                    }
                    break;
               default:
                    size += s;
                    break;
          }
     }

     stream->offset += size;

     if (read_out)
          *read_out = size;

     return DR_OK;
}
#endif

/*****************************************************************************/

#ifndef WIN32
static DirectResult
file_peek( DirectStream *stream,
           unsigned int  length,
           int           offset,
           void         *buf,
           unsigned int *read_out )
{
     DirectResult ret = DR_OK;
     ssize_t      size;

     if (lseek( stream->fd, offset, SEEK_CUR ) < 0)
          return DR_FAILURE;

     size = read( stream->fd, buf, length );
     switch (size) {
          case 0:
               ret = DR_EOF;
               break;
          case -1:
               ret = (errno == EAGAIN)
                     ? DR_BUFFEREMPTY
                     : errno2result( errno );
               size = 0;
               break;
     }

     if (lseek( stream->fd, - offset - size, SEEK_CUR ) < 0)
          return DR_FAILURE;

     if (read_out)
          *read_out = size;

     return ret;
}

static DirectResult
file_read( DirectStream *stream,
           unsigned int  length,
           void         *buf,
           unsigned int *read_out )
{
     ssize_t size;

     size = read( stream->fd, buf, length );
     switch (size) {
          case 0:
               return DR_EOF;
          case -1:
               if (errno == EAGAIN)
                    return DR_BUFFEREMPTY;
               return errno2result( errno );
     }

     stream->offset += size;

     if (read_out)
          *read_out = size;

     return DR_OK;
}

static DirectResult
file_seek( DirectStream *stream, unsigned int offset )
{
     off_t off;

     off = lseek( stream->fd, offset, SEEK_SET );
     if (off < 0)
          return DR_FAILURE;

     stream->offset = off;

     return DR_OK;
}
#else
static DirectResult
file_peek( DirectStream *stream,
           unsigned int  length,
           int           offset,
           void         *buf,
           unsigned int *read_out )
{
     DirectResult ret = DR_OK;
     size_t       size;

     ret = direct_file_seek( &stream->file, offset );
     if (ret)
          return ret;

     ret = direct_file_read( &stream->file, buf, length, &size );
     if (ret)
          return ret;

     ret = direct_file_seek( &stream->file, - offset - size );
     if (ret)
          return ret;

     if (read_out)
          *read_out = size;

     return ret;
}

static DirectResult
file_read( DirectStream *stream,
           unsigned int  length,
           void         *buf,
           unsigned int *read_out )
{
     DirectResult ret;
     size_t       size;

     ret = direct_file_read( &stream->file, buf, length, &size );
     if (ret)
          return ret;

     stream->offset += size;

     if (read_out)
          *read_out = size;

     return DR_OK;
}

static DirectResult
file_seek( DirectStream *stream, unsigned int offset )
{
     DirectResult ret;

     ret = direct_file_seek_to( &stream->file, offset );
     if (ret)
          return ret;

     stream->offset = offset;

     return DR_OK;
}
#endif


static DirectResult
file_open( DirectStream *stream, const char *filename, int fileno )
{
#ifndef WIN32
     if (filename) {
          stream->fd = open( filename, O_RDONLY | O_NONBLOCK );
     }
     else {
          stream->fd = dup( fileno );

          if (stream->fd < 0)
               return errno2result( errno );

          fcntl( stream->fd, F_SETFL,
                 fcntl( stream->fd, F_GETFL ) | O_NONBLOCK );
     }

     if (lseek( stream->fd, 0, SEEK_CUR ) < 0 && errno == ESPIPE) {
          stream->length = -1;
          stream->wait   = pipe_wait;
          stream->peek   = pipe_peek;
          stream->read   = pipe_read;
     }
     else {
          struct stat s;

          if (fstat( stream->fd, &s ) < 0)
               return errno2result( errno );

          stream->length = s.st_size;
          stream->peek   = file_peek;
          stream->read   = file_read;
          stream->seek   = file_seek;
     }
#else
     DirectResult   ret;
     DirectFileInfo info;

     if (filename) {
          ret = direct_file_open( &stream->file, filename, O_RDONLY | O_NONBLOCK, 0644 );
          if (ret)
               return ret;
     }
     else {
          D_UNIMPLEMENTED();

          return DR_UNIMPLEMENTED;
     }

     ret = direct_file_get_info( &stream->file, &info );
     if (ret) {
          direct_file_close( &stream->file );
          return ret;
     }

     stream->length = info.size;
     stream->peek   = file_peek;
     stream->read   = file_read;
     stream->seek   = file_seek;
#endif

     return DR_OK;
}

/*****************************************************************************/

DirectResult
direct_stream_create( const char    *filename,
                      DirectStream **ret_stream )
{
     DirectStream *stream;
     DirectResult  ret;

     D_ASSERT( filename != NULL );
     D_ASSERT( ret_stream != NULL );

     stream = D_CALLOC( 1, sizeof(DirectStream) );
     if (!stream)
          return D_OOM();

     D_MAGIC_SET( stream, DirectStream );

     stream->ref =  1;
     stream->fd  = -1;

#ifndef WIN32
     if (!strncmp( filename, "stdin:/", 7 )) {
          ret = file_open( stream, NULL, STDIN_FILENO );
     }
     else
#endif
     if (!strncmp( filename, "file:/", 6 )) {
          ret = file_open( stream, filename+6, -1 );
     }
     else if (!strncmp( filename, "fd:/", 4 )) {
          ret = (filename[4] >= '0' && filename[4] <= '9')
                ? file_open( stream, NULL, atoi(filename+4) ) : DR_INVARG;
     }
#if DIRECT_BUILD_NETWORK
     else if (!strncmp( filename, "http://", 7 ) ||
              !strncmp( filename, "unsv://", 7 )) {
          ret = http_open( stream, filename+7 );
     }
     else if (!strncmp( filename, "ftp://", 6 )) {
          ret = ftp_open( stream, filename+6 );
     }
     else if (!strncmp( filename, "rtsp://", 7 )) {
          ret = rtsp_open( stream, filename+7 );
     }
     else if (!strncmp( filename, "tcp://", 6 )) {
          ret = net_open( stream, filename+6, IPPROTO_TCP );
     }
     else if (!strncmp( filename, "udp://", 6 )) {
          ret = net_open( stream, filename+6, IPPROTO_UDP );
     }
#endif
     else {
          ret = file_open( stream, filename, -1 );
     }

     if (ret) {
          direct_stream_close( stream );
          D_FREE( stream );
          return ret;
     }

     *ret_stream = stream;

     return DR_OK;
}

DirectStream*
direct_stream_dup( DirectStream *stream )
{
     D_ASSERT( stream != NULL );

     D_MAGIC_ASSERT( stream, DirectStream );

     stream->ref++;

     return stream;
}

int
direct_stream_fileno( DirectStream  *stream )
{
     D_ASSERT( stream != NULL );

     D_MAGIC_ASSERT( stream, DirectStream );

     return stream->fd;
}

bool
direct_stream_seekable( DirectStream *stream )
{
     D_ASSERT( stream != NULL );

     D_MAGIC_ASSERT( stream, DirectStream );

     return stream->seek ? true : false;
}

bool
direct_stream_remote( DirectStream *stream )
{
     D_ASSERT( stream != NULL );

     D_MAGIC_ASSERT( stream, DirectStream );

#if DIRECT_BUILD_NETWORK
     if (stream->remote.host)
          return true;
#endif
     return false;
}

const char*
direct_stream_mime( DirectStream *stream )
{
     D_ASSERT( stream != NULL );

     D_MAGIC_ASSERT( stream, DirectStream );

     return stream->mime;
}

unsigned int
direct_stream_offset( DirectStream *stream )
{
     D_ASSERT( stream != NULL );

     D_MAGIC_ASSERT( stream, DirectStream );

     return stream->offset;
}

unsigned int
direct_stream_length( DirectStream *stream )
{
     D_ASSERT( stream != NULL );

     D_MAGIC_ASSERT( stream, DirectStream );

     return (unsigned int)((stream->length >= 0) ? stream->length : stream->offset);
}

DirectResult
direct_stream_wait( DirectStream   *stream,
                    unsigned int    length,
                    struct timeval *tv )
{
     D_ASSERT( stream != NULL );

     D_MAGIC_ASSERT( stream, DirectStream );

     if (length && stream->wait)
          return stream->wait( stream, length, tv );

     return DR_OK;
}

DirectResult
direct_stream_peek( DirectStream *stream,
                    unsigned int  length,
                    int           offset,
                    void         *buf,
                    unsigned int *read_out )
{
     D_ASSERT( stream != NULL );
     D_ASSERT( length != 0 );
     D_ASSERT( buf != NULL );

     D_MAGIC_ASSERT( stream, DirectStream );

     if (stream->length >= 0 && (stream->offset + offset) >= stream->length)
          return DR_EOF;

     if (stream->peek)
          return stream->peek( stream, length, offset, buf, read_out );

     return DR_UNSUPPORTED;
}

DirectResult
direct_stream_read( DirectStream *stream,
                    unsigned int  length,
                    void         *buf,
                    unsigned int *read_out )
{
     D_ASSERT( stream != NULL );
     D_ASSERT( length != 0 );
     D_ASSERT( buf != NULL );

     D_MAGIC_ASSERT( stream, DirectStream );

     if (stream->length >= 0 && stream->offset >= stream->length)
          return DR_EOF;

     if (stream->read)
          return stream->read( stream, length, buf, read_out );

     return DR_UNSUPPORTED;
}

DirectResult
direct_stream_seek( DirectStream *stream,
                    unsigned int  offset )
{
     D_ASSERT( stream != NULL );

     D_MAGIC_ASSERT( stream, DirectStream );

     if (stream->offset == offset)
          return DR_OK;

     if (stream->length >= 0 && offset > stream->length)
          offset = (unsigned int) stream->length;

     if (stream->seek)
          return stream->seek( stream, offset );

     return DR_UNSUPPORTED;
}

static void
direct_stream_close( DirectStream *stream )
{
#if DIRECT_BUILD_NETWORK
     if (stream->remote.host) {
          D_FREE( stream->remote.host );
          stream->remote.host = NULL;
     }

     if (stream->remote.user) {
          D_FREE( stream->remote.user );
          stream->remote.user = NULL;
     }

     if (stream->remote.pass) {
          D_FREE( stream->remote.pass );
          stream->remote.pass = NULL;
     }

     if (stream->remote.auth) {
          D_FREE( stream->remote.auth );
          stream->remote.auth = NULL;
     }

     if (stream->remote.path) {
          D_FREE( stream->remote.path );
          stream->remote.path = NULL;
     }

     if (stream->remote.addr) {
          freeaddrinfo( stream->remote.addr );
          stream->remote.addr = NULL;
     }

     if (stream->remote.data) {
          D_FREE( stream->remote.data );
          stream->remote.data = NULL;
     }

     if (stream->remote.sd > 0) {
          close( stream->remote.sd );
          stream->remote.sd = -1;
     }
#endif

     if (stream->mime) {
          D_FREE( stream->mime );
          stream->mime = NULL;
     }

     if (stream->cache) {
          D_FREE( stream->cache );
          stream->cache = NULL;
          stream->cache_size = 0;
     }

#ifndef WIN32
     if (stream->fd >= 0) {
          fcntl( stream->fd, F_SETFL,
                    fcntl( stream->fd, F_GETFL ) & ~O_NONBLOCK );
          close( stream->fd );
          stream->fd = -1;
     }
#else
	 direct_file_close( &stream->file );
#endif
}

void
direct_stream_destroy( DirectStream *stream )
{
     D_ASSERT( stream != NULL );

     D_MAGIC_ASSERT( stream, DirectStream );

     if (--stream->ref == 0) {
          direct_stream_close( stream );

          D_FREE( stream );
     }
}
