[rtmpdump] r61 - Makefile amf.c amf.h hand2.c rtmp2.c rtmp2.h rtmpd2.c

hyc subversion at mplayerhq.hu
Wed Dec 16 06:31:18 CET 2009


Author: hyc
Date: Wed Dec 16 06:31:17 2009
New Revision: 61

Log:
Rewrite in C, still buggy

Added:
   amf.c
   amf.h
   hand2.c
   rtmp2.c
   rtmp2.h
   rtmpd2.c
Modified:
   Makefile

Modified: Makefile
==============================================================================
--- Makefile	Mon Dec 14 01:37:51 2009	(r60)
+++ Makefile	Wed Dec 16 06:31:17 2009	(r61)
@@ -45,6 +45,9 @@ streams: bytes.o log.o rtmp.o AMFObject.
 rtmpdump: bytes.o log.o rtmp.o AMFObject.o rtmppacket.o rtmpdump.o parseurl.o dh.o handshake.o
 	$(CXX) $(LDFLAGS) $^ -o $@$(EXT) $(LIBS)
 
+rtmpd2: bytes.o log.o rtmp2.o dh.o amf.o rtmpd2.o parseurl.o
+	$(CC) $(LDFLAGS) $^ -o $@$(EXT) $(LIBS)
+
 bytes.o: bytes.c bytes.h Makefile
 log.o: log.c log.h Makefile
 rtmp.o: rtmp.cpp rtmp.h log.h AMFObject.h Makefile
@@ -56,3 +59,6 @@ streams.o: streams.cpp rtmp.h log.h Make
 dh.o: dh.c dh.h log.h Makefile
 handshake.o: handshake.cpp rtmp.h log.h Makefile
 
+rtmp2.o: rtmp2.c rtmp2.h hand2.c log.h amf.h Makefile
+amf.o: amf.c amf.h Makefile
+rtmpd2.o: rtmpd2.c rtmp2.h log.h amf.h Makefile

Added: amf.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ amf.c	Wed Dec 16 06:31:17 2009	(r61)
@@ -0,0 +1,1023 @@
+/*
+ *      Copyright (C) 2009 Howard Chu
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program 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 RTMPDump; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#include "amf.h"
+#include "log.h"
+#include "bytes.h"
+
+static const AMFObjectProperty AMFProp_Invalid = { {0, 0}, AMF_INVALID };
+static const AVal AV_empty = { 0, 0 };
+
+/* Data is Big-Endian */
+unsigned short
+AMF_DecodeInt16(const char *data)
+{
+  unsigned char *c = (unsigned char *) data;
+  unsigned short val;
+  val = (c[0] << 8) | c[1];
+  return val;
+}
+
+unsigned int
+AMF_DecodeInt24(const char *data)
+{
+  unsigned char *c = (unsigned char *) data;
+  unsigned int val;
+  val = (c[0] << 16) | (c[1] << 8) | c[2];
+  return val;
+}
+
+unsigned int
+AMF_DecodeInt32(const char *data)
+{
+  unsigned char *c = (unsigned char *) data;
+  unsigned int val;
+  val = (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3];
+  return val;
+}
+
+void
+AMF_DecodeString(const char *data, AVal *bv)
+{
+  bv->av_len = AMF_DecodeInt16(data);
+  bv->av_val = (bv->av_len > 0) ? (char *) data + 2 : NULL;
+}
+
+bool
+AMF_DecodeBoolean(const char *data)
+{
+  return *data != 0;
+}
+
+int
+AMF_EncodeInt16(char *output, short nVal)
+{
+  output[1] = nVal & 0xff;
+  output[0] = nVal >> 8;
+  return 2;
+}
+
+int
+AMF_EncodeInt24(char *output, int nVal)
+{
+  output[2] = nVal & 0xff;
+  output[1] = nVal >> 8;
+  output[0] = nVal >> 16;
+  return 3;
+}
+
+int
+AMF_EncodeInt32(char *output, int nVal)
+{
+  output[3] = nVal & 0xff;
+  output[2] = nVal >> 8;
+  output[1] = nVal >> 16;
+  output[0] = nVal >> 24;
+  return 4;
+}
+
+int
+AMF_EncodeString(char *output, const AVal *bv)
+{
+  char *buf = output;
+  *buf++ = AMF_STRING;
+
+  buf += AMF_EncodeInt16(buf, bv->av_len);
+
+  memcpy(buf, bv->av_val, bv->av_len);
+  buf += bv->av_len;
+
+  return buf - output;
+}
+
+int
+AMF_EncodeNumber(char *output, double dVal)
+{
+  char *buf = output;
+  *buf++ = AMF_NUMBER;		// type: Number
+
+  WriteNumber(buf, dVal);
+  buf += 8;
+
+  return 9;
+}
+
+int
+AMF_EncodeBoolean(char *output, bool bVal)
+{
+  char *buf = output;
+
+  *buf++ = AMF_BOOLEAN;
+
+  *buf = bVal ? 0x01 : 0x00;
+
+  return 2;
+}
+
+void
+AMFProp_GetName(AMFObjectProperty *prop, AVal *name)
+{
+  *name = prop->p_name;
+}
+
+void
+AMFProp_SetName(AMFObjectProperty *prop, AVal *name)
+{
+  prop->p_name = *name;
+}
+
+AMFDataType
+AMFProp_GetType(AMFObjectProperty *prop)
+{
+  return prop->p_type;
+}
+
+double
+AMFProp_GetNumber(AMFObjectProperty *prop)
+{
+  return prop->p_vu.p_number;
+}
+
+int
+AMFProp_GetBoolean(AMFObjectProperty *prop)
+{
+  return prop->p_vu.p_number != 0;
+}
+
+void
+AMFProp_GetString(AMFObjectProperty *prop, AVal *str)
+{
+  *str = prop->p_vu.p_aval;
+}
+
+void
+AMFProp_GetObject(AMFObjectProperty *prop, AMFObject *obj)
+{
+  *obj = prop->p_vu.p_object;
+}
+
+int
+AMFProp_IsValid(AMFObjectProperty *prop)
+{
+  return prop->p_type != AMF_INVALID;
+}
+
+int
+AMFProp_Encode(AMFObjectProperty *prop, char *pBuffer, int nSize)
+{
+  int nBytes = 0;
+
+  if (prop->p_type == AMF_INVALID)
+    return -1;
+
+  if (prop->p_type != AMF_NULL && nSize < prop->p_name.av_len + 2 + 1)
+    return -1;
+
+  if (prop->p_type != AMF_NULL && prop->p_name.av_len)
+    {
+      *pBuffer++ = prop->p_name.av_len >> 8;
+      *pBuffer++ = prop->p_name.av_len & 0xff;
+      memcpy(pBuffer, prop->p_name.av_val, prop->p_name.av_len);
+      pBuffer += prop->p_name.av_len;
+      nBytes += prop->p_name.av_len + 2;
+      nSize -= nBytes;
+    }
+
+  switch (prop->p_type)
+    {
+    case AMF_NUMBER:
+      if (nSize < 9)
+	return -1;
+      nBytes += AMF_EncodeNumber(pBuffer, prop->p_vu.p_number);
+      break;
+
+    case AMF_BOOLEAN:
+      if (nSize < 2)
+	return -1;
+      nBytes += AMF_EncodeBoolean(pBuffer, prop->p_vu.p_number != 0);
+      break;
+
+    case AMF_STRING:
+      if (nSize < prop->p_vu.p_aval.av_len + (int) sizeof(short))
+	return -1;
+      nBytes += AMF_EncodeString(pBuffer, &prop->p_vu.p_aval);
+      break;
+
+    case AMF_NULL:
+      if (nSize < 1)
+	return -1;
+      *pBuffer = AMF_NULL;
+      nBytes += 1;
+      break;
+
+    case AMF_OBJECT:
+      {
+	int nRes = AMF_Encode(&prop->p_vu.p_object, pBuffer, nSize);
+	if (nRes == -1)
+	  return -1;
+
+	nBytes += nRes;
+	break;
+      }
+    default:
+      Log(LOGERROR, "%s, invalid type. %d", __FUNCTION__, prop->p_type);
+      return -1;
+    };
+
+  return nBytes;
+}
+
+#define AMF3_INTEGER_MAX	268435455
+#define AMF3_INTEGER_MIN	-268435456
+
+int
+AMF3ReadInteger(const char *data, int32_t *valp)
+{
+  int i = 0;
+  int32_t val = 0;
+
+  while (i <= 2)
+    {				/* handle first 3 bytes */
+      if (data[i] & 0x80)
+	{			// byte used
+	  val <<= 7;		// shift up
+	  val |= (data[i] & 0x7f);	// add bits
+	  i++;
+	}
+      else
+	{
+	  break;
+	}
+    }
+
+  if (i > 2)
+    {				// use 4th byte, all 8bits
+      val <<= 8;
+      val |= data[3];
+
+      // range check
+      if (val > AMF3_INTEGER_MAX)
+	val -= (1 << 29);
+    }
+  else
+    {				// use 7bits of last unparsed byte (0xxxxxxx)
+      val <<= 7;
+      val |= data[i];
+    }
+
+  *valp = val;
+
+  return i > 2 ? 4 : i + 1;
+}
+
+int
+AMF3ReadString(const char *data, AVal *str)
+{
+  assert(str != 0);
+
+  int32_t ref = 0;
+  int len = AMF3ReadInteger(data, &ref);
+  data += len;
+
+  if ((ref & 0x1) == 0)
+    {				/* reference: 0xxx */
+      uint32_t refIndex = (ref >> 1);
+      Log(LOGDEBUG,
+	  "%s, string reference, index: %d, not supported, ignoring!",
+	  refIndex);
+      return len;
+    }
+  else
+    {
+      uint32_t nSize = (ref >> 1);
+
+      str->av_val = (char *) data;
+      str->av_len = nSize;
+
+      return len + nSize;
+    }
+  return len;
+}
+
+int
+AMF3Prop_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize,
+		int bDecodeName)
+{
+  int nOriginalSize = nSize;
+  AMF3DataType type;
+
+  prop->p_name.av_len = 0;
+  prop->p_name.av_val = NULL;
+
+  if (nSize == 0 || !pBuffer)
+    {
+      Log(LOGDEBUG, "empty buffer/no buffer pointer!");
+      return -1;
+    }
+
+  /* decode name */
+  if (bDecodeName)
+    {
+      AVal name;
+      int nRes = AMF3ReadString(pBuffer, &name);
+
+      if (name.av_len <= 0)
+	return nRes;
+
+      prop->p_name = name;
+      pBuffer += nRes;
+      nSize -= nRes;
+    }
+
+  /* decode */
+  type = *pBuffer++;
+  nSize--;
+
+  switch (type)
+    {
+    case AMF3_UNDEFINED:
+    case AMF3_NULL:
+      prop->p_type = AMF_NULL;
+      break;
+    case AMF3_FALSE:
+      prop->p_type = AMF_BOOLEAN;
+      prop->p_vu.p_number = 0.0;
+      break;
+    case AMF3_TRUE:
+      prop->p_type = AMF_BOOLEAN;
+      prop->p_vu.p_number = 1.0;
+      break;
+    case AMF3_INTEGER:
+      {
+	int32_t res = 0;
+	int len = AMF3ReadInteger(pBuffer, &res);
+	prop->p_vu.p_number = (double) res;
+	prop->p_type = AMF_NUMBER;
+	nSize -= len;
+	break;
+      }
+    case AMF3_DOUBLE:
+      if (nSize < 8)
+	return -1;
+      prop->p_vu.p_number = ReadNumber(pBuffer);
+      prop->p_type = AMF_NUMBER;
+      nSize -= 8;
+      break;
+    case AMF3_STRING:
+    case AMF3_XML_DOC:
+    case AMF3_XML:
+      {
+	int len = AMF3ReadString(pBuffer, &prop->p_vu.p_aval);
+	prop->p_type = AMF_STRING;
+	nSize -= len;
+	break;
+      }
+    case AMF3_DATE:
+      {
+	int32_t res = 0;
+	int len = AMF3ReadInteger(pBuffer, &res);
+
+	nSize -= len;
+	pBuffer += len;
+
+	if ((res & 0x1) == 0)
+	  {			/* reference */
+	    uint32_t nIndex = (res >> 1);
+	    Log(LOGDEBUG, "AMF3_DATE reference: %d, not supported!", nIndex);
+	  }
+	else
+	  {
+	    if (nSize < 8)
+	      return -1;
+
+	    prop->p_vu.p_number = ReadNumber(pBuffer);
+	    nSize -= 8;
+	    prop->p_type = AMF_NUMBER;
+	  }
+	break;
+      }
+    case AMF3_OBJECT:
+      {
+	int nRes = AMF3_Decode(&prop->p_vu.p_object, pBuffer, nSize, true);
+	if (nRes == -1)
+	  return -1;
+	nSize -= nRes;
+	prop->p_type = AMF_OBJECT;
+	break;
+      }
+    case AMF3_ARRAY:
+    case AMF3_BYTE_ARRAY:
+    default:
+      Log(LOGDEBUG, "%s - AMF3 unknown/unsupported datatype 0x%02x, @0x%08X",
+	  __FUNCTION__, (unsigned char) (*pBuffer), pBuffer);
+      return -1;
+    }
+
+  return nOriginalSize - nSize;
+}
+
+int
+AMFProp_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize,
+	       int bDecodeName)
+{
+  int nOriginalSize = nSize;
+
+  prop->p_name.av_len = 0;
+  prop->p_name.av_val = NULL;
+
+  if (nSize == 0 || !pBuffer)
+    {
+      Log(LOGDEBUG, "%s: Empty buffer/no buffer pointer!", __FUNCTION__);
+      return -1;
+    }
+
+  if (bDecodeName && nSize < 4)
+    {				/* at least name (length + at least 1 byte) and 1 byte of data */
+      Log(LOGDEBUG,
+	  "%s: Not enough data for decoding with name, less then 4 bytes!",
+	  __FUNCTION__);
+      return -1;
+    }
+
+  if (bDecodeName)
+    {
+      unsigned short nNameSize = AMF_DecodeInt16(pBuffer);
+      if (nNameSize > nSize - 2)
+	{
+	  Log(LOGDEBUG,
+	      "%s: Name size out of range: namesize (%d) > len (%d) - 2",
+	      __FUNCTION__, nNameSize, nSize);
+	  return -1;
+	}
+
+      AMF_DecodeString(pBuffer, &prop->p_name);
+      nSize -= 2 + nNameSize;
+      pBuffer += 2 + nNameSize;
+    }
+
+  if (nSize == 0)
+    {
+      return -1;
+    }
+
+  nSize--;
+
+  prop->p_type = *pBuffer++;
+  switch (prop->p_type)
+    {
+    case AMF_NUMBER:
+      if (nSize < 8)
+	return -1;
+      prop->p_vu.p_number = ReadNumber(pBuffer);
+      nSize -= 8;
+      break;
+    case AMF_BOOLEAN:
+      if (nSize < 1)
+	return -1;
+      prop->p_vu.p_number = (double) AMF_DecodeBoolean(pBuffer);
+      nSize--;
+      break;
+    case AMF_STRING:
+      {
+	unsigned short nStringSize = AMF_DecodeInt16(pBuffer);
+
+	if (nSize < (long) nStringSize + 2)
+	  return -1;
+	AMF_DecodeString(pBuffer, &prop->p_vu.p_aval);
+	nSize -= (2 + nStringSize);
+	break;
+      }
+    case AMF_OBJECT:
+      {
+	int nRes = AMF_Decode(&prop->p_vu.p_object, pBuffer, nSize, true);
+	if (nRes == -1)
+	  return -1;
+	nSize -= nRes;
+	break;
+      }
+    case AMF_MOVIECLIP:
+      {
+	Log(LOGERROR, "AMF_MOVIECLIP reserved!");
+	return -1;
+	break;
+      }
+    case AMF_NULL:
+    case AMF_UNDEFINED:
+    case AMF_UNSUPPORTED:
+      prop->p_type = AMF_NULL;
+      break;
+    case AMF_REFERENCE:
+      {
+	Log(LOGERROR, "AMF_REFERENCE not supported!");
+	return -1;
+	break;
+      }
+    case AMF_ECMA_ARRAY:
+      {
+	nSize -= 4;
+
+	/* next comes the rest, mixed array has a final 0x000009 mark and names, so its an object */
+	int nRes = AMF_Decode(&prop->p_vu.p_object, pBuffer + 4, nSize, true);
+	if (nRes == -1)
+	  return -1;
+	nSize -= nRes;
+	prop->p_type = AMF_OBJECT;
+	break;
+      }
+    case AMF_OBJECT_END:
+      {
+	return -1;
+	break;
+      }
+    case AMF_STRICT_ARRAY:
+      {
+	unsigned int nArrayLen = AMF_DecodeInt32(pBuffer);
+	nSize -= 4;
+
+	int nRes = AMF_DecodeArray(&prop->p_vu.p_object, pBuffer + 4, nSize,
+				   nArrayLen, false);
+	if (nRes == -1)
+	  return -1;
+	nSize -= nRes;
+	prop->p_type = AMF_OBJECT;
+	break;
+      }
+    case AMF_DATE:
+      {
+	Log(LOGDEBUG, "AMF_DATE");
+
+	if (nSize < 10)
+	  return -1;
+
+	prop->p_vu.p_number = ReadNumber(pBuffer);
+	prop->p_UTCoffset = AMF_DecodeInt16(pBuffer + 8);
+
+	nSize -= 10;
+	break;
+      }
+    case AMF_LONG_STRING:
+      {
+	unsigned int nStringSize = AMF_DecodeInt32(pBuffer);
+	if (nSize < (long) nStringSize + 4)
+	  return -1;
+	AMF_DecodeString(pBuffer, &prop->p_vu.p_aval);
+	nSize -= (4 + nStringSize);
+	prop->p_type = AMF_STRING;
+	break;
+      }
+    case AMF_RECORDSET:
+      {
+	Log(LOGERROR, "AMF_RECORDSET reserved!");
+	return -1;
+	break;
+      }
+    case AMF_XML_DOC:
+      {
+	Log(LOGERROR, "AMF_XML_DOC not supported!");
+	return -1;
+	break;
+      }
+    case AMF_TYPED_OBJECT:
+      {
+	Log(LOGERROR, "AMF_TYPED_OBJECT not supported!");
+	return -1;
+	break;
+      }
+    case AMF_AVMPLUS:
+      {
+	int nRes = AMF3_Decode(&prop->p_vu.p_object, pBuffer, nSize, true);
+	if (nRes == -1)
+	  return -1;
+	nSize -= nRes;
+	prop->p_type = AMF_OBJECT;
+	break;
+      }
+    default:
+      Log(LOGDEBUG, "%s - unknown datatype 0x%02x, @0x%08X", __FUNCTION__,
+	  prop->p_type, pBuffer - 1);
+      return -1;
+    }
+
+  return nOriginalSize - nSize;
+}
+
+void
+AMFProp_Dump(AMFObjectProperty *prop)
+{
+  char strRes[256];
+  char str[256];
+  AVal name;
+
+  if (prop->p_type == AMF_INVALID)
+    {
+      Log(LOGDEBUG, "Property: INVALID");
+      return;
+    }
+
+  if (prop->p_type == AMF_NULL)
+    {
+      Log(LOGDEBUG, "Property: NULL");
+      return;
+    }
+
+  if (prop->p_name.av_len) {
+    name = prop->p_name;
+  } else {
+    name.av_val = "no-name.";
+    name.av_len = sizeof("no-name.")-1;
+  }
+  if (name.av_len > 25)
+    name.av_len = 25;
+
+  snprintf(strRes, 255, "Name: %.*s, ", name.av_len, name.av_val);
+
+  if (prop->p_type == AMF_OBJECT)
+    {
+      Log(LOGDEBUG, "Property: <%sOBJECT>", strRes);
+      AMF_Dump(&prop->p_vu.p_object);
+      return;
+    }
+
+  switch (prop->p_type)
+    {
+    case AMF_NUMBER:
+      snprintf(str, 255, "NUMBER:\t%.2f", prop->p_vu.p_number);
+      break;
+    case AMF_BOOLEAN:
+      snprintf(str, 255, "BOOLEAN:\t%s",
+	       prop->p_vu.p_number != 0.0 ? "TRUE" : "FALSE");
+      break;
+    case AMF_STRING:
+      snprintf(str, 255, "STRING:\t%.*s", prop->p_vu.p_aval.av_len, prop->p_vu.p_aval.av_val);
+      break;
+    case AMF_DATE:
+      snprintf(str, 255, "DATE:\ttimestamp: %.2f, UTC offset: %d",
+	       prop->p_vu.p_number, prop->p_UTCoffset);
+      break;
+    default:
+      snprintf(str, 255, "INVALID TYPE 0x%02x", (unsigned char) prop->p_type);
+    }
+
+  Log(LOGDEBUG, "Property: <%s%s>", strRes, str);
+}
+
+void
+AMFProp_Reset(AMFObjectProperty *prop)
+{
+  if (prop->p_type == AMF_OBJECT)
+    AMF_Reset(&prop->p_vu.p_object);
+  else
+    {
+      prop->p_vu.p_aval.av_len = 0;
+      prop->p_vu.p_aval.av_val = NULL;
+    }
+  prop->p_type = AMF_INVALID;
+}
+
+/* AMFObject */
+
+int
+AMF_Encode(AMFObject *obj, char *pBuffer, int nSize)
+{
+  int nOriginalSize = nSize;
+  int i;
+
+  if (nSize < 4)
+    return -1;
+
+  *pBuffer = AMF_OBJECT;
+
+  for (i = 0; i < obj->o_num; i++)
+    {
+      int nRes = AMFProp_Encode(&obj->o_props[i], pBuffer, nSize);
+      if (nRes == -1)
+	{
+	  Log(LOGERROR, "AMF_Encode - failed to encode property in index %d",
+	      i);
+	}
+      else
+	{
+	  nSize -= nRes;
+	  pBuffer += nRes;
+	}
+    }
+
+  if (nSize < 3)
+    return -1;			// no room for the end marker
+
+  AMF_EncodeInt24(pBuffer, AMF_OBJECT_END);
+  nSize -= 3;
+
+  return nOriginalSize - nSize;
+}
+
+int
+AMF_DecodeArray(AMFObject *obj, const char *pBuffer, int nSize,
+		int nArrayLen, bool bDecodeName)
+{
+  int nOriginalSize = nSize;
+  bool bError = false;
+
+  obj->o_num = 0;
+  obj->o_props = NULL;
+  while (nArrayLen > 0)
+    {
+      nArrayLen--;
+
+      AMFObjectProperty prop;
+      int nRes = AMFProp_Decode(&prop, pBuffer, nSize, bDecodeName);
+      if (nRes == -1)
+	bError = true;
+      else
+	{
+	  nSize -= nRes;
+	  pBuffer += nRes;
+	  AMF_AddProp(obj, &prop);
+	}
+    }
+  if (bError)
+    return -1;
+
+  return nOriginalSize - nSize;
+}
+
+int
+AMF3_Decode(AMFObject *obj, const char *pBuffer, int nSize, bool bAMFData)
+{
+  int nOriginalSize = nSize;
+  int32_t ref;
+  int len;
+
+  obj->o_num = 0;
+  obj->o_props = NULL;
+  if (bAMFData)
+    {
+      if (*pBuffer != AMF3_OBJECT)
+	Log(LOGERROR,
+	    "AMF3 Object encapsulated in AMF stream does not start with AMF3_OBJECT!");
+      pBuffer++;
+      nSize--;
+    }
+
+  ref = 0;
+  len = AMF3ReadInteger(pBuffer, &ref);
+  pBuffer += len;
+  nSize -= len;
+
+  if ((ref & 1) == 0)
+    {				/* object reference, 0xxx */
+      uint32_t objectIndex = (ref >> 1);
+
+      Log(LOGDEBUG, "Object reference, index: %d", objectIndex);
+    }
+  else				/* object instance */
+    {
+      int32_t classRef = (ref >> 1);
+
+      AMF3ClassDef cd = { {0, 0}
+      };
+      AMFObjectProperty prop;
+
+      if ((classRef & 0x1) == 0)
+	{			/* class reference */
+	  uint32_t classIndex = (classRef >> 1);
+	  Log(LOGDEBUG, "Class reference: %d", classIndex);
+	}
+      else
+	{
+	  int32_t classExtRef = (classRef >> 1);
+	  int i;
+
+	  cd.cd_externalizable = (classExtRef & 0x1) == 1;
+	  cd.cd_dynamic = ((classExtRef >> 1) & 0x1) == 1;
+
+	  cd.cd_num = classExtRef >> 2;
+
+	  // class name
+
+	  len = AMF3ReadString(pBuffer, &cd.cd_name);
+	  nSize -= len;
+	  pBuffer += len;
+
+	  //std::string str = className;
+
+	  Log(LOGDEBUG,
+	      "Class name: %s, externalizable: %d, dynamic: %d, classMembers: %d",
+	      cd.cd_name.av_val, cd.cd_externalizable, cd.cd_dynamic,
+	      cd.cd_num);
+
+	  for (i = 0; i < cd.cd_num; i++)
+	    {
+	      AVal memberName;
+	      len = AMF3ReadString(pBuffer, &memberName);
+	      Log(LOGDEBUG, "Member: %s", memberName.av_val);
+	      AMF3CD_AddProp(&cd, &memberName);
+	      nSize -= len;
+	      pBuffer += len;
+	    }
+	}
+
+      /* add as referencable object */
+
+      if (cd.cd_externalizable)
+	{
+	  int nRes;
+	  AVal name = AVC("DEFAULT_ATTRIBUTE");
+
+	  Log(LOGDEBUG, "Externalizable, TODO check");
+
+	  nRes = AMF3Prop_Decode(&prop, pBuffer, nSize, false);
+	  if (nRes == -1)
+	    Log(LOGDEBUG, "%s, failed to decode AMF3 property!",
+		__FUNCTION__);
+	  else
+	    {
+	      nSize -= nRes;
+	      pBuffer += nRes;
+	    }
+
+	  AMFProp_SetName(&prop, &name);
+	  AMF_AddProp(obj, &prop);
+	}
+      else
+	{
+	  int nRes, i;
+	  for (i = 0; i < cd.cd_num; i++)	/* non-dynamic */
+	    {
+	      nRes = AMF3Prop_Decode(&prop, pBuffer, nSize, false);
+	      if (nRes == -1)
+		Log(LOGDEBUG, "%s, failed to decode AMF3 property!",
+		    __FUNCTION__);
+
+	      AMFProp_SetName(&prop, AMF3CD_GetProp(&cd, i));
+	      AMF_AddProp(obj, &prop);
+
+	      pBuffer += nRes;
+	      nSize -= nRes;
+	    }
+	  if (cd.cd_dynamic)
+	    {
+	      int len = 0;
+
+	      do
+		{
+		  nRes = AMF3Prop_Decode(&prop, pBuffer, nSize, true);
+		  AMF_AddProp(obj, &prop);
+
+		  pBuffer += nRes;
+		  nSize -= nRes;
+
+		  len = prop.p_name.av_len;
+		}
+	      while (len > 0);
+	    }
+	}
+      Log(LOGDEBUG, "class object!");
+    }
+  return nOriginalSize - nSize;
+}
+
+int
+AMF_Decode(AMFObject *obj, const char *pBuffer, int nSize, bool bDecodeName)
+{
+  int nOriginalSize = nSize;
+  bool bError = false;		/* if there is an error while decoding - try to at least find the end mark AMF_OBJECT_END */
+
+  obj->o_num = 0;
+  obj->o_props = NULL;
+  while (nSize >= 3)
+    {
+      AMFObjectProperty prop;
+      int nRes;
+
+      if (AMF_DecodeInt24(pBuffer) == AMF_OBJECT_END)
+	{
+	  nSize -= 3;
+	  bError = false;
+	  break;
+	}
+
+      if (bError)
+	{
+	  Log(LOGERROR,
+	      "DECODING ERROR, IGNORING BYTES UNTIL NEXT KNOWN PATTERN!");
+	  nSize--;
+	  pBuffer++;
+	  continue;
+	}
+
+      nRes = AMFProp_Decode(&prop, pBuffer, nSize, bDecodeName);
+      if (nRes == -1)
+	bError = true;
+      else
+	{
+	  nSize -= nRes;
+	  pBuffer += nRes;
+	  AMF_AddProp(obj, &prop);
+	}
+    }
+
+  if (bError)
+    return -1;
+
+  return nOriginalSize - nSize;
+}
+
+void
+AMF_AddProp(AMFObject *obj, const AMFObjectProperty *prop)
+{
+  if (!(obj->o_num & 0x0f))
+    obj->o_props =
+      realloc(obj->o_props, (obj->o_num + 16) * sizeof(AMFObjectProperty));
+  obj->o_props[obj->o_num++] = *prop;
+}
+
+int
+AMF_CountProp(AMFObject *obj)
+{
+  return obj->o_num;
+}
+
+AMFObjectProperty *
+AMF_GetProp(AMFObject *obj, const AVal *name, int nIndex)
+{
+  if (nIndex >= 0)
+    {
+      if (nIndex <= obj->o_num)
+	return &obj->o_props[nIndex];
+    }
+  else
+    {
+      int n;
+      for (n = 0; n < obj->o_num; n++)
+	{
+	  if (AVMATCH(&obj->o_props[n].p_name, name))
+	    return &obj->o_props[n];
+	}
+    }
+
+  return (AMFObjectProperty *) & AMFProp_Invalid;
+}
+
+void
+AMF_Dump(AMFObject *obj)
+{
+  int n;
+  for (n = 0; n < obj->o_num; n++)
+    {
+      AMFProp_Dump(&obj->o_props[n]);
+    }
+}
+
+void
+AMF_Reset(AMFObject *obj)
+{
+  int n;
+  for (n = 0; n < obj->o_num; n++)
+    {
+      AMFProp_Reset(&obj->o_props[n]);
+    }
+  free(obj->o_props);
+  obj->o_props = NULL;
+  obj->o_num = 0;
+}
+
+
+/* AMF3ClassDefinition */
+
+void
+AMF3CD_AddProp(AMF3ClassDef *cd, AVal *prop)
+{
+  if (!(cd->cd_num & 0x0f))
+    cd->cd_props = realloc(cd->cd_props, (cd->cd_num + 16) * sizeof(AVal));
+  cd->cd_props[cd->cd_num++] = *prop;
+}
+
+AVal *
+AMF3CD_GetProp(AMF3ClassDef *cd, int nIndex)
+{
+  if (nIndex >= cd->cd_num)
+    return (AVal *)&AV_empty;
+  return &cd->cd_props[nIndex];
+}

Added: amf.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ amf.h	Wed Dec 16 06:31:17 2009	(r61)
@@ -0,0 +1,150 @@
+#ifndef __AMF_H__
+#define __AMF_H__
+/*
+ *	Copyright (C) 2009 Howard Chu
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program 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 RTMPDump; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+  typedef enum
+  { AMF_NUMBER = 0, AMF_BOOLEAN, AMF_STRING, AMF_OBJECT,
+    AMF_MOVIECLIP,		/* reserved, not used */
+    AMF_NULL, AMF_UNDEFINED, AMF_REFERENCE, AMF_ECMA_ARRAY, AMF_OBJECT_END,
+    AMF_STRICT_ARRAY, AMF_DATE, AMF_LONG_STRING, AMF_UNSUPPORTED,
+    AMF_RECORDSET,		/* reserved, not used */
+    AMF_XML_DOC, AMF_TYPED_OBJECT,
+    AMF_AVMPLUS,		/* switch to AMF3 */
+    AMF_INVALID = 0xff
+  } AMFDataType;
+
+  typedef enum
+  { AMF3_UNDEFINED = 0, AMF3_NULL, AMF3_FALSE, AMF3_TRUE,
+    AMF3_INTEGER, AMF3_DOUBLE, AMF3_STRING, AMF3_XML_DOC, AMF3_DATE,
+    AMF3_ARRAY, AMF3_OBJECT, AMF3_XML, AMF3_BYTE_ARRAY
+  } AMF3DataType;
+
+  typedef struct AVal
+  {
+    char *av_val;
+    int av_len;
+  } AVal;
+#define AVC(str)	{str,sizeof(str)-1}
+#define AVMATCH(a1,a2)	((a1)->av_len == (a2)->av_len && !memcmp((a1)->av_val,(a2)->av_val,(a1)->av_len))
+
+#undef bool
+#undef true
+#undef false
+#define	bool	int
+#define true	1
+#define	false	0
+
+  struct AMFObjectProperty;
+
+  typedef struct AMFObject
+  {
+    int o_num;
+    struct AMFObjectProperty *o_props;
+  } AMFObject;
+
+  typedef struct AMFObjectProperty
+  {
+    AVal p_name;
+    AMFDataType p_type;
+    union
+    {
+      double p_number;
+      AVal p_aval;
+      AMFObject p_object;
+    } p_vu;
+    int16_t p_UTCoffset;
+  } AMFObjectProperty;
+
+  int AMF_EncodeString (char *output, const AVal * str);
+  int AMF_EncodeNumber (char *output, double dVal);
+  int AMF_EncodeInt16 (char *output, short nVal);
+  int AMF_EncodeInt24 (char *output, int nVal);
+  int AMF_EncodeInt32 (char *output, int nVal);
+  int AMF_EncodeBoolean (char *output, bool bVal);
+
+  unsigned short AMF_DecodeInt16 (const char *data);
+  unsigned int AMF_DecodeInt24 (const char *data);
+  unsigned int AMF_DecodeInt32 (const char *data);
+  void AMF_DecodeString (const char *data, AVal * str);
+  bool AMF_DecodeBoolean (const char *data);
+
+  int AMF_Encode (AMFObject * obj, char *pBuffer, int nSize);
+  int AMF_Decode (AMFObject * obj, const char *pBuffer, int nSize,
+		  bool bDecodeName);
+  int AMF_DecodeArray (AMFObject * obj, const char *pBuffer, int nSize,
+		       int nArrayLen, bool bDecodeName);
+  int AMF3_Decode (AMFObject * obj, const char *pBuffer, int nSize,
+		   bool bDecodeName);
+  void AMF_Dump (AMFObject * obj);
+  void AMF_Reset (AMFObject * obj);
+
+  void AMF_AddProp (AMFObject * obj, const AMFObjectProperty * prop);
+  int AMF_CountProp (AMFObject * obj);
+  AMFObjectProperty *AMF_GetProp (AMFObject * obj, const AVal *name, int nIndex);
+
+  AMFDataType AMFProp_GetType (AMFObjectProperty * prop);
+  void AMFProp_SetNumber (AMFObjectProperty * prop, double dval);
+  void AMFProp_SetBoolean (AMFObjectProperty * prop, bool bflag);
+  void AMFProp_SetString (AMFObjectProperty * prop, AVal * str);
+  void AMFProp_SetObject (AMFObjectProperty * prop, AMFObject * obj);
+
+  void AMFProp_GetName (AMFObjectProperty * prop, AVal * name);
+  void AMFProp_SetName (AMFObjectProperty * prop, AVal * name);
+  double AMFProp_GetNumber (AMFObjectProperty * prop);
+  bool AMFProp_GetBoolean (AMFObjectProperty * prop);
+  void AMFProp_GetString (AMFObjectProperty * prop, AVal * str);
+  void AMFProp_GetObject (AMFObjectProperty * prop, AMFObject * obj);
+
+  bool AMFProp_IsValid (AMFObjectProperty * prop);
+
+  int AMFProp_Encode (AMFObjectProperty * prop, char *pBuffer, int nSize);
+  int AMF3Prop_Decode (AMFObjectProperty * prop, const char *pBuffer,
+		       int nSize, bool bDecodeName);
+  int AMFProp_Decode (AMFObjectProperty * prop, const char *pBuffer,
+		      int nSize, bool bDecodeName);
+
+  void AMFProp_Dump (AMFObjectProperty * prop);
+  void AMFProp_Reset (AMFObjectProperty * prop);
+
+  typedef struct AMF3ClassDef
+  {
+    AVal cd_name;
+    char cd_externalizable;
+    char cd_dynamic;
+    int cd_num;
+    AVal *cd_props;
+  } AMF3ClassDef;
+
+  void AMF3CD_AddProp (AMF3ClassDef * cd, AVal * prop);
+  AVal *AMF3CD_GetProp (AMF3ClassDef * cd, int idx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif				/* __AMF_H__ */

Added: hand2.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ hand2.c	Wed Dec 16 06:31:17 2009	(r61)
@@ -0,0 +1,540 @@
+/*
+ *  Copyright (C) 2008-2009 Andrej Stepanchuk
+ *  Copyright (C) 2009 Howard Chu
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program 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 RTMPDump; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+/* This file is #included in rtmp.c, it is not meant to be compiled alone */
+
+#include <openssl/sha.h>
+#include <openssl/hmac.h>
+#include <openssl/rc4.h>
+
+#include "dh.h"
+
+static const char GenuineFMSKey[] = 
+{
+        0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c,
+        0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
+        0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001 
+
+        0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8, 0x2e, 0x00, 0xd0, 0xd1,
+        0x02, 0x9e, 0x7e, 0x57, 0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab, 0x93, 0xb8, 0xe6, 0x36,
+        0xcf, 0xeb, 0x31, 0xae 
+}; // 68
+
+static const char GenuineFPKey[] =
+{
+        0x47,0x65,0x6E,0x75,0x69,0x6E,0x65,0x20,0x41,0x64,0x6F,0x62,0x65,0x20,0x46,0x6C,
+        0x61,0x73,0x68,0x20,0x50,0x6C,0x61,0x79,0x65,0x72,0x20,0x30,0x30,0x31,0xF0,0xEE,
+        0xC2,0x4A,0x80,0x68,0xBE,0xE8,0x2E,0x00,0xD0,0xD1,0x02,0x9E,0x7E,0x57,0x6E,0xEC,
+        0x5D,0x2D,0x29,0x80,0x6F,0xAB,0x93,0xB8,0xE6,0x36,0xCF,0xEB,0x31,0xAE
+}; // 62
+
+static void InitRC4Encryption
+(
+        uint8_t *secretKey, 
+        uint8_t *pubKeyIn, 
+        uint8_t *pubKeyOut,
+        RC4_KEY **rc4keyIn,
+        RC4_KEY **rc4keyOut
+)
+{
+        uint8_t digest[SHA256_DIGEST_LENGTH];
+        unsigned int digestLen = 0;
+
+	*rc4keyIn = malloc(sizeof(RC4_KEY));
+	*rc4keyOut = malloc(sizeof(RC4_KEY));
+
+        HMAC_CTX ctx;
+        HMAC_CTX_init(&ctx);
+        HMAC_Init_ex(&ctx, secretKey, 128, EVP_sha256(), 0);
+        HMAC_Update(&ctx, pubKeyIn, 128);
+        HMAC_Final(&ctx, digest, &digestLen);
+        HMAC_CTX_cleanup(&ctx);
+
+	Log(LOGDEBUG, "RC4 Out Key: ");
+	LogHex(LOGDEBUG, (char*)digest, 16);
+
+        RC4_set_key(*rc4keyOut, 16, digest);
+
+        HMAC_CTX_init(&ctx);
+        HMAC_Init_ex(&ctx, secretKey, 128, EVP_sha256(), 0);
+        HMAC_Update(&ctx, pubKeyOut, 128);
+        HMAC_Final(&ctx, digest, &digestLen);
+        HMAC_CTX_cleanup(&ctx);
+
+	Log(LOGDEBUG, "RC4 In Key: ");
+	LogHex(LOGDEBUG, (char*)digest, 16);
+        
+	RC4_set_key(*rc4keyIn, 16, digest);
+}
+
+static unsigned int GetDHOffset2(char *handshake, unsigned int len)
+{
+        unsigned int offset = 0;
+        unsigned char *ptr = (unsigned char *)handshake + 768;
+
+        assert(RTMP_SIG_SIZE <= len);
+
+        offset += (*ptr); ptr++;
+        offset += (*ptr); ptr++;
+        offset += (*ptr); ptr++;
+        offset += (*ptr);
+
+        unsigned int res = (offset % 632) + 8;
+
+	if(res + 128 > 767) {
+		Log(LOGERROR, "%s: Couldn't calculate correct DH offset (got %d), exiting!\n", __FUNCTION__, res);	
+		exit(1);
+	}
+	return res;
+}
+
+static unsigned int GetDigestOffset2(char *handshake, unsigned int len)
+{
+        unsigned int offset = 0;
+        unsigned char *ptr = (unsigned char *)handshake + 772;
+
+        //assert(12 <= len);
+
+        offset += (*ptr); ptr++;
+        offset += (*ptr); ptr++;
+        offset += (*ptr); ptr++;
+        offset += (*ptr);
+
+        unsigned int res = (offset % 728) + 776;
+
+	if(res+32 > 1535) {
+		Log(LOGERROR, "%s: Couldn't calculate correct digest offset (got %d), exiting\n", __FUNCTION__, res);
+		exit(1);
+	}
+	return res;
+}
+
+static unsigned int GetDHOffset1(char *handshake, unsigned int len)
+{
+        unsigned int offset = 0;
+        unsigned char *ptr = (unsigned char *)handshake + 1532;
+
+        assert(RTMP_SIG_SIZE <= len);
+
+        offset += (*ptr); ptr++;
+        offset += (*ptr); ptr++;
+        offset += (*ptr); ptr++;
+        offset += (*ptr);
+
+        unsigned int res = (offset % 632) + 772;
+
+	if(res+128 > 1531) {
+		Log(LOGERROR, "%s: Couldn't calculate DH offset (got %d), exiting!\n", __FUNCTION__, res);
+		exit(1);
+	}
+
+	return res;
+}
+
+static unsigned int GetDigestOffset1(char *handshake, unsigned int len)
+{
+        unsigned int offset = 0;
+        unsigned char *ptr = (unsigned char *)handshake + 8;
+
+        assert(12 <= len);
+
+        offset += (*ptr); ptr++;
+        offset += (*ptr); ptr++;
+        offset += (*ptr); ptr++;
+        offset += (*ptr);
+
+        unsigned int res = (offset % 728) + 12;
+
+	if(res+32 > 771) {
+		Log(LOGDEBUG, "%s: Couldn't calculate digest offset (got %d), exiting!\n", __FUNCTION__, res);
+		exit(1);
+	}
+
+	return res;
+}
+
+static void HMACsha256(const char *message, size_t messageLen, const char *key, size_t keylen, char *digest)
+{
+	unsigned int digestLen;
+
+	HMAC_CTX ctx;
+        HMAC_CTX_init(&ctx);
+        HMAC_Init_ex(&ctx, (unsigned char*)key, keylen, EVP_sha256(), NULL);
+        HMAC_Update(&ctx, (unsigned char *)message, messageLen);
+        HMAC_Final(&ctx, (unsigned char *)digest, &digestLen);
+        HMAC_CTX_cleanup(&ctx);	
+
+	assert(digestLen == 32);
+}
+
+static void CalculateDigest(unsigned int digestPos, char *handshakeMessage, const char *key, size_t keyLen, char *digest)
+{
+	const int messageLen = RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH;
+        char message[messageLen];
+
+        memcpy(message, handshakeMessage, digestPos);
+        memcpy(message+digestPos, &handshakeMessage[digestPos+SHA256_DIGEST_LENGTH], messageLen-digestPos);
+
+        HMACsha256(message, messageLen, key, keyLen, digest);
+}
+
+static bool VerifyDigest(unsigned int digestPos, char *handshakeMessage, const char *key, size_t keyLen)
+{
+	char calcDigest[SHA256_DIGEST_LENGTH];
+
+	CalculateDigest(digestPos, handshakeMessage, key, keyLen, calcDigest);
+
+	return memcmp(&handshakeMessage[digestPos], calcDigest, SHA256_DIGEST_LENGTH)==0;
+}
+
+/* handshake
+ *
+ * Type		= [1 bytes] 0x06, 0x08 encrypted, 0x03 plain
+ * -------------------------------------------------------------------- [1536 bytes]
+ * Uptime	= [4 bytes] big endian unsigned number, uptime
+ * Version 	= [4 bytes] each byte represents a version number, e.g. 9.0.124.0
+ * ... 
+ *
+ */
+
+static bool HandShake(RTMP *r, bool FP9HandShake)
+{
+	int i;
+	bool encrypted = r->Link.protocol == RTMP_PROTOCOL_RTMPE || r->Link.protocol == RTMP_PROTOCOL_RTMPTE;
+
+	char clientsig[RTMP_SIG_SIZE+1];
+	char serversig[RTMP_SIG_SIZE];
+
+	if (encrypted || r->Link.SWFHash.av_len)
+	  FP9HandShake = true;
+	else
+	  FP9HandShake = false;
+
+	memset(clientsig, 0, RTMP_SIG_SIZE+1);
+
+	r->Link.rc4keyIn = r->Link.rc4keyOut = 0;
+
+	if(encrypted)
+		clientsig[0] = 0x06; // 0x08 is RTMPE as well
+	else
+		clientsig[0] = 0x03;
+
+#if 0
+	uint32_t uptime = htonl(GetTime());
+	memcpy(clientsig + 1, &uptime, 4);
+#else
+	clientsig[1] = 0;
+	clientsig[2] = 0;
+	clientsig[3] = 0;
+	clientsig[4] = 0;
+#endif
+
+	if(FP9HandShake) {
+		// set version to at least 9.0.115.0
+		clientsig[5] = 9;
+		clientsig[6] = 0;
+		clientsig[7] = 124;
+		clientsig[8] = 2;
+
+		Log(LOGDEBUG, "%s: Client type: %02X\n", __FUNCTION__, clientsig[0]);
+
+		//clientsig[0] = 0x08;
+
+		/*clientsig[1] = 0x00;
+		clientsig[2] = 0x00;
+		clientsig[3] = 0x04;
+		clientsig[4] = 0x60;
+		
+		clientsig[5] = 128; 
+                clientsig[6] = 0;
+                clientsig[7] = 1;
+                clientsig[8] = 2;
+		clientsig[9] = 0xBE;
+		clientsig[10] = 0xF6;
+
+		//*/
+	} else {
+		memset(&clientsig[5], 0, 4);
+	}
+
+	// generate random data
+#ifdef _DEBUG
+	for(i=9; i<=RTMP_SIG_SIZE; i++)
+		clientsig[i] = 0;//(char)(rand() % 256);//0xff;
+#else
+	for(i=9; i<=RTMP_SIG_SIZE; i++)
+	        clientsig[i] = (char)(rand() % 256);
+#endif
+
+	int dhposClient = 0;
+	RC4_KEY *keyIn = 0;
+	RC4_KEY *keyOut = 0;
+
+	if(encrypted) {
+		// generate Diffie-Hellmann parameters
+		r->Link.dh = DHInit(1024);
+		if(!r->Link.dh) {
+			Log(LOGERROR, "%s: Couldn't initialize Diffie-Hellmann!", __FUNCTION__);
+			return false;
+		}
+
+		dhposClient = GetDHOffset1(&clientsig[1], RTMP_SIG_SIZE);
+		Log(LOGDEBUG, "%s: DH pubkey position: %d", __FUNCTION__, dhposClient);
+
+		if(!DHGenerateKey(r->Link.dh)) {
+			Log(LOGERROR, "%s: Couldn't generate Diffie-Hellmann public key!", __FUNCTION__);
+			return false;
+		}
+
+		if(!DHGetPublicKey(r->Link.dh, (uint8_t*)&clientsig[1+dhposClient], 128)) {
+			Log(LOGERROR, "%s: Couldn't write public key!", __FUNCTION__);
+			return false;
+		}
+	}
+
+	// set handshake digest
+	if(FP9HandShake)
+	{
+		int digestPosClient = GetDigestOffset1(clientsig+1, RTMP_SIG_SIZE); // maybe reuse this value in verification
+		Log(LOGDEBUG, "%s: Client digest offset: %d", __FUNCTION__, digestPosClient);
+		
+		CalculateDigest(digestPosClient, clientsig+1, GenuineFPKey, 30, &clientsig[1+digestPosClient]);
+		
+		Log(LOGDEBUG, "%s: Initial client digest: ", __FUNCTION__);
+		LogHex(LOGDEBUG, (char *)clientsig+1+digestPosClient, SHA256_DIGEST_LENGTH);
+	}
+
+#ifdef _DEBUG
+	Log(LOGDEBUG, "Clientsig: ");
+	LogHex(LOGDEBUG, &clientsig[1], RTMP_SIG_SIZE);
+#endif
+
+	if(!WriteN(r, clientsig, RTMP_SIG_SIZE + 1))
+		return false;
+
+	char type;
+	if(ReadN(r, &type, 1) != 1) // 0x03 or 0x06
+		return false;
+
+	Log(LOGDEBUG, "%s: Type Answer   : %02X", __FUNCTION__, type);
+
+	if(type != clientsig[0])
+		Log(LOGWARNING, "%s: Type mismatch: client sent %d, server answered %d", __FUNCTION__, clientsig[0], type);
+
+	if(ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
+		return false;
+
+	// decode server response
+	uint32_t suptime;
+
+	memcpy(&suptime, serversig, 4);
+	suptime = ntohl(suptime);
+
+	Log(LOGDEBUG, "%s: Server Uptime : %d", __FUNCTION__, suptime);
+	Log(LOGDEBUG, "%s: FMS Version   : %d.%d.%d.%d", __FUNCTION__, serversig[4], serversig[5], serversig[6], serversig[7]);
+
+#ifdef _DEBUG
+	Log(LOGDEBUG,"Server signature:");
+	LogHex(LOGDEBUG, serversig, RTMP_SIG_SIZE);
+#endif
+
+	if(!FP9HandShake) {
+		if(!WriteN(r, serversig, RTMP_SIG_SIZE))
+			return false;
+	}
+	// we have to use this signature now to find the correct algorithms for getting the digest and DH positions
+	int digestPosServer = GetDigestOffset2(serversig, RTMP_SIG_SIZE);
+	int dhposServer     = GetDHOffset2(serversig, RTMP_SIG_SIZE);
+
+	if(FP9HandShake && !VerifyDigest(digestPosServer, serversig, GenuineFMSKey, 36)) {
+        	Log(LOGWARNING, "Trying different position for server digest!\n");
+                digestPosServer = GetDigestOffset1(serversig, RTMP_SIG_SIZE);
+		dhposServer     = GetDHOffset1(serversig, RTMP_SIG_SIZE);
+
+                if(!VerifyDigest(digestPosServer, serversig, GenuineFMSKey, 36)) {
+                	Log(LOGERROR, "Couldn't verify the server digest\n");//,  continuing anyway, will probably fail!\n");
+                	return false;
+		}
+        }
+
+	Log(LOGDEBUG, "%s: Server DH public key offset: %d", __FUNCTION__, dhposServer);
+
+	// generate SWFVerification token (SHA256 HMAC hash of decompressed SWF, key are the last 32 bytes of the server handshake)
+	if(r->Link.SWFHash.av_len) {
+		const char swfVerify[] = {0x01,0x01};
+
+		memcpy(r->Link.SWFVerificationResponse, swfVerify, 2);
+		AMF_EncodeInt32(&r->Link.SWFVerificationResponse[2], r->Link.SWFSize);
+		AMF_EncodeInt32(&r->Link.SWFVerificationResponse[6], r->Link.SWFSize);
+		HMACsha256(r->Link.SWFHash.av_val, SHA256_DIGEST_LENGTH, &serversig[RTMP_SIG_SIZE-SHA256_DIGEST_LENGTH], SHA256_DIGEST_LENGTH, &r->Link.SWFVerificationResponse[10]);
+	}
+
+	// do Diffie-Hellmann Key exchange for encrypted RTMP
+	if(encrypted) {
+		// compute secret key	
+		uint8_t secretKey[128] = {0};
+	
+		//Log(LOGDEBUG, "Expecting secure key at %d\nKeys at ", dhposServer);
+		//int i;
+		//int len=0;
+		int len = DHComputeSharedSecretKey(r->Link.dh, (uint8_t *)&serversig[dhposServer], 128, secretKey);
+		if(len < 0) {
+			Log(LOGDEBUG, "%s: Wrong secret key position!", __FUNCTION__);
+			return false;
+		}
+
+		/*
+		printf("sigpos: %d\n", sigpos);
+		for(i=8; i<1535-128; i++) {
+			if(i+128 < sigpos || i > sigpos+SHA256_DIGEST_LENGTH) {
+				int len1 = DHComputeSharedSecretKey(Link.dh, (uint8_t *)&serversig[i], 128, secretKey);
+				if(len1 > 0) {
+				LogPrintf("%d,", i);
+                        		//LogHex((char *)&serversig[i], 128);
+				}
+			}
+		}
+		LogPrintf("\n");//*/
+
+		if(len < 0) {
+			Log(LOGERROR, "%s: Couldn't compute secret key, the public key is probably insecure (FMS change?)\n", __FUNCTION__);
+			exit(1);
+			return false;
+		}
+
+		Log(LOGDEBUG, "%s: Secret key: ", __FUNCTION__);
+		LogHex(LOGDEBUG, (char *)secretKey, 128);
+		
+		InitRC4Encryption(
+			secretKey, 
+			(uint8_t*)&serversig[dhposServer], 
+			(uint8_t*)&clientsig[1+dhposClient],
+			&keyIn,
+			&keyOut);
+
+		// well here is another interesting key, lets see what it is for!
+		//HMACsha256(serversig, RTMP_SIG_SIZE, (char *)secretKey, 128, initialKey);
+		//Log(LOGDEBUG, "%s: Calculated initial key:", __FUNCTION__);
+		//LogHex(initialKey, SHA256_DIGEST_LENGTH);
+	}
+
+	// 2nd part of handshake
+	char resp[RTMP_SIG_SIZE];
+	if(ReadN(r, resp, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
+		return false;
+
+	#ifdef _DEBUG
+	Log(LOGDEBUG, "%s: 2nd handshake: ", __FUNCTION__);
+	LogHex(LOGDEBUG, resp, RTMP_SIG_SIZE);
+	#endif
+
+	if(FP9HandShake && resp[4] == 0 && resp[5] == 0 && resp[6] == 0 && resp[7] == 0) {
+		Log(LOGDEBUG, "%s: Wait, did the server just refuse signed authetication?", __FUNCTION__);
+	}
+
+	if(!FP9HandShake) {
+		if(memcmp(resp, clientsig + 1, RTMP_SIG_SIZE) != 0) {
+			Log(LOGWARNING, "%s: client signature does not match!", __FUNCTION__);
+		}
+	} else {
+		// verify server response
+		int digestPosClient = GetDigestOffset1(clientsig+1, RTMP_SIG_SIZE);
+	
+		char signature[SHA256_DIGEST_LENGTH];
+                char digest[SHA256_DIGEST_LENGTH];
+
+		Log(LOGDEBUG, "%s: Client signature digest position: %d", __FUNCTION__, digestPosClient);
+
+		HMACsha256(&clientsig[1+digestPosClient], SHA256_DIGEST_LENGTH, GenuineFMSKey, sizeof(GenuineFMSKey), digest);
+		HMACsha256(resp, RTMP_SIG_SIZE-SHA256_DIGEST_LENGTH, digest, SHA256_DIGEST_LENGTH, signature);
+
+		// show some information
+		Log(LOGDEBUG, "%s: Digest key: ", __FUNCTION__);
+		LogHex(LOGDEBUG, digest, SHA256_DIGEST_LENGTH);
+
+		Log(LOGDEBUG, "%s: Signature calculated:", __FUNCTION__);
+		LogHex(LOGDEBUG, signature, SHA256_DIGEST_LENGTH);
+
+		Log(LOGDEBUG, "%s: Server sent signature:", __FUNCTION__);
+		LogHex(LOGDEBUG, &resp[RTMP_SIG_SIZE-SHA256_DIGEST_LENGTH], SHA256_DIGEST_LENGTH);
+
+		if(memcmp(signature, &resp[RTMP_SIG_SIZE-SHA256_DIGEST_LENGTH], SHA256_DIGEST_LENGTH) != 0) {
+			Log(LOGWARNING, "%s: Server not genuine Adobe!", __FUNCTION__);
+			return false;
+		} else {
+			Log(LOGDEBUG, "%s: Genuine Adobe Flash Media Server", __FUNCTION__);
+		}
+		
+		// generate signed answer
+		char clientResp[RTMP_SIG_SIZE]; 
+#ifdef _DEBUG
+        	for(i=0; i<RTMP_SIG_SIZE; i++)
+                	clientResp[i] = 0;//(char)(rand() % 256);//0xff;
+#else
+        	for(i=0; i<RTMP_SIG_SIZE; i++)
+                	clientResp[i] = (char)(rand() % 256);
+#endif
+
+		// calculate response now
+                char signatureResp[SHA256_DIGEST_LENGTH];
+                char digestResp[SHA256_DIGEST_LENGTH];
+
+		HMACsha256(&serversig[digestPosServer], SHA256_DIGEST_LENGTH, GenuineFPKey, sizeof(GenuineFPKey), digestResp);
+		HMACsha256(clientResp, RTMP_SIG_SIZE-SHA256_DIGEST_LENGTH, digestResp, SHA256_DIGEST_LENGTH, signatureResp);
+
+		// some info output
+		Log(LOGDEBUG, "%s: Calculated digest key from secure key and server digest: ", __FUNCTION__);
+                LogHex(LOGDEBUG, digestResp, SHA256_DIGEST_LENGTH);
+
+                Log(LOGDEBUG, "%s: Client signature calculated:", __FUNCTION__);
+                LogHex(LOGDEBUG, signatureResp, SHA256_DIGEST_LENGTH);
+
+		memcpy(&clientResp[RTMP_SIG_SIZE-SHA256_DIGEST_LENGTH], signatureResp, SHA256_DIGEST_LENGTH);
+
+#ifdef _DEBUG
+		Log(LOGDEBUG, "%s: Sending final signed handshake response: ", __FUNCTION__);
+		LogHex(LOGDEBUG, clientResp, RTMP_SIG_SIZE);
+#endif
+
+		if(!WriteN(r, clientResp, RTMP_SIG_SIZE))
+			return false;
+	}
+	
+	if(encrypted) {
+		// set keys for encryption from now on
+        	r->Link.rc4keyIn  = keyIn;
+        	r->Link.rc4keyOut = keyOut;
+
+		char buff[RTMP_SIG_SIZE];
+
+		// update the keystreams 
+		if(r->Link.rc4keyIn) {
+                	RC4(r->Link.rc4keyIn, RTMP_SIG_SIZE, (uint8_t*)buff, (uint8_t*)buff);
+        	}
+
+		if(r->Link.rc4keyOut) {
+	        	RC4(r->Link.rc4keyOut, RTMP_SIG_SIZE, (uint8_t*)buff, (uint8_t*)buff);
+		}
+	}
+	
+	Log(LOGDEBUG, "%s: Handshaking finished....", __FUNCTION__);
+	return true;
+}

Added: rtmp2.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ rtmp2.c	Wed Dec 16 06:31:17 2009	(r61)
@@ -0,0 +1,1897 @@
+/*
+ *      Copyright (C) 2005-2008 Team XBMC
+ *      http://www.xbmc.org
+ *      Copyright (C) 2008-2009 Andrej Stepanchuk
+ *      Copyright (C) 2009 Howard Chu
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program 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 RTMPDump; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <assert.h>
+
+#ifdef WIN32
+#include <winsock.h>
+#define close(x)	closesocket(x)
+#else
+#include <sys/times.h>
+#endif
+
+#include "rtmp2.h"
+#include "log.h"
+#include "bytes.h"
+
+#define RTMP_SIG_SIZE 1536
+#define RTMP_LARGE_HEADER_SIZE 12
+
+static const int packetSize[] = { 12, 8, 4, 1 };
+#define RTMP_PACKET_SIZE_LARGE    0
+#define RTMP_PACKET_SIZE_MEDIUM   1
+#define RTMP_PACKET_SIZE_SMALL    2
+#define RTMP_PACKET_SIZE_MINIMUM  3
+
+extern bool bCtrlC;
+
+char RTMPProtocolStrings[][7] =
+{
+	"RTMP",
+	"RTMPT",
+	"RTMPS",
+	"RTMPE",
+	"RTMPTE",
+	"RTMFP"
+};
+
+char RTMPProtocolStringsLower[][7] =
+{
+        "rtmp",
+        "rtmpt",
+        "rtmps",
+        "rtmpe",
+        "rtmpte",
+        "rtmpfp"
+};
+
+static bool DumpMetaData(AMFObject *obj);
+static bool HandShake(RTMP *r, bool FP9HandShake);
+static bool SocksNegotiate(RTMP *r);
+
+static bool SendConnectPacket(RTMP *r);
+static bool SendServerBW(RTMP *r);
+static bool SendCheckBW(RTMP *r);
+static bool SendCheckBWResult(RTMP *r, double txn);
+static bool SendCtrl(RTMP *r, short nType, unsigned int nObject, unsigned int nTime);
+static bool SendBGHasStream(RTMP *r, double dId, AVal *playpath);
+static bool SendCreateStream(RTMP *r, double dStreamId);
+static bool SendDeleteStream(RTMP *r, double dStreamId);
+static bool SendFCSubscribe(RTMP *r, AVal *subscribepath);
+static bool SendPlay(RTMP *r);
+static bool SendSeek(RTMP *r, double dTime);
+static bool SendBytesReceived(RTMP *r);
+
+static int HandlePacket(RTMP *r, RTMPPacket *packet);
+static int HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize);
+static bool HandleMetadata(RTMP *r, char *body, unsigned int len);
+static void HandleChangeChunkSize(RTMP *r, const RTMPPacket *packet);
+static void HandleAudio(RTMP *r, const RTMPPacket *packet);
+static void HandleVideo(RTMP *r, const RTMPPacket *packet);
+static void HandleCtrl(RTMP *r, const RTMPPacket *packet);
+static void HandleServerBW(RTMP *r, const RTMPPacket *packet);
+static void HandleClientBW(RTMP *r, const RTMPPacket *packet);
+
+static int EncodeString(char *output, const AVal *name, const AVal *value);
+static int EncodeNumber(char *output, const AVal *name, double dVal);
+static int EncodeBoolean(char *output, const AVal *name, bool bVal);
+
+static bool SendRTMP(RTMP *r, RTMPPacket *packet, bool queue);
+
+static bool ReadPacket(RTMP *r, RTMPPacket *packet);
+static int ReadN(RTMP *r, char *buffer, int n);
+static bool WriteN(RTMP *r, const char *buffer, int n);
+
+static bool FillBuffer(RTMP *r);
+
+int32_t RTMP_GetTime()
+{
+#ifdef _DEBUG
+	return 0;
+#elif defined(WIN32)
+	return timeGetTime();
+#else
+	struct tms t;
+	return times(&t)*1000/sysconf(_SC_CLK_TCK);
+#endif
+}
+
+void RTMPPacket_Reset(RTMPPacket *p)
+{
+  p->m_headerType = 0;
+  p->m_packetType = 0;
+  p->m_nChannel = 0;
+  p->m_nInfoField1 = 0; 
+  p->m_nInfoField2 = 0; 
+  p->m_hasAbsTimestamp = false;
+  p->m_nBodySize = 0;
+  p->m_nBytesRead = 0;
+}
+
+void RTMPPacket_Dump(RTMPPacket *p)
+{
+  Log(LOGDEBUG,"RTMP PACKET: packet type: 0x%02x. channel: 0x%02x. info 1: %d info 2: %d. Body size: %lu. body: 0x%02x",
+	 p->m_packetType, p->m_nChannel,
+           p->m_nInfoField1, p->m_nInfoField2, p->m_nBodySize, p->m_body?(unsigned char)p->m_body[0]:0);
+}
+
+void RTMP_Init(RTMP *r)
+{
+  int i;
+  for (i=0; i<RTMP_CHANNELS; i++)
+  {
+    r->m_vecChannelsIn[i] = NULL;
+    r->m_vecChannelsOut[i] = NULL;
+  }
+  RTMP_Close(r);
+  r->m_nBufferMS = 300;
+  r->m_fDuration = 0;
+  r->m_stream_id = -1;
+  r->m_pBufferStart = NULL;
+  r->m_fAudioCodecs = 3191.0;
+  r->m_fVideoCodecs = 252.0;
+  r->m_bTimedout = false;
+  r->m_pausing = 0;
+  r->m_mediaChannel = 0;
+}
+
+double RTMP_GetDuration(RTMP *r) { return r->m_fDuration; }
+bool RTMP_IsConnected(RTMP *r) { return r->m_socket != 0; }
+bool RTMP_IsTimedout(RTMP *r) { return r->m_bTimedout; }
+
+void RTMP_SetBufferMS(RTMP *r, int size)
+{
+  r->m_nBufferMS = size;
+}
+
+void RTMP_UpdateBufferMS(RTMP *r)
+{
+  SendCtrl(r, 3, r->m_stream_id, r->m_nBufferMS);
+}
+
+void RTMP_SetupStream(
+	RTMP *r,
+	int protocol, 
+	const char *hostname, 
+	unsigned int port, 
+        const char *sockshost,
+	AVal *playpath, 
+	AVal *tcUrl, 
+	AVal *swfUrl, 
+	AVal *pageUrl, 
+	AVal *app, 
+	AVal *auth,
+	AVal *swfSHA256Hash,
+	uint32_t swfSize,
+	AVal *flashVer, 
+	AVal *subscribepath, 
+	double dTime,
+	uint32_t dLength,
+	bool bLiveStream,
+	long int timeout
+)
+{
+  assert(protocol < 6);
+
+  Log(LOGDEBUG, "Protocol : %s", RTMPProtocolStrings[protocol]);
+  Log(LOGDEBUG, "Hostname : %s", hostname);
+  Log(LOGDEBUG, "Port     : %d", port);
+  Log(LOGDEBUG, "Playpath : %s", playpath->av_val);
+
+  if(tcUrl)
+  	Log(LOGDEBUG, "tcUrl    : %s", tcUrl->av_val);
+  if(swfUrl)
+  	Log(LOGDEBUG, "swfUrl   : %s", swfUrl->av_val);
+  if(pageUrl)
+  	Log(LOGDEBUG, "pageUrl  : %s", pageUrl->av_val);
+  if(app)
+  	Log(LOGDEBUG, "app      : %s", app->av_val);
+  if(auth)
+  	Log(LOGDEBUG, "auth     : %s", auth->av_val);
+  if(subscribepath)
+  	Log(LOGDEBUG, "subscribepath : %s", subscribepath->av_val);
+  if(flashVer)
+  	Log(LOGDEBUG, "flashVer : %s", flashVer->av_val);
+  if(dTime > 0)
+  	Log(LOGDEBUG, "SeekTime      : %.3f sec", (double)dTime/1000.0);
+  if(dLength > 0)
+  	Log(LOGDEBUG, "playLength    : %.3f sec", (double)dLength/1000.0);
+
+  Log(LOGDEBUG,       "live     : %s", bLiveStream ? "yes":"no");
+  Log(LOGDEBUG,       "timeout  : %d sec", timeout);
+
+  if(swfSHA256Hash != NULL && swfSize > 0) {
+	r->Link.SWFHash = *swfSHA256Hash;
+	r->Link.SWFSize = swfSize;
+  	Log(LOGDEBUG, "SWFSHA256:");
+  	LogHex(LOGDEBUG, r->Link.SWFHash.av_val, 32);
+	Log(LOGDEBUG, "SWFSize  : %lu", r->Link.SWFSize);
+  } else {
+  	r->Link.SWFHash.av_len = 0;
+  	r->Link.SWFHash.av_val = NULL;
+	r->Link.SWFSize = 0;
+  }
+
+  if(sockshost)
+  {
+    const char *socksport = strchr(sockshost, ':');
+    char *hostname = strdup(sockshost);
+
+    if(socksport)
+      hostname[socksport - sockshost] = '\0';
+    r->Link.sockshost = hostname;
+
+    r->Link.socksport = socksport ? atoi(socksport + 1) : 1080;
+    Log(LOGDEBUG, "Connecting via SOCKS proxy: %s:%d", r->Link.sockshost, r->Link.socksport);
+  } else {
+    r->Link.sockshost = NULL;
+    r->Link.socksport = 0;
+  }
+
+
+  r->Link.tcUrl = *tcUrl;
+  r->Link.swfUrl = *swfUrl;
+  r->Link.pageUrl = *pageUrl;
+  r->Link.app = *app;
+  r->Link.auth = *auth;
+  r->Link.flashVer = *flashVer;
+  r->Link.subscribepath = *subscribepath;
+  r->Link.seekTime = dTime;
+  r->Link.length = dLength;
+  r->Link.bLiveStream = bLiveStream;
+  r->Link.timeout = timeout;
+
+  r->Link.protocol = protocol;
+  r->Link.hostname = hostname;
+  r->Link.port = port;
+  r->Link.playpath = *playpath;
+
+  if (r->Link.port == 0)
+    r->Link.port = 1935;
+}
+
+static bool add_addr_info(struct sockaddr_in* service, const char *hostname, int port)
+{
+  service->sin_addr.s_addr = inet_addr(hostname);
+  if (service->sin_addr.s_addr == INADDR_NONE)
+  {
+    struct hostent *host = gethostbyname(hostname);
+    if (host == NULL || host->h_addr == NULL)
+    {
+      Log(LOGERROR, "Problem accessing the DNS. (addr: %s)", hostname);
+      return false;
+    }
+    service->sin_addr = *(struct in_addr*)host->h_addr;
+  }
+
+  service->sin_port = htons(port);
+  return true;
+}
+
+bool RTMP_Connect(RTMP *r) {
+  struct sockaddr_in service;
+  if (!r->Link.hostname)
+     return false;
+
+  // close any previous connection
+  RTMP_Close(r);
+
+  r->m_bTimedout = false;
+  r->m_pausing = 0;
+  r->m_fDuration = 0.0;
+
+  memset(&service, 0, sizeof(struct sockaddr_in));
+  service.sin_family = AF_INET;
+
+  if (r->Link.socksport)
+  {
+    // Connect via SOCKS
+    if(!add_addr_info(&service, r->Link.sockshost, r->Link.socksport)) return false;
+  } else {
+    // Connect directly
+    if(!add_addr_info(&service, r->Link.hostname, r->Link.port)) return false;
+  }
+
+  r->m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+  if (r->m_socket != -1)
+  {
+    if (connect(r->m_socket, (struct sockaddr*) &service, sizeof(struct sockaddr)) < 0)
+    {
+      int err = GetSockError();
+      Log(LOGERROR, "%s, failed to connect socket. %d (%s)", __FUNCTION__,
+	err, strerror(err));
+      RTMP_Close(r);
+      return false;
+    }
+
+    if(r->Link.socksport) {
+      Log(LOGDEBUG, "%s ... SOCKS negotiation", __FUNCTION__);
+      if (!SocksNegotiate(r))
+      {
+        Log(LOGERROR, "%s, SOCKS negotiation failed.", __FUNCTION__);
+        RTMP_Close(r);
+        return false;
+      }
+    }
+
+    Log(LOGDEBUG, "%s, ... connected, handshaking", __FUNCTION__);
+    if (!HandShake(r, true))
+    {
+      Log(LOGERROR, "%s, handshake failed.", __FUNCTION__);
+      RTMP_Close(r);
+      return false;
+    }
+
+    Log(LOGDEBUG, "%s, handshaked", __FUNCTION__);
+    if (!SendConnectPacket(r))
+    {
+      Log(LOGERROR, "%s, RTMP connect failed.", __FUNCTION__);
+      RTMP_Close(r);
+      return false;
+    }
+    // set timeout
+    struct timeval tv;
+    memset(&tv, 0, sizeof(tv));
+    tv.tv_sec = r->Link.timeout;
+    if (setsockopt(r->m_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,  sizeof(tv))) {
+      	Log(LOGERROR,"%s, Setting socket timeout to %ds failed!", __FUNCTION__, tv.tv_sec);
+    }
+  }
+  else
+  {
+    Log(LOGERROR, "%s, failed to create socket. Error: %d", __FUNCTION__, GetSockError());
+    return false;
+  }
+
+  int on = 1;
+  setsockopt(r->m_socket, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+  return true;
+}
+
+static bool SocksNegotiate(RTMP *r) {
+  struct sockaddr_in service;
+  memset(&service, 0, sizeof(struct sockaddr_in));
+
+  add_addr_info(&service, r->Link.hostname, r->Link.port);
+  unsigned long addr = htonl(service.sin_addr.s_addr);
+
+  char packet[] = {
+      4, 1, // SOCKS 4, connect
+      (r->Link.port  >> 8) & 0xFF,
+      (r->Link.port) & 0xFF,
+      (char) (addr >> 24) & 0xFF, (char) (addr >> 16) & 0xFF,
+      (char) (addr >> 8)  & 0xFF, (char) addr & 0xFF,
+      0}; // NULL terminate
+
+  WriteN(r, packet, sizeof packet);
+
+  if(ReadN(r, packet, 8) != 8)
+    return false;
+
+  if(packet[0] == 0 && packet[1] == 90) {
+    return true;
+  } else {
+    Log(LOGERROR, "%s, SOCKS returned error code %d", packet[1]);
+    return false;
+  }
+}
+
+bool RTMP_ConnectStream(RTMP *r, double seekTime, uint32_t dLength) {
+  RTMPPacket packet;
+  if (seekTime >= -2.0)
+    r->Link.seekTime = seekTime;
+
+  if (dLength >= 0)
+    r->Link.length = dLength;
+
+  r->m_mediaChannel = 0;
+
+  RTMPPacket_Reset(&packet);
+  while (!r->m_bPlaying && RTMP_IsConnected(r) && ReadPacket(r, &packet)) {
+    if (!RTMPPacket_IsReady(&packet))
+    {
+      continue;
+    }
+    
+    if ((packet.m_packetType == RTMP_PACKET_TYPE_AUDIO) || \
+        (packet.m_packetType == RTMP_PACKET_TYPE_VIDEO) || \
+        (packet.m_packetType == RTMP_PACKET_TYPE_INFO))
+    {
+      Log(LOGDEBUG, "%s, received FLV packet before play()!", __FUNCTION__);
+      break;
+    }
+
+    HandlePacket(r, &packet);
+    RTMPPacket_Reset(&packet);
+  }
+
+  return r->m_bPlaying;
+}
+
+bool RTMP_ReconnectStream(RTMP *r, int bufferTime, double seekTime, uint32_t dLength) {
+  RTMP_DeleteStream(r);
+
+  SendCreateStream(r, 2.0);
+
+  RTMP_SetBufferMS(r, bufferTime);
+
+  return RTMP_ConnectStream(r, seekTime, dLength);
+}
+
+bool RTMP_ToggleStream(RTMP *r)
+{
+  bool res;
+
+  res = RTMP_SendPause(r, true, r->m_pauseStamp);
+  if (!res) return res;
+
+  r->m_pausing = 1;
+  sleep(1);
+  res = RTMP_SendPause(r, false, r->m_pauseStamp);
+  r->m_pausing = 3;
+  return res;
+}
+
+void RTMP_DeleteStream(RTMP *r) {
+  if (r->m_stream_id < 0)
+    return;
+
+  r->m_bPlaying = false;
+
+  SendDeleteStream(r, r->m_stream_id);
+}
+
+int RTMP_GetNextMediaPacket(RTMP *r, RTMPPacket *packet)
+{
+  int bHasMediaPacket = 0;
+
+  RTMPPacket_Reset(packet);
+  while (!bHasMediaPacket && RTMP_IsConnected(r) && ReadPacket(r, packet))
+  {
+    if (!RTMPPacket_IsReady(packet))
+    {
+      continue;
+    }
+
+    bHasMediaPacket = HandlePacket(r, packet);
+
+    if (!bHasMediaPacket) { 
+      RTMPPacket_Reset(packet);
+    } else if (r->m_pausing == 3) {
+      if (packet->m_nTimeStamp <= r->m_mediaStamp) {
+	bHasMediaPacket = 0;
+#ifdef _DEBUG
+	Log(LOGDEBUG, "Skipped type: %02X, size: %d, TS: %d ms, abs TS: %d, pause: %d ms",
+	packet->m_packetType, packet->m_nBodySize, packet->m_nTimeStamp, packet->m_hasAbsTimestamp, r->m_mediaStamp);
+#endif
+	continue;
+      }
+      r->m_pausing = 0;
+    }
+  }
+        
+  if (bHasMediaPacket)
+    r->m_bPlaying = true;
+  else if (r->m_bTimedout)
+    r->m_pauseStamp = r->m_channelTimestamp[r->m_mediaChannel];
+
+  return bHasMediaPacket;
+}
+
+static int HandlePacket(RTMP *r, RTMPPacket *packet) {
+  int bHasMediaPacket = 0;
+    switch (packet->m_packetType)
+    {
+      case 0x01:
+        // chunk size
+        HandleChangeChunkSize(r, packet);
+        break;
+
+      case 0x03:
+        // bytes read report
+        Log(LOGDEBUG, "%s, received: bytes read report", __FUNCTION__);
+        break;
+
+      case 0x04:
+        // ctrl
+        HandleCtrl(r, packet);
+        break;
+
+      case 0x05:
+        // server bw
+	HandleServerBW(r, packet);
+        break;
+
+      case 0x06:
+        // client bw
+	HandleClientBW(r, packet);
+        break;
+
+      case 0x08:
+        // audio data
+        //Log(LOGDEBUG, "%s, received: audio %lu bytes", __FUNCTION__, packet.m_nBodySize);
+        HandleAudio(r, packet);
+        bHasMediaPacket = 1;
+	if (!r->m_mediaChannel)
+	  r->m_mediaChannel = packet->m_nChannel;
+	if (!r->m_pausing)
+	  r->m_mediaStamp = packet->m_nTimeStamp;
+        break;
+
+      case 0x09:
+        // video data
+        //Log(LOGDEBUG, "%s, received: video %lu bytes", __FUNCTION__, packet.m_nBodySize);
+        HandleVideo(r, packet);
+        bHasMediaPacket = 1;
+	if (!r->m_mediaChannel)
+	  r->m_mediaChannel = packet->m_nChannel;
+	if (!r->m_pausing)
+	  r->m_mediaStamp = packet->m_nTimeStamp;
+        break;
+
+      case 0x0F: // flex stream send
+        Log(LOGDEBUG, "%s, flex stream send, size %lu bytes, not supported, ignoring", __FUNCTION__, packet->m_nBodySize);
+	break;
+
+      case 0x10: // flex shared object
+        Log(LOGDEBUG, "%s, flex shared object, size %lu bytes, not supported, ignoring", __FUNCTION__, packet->m_nBodySize);
+	break;
+
+      case 0x11: // flex message
+      {
+        Log(LOGDEBUG, "%s, flex message, size %lu bytes, not fully supported", __FUNCTION__, packet->m_nBodySize);
+	//LogHex(packet.m_body, packet.m_nBodySize);
+
+	// some DEBUG code
+	/*RTMP_LIB_AMFObject obj;
+        int nRes = obj.Decode(packet.m_body+1, packet.m_nBodySize-1);
+        if(nRes < 0) {
+                Log(LOGERROR, "%s, error decoding AMF3 packet", __FUNCTION__);
+                //return;
+        }
+
+        obj.Dump();*/
+
+	if ( HandleInvoke(r, packet->m_body+1, packet->m_nBodySize-1) == 1 )
+	  bHasMediaPacket = 2;
+	break;
+      }
+      case 0x12:
+        // metadata (notify)
+        Log(LOGDEBUG, "%s, received: notify %lu bytes", __FUNCTION__, packet->m_nBodySize);
+        if ( HandleMetadata(r, packet->m_body, packet->m_nBodySize) )
+          bHasMediaPacket = 1;
+        break;
+
+      case 0x13:
+      	Log(LOGDEBUG, "%s, shared object, not supported, ignoring", __FUNCTION__);
+	break;
+
+      case 0x14:
+        // invoke
+	Log(LOGDEBUG, "%s, received: invoke %lu bytes", __FUNCTION__, packet->m_nBodySize);
+        //LogHex(packet.m_body, packet.m_nBodySize);
+
+	if ( HandleInvoke(r, packet->m_body, packet->m_nBodySize) == 1 )
+		bHasMediaPacket = 2;
+        break;
+
+      case 0x16:
+      {
+	// go through FLV packets and handle metadata packets
+        unsigned int pos=0;
+	uint32_t nTimeStamp = packet->m_nTimeStamp;
+
+	Log(LOGDEBUG, "%s, received: FLV %lu bytes", __FUNCTION__, packet->m_nBodySize);
+
+        while(pos+11 < packet->m_nBodySize) {
+		uint32_t dataSize = AMF_DecodeInt24(packet->m_body+pos+1); // size without header (11) and prevTagSize (4)
+
+                if(pos+11+dataSize+4 > packet->m_nBodySize) {
+                        Log(LOGWARNING, "Stream corrupt?!");
+                	break;
+                }
+		if(packet->m_body[pos] == 0x12) {
+			HandleMetadata(r, packet->m_body+pos+11, dataSize);
+		} else if (packet->m_body[pos] == 8 || packet->m_body[pos] == 9) {
+			nTimeStamp = AMF_DecodeInt24(packet->m_body+pos+4);
+			nTimeStamp |= (packet->m_body[pos+7]<<24);
+		}
+                pos += (11+dataSize+4);
+	}
+	if (!r->m_pausing)
+	  r->m_mediaStamp = nTimeStamp;
+
+        // FLV tag(s)
+        //Log(LOGDEBUG, "%s, received: FLV tag(s) %lu bytes", __FUNCTION__, packet.m_nBodySize);
+        bHasMediaPacket = 1;
+        break;
+      }
+      default:
+        Log(LOGDEBUG, "%s, unknown packet type received: 0x%02x", __FUNCTION__, packet->m_packetType);
+	#ifdef _DEBUG
+	LogHex(LOGDEBUG, packet->m_body, packet->m_nBodySize);
+	#endif
+    }
+
+  Log(LOGDEBUG, "%s, done %lu bytes", __FUNCTION__, packet->m_nBodySize);
+  return bHasMediaPacket;
+}
+
+#ifdef _DEBUG
+extern FILE *netstackdump;
+extern FILE *netstackdump_read;
+#endif
+
+static int ReadN(RTMP *r, char *buffer, int n)
+{
+  int nOriginalSize = n;
+  char *ptr;
+  
+  r->m_bTimedout = false;
+
+#ifdef _DEBUG
+  memset(buffer, 0, n);
+#endif
+
+  ptr = buffer;
+  while (n > 0)
+  {
+    int nBytes = 0, nRead;
+    if(r->m_nBufferSize == 0)
+	if (!FillBuffer(r)) {
+	   if (!r->m_bTimedout)
+	     RTMP_Close(r);
+	   return 0;
+	}
+    nRead = ((n<r->m_nBufferSize)?n:r->m_nBufferSize);
+    if(nRead > 0) {
+    	memcpy(ptr, r->m_pBufferStart, nRead);
+	r->m_pBufferStart += nRead;
+	r->m_nBufferSize -= nRead;
+	nBytes = nRead;
+	r->m_nBytesIn += nRead;
+	if(r->m_nBytesIn > r->m_nBytesInSent + r->m_nClientBW/2 )
+		SendBytesReceived(r);
+    }
+
+    //Log(LOGDEBUG, "%s: %d bytes\n", __FUNCTION__, nBytes);
+#ifdef _DEBUG
+        fwrite(ptr, 1, nBytes, netstackdump_read);
+#endif
+
+    if (nBytes == 0)
+    {
+      Log(LOGDEBUG, "%s, RTMP socket closed by server", __FUNCTION__);
+      //goto again;
+      RTMP_Close(r);
+      break;
+    }
+  
+#ifdef CRYPTO
+    if(r->Link.rc4keyIn) {
+    	RC4(r->Link.rc4keyIn, nBytes, (uint8_t*)ptr, (uint8_t*)ptr);
+    }
+#endif
+
+    n -= nBytes;
+    ptr += nBytes;
+  }
+
+  return nOriginalSize - n;
+}
+
+static bool WriteN(RTMP *r, const char *buffer, int n)
+{
+  const char *ptr = buffer;
+#ifdef CRYPTO
+  char *encrypted = 0;
+  char buf[RTMP_BUFFER_CACHE_SIZE];
+ 
+  if(r->Link.rc4keyOut) {
+    if (n > sizeof(buf))
+      encrypted = (char *)malloc(n);
+    else
+      encrypted = (char *)buf;
+    ptr = encrypted;
+    RC4(r->Link.rc4keyOut, n, (uint8_t*)buffer, (uint8_t*)ptr);
+  }
+#endif
+  
+  while (n > 0)
+  {
+#ifdef _DEBUG
+	fwrite(ptr, 1, n, netstackdump);
+#endif
+
+    int nBytes = send(r->m_socket, ptr, n, 0);
+    //Log(LOGDEBUG, "%s: %d\n", __FUNCTION__, nBytes);
+    
+    if (nBytes < 0)
+    {
+      int sockerr = GetSockError();
+      Log(LOGERROR, "%s, RTMP send error %d (%d bytes)", __FUNCTION__, sockerr, n);
+
+      if (sockerr == EINTR && !bCtrlC)
+	continue;
+      
+      RTMP_Close(r);
+      n = 1;
+      break;
+    }
+    
+    if (nBytes == 0)
+      break;
+    
+    n -= nBytes;
+    ptr += nBytes;
+  }
+
+#ifdef CRYPTO
+  if(encrypted && encrypted != buf)
+    free(encrypted);
+#endif
+
+  return n == 0;
+}
+
+#define SAVC(x)	static const AVal av_##x = AVC(#x)
+
+SAVC(app);
+SAVC(connect);
+SAVC(flashVer);
+SAVC(swfUrl);
+SAVC(pageUrl);
+SAVC(tcUrl);
+SAVC(fpad);
+SAVC(capabilities);
+SAVC(audioCodecs);
+SAVC(videoCodecs);
+SAVC(videoFunction);
+SAVC(objectEncoding);
+
+static bool SendConnectPacket(RTMP *r)
+{
+  RTMPPacket packet;
+
+  packet.m_nChannel = 0x03;   // control channel (invoke)
+  packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
+  packet.m_packetType = 0x14; // INVOKE
+  packet.m_nInfoField1 = 0;
+  packet.m_nInfoField2 = 0;
+  packet.m_hasAbsTimestamp = 0;
+
+  char *enc = packet.m_body;
+  enc += AMF_EncodeString(enc, &av_connect);
+  enc += AMF_EncodeNumber(enc, 1.0);
+  *enc++ = AMF_OBJECT;
+ 
+  if(r->Link.app.av_len)
+  	enc += EncodeString(enc, &av_app, &r->Link.app);
+  if(r->Link.flashVer.av_len)
+  	enc += EncodeString(enc, &av_flashVer, &r->Link.flashVer);
+  if(r->Link.swfUrl.av_len)
+ 	enc += EncodeString(enc, &av_swfUrl, &r->Link.swfUrl);
+  if(r->Link.tcUrl.av_len)
+  	enc += EncodeString(enc, &av_tcUrl, &r->Link.tcUrl);
+  
+  enc += EncodeBoolean(enc, &av_fpad, false);
+  enc += EncodeNumber(enc, &av_capabilities, 15.0);
+  enc += EncodeNumber(enc, &av_audioCodecs, r->m_fAudioCodecs);
+  enc += EncodeNumber(enc, &av_videoCodecs, r->m_fVideoCodecs);
+  enc += EncodeNumber(enc, &av_videoFunction, 1.0);
+  if(r->Link.pageUrl.av_len)
+  	enc += EncodeString(enc, &av_pageUrl, &r->Link.pageUrl);
+
+  enc += EncodeNumber(enc, &av_objectEncoding, 0.0); // AMF0, AMF3 not supported yet
+  *enc++ = 0; *enc++ = 0; // end of object - 0x00 0x00 0x09
+  *enc++ = AMF_OBJECT_END;
+ 
+  // add auth string
+  if(r->Link.auth.av_len)
+  {
+  	*enc++ = 0x01;
+  	*enc++ = 0x01;
+
+  	enc += AMF_EncodeString(enc, &r->Link.auth);
+  }
+  packet.m_nBodySize = enc-packet.m_body;
+
+  return SendRTMP(r, &packet, true);
+}
+
+SAVC(bgHasStream);
+
+static bool SendBGHasStream(RTMP *r, double dId, AVal *playpath)
+{
+  RTMPPacket packet;
+  packet.m_nChannel = 0x03;   // control channel (invoke)
+  packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+  packet.m_packetType = 0x14; // INVOKE
+  packet.m_nInfoField1 = 0;
+  packet.m_nInfoField2 = 0;
+  packet.m_hasAbsTimestamp = 0;
+
+  char *enc = packet.m_body;
+  enc += AMF_EncodeString(enc, &av_bgHasStream);
+  enc += AMF_EncodeNumber(enc, dId);
+  *enc++ = AMF_NULL;
+
+  enc += AMF_EncodeString(enc, playpath);
+
+  packet.m_nBodySize = enc-packet.m_body;
+
+  return SendRTMP(r, &packet, true);
+}
+
+SAVC(createStream);
+
+static bool SendCreateStream(RTMP *r, double dStreamId)
+{
+  RTMPPacket packet;
+  packet.m_nChannel = 0x03;   // control channel (invoke)
+  packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+  packet.m_packetType = 0x14; // INVOKE
+  packet.m_nInfoField1 = 0;
+  packet.m_nInfoField2 = 0;
+  packet.m_hasAbsTimestamp = 0;
+
+  char *enc = packet.m_body;
+  enc += AMF_EncodeString(enc, &av_createStream);
+  enc += AMF_EncodeNumber(enc, dStreamId);
+  *enc++ = AMF_NULL; // NULL
+
+  packet.m_nBodySize = enc - packet.m_body;
+
+  return SendRTMP(r, &packet, true);
+}
+
+SAVC(FCSubscribe);
+
+static bool SendFCSubscribe(RTMP *r, AVal *subscribepath)
+{
+  RTMPPacket packet;
+  packet.m_nChannel = 0x03;   // control channel (invoke)
+  packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+  packet.m_packetType = 0x14; // INVOKE
+  packet.m_nInfoField1 = 0;
+  packet.m_nInfoField2 = 0;
+  packet.m_hasAbsTimestamp = 0;
+
+  Log(LOGDEBUG, "FCSubscribe: %s", subscribepath);
+  char *enc = packet.m_body;
+  enc += AMF_EncodeString(enc, &av_FCSubscribe);
+  enc += AMF_EncodeNumber(enc, 4.0);
+  *enc++ = AMF_NULL;
+  enc += AMF_EncodeString(enc, subscribepath);
+
+  packet.m_nBodySize = enc - packet.m_body;
+
+  return SendRTMP(r, &packet, true);
+}
+
+SAVC(deleteStream);
+
+static bool SendDeleteStream(RTMP *r, double dStreamId)
+{
+  RTMPPacket packet;
+
+  packet.m_nChannel = 0x03;   // control channel (invoke)
+  packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+  packet.m_packetType = 0x14; // INVOKE
+  packet.m_nInfoField1 = 0;
+  packet.m_nInfoField2 = 0;
+  packet.m_hasAbsTimestamp = 0;
+
+  char *enc = packet.m_body;
+  enc += AMF_EncodeString(enc, &av_deleteStream);
+  enc += AMF_EncodeNumber(enc, 0.0);
+  *enc++ = AMF_NULL;
+  enc += AMF_EncodeNumber(enc, dStreamId);
+
+  packet.m_nBodySize = enc - packet.m_body;
+
+  /* no response expected */
+  return SendRTMP(r, &packet, false);
+}
+
+SAVC(pause);
+
+bool RTMP_SendPause(RTMP *r, bool DoPause, double dTime)
+{
+  RTMPPacket packet;
+  packet.m_nChannel = 0x08;   // video channel 
+  packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+  packet.m_packetType = 0x14; // invoke
+  packet.m_nInfoField1 = 0;
+  packet.m_nInfoField2 = 0;
+  packet.m_hasAbsTimestamp = 0;
+
+  char *enc = packet.m_body;
+  enc += AMF_EncodeString(enc, &av_pause);
+  enc += AMF_EncodeNumber(enc, 0);
+  *enc++ = AMF_NULL;
+  enc += AMF_EncodeBoolean(enc, DoPause);
+  enc += AMF_EncodeNumber(enc, (double)dTime);
+
+  packet.m_nBodySize = enc - packet.m_body;
+
+  return SendRTMP(r, &packet, true);
+}
+
+SAVC(seek);
+
+static bool SendSeek(RTMP *r, double dTime)
+{
+  RTMPPacket packet;
+  packet.m_nChannel = 0x08;   // video channel 
+  packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+  packet.m_packetType = 0x14; // invoke
+  packet.m_nInfoField1 = 0;
+  packet.m_nInfoField2 = 0;
+  packet.m_hasAbsTimestamp = 0;
+
+  char *enc = packet.m_body;
+  enc += AMF_EncodeString(enc, &av_seek);
+  enc += AMF_EncodeNumber(enc, 0);
+  *enc++ = AMF_NULL;
+  enc += AMF_EncodeNumber(enc, dTime);
+
+  packet.m_nBodySize = enc - packet.m_body;
+
+  return SendRTMP(r, &packet, true);
+}
+
+static bool SendServerBW(RTMP *r)
+{
+  RTMPPacket packet;
+  packet.m_nChannel = 0x02;   // control channel (invoke)
+  packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
+  packet.m_packetType = 0x05; // Server BW
+  packet.m_nInfoField1 = 0;
+  packet.m_nInfoField2 = 0;
+  packet.m_hasAbsTimestamp = 0;
+
+  packet.m_nBodySize = 4;
+
+  AMF_EncodeInt32(packet.m_body, r->m_nServerBW);
+  return SendRTMP(r, &packet, false);
+}
+
+static bool SendBytesReceived(RTMP *r)
+{
+  RTMPPacket packet;
+  packet.m_nChannel = 0x02;   // control channel (invoke)
+  packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+  packet.m_packetType = 0x03; // bytes in
+  packet.m_nInfoField1 = 0;
+  packet.m_nInfoField2 = 0;
+  packet.m_hasAbsTimestamp = 0;
+
+  packet.m_nBodySize = 4;
+
+  AMF_EncodeInt32(packet.m_body, r->m_nBytesIn); // hard coded for now
+  r->m_nBytesInSent = r->m_nBytesIn;
+
+  //Log(LOGDEBUG, "Send bytes report. 0x%x (%d bytes)", (unsigned int)m_nBytesIn, m_nBytesIn);
+  return SendRTMP(r, &packet, false);
+}
+
+SAVC(_checkbw);
+
+static bool SendCheckBW(RTMP *r)
+{
+  RTMPPacket packet;
+  packet.m_nChannel = 0x03;   // control channel (invoke)
+  packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
+  packet.m_packetType = 0x14; // INVOKE
+  packet.m_nInfoField1 = RTMP_GetTime();
+  packet.m_nInfoField2 = 0;
+  packet.m_hasAbsTimestamp = 0;
+
+  char *enc = packet.m_body;
+  enc += AMF_EncodeString(enc, &av__checkbw);
+  enc += AMF_EncodeNumber(enc, 0);
+  *enc++ = AMF_NULL;
+
+  packet.m_nBodySize = enc - packet.m_body;
+
+  // triggers _onbwcheck and eventually results in _onbwdone 
+  return SendRTMP(r, &packet, false);
+}
+
+SAVC(_result);
+
+static bool SendCheckBWResult(RTMP *r, double txn)
+{
+  RTMPPacket packet;
+  packet.m_nChannel = 0x03;   // control channel (invoke)
+  packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+  packet.m_packetType = 0x14; // INVOKE
+  packet.m_nInfoField1 = 0x16 * r->m_nBWCheckCounter; // temp inc value. till we figure it out.
+  packet.m_nInfoField2 = 0;
+  packet.m_hasAbsTimestamp = 0;
+
+  char *enc = packet.m_body;
+  enc += AMF_EncodeString(enc, &av__result);
+  enc += AMF_EncodeNumber(enc, txn);
+  *enc++ = AMF_NULL;
+  enc += AMF_EncodeNumber(enc, (double)r->m_nBWCheckCounter++); 
+
+  packet.m_nBodySize = enc - packet.m_body;
+
+  return SendRTMP(r, &packet, false);
+}
+
+SAVC(play);
+
+static bool SendPlay(RTMP *r)
+{
+  RTMPPacket packet;
+  packet.m_nChannel = 0x08;   // we make 8 our stream channel
+  packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
+  packet.m_packetType = 0x14; // INVOKE
+  packet.m_nInfoField2 = r->m_stream_id; //0x01000000;
+  packet.m_nInfoField1 = 0;
+  packet.m_hasAbsTimestamp = 0;
+
+  char *enc = packet.m_body;
+  enc += AMF_EncodeString(enc, &av_play);
+  enc += AMF_EncodeNumber(enc, 0.0); // stream id??
+  *enc++ = AMF_NULL;
+
+  Log(LOGDEBUG, "%s, seekTime=%.2f, dLength=%d, sending play: %s", __FUNCTION__, r->Link.seekTime, r->Link.length, r->Link.playpath.av_val);
+  enc += AMF_EncodeString(enc, &r->Link.playpath);
+
+  // Optional parameters start and len.
+
+  // start: -2, -1, 0, positive number
+  //  -2: looks for a live stream, then a recorded stream, if not found any open a live stream
+  //  -1: plays a live stream
+  // >=0: plays a recorded streams from 'start' milliseconds
+  if(r->Link.bLiveStream)
+    enc += AMF_EncodeNumber(enc, -1000.0);
+  else {
+  if(r->Link.seekTime > 0.0)
+    enc += AMF_EncodeNumber(enc, r->Link.seekTime); // resume from here
+    else
+      enc += AMF_EncodeNumber(enc, 0.0);//-2000.0); // recorded as default, -2000.0 is not reliable since that freezes the player if the stream is not found
+  }
+  
+  // len: -1, 0, positive number
+  //  -1: plays live or recorded stream to the end (default)
+  //   0: plays a frame 'start' ms away from the beginning
+  //  >0: plays a live or recoded stream for 'len' milliseconds
+  //enc += EncodeNumber(enc, -1.0); // len
+  if(r->Link.length)
+    enc += AMF_EncodeNumber(enc, r->Link.length); // len
+
+  packet.m_nBodySize = enc - packet.m_body;
+
+  return SendRTMP(r, &packet, true);
+}
+/*
+from http://jira.red5.org/confluence/display/docs/Ping:
+
+Ping is the most mysterious message in RTMP and till now we haven't fully interpreted it yet. In summary, Ping message is used as a special command that are exchanged between client and server. This page aims to document all known Ping messages. Expect the list to grow.
+
+The type of Ping packet is 0x4 and contains two mandatory parameters and two optional parameters. The first parameter is the type of Ping and in short integer. The second parameter is the target of the ping. As Ping is always sent in Channel 2 (control channel) and the target object in RTMP header is always 0 which means the Connection object, it's necessary to put an extra parameter to indicate the exact target object the Ping is sent to. The second parameter takes this responsibility. The value has the same meaning as the target object field in RTMP header. (The second value could also be used as other purposes, like RTT Ping/Pong. It is used as the timestamp.) The third and fourth parameters are optional and could be looked upon as the parameter of the Ping packet. Below is an unexhausted list of Ping messages.
+
+    * type 0: Clear the stream. No third and fourth parameters. The second parameter could be 0. After the connection is established, a Ping 0,0 will be sent from server to client. The message will also be sent to client on the start of Play and in response of a Seek or Pause/Resume request. This Ping tells client to re-calibrate the clock with the timestamp of the next packet server sends.
+    * type 1: Tell the stream to clear the playing buffer.
+    * type 3: Buffer time of the client. The third parameter is the buffer time in millisecond.
+    * type 4: Reset a stream. Used together with type 0 in the case of VOD. Often sent before type 0.
+    * type 6: Ping the client from server. The second parameter is the current time.
+    * type 7: Pong reply from client. The second parameter is the time the server sent with his ping request.
+    * type 26: SWFVerification request
+    * type 27: SWFVerification response
+*/
+static bool SendCtrl(RTMP *r, short nType, unsigned int nObject, unsigned int nTime)
+{
+  Log(LOGDEBUG, "sending ctrl. type: 0x%04x", (unsigned short)nType);
+
+  RTMPPacket packet; 
+  packet.m_nChannel = 0x02;   // control channel (ping)
+  packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+  packet.m_packetType = 0x04; // ctrl
+  packet.m_nInfoField1 = RTMP_GetTime();
+  packet.m_nInfoField2 = 0;
+  packet.m_hasAbsTimestamp = 0;
+
+  int nSize = (nType==0x03?10:6); // type 3 is the buffer time and requires all 3 parameters. all in all 10 bytes.
+  if(nType == 0x1B) 
+    nSize = 44;
+
+  packet.m_nBodySize = nSize;
+
+  char *buf = packet.m_body;
+  buf += AMF_EncodeInt16(buf, nType);
+
+  if(nType == 0x1B) {
+#ifdef CRYPTO
+    memcpy(buf, r->Link.SWFVerificationResponse, 42);
+    Log(LOGDEBUG, "Sending SWFVerification response: ");
+    LogHex(LOGDEBUG, packet.m_body, packet.m_nBodySize);
+#endif
+  } else {
+    if (nSize > 2)
+      buf += AMF_EncodeInt32(buf, nObject);
+
+    if (nSize > 6)
+      buf += AMF_EncodeInt32(buf, nTime);
+  }
+  
+  return SendRTMP(r, &packet, false);
+}
+
+static void AV_erase(AVal *vals, int *num, int i, bool freeit)
+{
+  if (freeit)
+    free(vals[i].av_val);
+  (*num)--;
+  for (; i<*num; i++) {
+    vals[i] = vals[i+1];
+  }
+  vals[i].av_val = NULL;
+  vals[i].av_len = 0;
+}
+
+static void AV_queue(AVal **vals, int *num, AVal *av)
+{
+  char *tmp;
+  if (!(*num & 0x0f))
+    *vals = realloc(*vals, (*num+16) * sizeof(AVal));
+  tmp = malloc(av->av_len+1);
+  memcpy(tmp, av->av_val, av->av_len);
+  tmp[av->av_len] = '\0';
+  (*vals)[*num].av_len = av->av_len;
+  (*vals)[(*num)++].av_val = tmp;
+}
+
+static void AV_clear(AVal *vals, int num)
+{
+  int i;
+  for (i=0;i<num;i++)
+    free(vals[i].av_val);
+  free(vals);
+}
+
+SAVC(onBWDone);
+SAVC(onFCSubscribe);
+SAVC(onFCUnsubscribe);
+SAVC(_onbwcheck);
+SAVC(_onbwdone);
+SAVC(_error);
+SAVC(close);
+SAVC(code);
+SAVC(level);
+SAVC(onStatus);
+static const AVal av_NetStream_Failed = AVC("NetStream.Failed");
+static const AVal av_NetStream_Play_Failed = AVC("NetStream.Play.Failed");
+static const AVal av_NetStream_Play_StreamNotFound = AVC("NetStream.Play.StreamNotFound");
+static const AVal av_NetConnection_Connect_InvalidApp = AVC("NetConnection.Connect.InvalidApp");
+static const AVal av_NetStream_Play_Start = AVC("NetStream.Play.Start");
+static const AVal av_NetStream_Play_Complete = AVC("NetStream.Play.Complete");
+static const AVal av_NetStream_Play_Stop = AVC("NetStream.Play.Stop");
+
+// Returns 0 for OK/Failed/error, 1 for 'Stop or Complete'
+static int HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
+{
+  int ret = 0, nRes;
+  if (body[0] != 0x02) // make sure it is a string method name we start with
+  {
+    Log(LOGWARNING, "%s, Sanity failed. no string method in invoke packet", __FUNCTION__);
+    return 0;
+  }
+
+  AMFObject obj;
+  nRes = AMF_Decode(&obj, body, nBodySize, false);
+  if (nRes < 0)
+  { 
+    Log(LOGERROR, "%s, error decoding invoke packet", __FUNCTION__);
+    return 0;
+  }
+
+  AMF_Dump(&obj);
+  AVal method;
+  AMFProp_GetString(AMF_GetProp(&obj, NULL, 0), &method);
+  double txn = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 1));
+  Log(LOGDEBUG, "%s, server invoking <%s>", __FUNCTION__, method.av_val);
+
+  if (AVMATCH(&method, &av__result))
+  {
+    AVal methodInvoked = r->m_methodCalls[0];
+    AV_erase(r->m_methodCalls, &r->m_numCalls, 0, false);
+
+    Log(LOGDEBUG, "%s, received result for method call <%s>", __FUNCTION__, methodInvoked.av_val);
+  
+    if (AVMATCH(&methodInvoked,&av_connect))
+    {
+      SendServerBW(r);
+      SendCtrl(r, 3, 0, 300);
+
+      SendCreateStream(r, 2.0);
+
+      // Send the FCSubscribe if live stream or if subscribepath is set
+      if (r->Link.subscribepath.av_len)
+        SendFCSubscribe(r, &r->Link.subscribepath);
+      else if (r->Link.bLiveStream)
+        SendFCSubscribe(r, &r->Link.playpath);
+    }
+    else if (AVMATCH(&methodInvoked,&av_createStream))
+    {
+      r->m_stream_id = (int)AMFProp_GetNumber(AMF_GetProp(&obj,NULL,3));
+
+      SendPlay(r);
+      SendCtrl(r, 3, r->m_stream_id, r->m_nBufferMS);
+    }
+    else if (AVMATCH(&methodInvoked,&av_play))
+    {
+	r->m_bPlaying = true;
+    }
+    free(methodInvoked.av_val);
+  }
+  else if (AVMATCH(&method,&av_onBWDone))
+  {
+    SendCheckBW(r);
+  }
+  else if (AVMATCH(&method,&av_onFCSubscribe))
+  {
+    // SendOnFCSubscribe();
+  }
+  else if (AVMATCH(&method,&av_onFCUnsubscribe))
+  {
+    RTMP_Close(r);
+    ret = 1;
+  }
+  else if (AVMATCH(&method,&av__onbwcheck))
+  {
+    SendCheckBWResult(r, txn);
+  }
+  else if (AVMATCH(&method,&av__onbwdone))
+  {
+    int i;
+    for (i=0; i<r->m_numCalls; i++)
+      if (AVMATCH(&r->m_methodCalls[i],&av__checkbw)) {
+        AV_erase(r->m_methodCalls, &r->m_numCalls, i, true);
+        break;
+      }
+  }
+  else if (AVMATCH(&method,&av__error))
+  {
+    Log(LOGERROR, "rtmp server sent error");
+  }
+  else if (AVMATCH(&method,&av_close))
+  {
+    Log(LOGERROR, "rtmp server requested close");
+    RTMP_Close(r);
+  }
+  else if (AVMATCH(&method,&av_onStatus))
+  {
+    AMFObject obj2;
+    AVal code, level;
+    AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2);
+    AMFProp_GetString(AMF_GetProp(&obj2, &av_code, -1), &code);
+    AMFProp_GetString(AMF_GetProp(&obj2, &av_level, -1), &level);
+
+    Log(LOGDEBUG, "%s, onStatus: %s", __FUNCTION__, code.av_val );
+    if (AVMATCH(&code,&av_NetStream_Failed)
+    ||  AVMATCH(&code,&av_NetStream_Play_Failed)
+    ||  AVMATCH(&code,&av_NetStream_Play_StreamNotFound)
+    ||  AVMATCH(&code,&av_NetConnection_Connect_InvalidApp)) {
+      r->m_stream_id = -1;
+      RTMP_Close(r);
+    }
+
+    if (AVMATCH(&code, &av_NetStream_Play_Start)) {
+      int i;
+      r->m_bPlaying = true;
+      for (i=0; i<r->m_numCalls; i++) {
+        if (AVMATCH(&r->m_methodCalls[i], &av_play)) {
+          AV_erase(r->m_methodCalls, &r->m_numCalls, i, true);
+          break;
+        }
+      }
+    }
+
+    // Return 1 if this is a Play.Complete or Play.Stop
+    if (AVMATCH(&code,&av_NetStream_Play_Complete)
+    ||  AVMATCH(&code,&av_NetStream_Play_Stop)) {
+      RTMP_Close(r);
+      ret = 1;
+    }
+  }
+  else
+  {
+
+  }
+  AMF_Reset(&obj);
+  return ret;
+}
+
+bool RTMP_FindFirstMatchingProperty(AMFObject *obj, const AVal *name, AMFObjectProperty *p)
+{
+	int n;
+	/* this is a small object search to locate the "duration" property */
+	for (n=0; n<obj->o_num; n++) {
+		AMFObjectProperty *prop = AMF_GetProp(obj, NULL, n);
+
+		if(AVMATCH(&prop->p_name, name)) {
+			*p = *prop;
+			return true;
+		}
+
+		if(prop->p_type == AMF_OBJECT) {
+			return RTMP_FindFirstMatchingProperty(&prop->p_vu.p_object, name, p);
+		}
+	}
+	return false;
+}
+
+static bool DumpMetaData(AMFObject *obj)
+{
+        AMFObjectProperty *prop;
+	int n;
+	for (n=0; n<obj->o_num; n++) {
+		prop = AMF_GetProp(obj, NULL, n);
+		if ( prop->p_type != AMF_OBJECT ) {
+			char str[256]="";
+			switch( prop->p_type )
+			{
+				case AMF_NUMBER:
+					snprintf(str, 255, "%.2f", prop->p_vu.p_number);
+					break;
+				case AMF_BOOLEAN:
+					snprintf(str, 255, "%s", prop->p_vu.p_number != 0.?"TRUE":"FALSE");
+					break;
+				case AMF_STRING:
+					snprintf(str, 255, "%.*s", prop->p_vu.p_aval.av_len, prop->p_vu.p_aval.av_val);
+					break;
+				case AMF_DATE:
+					snprintf(str, 255, "timestamp:%.2f", prop->p_vu.p_number);
+					break;
+				default:
+					snprintf(str, 255, "INVALID TYPE 0x%02x", (unsigned char)prop->p_type );
+			}
+			if ( prop->p_name.av_len ) {
+				// chomp
+				if ( strlen(str) >= 1 && str[strlen(str)-1 ] == '\n')
+					str[strlen(str)-1] = '\0';
+				LogPrintf("  %-22.*s%s\n", prop->p_name.av_len, prop->p_name.av_val, str );
+			}
+		} else {
+			if ( prop->p_name.av_len )
+				LogPrintf("%.*s:\n", prop->p_name.av_len, prop->p_name.av_val );
+			DumpMetaData(&prop->p_vu.p_object);
+		}
+	}
+	return false;
+}
+
+SAVC(onMetaData);
+SAVC(duration);
+
+static bool HandleMetadata(RTMP *r, char *body, unsigned int len)
+{
+	// allright we get some info here, so parse it and print it
+	// also keep duration or filesize to make a nice progress bar
+
+	AMFObject obj;
+        AVal metastring;
+	bool ret = false;
+
+	int nRes = AMF_Decode(&obj, body, len, false);
+	if(nRes < 0) {
+		Log(LOGERROR, "%s, error decoding meta data packet", __FUNCTION__);
+		return false;
+	}
+
+	AMF_Dump(&obj);
+	AMFProp_GetString(AMF_GetProp(&obj, NULL, 0), &metastring);
+
+	if(AVMATCH(&metastring, &av_onMetaData)) {
+		AMFObjectProperty prop;
+		// Show metadata
+		LogPrintf("\r%s\n", "Metadata:                  " );
+		DumpMetaData(&obj);
+		if(RTMP_FindFirstMatchingProperty(&obj, &av_duration, &prop)) {
+			r->m_fDuration = prop.p_vu.p_number;
+			//Log(LOGDEBUG, "Set duration: %.2f", m_fDuration);
+		}
+		ret = true;
+	}
+	AMF_Reset(&obj);
+	return ret;
+}
+
+static void HandleChangeChunkSize(RTMP *r, const RTMPPacket *packet)
+{
+  if (packet->m_nBodySize >= 4)
+  {
+    r->m_chunkSize = AMF_DecodeInt32(packet->m_body);
+    Log(LOGDEBUG, "%s, received: chunk size change to %d", __FUNCTION__, r->m_chunkSize);
+  }
+}
+
+static void HandleAudio(RTMP *r, const RTMPPacket *packet)
+{
+}
+
+static void HandleVideo(RTMP *r, const RTMPPacket *packet)
+{
+}
+
+static void HandleCtrl(RTMP *r, const RTMPPacket *packet)
+{
+  short nType = -1;
+  unsigned int tmp;
+  if (packet->m_body && packet->m_nBodySize >= 2)
+    nType = AMF_DecodeInt16(packet->m_body);
+  Log(LOGDEBUG, "%s, received ctrl. type: %d, len: %d", __FUNCTION__, nType, packet->m_nBodySize);
+  //LogHex(packet.m_body, packet.m_nBodySize);
+
+  if (packet->m_nBodySize >= 6) {
+    switch(nType) {
+    case 0:
+      tmp = AMF_DecodeInt32(packet->m_body + 2);
+      Log(LOGDEBUG, "%s, Stream Begin %d", __FUNCTION__, tmp);
+      break;
+
+    case 1:
+      tmp = AMF_DecodeInt32(packet->m_body + 2);
+      Log(LOGDEBUG, "%s, Stream EOF %d", __FUNCTION__, tmp);
+      if (r->m_pausing == 1)
+        r->m_pausing = 2;
+      break;
+
+    case 2:
+      tmp = AMF_DecodeInt32(packet->m_body + 2);
+      Log(LOGDEBUG, "%s, Stream Dry %d", __FUNCTION__, tmp);
+      break;
+
+    case 4:
+      tmp = AMF_DecodeInt32(packet->m_body + 2);
+      Log(LOGDEBUG, "%s, Stream IsRecorded %d", __FUNCTION__, tmp);
+      break;
+
+    case 6: // server ping. reply with pong.
+      tmp = AMF_DecodeInt32(packet->m_body + 2);
+      Log(LOGDEBUG, "%s, Ping %d", __FUNCTION__, tmp);
+      SendCtrl(r, 0x07, tmp, 0);
+      break;
+
+    case 31:
+      tmp = AMF_DecodeInt32(packet->m_body + 2);
+      Log(LOGDEBUG, "%s, Stream BufferEmpty %d", __FUNCTION__, tmp);
+      if (!r->m_pausing) {
+	r->m_pauseStamp = r->m_channelTimestamp[r->m_mediaChannel];
+        RTMP_SendPause(r, true, r->m_pauseStamp);
+        r->m_pausing = 1;
+      } else if (r->m_pausing == 2) {
+        RTMP_SendPause(r, false, r->m_pauseStamp);
+        r->m_pausing = 3;
+      }
+      break;
+
+    case 32:
+      tmp = AMF_DecodeInt32(packet->m_body + 2);
+      Log(LOGDEBUG, "%s, Stream BufferReady %d", __FUNCTION__, tmp);
+      break;
+
+    default:
+      tmp = AMF_DecodeInt32(packet->m_body + 2);
+      Log(LOGDEBUG, "%s, Stream xx %d", __FUNCTION__, tmp);
+      break;
+    }
+
+  }
+
+  if (nType == 0x1A) {
+  	Log(LOGDEBUG, "%s, SWFVerification ping received: ", __FUNCTION__);
+	//LogHex(packet.m_body, packet.m_nBodySize);
+
+	// respond with HMAC SHA256 of decompressed SWF, key is the 30byte player key, also the last 30 bytes of the server handshake are applied
+	if(r->Link.SWFHash.av_len) {
+	  SendCtrl(r, 0x1B, 0, 0);
+	} else {
+	  Log(LOGWARNING, "%s: Ignoring SWFVerification request, use --swfhash and --swfsize!", __FUNCTION__);
+	}
+  }
+}
+
+static void HandleServerBW(RTMP *r, const RTMPPacket *packet) {
+  r->m_nServerBW = AMF_DecodeInt32(packet->m_body);
+  Log(LOGDEBUG, "%s: server BW = %d", __FUNCTION__, r->m_nServerBW);
+}
+
+static void HandleClientBW(RTMP *r, const RTMPPacket *packet) {
+  r->m_nClientBW = AMF_DecodeInt32(packet->m_body);
+  if (packet->m_nBodySize > 4)
+    r->m_nClientBW2 = packet->m_body[4];
+  else
+    r->m_nClientBW2 = -1;
+  Log(LOGDEBUG, "%s: client BW = %d %d", __FUNCTION__, r->m_nClientBW, r->m_nClientBW2);
+}
+
+static bool ReadPacket(RTMP *r, RTMPPacket *packet)
+{
+  char type;
+  if (ReadN(r, &type,1) == 0)
+  {
+    Log(LOGERROR, "%s, failed to read RTMP packet header", __FUNCTION__);
+    return false;
+  } 
+
+  packet->m_headerType = (type & 0xc0) >> 6;
+  packet->m_nChannel = (type & 0x3f);
+  if ( packet->m_nChannel == 0 )
+  {
+	if (ReadN(r,&type,1) != 1)
+	{
+	  Log(LOGERROR, "%s, failed to read RTMP packet header 2nd byte", __FUNCTION__);
+	  return false;
+	} 
+	packet->m_nChannel = (unsigned)type;
+	packet->m_nChannel += 64;
+  } else if ( packet->m_nChannel == 1 )
+  {
+    char t[2];
+	int tmp;
+    if (ReadN(r,t,2) != 2)
+	{
+	  Log(LOGERROR, "%s, failed to read RTMP packet header 3nd byte", __FUNCTION__);
+	  return false;
+	} 
+	tmp = (((unsigned)t[1])<<8) + (unsigned)t[0];
+	packet->m_nChannel = tmp + 64;
+    Log(LOGDEBUG, "%s, m_nChannel: %0x", __FUNCTION__, packet->m_nChannel);
+  }
+
+  int nSize = packetSize[packet->m_headerType];
+  
+  if (nSize == RTMP_LARGE_HEADER_SIZE) // if we get a full header the timestamp is absolute
+    packet->m_hasAbsTimestamp = true; 
+
+  if (nSize < RTMP_LARGE_HEADER_SIZE) { // using values from the last message of this channel
+	if (r->m_vecChannelsIn[packet->m_nChannel])
+		memcpy(packet, r->m_vecChannelsIn[packet->m_nChannel], offsetof(RTMPPacket,m_header));
+  }
+  
+  nSize--;
+
+  char *header = packet->m_header;
+  if (nSize > 0 && ReadN(r, header,nSize) != nSize)
+  {
+    Log(LOGERROR, "%s, failed to read RTMP packet header. type: %x", __FUNCTION__, (unsigned int)type);
+    return false;
+  }
+
+  if (nSize >= 3)
+    packet->m_nInfoField1 = AMF_DecodeInt24(header);
+
+  //Log(LOGDEBUG, "%s, reading RTMP packet chunk on channel %x, headersz %i, timestamp %i, abs timestamp %i", __FUNCTION__, packet.m_nChannel, nSize, packet.m_nInfoField1, packet.m_hasAbsTimestamp); 
+
+  if (nSize >= 6)
+  {
+    packet->m_nBodySize = AMF_DecodeInt24(header + 3);
+    packet->m_nBytesRead = 0;
+  }
+  
+  if (nSize > 6)
+    packet->m_packetType = header[6];
+
+  if (nSize == 11)
+    packet->m_nInfoField2 = ReadInt32LE(header+7);
+
+  int nToRead = packet->m_nBodySize - packet->m_nBytesRead;
+  int nChunk = r->m_chunkSize;
+  if (nToRead < nChunk)
+     nChunk = nToRead;
+
+  if (ReadN(r, packet->m_body + packet->m_nBytesRead, nChunk) != nChunk)
+  {
+    Log(LOGERROR, "%s, failed to read RTMP packet body. len: %lu", __FUNCTION__, packet->m_nBodySize);
+    return false;  
+  }
+
+  packet->m_nBytesRead += nChunk;
+
+  // keep the packet as ref for other packets on this channel
+  if (!r->m_vecChannelsIn[packet->m_nChannel])
+  	r->m_vecChannelsIn[packet->m_nChannel] = malloc(offsetof(RTMPPacket,m_header));
+  memcpy(r->m_vecChannelsIn[packet->m_nChannel], packet, offsetof(RTMPPacket,m_header));
+
+  if (RTMPPacket_IsReady(packet))
+  {
+    packet->m_nTimeStamp = packet->m_nInfoField1;
+    
+    // make packet's timestamp absolute 
+    if (!packet->m_hasAbsTimestamp) 
+      packet->m_nTimeStamp += r->m_channelTimestamp[packet->m_nChannel]; // timestamps seem to be always relative!! 
+      
+    r->m_channelTimestamp[packet->m_nChannel] = packet->m_nTimeStamp; 
+ 
+    // reset the data from the stored packet. we keep the header since we may use it later if a new packet for this channel
+    // arrives and requests to re-use some info (small packet header)
+    r->m_vecChannelsIn[packet->m_nChannel]->m_nBytesRead = 0;
+    r->m_vecChannelsIn[packet->m_nChannel]->m_hasAbsTimestamp = false; // can only be false if we reuse header
+  }
+
+  return true;
+}
+
+static int EncodeString(char *output, const AVal *strName, const AVal *strValue)
+{
+  char *buf = output;
+  buf += AMF_EncodeInt16(output, strName->av_len);
+
+  memcpy(buf, strName->av_val, strName->av_len);
+  buf += strName->av_len;
+  
+  buf += AMF_EncodeString(buf, strValue);
+  return buf - output;
+}
+
+static int EncodeNumber(char *output, const AVal *strName, double dVal)
+{
+  char *buf = output;
+  buf += AMF_EncodeInt16(output, strName->av_len);
+
+  memcpy(buf, strName->av_val, strName->av_len);
+  buf += strName->av_len;
+
+  buf += AMF_EncodeNumber(buf, dVal);
+  return buf - output;
+}
+
+static int EncodeBoolean(char *output, const AVal *strName, bool bVal)
+{
+  char *buf = output;
+  buf += AMF_EncodeInt16(output, strName->av_len);
+
+  memcpy(buf, strName->av_val, strName->av_len);
+  buf += strName->av_len;
+
+  buf += AMF_EncodeBoolean(buf, bVal);
+
+  return buf - output;
+}
+
+#ifdef CRYPTO
+#include "hand2.c"
+#else
+static bool HandShake(RTMP *r, bool FP9HandShake)
+{
+  int i;
+  char clientsig[RTMP_SIG_SIZE+1];
+  char serversig[RTMP_SIG_SIZE];
+
+  clientsig[0] = 0x03; // not encrypted
+  
+  uint32_t uptime = htonl(RTMP_GetTime());
+  memcpy(clientsig + 1, &uptime, 4);
+
+  memset(&clientsig[5], 0, 4);
+
+#ifdef _DEBUG
+    for (i=9; i<RTMP_SIG_SIZE; i++) 
+      clientsig[i] = 0xff;
+#else
+    for (i=9; i<RTMP_SIG_SIZE; i++)
+      clientsig[i] = (char)(rand() % 256);
+#endif
+
+  if (!WriteN(r, clientsig, RTMP_SIG_SIZE + 1))
+    return false;
+
+  char type;
+  if (ReadN(r, &type, 1) != 1) // 0x03 or 0x06
+    return false;
+
+  Log(LOGDEBUG, "%s: Type Answer   : %02X", __FUNCTION__, type);
+  
+  if(type != clientsig[0])
+  	Log(LOGWARNING, "%s: Type mismatch: client sent %d, server answered %d", __FUNCTION__, clientsig[0], type);
+
+  if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
+    return false;
+
+  // decode server response
+  uint32_t suptime;
+
+  memcpy(&suptime, serversig, 4);
+  suptime = ntohl(suptime);
+
+  Log(LOGDEBUG, "%s: Server Uptime : %d", __FUNCTION__, suptime);
+  Log(LOGDEBUG, "%s: FMS Version   : %d.%d.%d.%d", __FUNCTION__, serversig[4], serversig[5], serversig[6], serversig[7]);
+
+  // 2nd part of handshake
+  if (!WriteN(r, serversig, RTMP_SIG_SIZE))
+    return false;
+
+  char resp[RTMP_SIG_SIZE];
+  if (ReadN(r, resp, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
+    return false;
+
+  bool bMatch = (memcmp(resp, clientsig + 1, RTMP_SIG_SIZE) == 0);
+  if (!bMatch)
+  {
+    Log(LOGWARNING, "%s, client signature does not match!",__FUNCTION__);
+  }
+  return true;
+}
+#endif
+
+static bool SendRTMP(RTMP *r, RTMPPacket *packet, bool queue)
+{
+  const RTMPPacket *prevPacket = r->m_vecChannelsOut[packet->m_nChannel];
+  if (prevPacket && packet->m_headerType != RTMP_PACKET_SIZE_LARGE)
+  {
+    // compress a bit by using the prev packet's attributes
+    if (prevPacket->m_nBodySize == packet->m_nBodySize && packet->m_headerType == RTMP_PACKET_SIZE_MEDIUM) 
+      packet->m_headerType = RTMP_PACKET_SIZE_SMALL;
+
+    if (prevPacket->m_nInfoField2 == packet->m_nInfoField2 && packet->m_headerType == RTMP_PACKET_SIZE_SMALL)
+      packet->m_headerType = RTMP_PACKET_SIZE_MINIMUM;
+      
+  }
+
+  if (packet->m_headerType > 3) // sanity
+  { 
+    Log(LOGERROR, "sanity failed!! trying to send header of type: 0x%02x.", (unsigned char)packet->m_headerType);
+    return false;
+  }
+
+  int nSize = packetSize[packet->m_headerType];
+  int hSize = nSize;
+  char *header = packet->m_body - nSize;
+  header[0] = (char)((packet->m_headerType << 6) | packet->m_nChannel);
+  if (nSize > 1)
+    AMF_EncodeInt24(header+1, packet->m_nInfoField1);
+  
+  if (nSize > 4)
+  {
+    AMF_EncodeInt24(header+4, packet->m_nBodySize);
+    header[7] = packet->m_packetType;
+  }
+
+  if (nSize > 8)
+    EncodeInt32LE(header+8, packet->m_nInfoField2);
+
+  nSize = packet->m_nBodySize;
+  char *buffer = packet->m_body;
+  int nChunkSize = RTMP_DEFAULT_CHUNKSIZE;
+
+  while (nSize)
+  {
+    int wrote;
+
+    if (nSize < nChunkSize)
+      nChunkSize = nSize;
+
+    if (header) {
+      wrote=WriteN(r, header, nChunkSize+hSize);
+      header = NULL;
+    } else {
+      wrote=WriteN(r, buffer, nChunkSize);
+    }
+    if (!wrote)
+      return false;
+
+    nSize -= nChunkSize;
+    buffer += nChunkSize;
+
+    if (nSize > 0)
+    {
+      header = buffer-1;
+      hSize = 1;
+      *header = (0xc0 | packet->m_nChannel);
+    }
+  }
+
+  if (packet->m_packetType == 0x14 && queue) { // we invoked a remote method, keep it in call queue till result arrives
+    AVal method;
+    AMF_DecodeString(packet->m_body+1, &method);
+    AV_queue(&r->m_methodCalls, &r->m_numCalls, &method);
+    Log(LOGDEBUG, "Invoking %s", method.av_val);
+  }
+
+  if (!r->m_vecChannelsOut[packet->m_nChannel])
+    r->m_vecChannelsOut[packet->m_nChannel] = malloc(offsetof(RTMPPacket,m_header));
+  memcpy(r->m_vecChannelsOut[packet->m_nChannel], packet, offsetof(RTMPPacket,m_header));
+  return true;
+}
+
+void RTMP_Close(RTMP *r)
+{
+  int i;
+
+  if (RTMP_IsConnected(r))
+    close(r->m_socket);
+
+  r->m_stream_id = -1;
+  r->m_socket = 0;
+  r->m_chunkSize = RTMP_DEFAULT_CHUNKSIZE;
+  r->m_nBWCheckCounter = 0;
+  r->m_nBytesIn = 0;
+  r->m_nBytesInSent = 0;
+  r->m_nClientBW = 2500000;
+  r->m_nClientBW2 = 2;
+  r->m_nServerBW = 2500000;
+
+  for (i=0; i<RTMP_CHANNELS; i++)
+  {
+    if (r->m_vecChannelsIn[i]) {
+	  free(r->m_vecChannelsIn[i]);
+	  r->m_vecChannelsIn[i] = NULL;
+	}
+	if (r->m_vecChannelsOut[i]) {
+	  free(r->m_vecChannelsOut[i]);
+	  r->m_vecChannelsOut[i] = NULL;
+	}
+  }
+  AV_clear(r->m_methodCalls, r->m_numCalls);
+  r->m_methodCalls = NULL;
+  r->m_numCalls = 0;
+
+  r->m_bPlaying = false;
+  r->m_nBufferSize = 0;
+}
+
+static bool FillBuffer(RTMP *r)
+{
+    assert(r->m_nBufferSize == 0); // only fill buffer when it's empty
+    int nBytes;
+
+again:
+    nBytes = recv(r->m_socket, r->m_pBuffer, sizeof(r->m_pBuffer), 0);
+    if(nBytes != -1) {
+    	r->m_nBufferSize += nBytes;
+	r->m_pBufferStart = r->m_pBuffer;
+    }
+    else
+    {
+      int sockerr = GetSockError();
+      Log(LOGDEBUG, "%s, recv returned %d. GetSockError(): %d (%s)", __FUNCTION__, nBytes,
+         sockerr, strerror(sockerr));
+      if (sockerr == EINTR && !bCtrlC)
+        goto again;
+
+      if (sockerr == EWOULDBLOCK || sockerr == EAGAIN)
+        r->m_bTimedout = true;
+      else
+        RTMP_Close(r);
+      return false;
+    }
+
+  return true;
+}

Added: rtmp2.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ rtmp2.h	Wed Dec 16 06:31:17 2009	(r61)
@@ -0,0 +1,206 @@
+#ifndef __RTMP_H__
+#define __RTMP_H__
+/*
+ *      Copyright (C) 2005-2008 Team XBMC
+ *      http://www.xbmc.org
+ *      Copyright (C) 2008-2009 Andrej Stepanchuk
+ *      Copyright (C) 2009 Howard Chu
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program 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 RTMPDump; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#ifdef WIN32
+#include <winsock.h>
+#define	GetSockError()	WSAGetLastError()
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <errno.h>
+#define	GetSockError()	errno
+#endif
+
+#include <stdint.h>
+
+#include "log.h"
+
+#ifdef CRYPTO
+#include "dh.h"
+#endif
+
+#include "amf.h"
+
+#define RTMP_PROTOCOL_UNDEFINED	-1
+#define RTMP_PROTOCOL_RTMP      0
+#define RTMP_PROTOCOL_RTMPT     1	// not yet supported
+#define RTMP_PROTOCOL_RTMPS     2	// not yet supported
+#define RTMP_PROTOCOL_RTMPE     3
+#define RTMP_PROTOCOL_RTMPTE    4	// not yet supported
+#define RTMP_PROTOCOL_RTMFP     5	// not yet supported
+
+#define RTMP_DEFAULT_CHUNKSIZE	128
+
+#define RTMP_BUFFER_CACHE_SIZE (16*1024) // needs to fit largest number of bytes recv() may return
+
+#define	RTMP_CHANNELS	65600
+
+extern char RTMPProtocolStringsLower[][7];
+
+int32_t RTMP_GetTime();
+
+#define RTMP_PACKET_TYPE_AUDIO 0x08
+#define RTMP_PACKET_TYPE_VIDEO 0x09
+#define RTMP_PACKET_TYPE_INFO  0x12
+
+#define RTMP_MAX_HEADER_SIZE 14
+
+typedef unsigned char BYTE;
+
+typedef struct RTMPPacket
+{
+  BYTE m_headerType;
+  BYTE m_packetType;
+  int m_nChannel;
+  int32_t m_nInfoField1;	// 3 first bytes
+  int32_t m_nInfoField2;	// last 4 bytes in a long header, absolute timestamp for long headers, relative timestamp for short headers 
+  bool m_hasAbsTimestamp;	// timestamp absolute or relative?
+  uint32_t m_nTimeStamp;	// absolute timestamp
+  uint32_t m_nBodySize;
+  uint32_t m_nBytesRead;
+  char m_header[RTMP_MAX_HEADER_SIZE];
+  char m_body[1024];
+} RTMPPacket;
+
+void RTMPPacket_Reset(RTMPPacket * p);
+void RTMPPacket_Dump(RTMPPacket * p);
+#define RTMPPacket_IsReady(a)	((a)->m_nBytesRead == (a)->m_nBodySize)
+
+typedef struct RTMP_LNK
+{
+  const char *hostname;
+  unsigned int port;
+  int protocol;
+
+  AVal playpath;
+  AVal tcUrl;
+  AVal swfUrl;
+  AVal pageUrl;
+  AVal app;
+  AVal auth;
+  AVal flashVer;
+  AVal subscribepath;
+
+  double seekTime;
+  uint32_t length;
+  bool bLiveStream;
+
+  long int timeout;		// number of seconds before connection times out
+
+  const char *sockshost;
+  unsigned short socksport;
+
+#ifdef CRYPTO
+  DH *dh;			// for encryption
+  RC4_KEY *rc4keyIn;
+  RC4_KEY *rc4keyOut;
+
+  AVal SWFHash;
+  uint32_t SWFSize;
+  char SWFVerificationResponse[42];
+#endif
+} RTMP_LNK;
+
+typedef struct RTMP
+{
+  int m_socket;
+  int m_chunkSize;
+  int m_nBWCheckCounter;
+  int m_nBytesIn;
+  int m_nBytesInSent;
+  int m_nBufferMS;
+  int m_stream_id;		// returned in _result from invoking createStream
+  int m_mediaChannel;
+  uint32_t m_mediaStamp;
+  uint32_t m_pauseStamp;
+  int m_pausing;
+  int m_nServerBW;
+  int m_nClientBW;
+  uint8_t m_nClientBW2;
+  bool m_bPlaying;
+  bool m_bTimedout;
+
+  AVal *m_methodCalls;		/* remote method calls queue */
+  int m_numCalls;
+
+  RTMP_LNK Link;
+  char *m_pBufferStart;		// pointer into m_pBuffer of next byte to process
+  int m_nBufferSize;		// number of unprocessed bytes in buffer
+  RTMPPacket *m_vecChannelsIn[RTMP_CHANNELS];
+  RTMPPacket *m_vecChannelsOut[RTMP_CHANNELS];
+  int m_channelTimestamp[RTMP_CHANNELS];	// abs timestamp of last packet
+
+  double m_fAudioCodecs;	// audioCodecs for the connect packet
+  double m_fVideoCodecs;	// videoCodecs for the connect packet
+
+  double m_fDuration;		// duration of stream in seconds
+  char m_pBuffer[RTMP_BUFFER_CACHE_SIZE];		// data read from socket
+} RTMP;
+
+void RTMP_SetBufferMS(RTMP *r, int size);
+void RTMP_UpdateBufferMS(RTMP *r);
+
+void RTMP_SetupStream(RTMP *r, int protocol,
+		      const char *hostname,
+		      unsigned int port,
+		      const char *sockshost,
+		      AVal *playpath,
+		      AVal *tcUrl,
+		      AVal *swfUrl,
+		      AVal *pageUrl,
+		      AVal *app,
+		      AVal *auth,
+		      AVal *swfSHA256Hash,
+		      uint32_t swfSize,
+		      AVal *flashVer,
+		      AVal *subscribepath,
+		      double dTime,
+		      uint32_t dLength, bool bLiveStream, long int timeout);
+
+bool RTMP_Connect(RTMP *r);
+
+bool RTMP_IsConnected(RTMP *r);
+bool RTMP_IsTimedout(RTMP *r);
+double RTMP_GetDuration(RTMP *r);
+bool RTMP_ToggleStream(RTMP *r);
+
+bool RTMP_ConnectStream(RTMP *r, double seekTime, uint32_t dLength);
+bool RTMP_ReconnectStream(RTMP *r, int bufferTime, double seekTime, uint32_t dLength);
+void RTMP_DeleteStream(RTMP *r);
+int RTMP_GetNextMediaPacket(RTMP *r, RTMPPacket *packet);
+
+void RTMP_Init(RTMP *r);
+void RTMP_Close(RTMP *r);
+
+bool RTMP_SendPause(RTMP *r, bool DoPause, double dTime);
+bool RTMP_FindFirstMatchingProperty(AMFObject *obj, const AVal *name,
+				      AMFObjectProperty *p);
+
+
+#endif

Added: rtmpd2.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ rtmpd2.c	Wed Dec 16 06:31:17 2009	(r61)
@@ -0,0 +1,1394 @@
+/*  RTMPDump
+ *  Copyright (C) 2009 Andrej Stepanchuk
+ *  Copyright (C) 2009 Howard Chu
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program 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 RTMPDump; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#define _FILE_OFFSET_BITS	64
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <unistd.h>
+
+#include <signal.h> // to catch Ctrl-C
+#include <getopt.h>
+
+#ifdef WIN32
+#define fseeko fseeko64
+#define ftello ftello64
+#include <winsock.h>
+#include <stdio.h>
+#include <io.h>
+#include <fcntl.h>
+#define	SET_BINMODE(f)	setmode(fileno(f), O_BINARY)
+#else
+#define	SET_BINMODE(f)
+#endif
+
+#include "rtmp2.h"
+#include "log.h"
+#include "parseurl.h"
+
+int debuglevel = 1;
+
+#define RTMPDUMP_VERSION	"v2.0"
+
+#define RD_SUCCESS		0
+#define RD_FAILED		1
+#define RD_INCOMPLETE		2
+
+// starts sockets
+bool InitSockets() 
+{
+#ifdef WIN32
+        WORD version;
+        WSADATA wsaData;
+
+        version = MAKEWORD(1,1);
+        return (WSAStartup(version, &wsaData)==0);
+#else
+	return true;
+#endif
+}
+
+inline void CleanupSockets() {
+#ifdef WIN32
+        WSACleanup();
+#endif
+}
+
+#ifdef _DEBUG
+uint32_t debugTS = 0;
+int pnum=0;
+
+FILE *netstackdump = 0;
+FILE *netstackdump_read = 0;
+#endif
+
+uint32_t nIgnoredFlvFrameCounter = 0;
+uint32_t nIgnoredFrameCounter = 0;
+#define MAX_IGNORED_FRAMES	50
+
+FILE *file = 0;
+bool bCtrlC = false;
+
+void sigIntHandler(int sig) {
+	bCtrlC = true;
+	LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig);
+	// ignore all these signals now and let the connection close
+	signal(SIGHUP, SIG_IGN);
+	signal(SIGINT, SIG_IGN);
+	signal(SIGPIPE, SIG_IGN);
+	signal(SIGTERM, SIG_IGN);
+	signal(SIGQUIT, SIG_IGN);
+}
+
+int WriteHeader(
+	        char **buf,                     // target pointer, maybe preallocated
+                unsigned int len                // length of buffer if preallocated
+	)
+{
+        char flvHeader[] = { 'F', 'L', 'V', 0x01,
+                             0x05, // video + audio, we finalize later if the value is different
+                             0x00, 0x00, 0x00, 0x09,
+                             0x00, 0x00, 0x00, 0x00 // first prevTagSize=0
+        };
+
+	unsigned int size = sizeof(flvHeader);
+
+	if(size > len) {
+        	*buf = (char *)realloc(*buf,  size);
+                if(*buf == 0) {
+                	Log(LOGERROR, "Couldn't reallocate memory!");
+                        return -1; // fatal error
+                }
+        }
+	memcpy(*buf, flvHeader, sizeof(flvHeader));
+	return size;
+}
+
+static const AVal av_onMetaData = AVC("onMetaData");
+static const AVal av_duration = AVC("duration");
+
+// Returns -3 if Play.Close/Stop, -2 if fatal error, -1 if no more media packets, 0 if ignorable error, >0 if there is a media packet
+int WriteStream(
+		RTMP* rtmp, 
+		char **buf,			// target pointer, maybe preallocated
+		unsigned int len, 		// length of buffer if preallocated
+		uint32_t *tsm, 			// pointer to timestamp, will contain timestamp of last video packet returned
+		bool bResume, 		        // resuming mode, will not write FLV header and compare metaHeader and first kexframe
+		bool bLiveStream, 		// live mode, will not report absolute timestamps
+		uint32_t nResumeTS,		// resume keyframe timestamp
+		char *metaHeader, 		// pointer to meta header (if bResume == TRUE)
+		uint32_t nMetaHeaderSize,	// length of meta header, if zero meta header check omitted (if bResume == TRUE)
+		char *initialFrame,		// pointer to initial keyframe (no FLV header or tagSize, raw data) (if bResume == TRUE)
+		uint8_t initialFrameType,	// initial frame type (audio or video)
+		uint32_t nInitialFrameSize,	// length of initial frame in bytes, if zero initial frame check omitted (if bResume == TRUE)
+		uint8_t *dataType		// whenever we get a video/audio packet we set an appropriate flag here, this will be later written to the FLV header
+	)
+{
+	static bool bStopIgnoring = false;
+	static bool bFoundKeyframe = false;
+	static bool bFoundFlvKeyframe = false;
+
+	uint32_t prevTagSize = 0;
+	int rtnGetNextMediaPacket = 0;
+	char pbuf[131072];
+	RTMPPacket *packet = (RTMPPacket *)pbuf;
+
+	rtnGetNextMediaPacket = RTMP_GetNextMediaPacket(rtmp, packet);
+	if(rtnGetNextMediaPacket)
+	{
+		char *packetBody	= packet->m_body;
+		unsigned int nPacketLen	= packet->m_nBodySize;
+
+                // Return -3 if this was completed nicely with invoke message Play.Stop or Play.Complete
+                if (rtnGetNextMediaPacket == 2) {
+                        Log(LOGDEBUG, "Got Play.Complete or Play.Stop from server. Assuming stream is complete");
+                        return -3;
+                }
+
+		// skip video info/command packets
+		if(packet->m_packetType == 0x09 && 
+		   nPacketLen == 2 &&
+		((*packetBody & 0xf0) == 0x50)) {
+			return 0;
+		}
+
+		if(packet->m_packetType == 0x09 && nPacketLen <= 5) {
+			Log(LOGWARNING, "ignoring too small video packet: size: %d", nPacketLen);
+			return 0;
+		}
+		if(packet->m_packetType == 0x08 && nPacketLen <= 1) {
+			Log(LOGWARNING, "ignoring too small audio packet: size: %d", nPacketLen);
+			return 0;
+		}
+#ifdef _DEBUG
+		Log(LOGDEBUG, "type: %02X, size: %d, TS: %d ms, abs TS: %d", packet->m_packetType, nPacketLen, packet->m_nTimeStamp, packet->m_hasAbsTimestamp);
+		if(packet->m_packetType == 0x09)
+			Log(LOGDEBUG, "frametype: %02X", (*packetBody & 0xf0));
+#endif
+
+		// check the header if we get one
+		if(bResume && packet->m_nTimeStamp == 0) {
+			if(nMetaHeaderSize > 0 && packet->m_packetType == 0x12) {
+			
+				AMFObject metaObj;
+                        	int nRes = AMF_Decode(&metaObj, packetBody, nPacketLen, false);
+                        	if(nRes >= 0) {
+					AVal metastring;
+					AMFProp_GetString(AMF_GetProp(&metaObj, NULL, 0), &metastring);
+
+                                	if(AVMATCH(&metastring, &av_onMetaData)) {
+						// compare
+						if((nMetaHeaderSize != nPacketLen) || 
+						   (memcmp(metaHeader, packetBody, nMetaHeaderSize) != 0)) {
+							return -2;
+						}
+					}                     	
+				}
+			}
+
+			// check first keyframe to make sure we got the right position in the stream!
+			// (the first non ignored frame)
+			if(nInitialFrameSize > 0) {
+
+				// video or audio data
+				if(packet->m_packetType == initialFrameType && nInitialFrameSize == nPacketLen) {
+					// we don't compare the sizes since the packet can contain several FLV packets, just make
+					// sure the first frame is our keyframe (which we are going to rewrite)
+					if(memcmp(initialFrame, packetBody, nInitialFrameSize) == 0) {
+						Log(LOGDEBUG, "Checked keyframe successfully!");
+						bFoundKeyframe = true;
+						return 0; // ignore it! (what about audio data after it? it is handled by ignoring all 0ms frames, see below)
+					}
+				}
+
+				// hande FLV streams, even though the server resends the keyframe as an extra video packet
+				// it is also included in the first FLV stream chunk and we have to compare it and
+				// filter it out !!
+				//
+				if(packet->m_packetType == 0x16) {
+					// basically we have to find the keyframe with the correct TS being nResumeTS
+					unsigned int pos=0;
+					uint32_t ts = 0;
+
+                        		while(pos+11 < nPacketLen) {
+                                		uint32_t dataSize = AMF_DecodeInt24(packetBody+pos+1); // size without header (11) and prevTagSize (4)
+                                		ts = AMF_DecodeInt24(packetBody+pos+4);
+                                		ts |= (packetBody[pos+7]<<24);
+						
+#ifdef _DEBUG	
+						Log(LOGDEBUG, "keyframe search: FLV Packet: type %02X, dataSize: %d, timeStamp: %d ms",
+						                                                packetBody[pos], dataSize, ts);
+#endif
+						// ok, is it a keyframe!!!: well doesn't work for audio!
+						if(packetBody[pos /*6928, test 0*/] == initialFrameType /* && (packetBody[11]&0xf0) == 0x10*/) {
+							if(ts == nResumeTS) {
+								Log(LOGDEBUG, "Found keyframe with resume-keyframe timestamp!");
+								if(nInitialFrameSize != dataSize || memcmp(initialFrame, packetBody+pos+11, nInitialFrameSize) != 0) {
+									Log(LOGERROR, "FLV Stream: Keyframe doesn't match!");
+									return -2;
+								}
+								bFoundFlvKeyframe = true;
+
+								// ok, skip this packet
+								// check whether skipable:
+								if(pos+11+dataSize+4 > nPacketLen) {
+									Log(LOGWARNING, "Non skipable packet since it doesn't end with chunk, stream corrupt!");
+									return -2;
+								}
+								packetBody += (pos+11+dataSize+4);
+								nPacketLen -= (pos+11+dataSize+4);
+
+								goto stopKeyframeSearch;
+
+							} else if(nResumeTS < ts) {
+								goto stopKeyframeSearch; // the timestamp ts will only increase with further packets, wait for seek
+							}
+						} 
+                                		pos += (11+dataSize+4);
+                        		}
+					if(ts < nResumeTS) {
+						Log(LOGERROR, "First packet does not contain keyframe, all timestamps are smaller than the keyframe timestamp, so probably the resume seek failed?");
+					}
+stopKeyframeSearch:
+					;
+					if(!bFoundFlvKeyframe) {
+						Log(LOGERROR, "Couldn't find the seeked keyframe in this chunk!");
+						return 0;
+					}
+				}
+			}
+		}
+		
+		if(bResume && packet->m_nTimeStamp > 0 && (bFoundFlvKeyframe || bFoundKeyframe)) {
+			// another problem is that the server can actually change from 09/08 video/audio packets to an FLV stream
+			// or vice versa and our keyframe check will prevent us from going along with the new stream if we resumed
+			//
+			// in this case set the 'found keyframe' variables to true
+			// We assume that if we found one keyframe somewhere and were already beyond TS > 0 we have written
+			// data to the output which means we can accept all forthcoming data inclusing the change between 08/09 <-> FLV
+			// packets
+			bFoundFlvKeyframe = true;
+			bFoundKeyframe = true;
+		}
+
+                // skip till we find out keyframe (seeking might put us somewhere before it)
+		if(bResume && !bFoundKeyframe && packet->m_packetType != 0x16) {
+                        Log(LOGWARNING, "Stream does not start with requested frame, ignoring data... ");
+                        nIgnoredFrameCounter++;
+                        if(nIgnoredFrameCounter > MAX_IGNORED_FRAMES)
+                                return -2; // fatal error, couldn't continue stream
+                        return 0;
+                }
+		// ok, do the same for FLV streams
+		if(bResume && !bFoundFlvKeyframe && packet->m_packetType == 0x16) {
+                        Log(LOGWARNING, "Stream does not start with requested FLV frame, ignoring data... ");
+                        nIgnoredFlvFrameCounter++;
+                        if(nIgnoredFlvFrameCounter > MAX_IGNORED_FRAMES)
+                                return -2;
+                        return 0;
+                }	
+
+		// if bResume, we continue a stream, we have to ignore the 0ms frames since these are the first keyframes, we've got these
+		// so don't mess around with multiple copies sent by the server to us! (if the keyframe is found at a later position
+		// there is only one copy and it will be ignored by the preceding if clause)
+		if(!bStopIgnoring && bResume && packet->m_packetType != 0x16) { // exclude type 0x16 (FLV) since it can conatin several FLV packets
+			if(packet->m_nTimeStamp == 0) {
+				return 0;
+			} else {
+				bStopIgnoring = true; // stop ignoring packets
+			}
+		}
+
+		// calculate packet size and reallocate buffer if necessary
+		unsigned int size = nPacketLen 
+			+ ((packet->m_packetType == 0x08 || packet->m_packetType == 0x09 || packet->m_packetType == 0x12) ? 11 : 0)
+			+ (packet->m_packetType != 0x16 ? 4 : 0);
+		
+		if(size+4 > len) { // the extra 4 is for the case of an FLV stream without a last prevTagSize (we need extra 4 bytes to append it)
+			*buf = (char *)realloc(*buf, size+4);
+			if(*buf == 0) {
+				Log(LOGERROR, "Couldn't reallocate memory!");
+				return -1; // fatal error
+			}
+		}
+		char *ptr = *buf;
+
+		uint32_t nTimeStamp = 0; // use to return timestamp of last processed packet
+
+		// audio (0x08), video (0x09) or metadata (0x12) packets :
+		// construct 11 byte header then add rtmp packet's data
+		if(packet->m_packetType == 0x08 || packet->m_packetType == 0x09 || packet->m_packetType == 0x12)
+		{
+			// set data type
+			*dataType |= (((packet->m_packetType == 0x08)<<2)|(packet->m_packetType == 0x09));
+
+			nTimeStamp = nResumeTS + packet->m_nTimeStamp;
+			prevTagSize = 11 + nPacketLen;
+
+			*ptr = packet->m_packetType;
+			ptr++;
+			ptr += AMF_EncodeInt24(ptr, nPacketLen);
+
+			/*if(packet.m_packetType == 0x09) { // video
+
+				// H264 fix:
+				if((packetBody[0] & 0x0f) == 7) { // CodecId = H264
+					uint8_t packetType = *(packetBody+1);
+					
+					uint32_t ts = AMF_DecodeInt24(packetBody+2); // composition time
+					int32_t cts = (ts+0xff800000)^0xff800000;
+					Log(LOGDEBUG, "cts  : %d\n", cts);
+
+					nTimeStamp -= cts;
+					// get rid of the composition time
+					CRTMP::EncodeInt24(packetBody+2, 0);
+				}
+				Log(LOGDEBUG, "VIDEO: nTimeStamp: 0x%08X (%d)\n", nTimeStamp, nTimeStamp);
+			}*/
+
+			ptr += AMF_EncodeInt24(ptr, nTimeStamp);
+			*ptr = (char)((nTimeStamp & 0xFF000000) >> 24);
+			ptr++;
+
+			// stream id
+			ptr += AMF_EncodeInt24(ptr, 0);
+		}
+
+		memcpy(ptr, packetBody, nPacketLen);
+		unsigned int len = nPacketLen;
+		
+		// correct tagSize and obtain timestamp if we have an FLV stream
+		if(packet->m_packetType == 0x16) 
+		{
+			unsigned int pos=0;
+
+                        while(pos+11 < nPacketLen) 
+			{
+				uint32_t dataSize = AMF_DecodeInt24(packetBody+pos+1); // size without header (11) and without prevTagSize (4)
+                                nTimeStamp = AMF_DecodeInt24(packetBody+pos+4);
+                                nTimeStamp |= (packetBody[pos+7]<<24);
+
+				/*
+				CRTMP::EncodeInt24(ptr+pos+4, nTimeStamp);
+				ptr[pos+7] = (nTimeStamp>>24)&0xff;//*/
+
+				// set data type
+				*dataType |= (((*(packetBody+pos) == 0x08)<<2)|(*(packetBody+pos) == 0x09));
+
+                                if(pos+11+dataSize+4 > nPacketLen) {
+					if(pos+11+dataSize > nPacketLen) {
+						Log(LOGERROR, "Wrong data size (%lu), stream corrupted, aborting!", dataSize);
+						return -2;
+					}
+                                	Log(LOGWARNING, "No tagSize found, appending!");
+                                                
+					// we have to append a last tagSize!
+                                        prevTagSize = dataSize+11;
+                                        AMF_EncodeInt32(ptr+pos+11+dataSize, prevTagSize);
+                                        size+=4; len+=4;
+                                } else {
+                                        prevTagSize = AMF_DecodeInt32(packetBody+pos+11+dataSize);
+                                        
+#ifdef _DEBUG
+					Log(LOGDEBUG, "FLV Packet: type %02X, dataSize: %lu, tagSize: %lu, timeStamp: %lu ms",
+                                                (unsigned char)packetBody[pos], dataSize, prevTagSize, nTimeStamp);
+#endif
+
+                                        if(prevTagSize != (dataSize+11)) {
+#ifdef _DEBUG
+						Log(LOGWARNING, "Tag and data size are not consitent, writing tag size according to dataSize+11: %d", dataSize+11);
+#endif
+
+						prevTagSize = dataSize+11;
+                                                AMF_EncodeInt32(ptr+pos+11+dataSize, prevTagSize);
+                                        }
+                                }
+
+                                pos += prevTagSize+4;//(11+dataSize+4);
+			}
+		}
+		ptr += len;
+
+		if(packet->m_packetType != 0x16) { // FLV tag packets contain their own prevTagSize
+			AMF_EncodeInt32(ptr, prevTagSize);
+			//ptr += 4;
+		}
+
+		// In non-live this nTimeStamp can contain an absolute TS.
+		// Update ext timestamp with this absolute offset in non-live mode otherwise report the relative one
+		// LogPrintf("\nDEBUG: type: %02X, size: %d, pktTS: %dms, TS: %dms, bLiveStream: %d", packet.m_packetType, nPacketLen, packet.m_nTimeStamp, nTimeStamp, bLiveStream);
+		if(tsm)
+ 			*tsm = bLiveStream ? packet->m_nTimeStamp : nTimeStamp;
+
+
+		return size;
+	}
+
+	return -1; // no more media packets
+}
+
+int OpenResumeFile(const char *flvFile,        // file name [in]
+		   FILE **file,                // opened file [out]
+		   off_t *size,                // size of the file [out]
+		   char **metaHeader,          // meta data read from the file [out]
+		   uint32_t *nMetaHeaderSize,  // length of metaHeader [out]
+		   double *duration)           // duration of the stream in ms [out]
+{
+	const size_t bufferSize = 1024;
+	char buffer[bufferSize];
+
+	*nMetaHeaderSize = 0;
+	*size = 0;
+
+	*file = fopen(flvFile, "r+b");
+	if (!*file)
+		return RD_SUCCESS; // RD_SUCCESS, because we go to fresh file mode instead of quiting
+	
+	fseek(*file, 0, SEEK_END);
+	*size = ftello(*file);
+	fseek(*file, 0, SEEK_SET);
+
+	if(*size > 0) {
+		// verify FLV format and read header 
+		uint32_t prevTagSize = 0;
+
+		// check we've got a valid FLV file to continue!
+		if(fread(buffer, 1, 13, *file) != 13) {
+			Log(LOGERROR, "Couldn't read FLV file header!");
+			return RD_FAILED;
+		}
+		if(buffer[0] != 'F' || buffer[1] != 'L' || buffer[2] != 'V' || buffer[3] != 0x01) {
+			Log(LOGERROR, "Inavlid FLV file!");
+			return RD_FAILED;
+		}
+
+		if((buffer[4]&0x05) == 0) {
+			Log(LOGERROR, "FLV file contains neither video nor audio, aborting!");
+			return RD_FAILED;
+		}
+	
+		uint32_t dataOffset = AMF_DecodeInt32(buffer+5);
+		fseek(*file, dataOffset, SEEK_SET);
+
+		if(fread(buffer, 1, 4, *file) != 4) {
+			Log(LOGERROR, "Invalid FLV file: missing first prevTagSize!");
+			return RD_FAILED;
+		}
+		prevTagSize = AMF_DecodeInt32(buffer);
+		if(prevTagSize != 0) {
+			Log(LOGWARNING, "First prevTagSize is not zero: prevTagSize = 0x%08X", prevTagSize);
+		}
+
+		// go through the file to find the meta data!
+		off_t pos = dataOffset+4;
+		bool bFoundMetaHeader = false;
+
+		while(pos < *size-4 && !bFoundMetaHeader) {
+			fseeko(*file, pos, SEEK_SET);
+			if(fread(buffer, 1, 4, *file)!=4)
+				break;
+
+			uint32_t dataSize = AMF_DecodeInt24(buffer+1);
+				
+			if(buffer[0] == 0x12) {
+				if (dataSize > bufferSize) {
+					Log(LOGERROR, "%s: dataSize (%d) > bufferSize (%d)", __FUNCTION__, dataSize, bufferSize);
+					return RD_FAILED;
+				}
+
+				fseeko(*file, pos+11, SEEK_SET);
+				if(fread(buffer, 1, dataSize, *file) != dataSize)
+					break;
+				
+				AMFObject metaObj;
+				int nRes = AMF_Decode(&metaObj, buffer, dataSize, false);
+				if(nRes < 0) {
+					Log(LOGERROR, "%s, error decoding meta data packet", __FUNCTION__);
+					break;
+				}
+					
+				AVal metastring;
+				AMFProp_GetString(AMF_GetProp(&metaObj, NULL, 0), &metastring);
+
+                               	if(AVMATCH(&metastring, &av_onMetaData)) {
+					AMF_Dump(&metaObj);
+						
+					*nMetaHeaderSize = dataSize;
+					if (*metaHeader) free(*metaHeader);
+					*metaHeader = (char *)malloc(*nMetaHeaderSize);
+					memcpy(*metaHeader, buffer, *nMetaHeaderSize);
+
+					// get duration
+					AMFObjectProperty prop;
+					if(RTMP_FindFirstMatchingProperty(&metaObj, &av_duration, &prop)) {
+						*duration = AMFProp_GetNumber(&prop);
+						Log(LOGDEBUG, "File has duration: %f", *duration);
+					}
+
+					bFoundMetaHeader = true;
+					break;
+				}
+				//metaObj.Reset();
+				//delete obj;
+			}
+			pos += (dataSize+11+4);
+		}
+
+		if(!bFoundMetaHeader)
+			Log(LOGWARNING, "Couldn't locate meta data!");
+	}
+
+	return RD_SUCCESS;
+}
+
+int GetLastKeyframe(FILE *file,                   // output file [in]
+		    int nSkipKeyFrames,           // max number of frames to skip when searching for key frame [in]
+		    uint32_t *dSeek,              // offset of the last key frame [out]
+		    char **initialFrame,          // content of the last keyframe [out]
+		    int *initialFrameType,        // initial frame type (audio/video) [out]
+		    uint32_t *nInitialFrameSize)  // length of initialFrame [out]
+{
+	const size_t bufferSize = 16;
+	char buffer[bufferSize];
+        uint8_t dataType;
+	bool bAudioOnly;
+	off_t size;
+
+	fseek(file, 0, SEEK_END);
+	size = ftello(file);
+
+        fseek(file, 4, SEEK_SET);
+        fread(&dataType, sizeof(uint8_t), 1, file);
+        bAudioOnly = (dataType & 0x4) && !(dataType & 0x1);
+
+	Log(LOGDEBUG, "bAudioOnly: %d, size: %llu", bAudioOnly, (unsigned long long)size);
+
+	// ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams) 
+
+		//if(!bAudioOnly) // we have to handle video/video+audio different since we have non-seekable frames
+		//{
+			// find the last seekable frame
+			off_t tsize = 0;
+			uint32_t prevTagSize = 0;
+
+			// go through the file and find the last video keyframe
+			do {
+				int xread;
+skipkeyframe:
+				if(size-tsize < 13) {
+					Log(LOGERROR, "Unexpected start of file, error in tag sizes, couldn't arrive at prevTagSize=0");
+					return RD_FAILED;
+				}
+				fseeko(file, size-tsize-4, SEEK_SET);
+				xread = fread(buffer, 1, 4, file);
+				if(xread != 4) {
+					Log(LOGERROR, "Couldn't read prevTagSize from file!");
+					return RD_FAILED;
+				}
+
+				prevTagSize = AMF_DecodeInt32(buffer);
+				//Log(LOGDEBUG, "Last packet: prevTagSize: %d", prevTagSize);
+				
+				if(prevTagSize == 0) {
+					Log(LOGERROR, "Couldn't find keyframe to resume from!");
+					return RD_FAILED;
+				}
+
+				if(prevTagSize < 0 || prevTagSize > size-4-13) {
+					Log(LOGERROR, "Last tag size must be greater/equal zero (prevTagSize=%d) and smaller then filesize, corrupt file!", prevTagSize);
+					return RD_FAILED;
+				}
+				tsize += prevTagSize+4;
+
+				// read header
+				fseeko(file, size-tsize, SEEK_SET);
+				if(fread(buffer, 1, 12, file) != 12) {
+					Log(LOGERROR, "Couldn't read header!");
+					return RD_FAILED;
+				}
+				//*
+#ifdef _DEBUG
+				uint32_t ts = AMF_DecodeInt24(buffer+4);
+				ts |= (buffer[7]<<24);
+				Log(LOGDEBUG, "%02X: TS: %d ms", buffer[0], ts);
+#endif	//*/
+
+				// this just continues the loop whenever the number of skipped frames is > 0,
+				// so we look for the next keyframe to continue with
+				//
+				// this helps if resuming from the last keyframe fails and one doesn't want to start
+				// the download from the beginning
+				//
+				if(nSkipKeyFrames > 0 && !(!bAudioOnly && (buffer[0] != 0x09 || (buffer[11]&0xf0) != 0x10))) {
+					#ifdef _DEBUG
+					Log(LOGDEBUG, "xxxxxxxxxxxxxxxxxxxxxxxx Well, lets go one more back!");
+					#endif
+					nSkipKeyFrames--;
+					goto skipkeyframe;
+				}
+
+			} while(
+				(bAudioOnly && buffer[0] != 0x08) ||
+				(!bAudioOnly && (buffer[0] != 0x09 || (buffer[11]&0xf0) != 0x10))
+				); // as long as we don't have a keyframe / last audio frame
+		
+			// save keyframe to compare/find position in stream
+			*initialFrameType = buffer[0];
+			*nInitialFrameSize = prevTagSize-11;
+			*initialFrame = (char *)malloc(*nInitialFrameSize);
+			
+			fseeko(file, size-tsize+11, SEEK_SET);
+			if(fread(*initialFrame, 1, *nInitialFrameSize, file) != *nInitialFrameSize) {
+				Log(LOGERROR, "Couldn't read last keyframe, aborting!");
+				return RD_FAILED;
+			}
+
+			*dSeek = AMF_DecodeInt24(buffer+4); // set seek position to keyframe tmestamp
+			*dSeek |= (buffer[7]<<24);
+		//} 
+		//else // handle audio only, we can seek anywhere we'd like
+		//{
+		//}
+
+		if(*dSeek < 0) {
+			Log(LOGERROR, "Last keyframe timestamp is negative, aborting, your file is corrupt!");
+			return RD_FAILED;
+		}
+		Log(LOGDEBUG,"Last keyframe found at: %d ms, size: %d, type: %02X", *dSeek, *nInitialFrameSize, *initialFrameType);
+
+		/*
+		// now read the timestamp of the frame before the seekable keyframe:
+		fseeko(file, size-tsize-4, SEEK_SET);
+		if(fread(buffer, 1, 4, file) != 4) {
+			Log(LOGERROR, "Couldn't read prevTagSize from file!");
+			goto start;
+		}
+		uint32_t prevTagSize = RTMP_LIB::AMF_DecodeInt32(buffer);
+		fseeko(file, size-tsize-4-prevTagSize+4, SEEK_SET);
+		if(fread(buffer, 1, 4, file) != 4) {
+			Log(LOGERROR, "Couldn't read previous timestamp!");
+			goto start;
+                }
+		uint32_t timestamp = RTMP_LIB::AMF_DecodeInt24(buffer);
+		timestamp |= (buffer[3]<<24);
+
+		Log(LOGDEBUG, "Previuos timestamp: %d ms", timestamp);
+		*/
+
+		if(*dSeek != 0) {
+			// seek to position after keyframe in our file (we will ignore the keyframes resent by the server
+			// since they are sent a couple of times and handling this would be a mess)
+			fseeko(file, size-tsize+prevTagSize+4, SEEK_SET);
+			
+			// make sure the WriteStream doesn't write headers and ignores all the 0ms TS packets
+			// (including several meta data headers and the keyframe we seeked to)
+			//bNoHeader = true; if bResume==true this is true anyway
+		}
+
+	//}
+
+	return RD_SUCCESS;
+}
+
+int Download(RTMP *rtmp,                      // connected RTMP object
+	     FILE *file,
+	     uint32_t dSeek, 
+	     uint32_t dLength, 
+	     double duration, 
+	     bool bResume,
+	     char *metaHeader, 
+	     uint32_t nMetaHeaderSize, 
+	     char *initialFrame, 
+	     int initialFrameType, 
+	     uint32_t nInitialFrameSize,
+	     int nSkipKeyFrames,
+	     bool bStdoutMode,
+	     bool bLiveStream,
+	     bool bHashes,
+	     bool bOverrideBufferTime,
+	     uint32_t bufferTime,
+	     double *percent)                  // percentage downloaded [out]
+{
+	uint32_t timestamp = dSeek;
+	int32_t now, lastUpdate;
+	uint8_t dataType = 0;    // will be written into the FLV header (position 4)
+	int bufferSize = 1024*1024;
+	char *buffer = (char *)malloc(bufferSize);
+        int nRead = 0;
+	off_t size = ftello(file);
+	unsigned long lastPercent = 0;
+
+	memset(buffer, 0, bufferSize);
+
+	*percent = 0.0;
+
+	if(timestamp) {
+		Log(LOGDEBUG, "Continuing at TS: %d ms\n", timestamp);
+	}
+
+	if(bLiveStream) {
+		LogPrintf("Starting Live Stream\n");
+	} else {
+		// print initial status
+		// Workaround to exit with 0 if the file is fully (> 99.9%) downloaded
+		if( duration > 0 ) {
+			if  ((double)timestamp >= (double)duration*999.0 ) {
+				LogPrintf("Already Completed at: %.3f sec Duration=%.3f sec\n", (double)timestamp/1000.0, (double)duration/1000.0);
+				return RD_SUCCESS;
+			} else {
+				*percent = ((double)timestamp) / (duration*1000.0)*100.0;
+				*percent = ((double)(int)(*percent*10.0))/10.0;
+				LogPrintf("%s download at: %.3f kB / %.3f sec (%.1f%%)\n",
+					bResume ? "Resuming":"Starting",
+					(double)size/1024.0, (double)timestamp/1000.0, *percent);
+			}
+		} else {
+			LogPrintf("%s download at: %.3f kB\n", bResume ? "Resuming":"Starting",(double)size/1024.0);
+		}
+	}
+
+	if (dLength > 0)
+		LogPrintf("For duration: %.3f sec\n", (double)dLength/1000.0);
+
+	// write FLV header if not resuming
+	if(!bResume) {
+		nRead = WriteHeader(&buffer, bufferSize);
+		if(nRead > 0) {
+			if(fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t)nRead) {
+				Log(LOGERROR, "%s: Failed writing FLV header, exiting!", __FUNCTION__);
+				free(buffer);
+				return RD_FAILED;
+			}
+			size += nRead;
+		} else {
+			Log(LOGERROR, "Couldn't obtain FLV header, exiting!");
+			free(buffer);
+			return RD_FAILED;
+		}
+	}
+
+	now = RTMP_GetTime();
+	lastUpdate = now-1000;
+	do
+	{
+		nRead = WriteStream(rtmp, &buffer, bufferSize, &timestamp, bResume && nInitialFrameSize > 0, bLiveStream, dSeek, metaHeader, nMetaHeaderSize, initialFrame, initialFrameType, nInitialFrameSize, &dataType);
+
+		//LogPrintf("nRead: %d\n", nRead);
+		if(nRead > 0) {
+			if(fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t)nRead) {
+				Log(LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__);
+				free(buffer);
+				return RD_FAILED;
+                        }
+			size += nRead;
+	
+			//LogPrintf("write %dbytes (%.1f kB)\n", nRead, nRead/1024.0);
+			if(duration <= 0) // if duration unknown try to get it from the stream (onMetaData)
+				duration = RTMP_GetDuration(rtmp);
+
+			if(duration > 0) {
+				// make sure we claim to have enough buffer time!
+				if(!bOverrideBufferTime && bufferTime < (duration*1000.0)) {
+					bufferTime = (uint32_t)(duration*1000.0)+5000; // extra 5sec to make sure we've got enough
+					
+					Log(LOGDEBUG, "Detected that buffer time is less than duration, resetting to: %dms", bufferTime);
+					RTMP_SetBufferMS(rtmp, bufferTime);
+					RTMP_UpdateBufferMS(rtmp);
+				}
+				*percent = ((double)timestamp) / (duration*1000.0)*100.0;
+				*percent = ((double)(int)(*percent*10.0))/10.0;
+				if (bHashes) {
+					if ( lastPercent + 1 <= *percent ) {
+						LogStatus("#");
+						lastPercent = (unsigned long)*percent;
+					}
+				} else {
+					now = RTMP_GetTime();
+					if (abs(now - lastUpdate) > 200) {
+						LogStatus("\r%.3f kB / %.2f sec (%.1f%%)", (double)size/1024.0, (double)(timestamp)/1000.0, *percent);
+						lastUpdate = now;
+					}
+				}
+			} else {
+				now = RTMP_GetTime();
+				if (abs(now - lastUpdate) > 200) {
+					if (bHashes)
+						LogStatus("#");
+					else
+						LogStatus("\r%.3f kB / %.2f sec", (double)size/1024.0, (double)(timestamp)/1000.0);
+					lastUpdate = now;
+				}
+			}
+		}
+#ifdef _DEBUG
+		else { Log(LOGDEBUG, "zero read!"); }
+#endif
+
+	} while(!bCtrlC && nRead > -1 && RTMP_IsConnected(rtmp));
+	free(buffer);
+
+	Log(LOGDEBUG, "WriteStream returned: %d", nRead);
+
+	if(bResume && nRead == -2) {
+		LogPrintf("Couldn't resume FLV file, try --skip %d\n\n", nSkipKeyFrames+1);
+		return RD_FAILED;
+	}
+
+	// finalize header by writing the correct dataType (video, audio, video+audio)
+	if(!bResume && dataType != 0x5 && !bStdoutMode) {
+		//Log(LOGDEBUG, "Writing data type: %02X", dataType);
+		fseek(file, 4, SEEK_SET);
+		fwrite(&dataType, sizeof(unsigned char), 1, file);
+	}
+
+	if(nRead == -3)
+		return RD_SUCCESS;
+
+	if((duration > 0 && *percent < 99.9) || bCtrlC || nRead < 0 || RTMP_IsTimedout(rtmp)) {
+		return RD_INCOMPLETE;
+	}
+
+	return RD_SUCCESS;
+}
+
+#define STR2AVAL(av,str)	av.av_val = str; av.av_len = strlen(av.av_val)
+
+int main(int argc, char **argv)
+{
+	extern char *optarg;
+
+	int nStatus = RD_SUCCESS;
+	double percent = 0;
+	double duration = 0.0;
+
+	int nSkipKeyFrames = 0;  // skip this number of keyframes when resuming
+
+	bool bOverrideBufferTime = false; // if the user specifies a buffer time override this is true
+	bool bStdoutMode = true;// if true print the stream directly to stdout, messages go to stderr
+	bool bResume = false;    // true in resume mode
+	uint32_t dSeek = 0;	 // seek position in resume mode, 0 otherwise
+	uint32_t bufferTime = 10*60*60*1000; // 10 hours as default
+
+	// meta header and initial frame for the resume mode (they are read from the file and compared with
+	// the stream we are trying to continue
+	char *metaHeader = 0;
+	uint32_t nMetaHeaderSize = 0;
+	
+	// video keyframe for matching
+	char *initialFrame = 0;
+	uint32_t nInitialFrameSize = 0;
+	int initialFrameType = 0; // tye: audio or video
+
+	char *hostname = 0;
+	AVal playpath = {0,0};
+	AVal subscribepath = {0,0};
+	int port = -1;
+	int protocol = RTMP_PROTOCOL_UNDEFINED;
+	bool bLiveStream = false; // is it a live stream? then we can't seek/resume
+	bool bHashes = false; // display byte counters not hashes by default
+
+	long int timeout = 120; // timeout connection after 120 seconds
+	uint32_t dStartOffset = 0; // seek position in non-live mode
+	uint32_t dStopOffset = 0;
+	uint32_t dLength = 0; // length to play from stream - calculated from seek position and dStopOffset
+
+	char *rtmpurl = 0;
+	AVal swfUrl = {0,0};
+	AVal tcUrl = {0,0};
+	AVal pageUrl = {0,0};
+	AVal app = {0,0};
+	AVal auth = {0,0};
+	AVal swfHash = {0,0};
+	uint32_t swfSize = 0;
+	AVal flashVer = {0,0};
+        char *sockshost = 0;
+
+	char *flvFile = 0;
+
+	char DEFAULT_FLASH_VER[]  = "LNX 10,0,22,87";
+	
+	signal(SIGHUP, sigIntHandler);
+	signal(SIGINT, sigIntHandler);
+	signal(SIGPIPE, sigIntHandler);
+	signal(SIGTERM, sigIntHandler);
+	signal(SIGQUIT, sigIntHandler);
+
+	/* sleep(30); */
+
+	// Check for --quiet option before printing any output
+	int index = 0;
+       	while (index < argc)
+	{
+		if ( strcmp( argv[index], "--quiet")==0 || strcmp( argv[index], "-q")==0 )
+			debuglevel = LOGCRIT;
+		index++;
+	}
+
+ 	LogPrintf("RTMPDump %s\n", RTMPDUMP_VERSION);
+	LogPrintf("(c) 2009 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL\n");
+
+	int opt;
+	struct option longopts[] = {
+		{"help",    0, NULL, 'h'},
+		{"host",    1, NULL, 'n'},
+		{"port",    1, NULL, 'c'},
+		{"socks",   1, NULL, 'S'},
+		{"protocol",1, NULL, 'l'},
+		{"playpath",1, NULL, 'y'},
+		{"rtmp",    1, NULL, 'r'},
+		{"swfUrl",  1, NULL, 's'},
+		{"tcUrl",   1, NULL, 't'},
+		{"pageUrl", 1, NULL, 'p'},
+		{"app",     1, NULL, 'a'},
+		{"auth",    1, NULL, 'u'},
+#ifdef CRYPTO
+		{"swfhash", 1, NULL, 'w'},
+		{"swfsize", 1, NULL, 'x'},
+#endif
+		{"flashVer",1, NULL, 'f'},
+		{"live"	   ,0, NULL, 'v'},
+		{"flv",     1, NULL, 'o'},
+		{"resume",  0, NULL, 'e'},
+		{"timeout", 1, NULL, 'm'},
+		{"buffer",  1, NULL, 'b'},
+		{"skip",    1, NULL, 'k'},
+		{"subscribe",1,NULL, 'd'},
+		{"start",   1, NULL, 'A'},
+		{"stop",    1, NULL, 'B'},
+		{"hashes",  0, NULL, '#'},
+		{"debug",   0, NULL, 'z'},
+		{"quiet",   0, NULL, 'q'},
+		{"verbose", 0, NULL, 'V'},
+		{0,0,0,0}
+	};
+
+	while((opt = getopt_long(argc, argv, "hVveqzr:s:t:p:a:b:f:o:u:n:c:l:y:m:k:d:A:B:w:x:S:#", longopts, NULL)) != -1) {
+		switch(opt) {
+			case 'h':
+				LogPrintf("\nThis program dumps the media content streamed over rtmp.\n\n");
+				LogPrintf("--help|-h               Prints this help screen.\n");
+				LogPrintf("--rtmp|-r url           URL (e.g. rtmp//hotname[:port]/path)\n");
+				LogPrintf("--host|-n hostname      Overrides the hostname in the rtmp url\n");
+				LogPrintf("--port|-c port          Overrides the port in the rtmp url\n");
+				LogPrintf("--socks|-S host:port    Use the specified SOCKS proxy\n");
+				LogPrintf("--protocol|-l           Overrides the protocol in the rtmp url (0 - RTMP, 3 - RTMPE)\n");
+				LogPrintf("--playpath|-y           Overrides the playpath parsed from rtmp url\n");
+				LogPrintf("--swfUrl|-s url         URL to player swf file\n");
+				LogPrintf("--tcUrl|-t url          URL to played stream (default: \"rtmp://host[:port]/app\")\n");
+				LogPrintf("--pageUrl|-p url        Web URL of played programme\n");
+				LogPrintf("--app|-a app            Name of player used\n");
+#ifdef CRYPTO
+				LogPrintf("--swfhash|-w hexstring  SHA256 hash of the decompressed SWF file (32 bytes)\n");
+				LogPrintf("--swfsize|-x num        Size of the decompressed SWF file, required for SWFVerification\n");
+#endif
+				LogPrintf("--auth|-u string        Authentication string to be appended to the connect string\n");
+				LogPrintf("--flashVer|-f string    Flash version string (default: \"%s\")\n", DEFAULT_FLASH_VER);
+				LogPrintf("--live|-v               Save a live stream, no --resume (seeking) of live streams possible\n");
+				LogPrintf("--subscribe|-d string   Stream name to subscribe to (otherwise defaults to playpath if live is specifed)\n");
+				LogPrintf("--flv|-o string         FLV output file name, if the file name is - print stream to stdout\n");
+				LogPrintf("--resume|-e             Resume a partial RTMP download\n");
+				LogPrintf("--timeout|-m num        Timeout connection num seconds (default: %lu)\n", timeout);
+				LogPrintf("--start|-A num          Start at num seconds into stream (not valid when using --live)\n");
+				LogPrintf("--stop|-B num           Stop at num seconds into stream\n");
+				LogPrintf("--hashes|-#             Display progress with hashes, not with the byte counter\n");
+				LogPrintf("--buffer|-b             Buffer time in milliseconds (default: %lu), this option makes only sense in stdout mode (-o -)\n", 
+					bufferTime);
+				LogPrintf("--skip|-k num           Skip num keyframes when looking for last keyframe to resume from. Useful if resume fails (default: %d)\n\n",
+					nSkipKeyFrames);
+				LogPrintf("--quiet|-q              Supresses all command output.\n");
+				LogPrintf("--verbose|-V            Verbose command output.\n");
+				LogPrintf("--debug|-z              Debug level command output.\n");
+				LogPrintf("If you don't pass parameters for swfUrl, pageUrl, app or auth these propertiews will not be included in the connect ");
+				LogPrintf("packet.\n\n");
+				return RD_SUCCESS;
+#ifdef CRYPTO
+			case 'w':
+			{
+				int res = hex2bin(optarg, &swfHash.av_val);
+				if(res!=32) {
+					swfHash.av_val = NULL;
+					Log(LOGWARNING, "Couldn't parse swf hash hex string, not heyxstring or not 32 bytes, ignoring!");
+				}
+				swfHash.av_len = 32;
+				break;
+			}
+			case 'x':
+			{
+				int size = atoi(optarg);
+				if(size <= 0) {
+					Log(LOGERROR, "SWF Size must be at least 1, ignoring\n");
+				} else {
+					swfSize = size;
+				}
+				break;
+			}
+#endif
+			case 'k':
+				nSkipKeyFrames = atoi(optarg);
+				if(nSkipKeyFrames < 0) {
+					Log(LOGERROR, "Number of keyframes skipped must be greater or equal zero, using zero!");
+					nSkipKeyFrames = 0;
+				} else {
+					Log(LOGDEBUG, "Number of skipped key frames for resume: %d", nSkipKeyFrames);
+				}
+				break;
+			case 'b':
+			{
+				int32_t bt = atol(optarg);
+				if(bt < 0) {
+					Log(LOGERROR, "Buffer time must be greater than zero, ignoring the specified value %d!", bt);
+				} else {
+					bufferTime = bt;
+					bOverrideBufferTime = true;
+				}
+				break;
+			}
+			case 'v':
+				bLiveStream = true; // no seeking or resuming possible!
+				break;
+			case 'd':
+				STR2AVAL(subscribepath, optarg);
+				break;
+			case 'n':
+				hostname = optarg;
+				break;
+			case 'c':
+				port = atoi(optarg);
+				break;
+			case 'l':
+				protocol = atoi(optarg);
+				if(protocol != RTMP_PROTOCOL_RTMP && protocol != RTMP_PROTOCOL_RTMPE) {
+					Log(LOGERROR, "Unknown protocol specified: %d", protocol);
+					return RD_FAILED;
+				}
+				break;
+			case 'y':
+				STR2AVAL(playpath, optarg);
+				break;
+			case 'r':
+			{
+				rtmpurl = optarg;
+
+				char *parsedHost = 0;
+				unsigned int parsedPort = 0;
+				char *parsedPlaypath = 0;
+				char *parsedApp = 0;
+				int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
+
+				if(!ParseUrl(rtmpurl, &parsedProtocol, &parsedHost, &parsedPort, &parsedPlaypath, &parsedApp)) {
+					Log(LOGWARNING, "Couldn't parse the specified url (%s)!", optarg);
+				} else {
+					if(hostname == 0)
+						hostname = parsedHost;
+					if(port == -1)
+						port = parsedPort;
+					if(playpath.av_len == 0 && parsedPlaypath) {
+						STR2AVAL(playpath, parsedPlaypath);
+					}
+					if(protocol == RTMP_PROTOCOL_UNDEFINED)
+						protocol = parsedProtocol;
+					if(app.av_len == 0 && parsedApp) {
+						STR2AVAL(app, parsedApp);
+					}
+				}
+				break;
+			}
+			case 's':
+				STR2AVAL(swfUrl, optarg);
+				break;
+			case 't':
+				STR2AVAL(tcUrl, optarg);
+				break;
+			case 'p':
+				STR2AVAL(pageUrl, optarg);
+				break;
+			case 'a':
+				STR2AVAL(app, optarg);
+				break;
+			case 'f':
+				STR2AVAL(flashVer, optarg);
+				break;
+			case 'o':
+				flvFile = optarg;
+				if(strcmp(flvFile, "-"))
+					bStdoutMode = false;
+
+				break;
+			case 'e':
+				bResume = true;
+				break;
+			case 'u':
+				STR2AVAL(auth, optarg);
+				break;
+			case 'm':
+				timeout = atoi(optarg);
+				break;
+			case 'A':
+				dStartOffset = (int)(atof(optarg)*1000.0);
+				break;
+			case 'B':
+				dStopOffset = (int)(atof(optarg)*1000.0);
+				break;
+			case '#':
+				bHashes = true;
+				break;
+			case 'q':
+				debuglevel = LOGCRIT;
+				break;
+			case 'V':
+				debuglevel = LOGDEBUG;
+				break;
+			case 'z':
+				debuglevel = LOGALL;
+				break;
+                        case 'S':
+                                sockshost = optarg;
+				break;
+			default:
+				LogPrintf("unknown option: %c\n", opt);
+				break;
+		}
+	}
+
+	if(hostname == 0) {
+		Log(LOGERROR, "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
+		return RD_FAILED;
+	}
+	if(playpath.av_len == 0) {
+		Log(LOGERROR, "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
+		return RD_FAILED;
+	}
+		
+	if(port == -1) {
+		Log(LOGWARNING, "You haven't specified a port (--port) or rtmp url (-r), using default port 1935");
+		port = 1935;
+	}
+	if(port == 0) {
+		port = 1935;
+	}
+	if(protocol == RTMP_PROTOCOL_UNDEFINED) {
+		Log(LOGWARNING, "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
+		protocol = RTMP_PROTOCOL_RTMP;
+	}
+	if(flvFile == 0) {
+		Log(LOGWARNING, "You haven't specified an output file (-o filename), using stdout");
+		bStdoutMode = true;
+	}
+
+	if(bStdoutMode && bResume) {
+		Log(LOGWARNING, "Can't resume in stdout mode, ignoring --resume option");
+		bResume = false;
+	}
+
+	if(bLiveStream && bResume) {
+		Log(LOGWARNING, "Can't resume live stream, ignoring --resume option");
+		bResume = false;
+	}
+
+	if(swfHash.av_len == 0 && swfSize > 0) {
+		Log(LOGWARNING, "Ignoring SWF size, supply also the hash with --swfhash");
+		swfSize=0;
+	}
+
+	if(swfHash.av_len != 0 && swfSize == 0) {
+		Log(LOGWARNING, "Ignoring SWF hash, supply also the swf size  with --swfsize");
+		swfHash.av_len = 0;
+		swfHash.av_val = NULL;
+	}
+
+	if(flashVer.av_len == 0) {
+		STR2AVAL(flashVer, DEFAULT_FLASH_VER);
+	}
+
+	if(!InitSockets()) {
+		Log(LOGERROR, "Couldn't load sockets support on your platform, exiting!");
+		return RD_FAILED;
+	}
+
+	if(tcUrl.av_len == 0 && app.av_len != 0) {
+		char str[512]={0};
+
+		snprintf(str, 511, "%s://%s:%d/%s", RTMPProtocolStringsLower[protocol], hostname, port, app.av_val);
+		tcUrl.av_len = strlen(str);
+		tcUrl.av_val = (char *)malloc(tcUrl.av_len+1);
+		strcpy(tcUrl.av_val, str);
+	}
+
+	int first = 1;
+
+	// User defined seek offset
+	if (dStartOffset > 0) {
+	        // Live stream
+	        if (bLiveStream) {
+                	Log(LOGWARNING, "Can't seek in a live stream, ignoring --start option");
+                	dStartOffset = 0;
+                }
+        }
+
+	RTMP  rtmp = {0};
+	RTMP_Init(&rtmp);
+	RTMP_SetupStream(&rtmp, protocol, hostname, port, sockshost, &playpath, &tcUrl, &swfUrl,
+		&pageUrl, &app, &auth, &swfHash, swfSize, &flashVer, &subscribepath,
+		dSeek, 0, bLiveStream, timeout);
+
+	off_t size = 0;
+
+	// ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams) 
+	if(bResume) {
+		nStatus = OpenResumeFile(flvFile, &file, &size, &metaHeader, &nMetaHeaderSize, &duration);
+		if (nStatus == RD_FAILED)
+			goto clean;
+
+		if (!file) {
+			// file does not exist, so go back into normal mode
+			bResume = false; // we are back in fresh file mode (otherwise finalizing file won't be done)
+		} else {
+			nStatus = GetLastKeyframe(file, nSkipKeyFrames,
+						  &dSeek, &initialFrame,
+						  &initialFrameType,
+						  &nInitialFrameSize);
+			if (nStatus == RD_FAILED) {
+				Log(LOGDEBUG, "Failed to get last keyframe.");
+				goto clean;
+			}
+
+			if (dSeek == 0) {
+				Log(LOGDEBUG, "Last keyframe is first frame in stream, switching from resume to normal mode!");
+				bResume = false;
+			}
+		}
+	}
+
+	if (!file) {
+		if(bStdoutMode) {
+			file = stdout;
+			SET_BINMODE(file);
+		} else
+		{
+			file = fopen(flvFile, "w+b");
+			if(file == 0) {
+                        	LogPrintf("Failed to open file! %s\n", flvFile);
+                        	return RD_FAILED;
+                	}
+		}
+	}
+       
+#ifdef _DEBUG
+	netstackdump = fopen("netstackdump", "wb");
+	netstackdump_read = fopen("netstackdump_read", "wb");
+#endif
+
+	while (!bCtrlC) {
+		Log(LOGDEBUG, "Setting buffer time to: %dms", bufferTime);
+		RTMP_SetBufferMS(&rtmp, bufferTime);
+
+		if (first) {
+			first = 0;
+			LogPrintf("Connecting ...\n");
+
+			if (!RTMP_Connect(&rtmp)) {
+				nStatus = RD_FAILED;
+				break;
+			}
+
+			Log(LOGINFO, "Connected...");
+
+			// User defined seek offset
+			if (dStartOffset > 0) {
+				// Don't need the start offset if resuming an existing file
+				if (bResume) {
+					Log(LOGWARNING, "Can't seek a resumed stream, ignoring --start option");
+					dStartOffset = 0;
+				} else {
+					dSeek = dStartOffset;
+				}
+			}
+
+		        // Calculate the length of the stream to still play
+			if (dStopOffset > 0) {
+				dLength = dStopOffset - dSeek;
+
+				// Quit if start seek is past required stop offset
+				if(dLength <= 0) {
+					LogPrintf("Already Completed\n");
+					nStatus = RD_SUCCESS;
+					break;
+				}
+			}
+
+			if (!RTMP_ConnectStream(&rtmp, dSeek, dLength)) {
+				nStatus = RD_FAILED;
+				break;
+			}
+		} else {
+			nInitialFrameSize = 0;
+
+			Log(LOGINFO, "Connection timed out, trying to resume.\n\n");
+			if (!RTMP_ToggleStream(&rtmp)) {
+				Log(LOGERROR, "Failed to resume the stream\n\n");
+				if (!RTMP_IsTimedout(&rtmp))
+				  nStatus = RD_FAILED;
+				else
+				  nStatus = RD_INCOMPLETE;
+				break;
+			}
+			bResume = true;
+		}
+
+		nStatus = Download(&rtmp, file, dSeek, dLength, duration, bResume,
+                           metaHeader, nMetaHeaderSize, initialFrame,
+                           initialFrameType, nInitialFrameSize,
+                           nSkipKeyFrames, bStdoutMode, bLiveStream, bHashes,
+                           bOverrideBufferTime, bufferTime, &percent);
+		free(initialFrame);
+		initialFrame = NULL;
+
+		/* If we succeeded, we're done.
+		 */
+		if (nStatus != RD_INCOMPLETE || !RTMP_IsTimedout(&rtmp) || bLiveStream)
+			break;
+	}
+
+	if (nStatus == RD_SUCCESS) {
+		LogPrintf("Download complete\n");
+	} else if (nStatus == RD_INCOMPLETE)  {
+		LogPrintf("Download may be incomplete (downloaded about %.2f%%), try resuming\n", percent);
+	}
+
+clean:
+	Log(LOGDEBUG, "Closing connection.\n");
+	RTMP_Close(&rtmp);
+
+	if(file != 0)
+		fclose(file);
+
+	CleanupSockets();
+
+#ifdef _DEBUG
+	if(netstackdump != 0)
+		fclose(netstackdump);
+	if(netstackdump_read != 0)
+                fclose(netstackdump_read);	
+#endif
+	return nStatus;
+}
+


More information about the rtmpdump mailing list