view src/wma/libffwma/mms.c @ 74:11d45400f5ed trunk

[svn] The mixer device isn't opened and closed for every action now, instead it gets opened as soon as needed and closed if it's not needed anymore. This fixes a lot of bugs with the mixer of the OSS plugin, including the one that settigns the volume on OpenBSD via OSS didn't work at all.
author js
date Mon, 02 Oct 2006 11:29:31 -0700
parents 3da1b8942b8b
children
line wrap: on
line source

/*
 * Copyright (C) 2002-2004 the xine project
 * 
 * This file is part of LibMMS, an MMS protocol handling library.
 * 
 * xine is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the ree Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * xine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * $Id: mms.c,v 1.21 2006/07/13 12:32:20 shawarma Exp $
 *
 * MMS over TCP protocol
 *   based on work from major mms
 *   utility functions to handle communication with an mms server
 *
 * TODO:
 *   error messages
 *   enable seeking !
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <time.h>

/********** logging **********/
#define LOG_MODULE "mms"
#define LOG_VERBOSE
#ifdef DEBUG
# define lprintf printf
#else
# define lprintf(x...)
#endif


#include "bswap.h"
#include "mms.h"
#include "asfheader.h"
#include "uri.h"


/* 
 * mms specific types 
 */

#define MMST_PORT 1755

#define BUF_SIZE 102400

#define CMD_HEADER_LEN   40
#define CMD_PREFIX_LEN    8
#define CMD_BODY_LEN   1024

#define ASF_HEADER_LEN 8192


#define MMS_PACKET_ERR        0
#define MMS_PACKET_COMMAND    1
#define MMS_PACKET_ASF_HEADER 2
#define MMS_PACKET_ASF_PACKET 3

#define ASF_HEADER_PACKET_ID_TYPE 2
#define ASF_MEDIA_PACKET_ID_TYPE  4


typedef struct mms_buffer_s mms_buffer_t;
struct mms_buffer_s {
  uint8_t *buffer;
  int pos;
};

typedef struct mms_packet_header_s mms_packet_header_t;
struct mms_packet_header_s {
  uint32_t  packet_len;
  uint8_t   flags;
  uint8_t   packet_id_type;
  uint32_t  packet_seq;
};


struct mms_s {

  /* FIXME: de-xine-ification */
  void *custom_data;
  
  int           s;
  
  /* url parsing */
  char         *url;
  char         *proto;
  char         *host;
  int           port;
  char         *user;
  char         *password;
  char         *uri;

  /* command to send */
  char          scmd[CMD_HEADER_LEN + CMD_BODY_LEN];
  char         *scmd_body; /* pointer to &scmd[CMD_HEADER_LEN] */
  int           scmd_len; /* num bytes written in header */
  
  char          str[1024]; /* scratch buffer to built strings */
  
  /* receive buffer */
  uint8_t       buf[BUF_SIZE];
  int           buf_size;
  int           buf_read;
  
  uint8_t       asf_header[ASF_HEADER_LEN];
  uint32_t      asf_header_len;
  uint32_t      asf_header_read;
  int           seq_num;
  int           num_stream_ids;
  int           stream_ids[ASF_MAX_NUM_STREAMS];
  int           stream_types[ASF_MAX_NUM_STREAMS];
  off_t         start_packet_seq; /* for live streams != 0, need to keep it around */
  int           need_discont; /* whether we need to set start_packet_seq */
  uint32_t      asf_packet_len;
  uint64_t      file_len;
  char          guid[37];
  uint32_t      bitrates[ASF_MAX_NUM_STREAMS];
  uint32_t      bitrates_pos[ASF_MAX_NUM_STREAMS];
  int           bandwidth;
  
  int           has_audio;
  int           has_video;
  int           live_flag;
  off_t         current_pos;
  int           eos;
};

static int fallback_io_select(void *data, int socket, int state, int timeout_msec)
{
  fd_set set;
  struct timeval tv = { timeout_msec / 1000, (timeout_msec % 1000) * 1000};
  FD_ZERO(&set);
  FD_SET(socket, &set);
  return select(1, (state == MMS_IO_READ_READY) ? &set : NULL,
		       (state == MMS_IO_WRITE_READY) ? &set : NULL, NULL, &tv);
}

static off_t fallback_io_read(void *data, int socket, char *buf, off_t num)
{
  off_t len = 0, ret;
/*   lprintf("%d\n", fallback_io_select(data, socket, MMS_IO_READ_READY, 1000)); */
  errno = 0;
  while (len < num)
  {
    ret = (off_t)read(socket, buf + len, num - len);
    if(ret == 0)
      break; /* EOF */
    if(ret < 0)
      switch(errno)
      {
	  case EAGAIN:
	    lprintf("len == %lld\n", (long long int) len);
	    break;
	  default:
	    lprintf("len == %lld\n", (long long int) len);
	    perror(NULL);
	    /* if already read something, return it, we will fail next time */
	    return len ? len : ret; 
      }
    len += ret;
  }
  lprintf("ret len == %lld\nnum == %lld\n", (long long int) len, (long long int) num);
  return len;
}

static off_t fallback_io_write(void *data, int socket, char *buf, off_t num)
{
  return (off_t)write(socket, buf, num);
}

static int fallback_io_tcp_connect(void *data, const char *host, int port)
{
  
  struct hostent *h;
  int i, s;
  
  h = gethostbyname(host);
  if (h == NULL) {
/*     fprintf(stderr, "unable to resolve host: %s\n", host); */
    return -1;
  }

  s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);  
  if (s == -1) {
/*     fprintf(stderr, "failed to create socket: %s", strerror(errno)); */
    return -1;
  }

  if (fcntl (s, F_SETFL, fcntl (s, F_GETFL) & ~O_NONBLOCK) == -1) {
/*     _x_message(stream, XINE_MSG_CONNECTION_REFUSED, "can't put socket in non-blocking mode", strerror(errno), NULL); */
    return -1;
  }

  for (i = 0; h->h_addr_list[i]; i++) {
    struct in_addr ia;
    struct sockaddr_in sin;
 
    memcpy (&ia, h->h_addr_list[i], 4);
    sin.sin_family = AF_INET;
    sin.sin_addr   = ia;
    sin.sin_port   = htons(port);
    
    if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) ==-1 && errno != EINPROGRESS) {

      /* FIXME: de-xine-ification */
/*       _x_message(stream, XINE_MSG_CONNECTION_REFUSED, strerror(errno), NULL); */
      close(s);
      continue;
    }
    
    return s;
  }
  return -1;
}


static mms_io_t fallback_io =
  {
    &fallback_io_select,
    NULL,
    &fallback_io_read,
    NULL,
    &fallback_io_write,
    NULL,
    &fallback_io_tcp_connect,
    NULL,
  };

static mms_io_t default_io =   {
    &fallback_io_select,
    NULL,
    &fallback_io_read,
    NULL,
    &fallback_io_write,
    NULL,
    &fallback_io_tcp_connect,
    NULL,
  };


#define io_read(io, args...) ((io) ? (io)->read(io->read_data , ## args) : default_io.read(NULL , ## args))
#define io_write(io, args...) ((io) ? (io)->write(io->write_data , ## args) : default_io.write(NULL , ## args))
#define io_select(io, args...) ((io) ? (io)->select(io->select_data , ## args) : default_io.select(NULL , ## args))
#define io_connect(io, args...) ((io) ? (io)->connect(io->connect_data , ## args) : default_io.connect(NULL , ## args))
  
const mms_io_t* mms_get_default_io_impl()
{
  return &default_io;
}

void mms_set_default_io_impl(const mms_io_t *io)
{
  if(io->select)
  {
    default_io.select = io->select;
    default_io.select_data = io->select_data;
  } else
  {
    default_io.select = fallback_io.select;
    default_io.select_data = fallback_io.select_data;
  }
  if(io->read)
  {
    default_io.read = io->read;
    default_io.read_data = io->read_data;
  } else
  {
    default_io.read = fallback_io.read;
    default_io.read_data = fallback_io.read_data;
  }
  if(io->write)
  {
    default_io.write = io->write;
    default_io.write_data = io->write_data;
  } else
  {
    default_io.write = fallback_io.write;
    default_io.write_data = fallback_io.write_data;
  }
  if(io->connect)
  {
    default_io.connect = io->connect;
    default_io.connect_data = io->connect_data;
  } else
  {
    default_io.connect = fallback_io.connect;
    default_io.connect_data = fallback_io.connect_data;
  }
}

static void mms_buffer_init (mms_buffer_t *mms_buffer, uint8_t *buffer) {
  mms_buffer->buffer = buffer;
  mms_buffer->pos = 0;
}

static void mms_buffer_put_8 (mms_buffer_t *mms_buffer, uint8_t value) {

  mms_buffer->buffer[mms_buffer->pos]     = value          & 0xff;

  mms_buffer->pos += 1;
}

#if 0
static void mms_buffer_put_16 (mms_buffer_t *mms_buffer, uint16_t value) {

  mms_buffer->buffer[mms_buffer->pos]     = value          & 0xff;
  mms_buffer->buffer[mms_buffer->pos + 1] = (value  >> 8)  & 0xff;

  mms_buffer->pos += 2;
}
#endif

static void mms_buffer_put_32 (mms_buffer_t *mms_buffer, uint32_t value) {

  mms_buffer->buffer[mms_buffer->pos]     = value          & 0xff;
  mms_buffer->buffer[mms_buffer->pos + 1] = (value  >> 8)  & 0xff;
  mms_buffer->buffer[mms_buffer->pos + 2] = (value  >> 16) & 0xff;
  mms_buffer->buffer[mms_buffer->pos + 3] = (value  >> 24) & 0xff;

  mms_buffer->pos += 4;
}

static int get_guid (unsigned char *buffer, int offset) {
  int i;
  GUID g;
  
  g.Data1 = LE_32(buffer + offset);
  g.Data2 = LE_16(buffer + offset + 4);
  g.Data3 = LE_16(buffer + offset + 6);
  for(i = 0; i < 8; i++) {
    g.Data4[i] = buffer[offset + 8 + i];
  }
  
  for (i = 1; i < GUID_END; i++) {
    if (!memcmp(&g, &guids[i].guid, sizeof(GUID))) {
      lprintf("GUID: %s\n", guids[i].name);
      return i;
    }
  }
  
  lprintf("unknown GUID: 0x%x, 0x%x, 0x%x, "
	   "{ 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx }\n",
	   g.Data1, g.Data2, g.Data3,
	   g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3], 
	   g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]);

  return GUID_ERROR;
}


static void print_command (char *data, int len) {

#ifdef DEBUG
  int i;
  int dir = LE_32 (data + 36) >> 16;
  int comm = LE_32 (data + 36) & 0xFFFF;

  lprintf ("----------------------------------------------\n");
  if (dir == 3) {
    lprintf ("send command 0x%02x, %d bytes\n", comm, len);
  } else {
    lprintf ("receive command 0x%02x, %d bytes\n", comm, len);
  }
  lprintf ("  start sequence %08x\n", LE_32 (data +  0));
  lprintf ("  command id     %08x\n", LE_32 (data +  4));
  lprintf ("  length         %8x \n", LE_32 (data +  8));
  lprintf ("  protocol       %08x\n", LE_32 (data + 12));
  lprintf ("  len8           %8x \n", LE_32 (data + 16));
  lprintf ("  sequence #     %08x\n", LE_32 (data + 20));
  lprintf ("  len8  (II)     %8x \n", LE_32 (data + 32));
  lprintf ("  dir | comm     %08x\n", LE_32 (data + 36));
  if (len >= 4)
    lprintf ("  prefix1        %08x\n", LE_32 (data + 40));
  if (len >= 8)
    lprintf ("  prefix2        %08x\n", LE_32 (data + 44));

  for (i = (CMD_HEADER_LEN + CMD_PREFIX_LEN); i < (CMD_HEADER_LEN + CMD_PREFIX_LEN + len); i += 1) {
    unsigned char c = data[i];
    
    if ((c >= 32) && (c < 128))
      lprintf ("%c", c);
    else
      lprintf (" %02x ", c);
    
  }
  if (len > CMD_HEADER_LEN)
    lprintf ("\n");
  lprintf ("----------------------------------------------\n");
#endif
}  



static int send_command (mms_io_t *io, mms_t *this, int command,
                         uint32_t prefix1, uint32_t prefix2,
                         int length) {
  int    len8;
  off_t  n;
  mms_buffer_t command_buffer;

  len8 = (length + 7) / 8;

  this->scmd_len = 0;

  mms_buffer_init(&command_buffer, this->scmd);
  mms_buffer_put_32 (&command_buffer, 0x00000001);   /* start sequence */
  mms_buffer_put_32 (&command_buffer, 0xB00BFACE);   /* #-)) */
  mms_buffer_put_32 (&command_buffer, len8 * 8 + 32);
  mms_buffer_put_32 (&command_buffer, 0x20534d4d);   /* protocol type "MMS " */
  mms_buffer_put_32 (&command_buffer, len8 + 4);
  mms_buffer_put_32 (&command_buffer, this->seq_num);
  this->seq_num++;
  mms_buffer_put_32 (&command_buffer, 0x0);          /* timestamp */
  mms_buffer_put_32 (&command_buffer, 0x0);
  mms_buffer_put_32 (&command_buffer, len8 + 2);
  mms_buffer_put_32 (&command_buffer, 0x00030000 | command); /* dir | command */
  /* end of the 40 byte command header */
  
  mms_buffer_put_32 (&command_buffer, prefix1);
  mms_buffer_put_32 (&command_buffer, prefix2);

  if (length & 7)
	  memset(this->scmd + length + CMD_HEADER_LEN + CMD_PREFIX_LEN, 0, 8 - (length & 7));

  n = io_write(io,  this->s, this->scmd, len8 * 8 + CMD_HEADER_LEN + CMD_PREFIX_LEN);
  if (n != (len8 * 8 + CMD_HEADER_LEN + CMD_PREFIX_LEN)) {
    return 0;
  }

  print_command (this->scmd, length);

  return 1;
}

static void string_utf16(int unused, char *dest, char *src, int len) {
  int i;

  memset (dest, 0, 2 * len);

  for (i = 0; i < len; i++) {
    dest[i * 2] = src[i];
    dest[i * 2 + 1] = 0;
  }

  dest[i * 2] = 0;
  dest[i * 2 + 1] = 0;
}

/*
 * return packet type
 */
static int get_packet_header (mms_io_t *io, mms_t *this, mms_packet_header_t *header) {
  size_t len;
  int packet_type;

  header->packet_len     = 0;
  header->packet_seq     = 0;
  header->flags          = 0;
  header->packet_id_type = 0;
  len = io_read(io,  this->s, this->buf, 8);
  if (len != 8)
    goto error;

  if (LE_32(this->buf + 4) == 0xb00bface) {
    /* command packet */
    header->flags = this->buf[3];
    len = io_read(io,  this->s, this->buf + 8, 4);
    if (len != 4)
      goto error;
    
    header->packet_len = LE_32(this->buf + 8) + 4;
    if (header->packet_len > BUF_SIZE - 12) {
        header->packet_len = 0;
        goto error;
    }
    lprintf("mms command\n");
    packet_type = MMS_PACKET_COMMAND;
  } else {
    header->packet_seq     = LE_32(this->buf);
    header->packet_id_type = this->buf[4];
    header->flags          = this->buf[5];
    header->packet_len     = (LE_16(this->buf + 6) - 8) & 0xffff;
    if (header->packet_id_type == ASF_HEADER_PACKET_ID_TYPE) {
      lprintf("asf header\n");
      packet_type = MMS_PACKET_ASF_HEADER;
    } else {
      lprintf("asf packet\n");
      packet_type = MMS_PACKET_ASF_PACKET;
    }
  }
  
  return packet_type;
  
error:
  lprintf("read error, len=%d\n", len);
  perror("Could not read packet header");
  return MMS_PACKET_ERR;
}


static int get_packet_command (mms_io_t *io, mms_t *this, uint32_t packet_len) {


  int  command = 0;
  size_t len;
  
  /* always enter this loop */
  lprintf("packet_len: %d bytes\n", packet_len);

  len = io_read(io,  this->s, this->buf + 12, packet_len) ;
  if (len != packet_len) {
    return 0;
  }

  print_command (this->buf, len);
  
  /* check protocol type ("MMS ") */
  if (LE_32(this->buf + 12) != 0x20534D4D) {
    lprintf("unknown protocol type: %c%c%c%c (0x%08X)\n",
            this->buf[12], this->buf[13], this->buf[14], this->buf[15],
            LE_32(this->buf + 12));  
    return 0;
  }

  command = LE_32 (this->buf + 36) & 0xFFFF;
  lprintf("command = 0x%2x\n", command);
    
  return command;
}

static int get_answer (mms_io_t *io, mms_t *this) {
  int command = 0;
  mms_packet_header_t header;

  switch (get_packet_header (io, this, &header)) {
    case MMS_PACKET_ERR:
  /* FIXME: de-xine-ification */
      lprintf( "***LOG:*** -- "
	      "libmms: failed to read mms packet header\n");
      break;
    case MMS_PACKET_COMMAND:
      command = get_packet_command (io, this, header.packet_len);
      
      if (command == 0x1b) {
    
        if (!send_command (io, this, 0x1b, 0, 0, 0)) {
  /* FIXME: de-xine-ification */
          lprintf( "***LOG:*** -- "
		  "libmms: failed to send command\n");
          return 0;
        }
        /* FIXME: limit recursion */
        command = get_answer (io, this);
      }
      break;
    case MMS_PACKET_ASF_HEADER:
  /* FIXME: de-xine-ification */
      lprintf( "***LOG:*** -- "
	      "libmms: unexpected asf header packet\n");
      break;
    case MMS_PACKET_ASF_PACKET:
  /* FIXME: de-xine-ification */
      lprintf( "***LOG:*** -- "
"libmms: unexpected asf packet\n");
      break;
  }
  
  return command;
}


static int get_asf_header (mms_io_t *io, mms_t *this) {

  off_t len;
  int stop = 0;
  
  this->asf_header_read = 0;
  this->asf_header_len = 0;

  while (!stop) {
    mms_packet_header_t header;
    int command;

    switch (get_packet_header (io, this, &header)) {
      case MMS_PACKET_ERR:
  /* FIXME: de-xine-ification */
        lprintf( "***LOG:*** -- "
	       "libmms: failed to read mms packet header\n");
        return 0;
        break;
      case MMS_PACKET_COMMAND:
        command = get_packet_command (io, this, header.packet_len);
      
        if (command == 0x1b) {
    
          if (!send_command (io, this, 0x1b, 0, 0, 0)) {
  /* FIXME: de-xine-ification */
            lprintf( "***LOG:*** -- "
		   "libmms: failed to send command\n");
            return 0;
          }
          command = get_answer (io, this);
        } else {
  /* FIXME: de-xine-ification */
          lprintf( "***LOG:*** -- "
		 "libmms: unexpected command packet\n");
        }
        break;
      case MMS_PACKET_ASF_HEADER:
      case MMS_PACKET_ASF_PACKET:
        if (header.packet_len + this->asf_header_len > ASF_HEADER_LEN) {
            lprintf( "***LOG:*** -- "
                     "libmms: asf packet too large\n");
            return 0;
        }
        len = io_read(io,  this->s,
                              this->asf_header + this->asf_header_len, header.packet_len);
        if (len != header.packet_len) {
  /* FIXME: de-xine-ification */
          lprintf( "***LOG:*** -- "
		 "libmms: get_asf_header failed\n");
           return 0;
        }
        this->asf_header_len += header.packet_len;
        lprintf("header flags: %d\n", header.flags);
        if ((header.flags == 0X08) || (header.flags == 0X0C))
          stop = 1;
        break;
    }
  }
  lprintf ("get header packet succ\n");
  return 1;
}

static void interp_asf_header (mms_t *this) {

  int i;

  this->asf_packet_len = 0;
  this->num_stream_ids = 0;
  /*
   * parse header
   */
   
  i = 30;
  while (i < this->asf_header_len) {
    
    int guid;
    uint64_t length;

    guid = get_guid(this->asf_header, i);
    i += 16;
        
    length = LE_64(this->asf_header + i);
    i += 8;

    switch (guid) {
    
      case GUID_ASF_FILE_PROPERTIES:

        this->asf_packet_len = LE_32(this->asf_header + i + 92 - 24);
        if (this->asf_packet_len > BUF_SIZE) {
          this->asf_packet_len = 0;
          lprintf( "***LOG:*** -- "
                   "libmms: asf packet len too large\n");
          break;
        }
        this->file_len       = LE_64(this->asf_header + i + 40 - 24);
        lprintf ("file object, packet length = %d (%d)\n",
                 this->asf_packet_len, LE_32(this->asf_header + i + 96 - 24));
        break;

      case GUID_ASF_STREAM_PROPERTIES:
        {
	  uint16_t flags;
          uint16_t stream_id;
          int      type;
          int      encrypted;

          guid = get_guid(this->asf_header, i);
          switch (guid) {
            case GUID_ASF_AUDIO_MEDIA:
              type = ASF_STREAM_TYPE_AUDIO;
              this->has_audio = 1;
              break;
    
            case GUID_ASF_VIDEO_MEDIA:
	    case GUID_ASF_JFIF_MEDIA:
	    case GUID_ASF_DEGRADABLE_JPEG_MEDIA:
              type = ASF_STREAM_TYPE_VIDEO;
              this->has_video = 1;
              break;
          
            case GUID_ASF_COMMAND_MEDIA:
              type = ASF_STREAM_TYPE_CONTROL;
              break;
        
            default:
              type = ASF_STREAM_TYPE_UNKNOWN;
          }

          flags = LE_16(this->asf_header + i + 48);
	  stream_id = flags & 0x7F;
	  encrypted = flags >> 15;

          lprintf ("stream object, stream id: %d, type: %d, encrypted: %d\n",
		   stream_id, type, encrypted);

	  if (this->num_stream_ids < ASF_MAX_NUM_STREAMS && stream_id < ASF_MAX_NUM_STREAMS) {
	    this->stream_types[stream_id] = type;
	    this->stream_ids[this->num_stream_ids] = stream_id;
	    this->num_stream_ids++;
	  } else {
	    lprintf ("too many streams, skipping\n");
	  }
        }
        break;

      case GUID_ASF_STREAM_BITRATE_PROPERTIES:
        {
          uint16_t streams = LE_16(this->asf_header + i);
          uint16_t stream_id;
          int j;

          lprintf ("stream bitrate properties\n");
          lprintf ("streams %d\n", streams); 

          for(j = 0; j < streams; j++) {
            stream_id = LE_16(this->asf_header + i + 2 + j * 6);
            lprintf ("stream id %d\n", stream_id); 
            this->bitrates[stream_id] = LE_32(this->asf_header + i + 4 + j * 6);
            this->bitrates_pos[stream_id] = i + 4 + j * 6;
            lprintf ("stream id %d, bitrate %d\n", stream_id, 
                     this->bitrates[stream_id]);
          }
        }
        break;
    
      default:
        lprintf ("unknown object\n");
        break;
    }

    lprintf ("length    : %lld\n", length);

    if (length > 24) {
      i += length - 24;
    }
  }
}

const static char *const mmst_proto_s[] = { "mms", "mmst", NULL };

static int mmst_valid_proto (char *proto) {
  int i = 0;

  lprintf("mmst_valid_proto\n");

  if (!proto)
    return 0;

  while(mmst_proto_s[i]) {
    if (!strcasecmp(proto, mmst_proto_s[i])) {
      return 1;
    }
    i++;
  }
  return 0;
}

/* FIXME: de-xine-ification */

/* static void report_progress (void *data, int p) {

  xine_event_t             event;
  xine_progress_data_t     prg;

  prg.description = _("Connecting MMS server (over tcp)...");
  prg.percent = p;
  
  event.type = XINE_EVENT_PROGRESS;
  event.data = &prg;
  event.data_length = sizeof (xine_progress_data_t);
  
  xine_event_send (stream, &event);
} */


/*
 * returns 1 on error
 */
static int mms_tcp_connect(mms_io_t *io, mms_t *this) {
  int progress, res;
  
  if (!this->port) this->port = MMST_PORT;

  /* 
   * try to connect 
   */
  lprintf("try to connect to %s on port %d \n", this->host, this->port);
  this->s = io_connect(io,  this->host, this->port);
  if (this->s == -1) {
  /* FIXME: de-xine-ification */
    lprintf ( "***LOG:*** -- "
	    "failed to connect '%s'\n", this->host);
    return 1;
  }

  /* connection timeout 15s */
  progress = 0;
  do {
    /*FIXME: de-xine-ification */
/*     report_progress(this->stream, progress); */
    res = io_select(io,  this->s, MMS_IO_WRITE_READY, 500);
    progress += 1;
  } while ((res == MMS_IO_STATUS_TIMEOUT) && (progress < 30));
  if (res != MMS_IO_STATUS_READY) {
    return 1;
  }
  lprintf ("connected\n");
  return 0;
}

static void mms_gen_guid(char guid[]) {
  static char digit[16] = "0123456789ABCDEF";
  int i = 0;

  srand(time(NULL));
  for (i = 0; i < 36; i++) {
    guid[i] = digit[(int) ((16.0*rand())/(RAND_MAX+1.0))];
  }
  guid[8] = '-'; guid[13] = '-'; guid[18] = '-'; guid[23] = '-';
  guid[36] = '\0';
}

/*
 * return 0 on error
 */
int static mms_choose_best_streams(mms_io_t *io, mms_t *this) {
  int     i;
  int     video_stream = 0;
  int     audio_stream = 0;
  int     max_arate    = 0;
  int     min_vrate    = 0;
  int     min_bw_left  = 0;
  int     stream_id;
  int     bandwitdh_left;
  int     res;

  /* command 0x33 */
  /* choose the best quality for the audio stream */
  /* i've never seen more than one audio stream */
  lprintf("num_stream_ids=%d\n", this->num_stream_ids);
  for (i = 0; i < this->num_stream_ids; i++) {
    stream_id = this->stream_ids[i];
    switch (this->stream_types[stream_id]) {
      case ASF_STREAM_TYPE_AUDIO:
        if (this->bitrates[stream_id] > max_arate) {
          audio_stream = stream_id;
          max_arate = this->bitrates[stream_id];
        }
        break;
      default:
        break;
    }
  }
  
  /* choose a video stream adapted to the user bandwidth */
  bandwitdh_left = this->bandwidth - max_arate;
  if (bandwitdh_left < 0) {
    bandwitdh_left = 0;
  }
  lprintf("bandwitdh %d, left %d\n", this->bandwidth, bandwitdh_left);

  min_bw_left = bandwitdh_left;
  for (i = 0; i < this->num_stream_ids; i++) {
    stream_id = this->stream_ids[i];
    switch (this->stream_types[stream_id]) {
      case ASF_STREAM_TYPE_VIDEO:
        if (((bandwitdh_left - this->bitrates[stream_id]) < min_bw_left) &&
            (bandwitdh_left >= this->bitrates[stream_id])) {
          video_stream = stream_id;
          min_bw_left = bandwitdh_left - this->bitrates[stream_id];
        }
        break;
      default:
        break;
    }
  }  

  /* choose the lower bitrate of */
  if (!video_stream && this->has_video) {
    for (i = 0; i < this->num_stream_ids; i++) {
      stream_id = this->stream_ids[i];
      switch (this->stream_types[stream_id]) {
        case ASF_STREAM_TYPE_VIDEO:
          if ((this->bitrates[stream_id] < min_vrate) ||
              (!min_vrate)) {
            video_stream = stream_id;
            min_vrate = this->bitrates[stream_id];
          }
          break;
        default:
          break;
      }
    }
  }
    
  lprintf("selected streams: audio %d, video %d\n", audio_stream, video_stream);
  lprintf("disabling other streams\n");
  memset (this->scmd_body, 0, 40);
  for (i = 1; i < this->num_stream_ids; i++) {
    this->scmd_body [ (i - 1) * 6 + 2 ] = 0xFF;
    this->scmd_body [ (i - 1) * 6 + 3 ] = 0xFF;
    this->scmd_body [ (i - 1) * 6 + 4 ] = this->stream_ids[i] ;
    this->scmd_body [ (i - 1) * 6 + 5 ] = this->stream_ids[i] >> 8;
    if ((this->stream_ids[i] == audio_stream) ||
        (this->stream_ids[i] == video_stream)) {
      this->scmd_body [ (i - 1) * 6 + 6 ] = 0x00;
      this->scmd_body [ (i - 1) * 6 + 7 ] = 0x00;
    } else {
      lprintf("disabling stream %d\n", this->stream_ids[i]);
      this->scmd_body [ (i - 1) * 6 + 6 ] = 0x02;
      this->scmd_body [ (i - 1) * 6 + 7 ] = 0x00;
      
      /* forces the asf demuxer to not choose this stream */
      if (this->bitrates_pos[this->stream_ids[i]]) {
        this->asf_header[this->bitrates_pos[this->stream_ids[i]]]     = 0;
        this->asf_header[this->bitrates_pos[this->stream_ids[i]] + 1] = 0;
        this->asf_header[this->bitrates_pos[this->stream_ids[i]] + 2] = 0;
        this->asf_header[this->bitrates_pos[this->stream_ids[i]] + 3] = 0;
      }
    }
  }

  if (!send_command (io, this, 0x33, this->num_stream_ids, 
                     0xFFFF | this->stream_ids[0] << 16, 
                     this->num_stream_ids * 6 + 2)) {
  /* FIXME: de-xine-ification */
    lprintf ( "***LOG:*** -- "
	     "libmms: mms_choose_best_streams failed\n");
    return 0;
  }

  if ((res = get_answer (io, this)) != 0x21) {
    /* FIXME: de-xine-ification */
    lprintf ( "***LOG:*** -- "
	     "libmms: unexpected response: %02x (0x21)\n", res);
  }

  return 1;
}

/*
 * TODO: error messages
 *       network timing request
 */
/* FIXME: got somewhat broken during xine_stream_t->(void*) conversion */
mms_t *mms_connect (mms_io_t *io, void *data, const char *url, int bandwidth) {
  int     url_conv = 0;
  mms_t  *this;
  int     res;
  GURI   *uri;
  
  if (!url)
    return NULL;

  /* FIXME: needs proper error-signalling work */
  this = (mms_t*) malloc (sizeof (mms_t));

  this->custom_data     = data;
  this->url             = strdup (url);
  this->s               = -1;
  this->seq_num         = 0;
  this->scmd_body       = this->scmd + CMD_HEADER_LEN + CMD_PREFIX_LEN;
  this->asf_header_len  = 0;
  this->asf_header_read = 0;
  this->num_stream_ids  = 0;
  this->asf_packet_len  = 0;
  this->start_packet_seq= 0;
  this->need_discont    = 1;
  this->buf_size        = 0;
  this->buf_read        = 0;
  this->has_audio       = 0;
  this->has_video       = 0;
  this->bandwidth       = bandwidth;
  this->current_pos     = 0;
  this->eos             = 0;

  /* FIXME de-xine-ification */
/*   report_progress (stream, 0); */
  
  uri = gnet_uri_new(this->url);
  if(!uri) {
    lprintf ("invalid url\n");
    goto fail;
  }
  this->proto = uri->scheme;
  this->user = uri->user;
  this->host = uri->hostname;
  this->port = uri->port;
  this->password = uri->passwd;
  this->uri = uri->path;
  
  if (!mmst_valid_proto(this->proto)) {
    lprintf ("unsupported protocol\n");
    goto fail;
  }
  
  if (mms_tcp_connect(io, this)) {
    goto fail;
  }
  /* FIXME de-xine-ification */
/*   report_progress (stream, 30); */
  
  /*
   * let the negotiations begin...
   */

  /* command 0x1 */
  lprintf("send command 0x01\n");
  mms_gen_guid(this->guid);
  sprintf (this->str, "\x1c\x03NSPlayer/7.0.0.1956; {%s}; Host: %s",
    this->guid, this->host);
  string_utf16 (url_conv, this->scmd_body, this->str, strlen(this->str) + 2);

  if (!send_command (io, this, 1, 0, 0x0004000b, strlen(this->str) * 2 + 8)) {
  /* FIXME: de-xine-ification */
    lprintf( "***LOG:*** -- "
	    "libmms: failed to send command 0x01\n");
    goto fail;
  }
  
  if ((res = get_answer (io, this)) != 0x01) {
  /* FIXME: de-xine-ification */
    lprintf ( "***LOG:*** -- "
	     "libmms: unexpected response: %02x (0x01)\n", res);
    lprintf("answer: %d\n", res);
    goto fail;
  }
  
  /* FIXME de-xine-ification */
/*   report_progress (stream, 40); */

  /* TODO: insert network timing request here */
  /* command 0x2 */
  lprintf("send command 0x02\n");
  string_utf16 (url_conv, &this->scmd_body[8], "\002\000\\\\192.168.0.129\\TCP\\1037\0000", 28);
  memset (this->scmd_body, 0, 8);
  if (!send_command (io, this, 2, 0, 0, 28 * 2 + 8)) {
  /* FIXME: de-xine-ification */
    lprintf( "***LOG:*** -- "
	    "libmms: failed to send command 0x02\n");

    goto fail;
  }

  switch (res = get_answer (io, this)) {
    case 0x02:
      /* protocol accepted */
      break;
    case 0x03:
  /* FIXME: de-xine-ification */
      lprintf( "***LOG:*** -- "
	      "libmms: protocol failed\n");
      goto fail;
      break;
    default:
      lprintf("unexpected response: %02x (0x02 or 0x03)\n", res);
      goto fail;
  }

  /* FIXME de-xine-ification */
/*   report_progress (stream, 50); */

  /* command 0x5 */
  {
    mms_buffer_t command_buffer;
    
    lprintf("send command 0x05\n");
    mms_buffer_init(&command_buffer, this->scmd_body);
    mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
    mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */
    string_utf16 (url_conv, this->scmd_body + command_buffer.pos, this->uri, strlen(this->uri));
    if (!send_command (io, this, 5, 1, 0xffffffff, strlen(this->uri) * 2 + 12))
      goto fail;
  }
  
  switch (res = get_answer (io, this)) {
    case 0x06:
      {
        int xx, yy;
        /* no authentication required */
      
        /* Warning: sdp is not right here */
        xx = this->buf[62];
        yy = this->buf[63];
        this->live_flag = ((xx == 0) && ((yy & 0xf) == 2));
        lprintf("live: live_flag=%d, xx=%d, yy=%d\n", this->live_flag, xx, yy);
      }
      break;
    case 0x1A:
      /* authentication request, not yet supported */
  /* FIXME: de-xine-ification */
      lprintf ( "***LOG:*** -- "
	       "libmms: authentication request, not yet supported\n");
      goto fail;
      break;
    default:
  /* FIXME: de-xine-ification */
    lprintf ( "***LOG:*** -- "
	     "libmms: unexpected response: %02x (0x06 or 0x1A)\n", res);
      goto fail;
  }

  /* FIXME de-xine-ification */
/*   report_progress (stream, 60); */

  /* command 0x15 */
  lprintf("send command 0x15\n");
  {
    mms_buffer_t command_buffer;
    mms_buffer_init(&command_buffer, this->scmd_body);
    mms_buffer_put_32 (&command_buffer, 0x00000000);                  /* ?? */
    mms_buffer_put_32 (&command_buffer, 0x00800000);                  /* ?? */
    mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF);                  /* ?? */
    mms_buffer_put_32 (&command_buffer, 0x00000000);                  /* ?? */
    mms_buffer_put_32 (&command_buffer, 0x00000000);                  /* ?? */
    mms_buffer_put_32 (&command_buffer, 0x00000000);                  /* ?? */
    mms_buffer_put_32 (&command_buffer, 0x00000000);                  /* ?? */
    mms_buffer_put_32 (&command_buffer, 0x40AC2000);                  /* ?? */
    mms_buffer_put_32 (&command_buffer, ASF_HEADER_PACKET_ID_TYPE);   /* Header Packet ID type */
    mms_buffer_put_32 (&command_buffer, 0x00000000);                  /* ?? */
    if (!send_command (io, this, 0x15, 1, 0, command_buffer.pos)) {
  /* FIXME: de-xine-ification */
      lprintf ( "***LOG:*** -- "
	       "libmms: failed to send command 0x15\n");
      goto fail;
    }
  }
  
  if ((res = get_answer (io, this)) != 0x11) {
  /* FIXME: de-xine-ification */
    lprintf ( "***LOG:*** -- "
             "libmms: unexpected response: %02x (0x11)\n", res);
    goto fail;
  }

  this->num_stream_ids = 0;

  if (!get_asf_header (io, this))
    goto fail;

  interp_asf_header (this);

  /* FIXME de-xine-ification */
/*   report_progress (stream, 70); */

  if (!mms_choose_best_streams(io, this)) {
  /* FIXME: de-xine-ification */
    lprintf ( "***LOG:*** -- "
	     "libmms: mms_choose_best_streams failed");
    goto fail;
  }

  /* FIXME de-xine-ification */
/*   report_progress (stream, 80); */

  /* command 0x07 */
  {
    mms_buffer_t command_buffer;
    mms_buffer_init(&command_buffer, this->scmd_body);
    mms_buffer_put_32 (&command_buffer, 0x00000000);                  /* 64 byte float timestamp */
    mms_buffer_put_32 (&command_buffer, 0x00000000);                  
    mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF);                  /* ?? */
    mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF);                  /* first packet sequence */
    mms_buffer_put_8  (&command_buffer, 0xFF);                        /* max stream time limit (3 bytes) */
    mms_buffer_put_8  (&command_buffer, 0xFF);
    mms_buffer_put_8  (&command_buffer, 0xFF);
    mms_buffer_put_8  (&command_buffer, 0x00);                        /* stream time limit flag */
    mms_buffer_put_32 (&command_buffer, ASF_MEDIA_PACKET_ID_TYPE);    /* asf media packet id type */
    if (!send_command (io, this, 0x07, 1, 0x0001FFFF, command_buffer.pos)) {
  /* FIXME: de-xine-ification */
      lprintf ( "***LOG:*** -- "
	       "libmms: failed to send command 0x07\n");
      goto fail;
    }
  }

/*   report_progress (stream, 100); */

  lprintf("mms_connect: passed\n" );
 
  return this;

fail:
  if (this->s != -1)
    close (this->s);
  if (this->url)
    free(this->url);
  if (this->proto)
    free(this->proto);
  if (this->host)
    free(this->host);
  if (this->user)
    free(this->user);
  if (this->password)
    free(this->password);
  if (this->uri)
    free(this->uri);

  free (this);
  return NULL;
}

static int get_media_packet (mms_io_t *io, mms_t *this) {
  mms_packet_header_t header;
  off_t len;
  
  switch (get_packet_header (io, this, &header)) {
    case MMS_PACKET_ERR:
  /* FIXME: de-xine-ification */
      lprintf( "***LOG:*** -- "
	      "libmms: failed to read mms packet header\n");
      return 0;
      break;
    
    case MMS_PACKET_COMMAND:
      {
        int command;
        command = get_packet_command (io, this, header.packet_len);
      
        switch (command) {
          case 0x1e:
            {
              uint32_t error_code;

              /* Warning: sdp is incomplete. Do not stop if error_code==1 */
              error_code = LE_32(this->buf + CMD_HEADER_LEN);
              lprintf ("End of the current stream. Continue=%d\n", error_code);

              if (error_code == 0) {
                this->eos = 1;
                return 0;
              }
              
            }
            break;
  
          case 0x20:
            {
              lprintf ("new stream.\n");
              /* asf header */
              if (!get_asf_header (io, this)) {
		/* FIXME: de-xine-ification */
                lprintf ( "***LOG:*** -- "
			 "failed to read new ASF header\n");
                return 0;
              }

              interp_asf_header (this);

              if (!mms_choose_best_streams(io, this))
                return 0;

              /* send command 0x07 */
              /* TODO: ugly */
              /* command 0x07 */
              {
                mms_buffer_t command_buffer;
                mms_buffer_init(&command_buffer, this->scmd_body);
                mms_buffer_put_32 (&command_buffer, 0x00000000);                  /* 64 byte float timestamp */
                mms_buffer_put_32 (&command_buffer, 0x00000000);                  
                mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF);                  /* ?? */
                mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF);                  /* first packet sequence */
                mms_buffer_put_8  (&command_buffer, 0xFF);                        /* max stream time limit (3 bytes) */
                mms_buffer_put_8  (&command_buffer, 0xFF);
                mms_buffer_put_8  (&command_buffer, 0xFF);
                mms_buffer_put_8  (&command_buffer, 0x00);                        /* stream time limit flag */
                mms_buffer_put_32 (&command_buffer, ASF_MEDIA_PACKET_ID_TYPE);    /* asf media packet id type */
                if (!send_command (io, this, 0x07, 1, 0x0001FFFF, command_buffer.pos)) {
		  /* FIXME: de-xine-ification */
                  lprintf ( "***LOG:*** -- "
			   "libmms: failed to send command 0x07\n");
                  return 0;
                }
              }
              this->current_pos = 0;
            }
            break;

          case 0x1b:
            {
              if (!send_command (io, this, 0x1b, 0, 0, 0)) {
		/* FIXME: de-xine-ification */
                lprintf( "***LOG:*** -- "
			"libmms: failed to send command\n");
                return 0;
              }
            }
            break;
          
          case 0x05:
            break;
  
          default:
	    /* FIXME: de-xine-ification */
	    lprintf ( "***LOG:*** -- "
                     "unexpected mms command %02x\n", command);
        }
        this->buf_size = 0;
      }
      break;

    case MMS_PACKET_ASF_HEADER:
      /* FIXME: de-xine-ification */
      lprintf( "***LOG:*** -- "
	      "libmms: unexpected asf header packet\n");
      this->buf_size = 0;
      break;

    case MMS_PACKET_ASF_PACKET:
      {
        /* media packet */

	/* FIXME: probably needs some more sophisticated logic, but
	   until we do seeking, this should work */
	if(this->need_discont)
	{
	  this->need_discont = 0;
	  this->start_packet_seq = header.packet_seq;
	}
	
        lprintf ("asf media packet detected, packet_len=%d, packet_seq=%d\n",
                 header.packet_len, header.packet_seq);
        if (header.packet_len > this->asf_packet_len) {
	  /* FIXME: de-xine-ification */
	  lprintf ( "***LOG:*** -- "
                   "libmms: invalid asf packet len: %d bytes\n", header.packet_len);
          return 0;
        }
    
        /* simulate a seek */
        this->current_pos = (off_t)this->asf_header_len +
	  ((off_t)header.packet_seq - this->start_packet_seq) * (off_t)this->asf_packet_len;

        len = io_read(io,  this->s, this->buf, header.packet_len);
        if (len != header.packet_len) {
	  /* FIXME: de-xine-ification */
          lprintf ( "***LOG:*** -- "
		   "libmms: read failed\n");
          return 0;
        }

        /* explicit padding with 0 */
        lprintf("padding: %d bytes\n", this->asf_packet_len - header.packet_len);
	{
	  char *base  = (char *)(this->buf);
	  char *start = base + header.packet_len;
	  char *end   = start + this->asf_packet_len - header.packet_len;
	  if ((start > base) && (start < (base+BUF_SIZE-1)) &&
	      (start < end)  && (end < (base+BUF_SIZE-1))) {
	    memset(this->buf + header.packet_len, 0, this->asf_packet_len - header.packet_len);
 	  }
	  if (this->asf_packet_len > BUF_SIZE) {
            this->buf_size = BUF_SIZE;
          } else {
            this->buf_size = this->asf_packet_len;
          }
	}
      }
      break;
  }
  
  lprintf ("get media packet succ\n");

  return 1;
}


int mms_peek_header (mms_t *this, char *data, int maxsize) {

  int len;

  len = (this->asf_header_len < maxsize) ? this->asf_header_len : maxsize;

  memcpy(data, this->asf_header, len);
  return len;
}

int mms_read (mms_io_t *io, mms_t *this, char *data, int len) {
  int total;

  total = 0;
  while (total < len && !this->eos) {

    if (this->asf_header_read < this->asf_header_len) {
      int n, bytes_left;

      bytes_left = this->asf_header_len - this->asf_header_read ;

      if ((len - total) < bytes_left)
        n = len-total;
      else
        n = bytes_left;

      memcpy (&data[total], &this->asf_header[this->asf_header_read], n);

      this->asf_header_read += n;
      total += n;
      this->current_pos += n;
    } else {

      int n, bytes_left;

      bytes_left = this->buf_size - this->buf_read;
      if (bytes_left == 0) {
        this->buf_size = this->buf_read = 0;
        if (!get_media_packet (io, this)) {
  /* FIXME: de-xine-ification */
          lprintf ( "***LOG:*** -- "
		   "libmms: get_media_packet failed\n");
          return total;
        }
        bytes_left = this->buf_size;
      }

      if ((len - total) < bytes_left)
        n = len - total;
      else
        n = bytes_left;

      memcpy (&data[total], &this->buf[this->buf_read], n);

      this->buf_read += n;
      total += n;
      this->current_pos += n;
    }
  }
  return total;
}


void mms_close (mms_t *this) {

  if (this->s != -1)
    close (this->s);
  if (this->url)
    free(this->url);
  if (this->proto)
    free(this->proto);
  if (this->host)
    free(this->host);
  if (this->user)
    free(this->user);
  if (this->password)
    free(this->password);
  if (this->uri)
    free(this->uri);

  free (this);
}

uint32_t mms_get_length (mms_t *this) {
  return this->file_len;
}

off_t mms_get_current_pos (mms_t *this) {
  return this->current_pos;
}