--- /dev/null
+/*
+ * Copyright (C) 2005-2008 Team XBMC
+ * http://www.xbmc.org
+ * Copyright (C) 2008-2009 Andrej Stepanchuk
+ *
+ * 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 <stdio.h>
+//#include <time.h>
+//#include <arpa/inet.h>
+
+#ifdef WIN32
+#include <winsock.h> // for htons
+#endif
+
+#include "AMFObject.h"
+#include "log.h"
+#include "rtmp.h"
+#include "bytes.h"
+
+RTMP_LIB::AMFObjectProperty RTMP_LIB::AMFObject::m_invalidProp;
+
+RTMP_LIB::AMFObjectProperty::AMFObjectProperty()
+{
+ Reset();
+}
+
+RTMP_LIB::AMFObjectProperty::AMFObjectProperty(const std::string & strName, double dValue)
+{
+ Reset();
+}
+
+RTMP_LIB::AMFObjectProperty::AMFObjectProperty(const std::string & strName, bool bValue)
+{
+ Reset();
+}
+
+RTMP_LIB::AMFObjectProperty::AMFObjectProperty(const std::string & strName, const std::string & strValue)
+{
+ Reset();
+}
+
+RTMP_LIB::AMFObjectProperty::AMFObjectProperty(const std::string & strName, const AMFObject & objValue)
+{
+ Reset();
+}
+
+RTMP_LIB::AMFObjectProperty::~ AMFObjectProperty()
+{
+}
+
+const std::string &RTMP_LIB::AMFObjectProperty::GetPropName() const
+{
+ return m_strName;
+}
+
+void RTMP_LIB::AMFObjectProperty::SetPropName(const std::string& strName)
+{
+ m_strName = strName;
+}
+
+RTMP_LIB::AMFDataType RTMP_LIB::AMFObjectProperty::GetType() const
+{
+ return m_type;
+}
+
+double RTMP_LIB::AMFObjectProperty::GetNumber() const
+{
+ return m_dNumVal;
+}
+
+bool RTMP_LIB::AMFObjectProperty::GetBoolean() const
+{
+ return m_dNumVal != 0;
+}
+
+const std::string &RTMP_LIB::AMFObjectProperty::GetString() const
+{
+ return m_strVal;
+}
+
+const RTMP_LIB::AMFObject &RTMP_LIB::AMFObjectProperty::GetObject() const
+{
+ return m_objVal;
+}
+
+bool RTMP_LIB::AMFObjectProperty::IsValid() const
+{
+ return (m_type != AMF_INVALID);
+}
+
+int RTMP_LIB::AMFObjectProperty::Encode(char * pBuffer, int nSize) const
+{
+ int nBytes = 0;
+
+ if (m_type == AMF_INVALID)
+ return -1;
+
+ if (m_type != AMF_NULL && nSize < (int)m_strName.size() + (int)sizeof(short) + 1)
+ return -1;
+
+ if (m_type != AMF_NULL && !m_strName.empty())
+ {
+ nBytes += EncodeName(pBuffer);
+ pBuffer += nBytes;
+ nSize -= nBytes;
+ }
+
+ switch (m_type)
+ {
+ case AMF_NUMBER:
+ if (nSize < 9)
+ return -1;
+ nBytes += RTMP_LIB::CRTMP::EncodeNumber(pBuffer, GetNumber());
+ break;
+
+ case AMF_BOOLEAN:
+ if (nSize < 2)
+ return -1;
+ nBytes += RTMP_LIB::CRTMP::EncodeBoolean(pBuffer, GetBoolean());
+ break;
+
+ case AMF_STRING:
+ if (nSize < (int)m_strVal.size() + (int)sizeof(short))
+ return -1;
+ nBytes += RTMP_LIB::CRTMP::EncodeString(pBuffer, GetString());
+ break;
+
+ case AMF_NULL:
+ if (nSize < 1)
+ return -1;
+ *pBuffer = 0x05;
+ nBytes += 1;
+ break;
+
+ case AMF_OBJECT:
+ {
+ int nRes = m_objVal.Encode(pBuffer, nSize);
+ if (nRes == -1)
+ return -1;
+
+ nBytes += nRes;
+ break;
+ }
+ default:
+ Log(LOGERROR,"%s, invalid type. %d", __FUNCTION__, m_type);
+ return -1;
+ };
+
+ return nBytes;
+}
+
+//* TODO AMF3
+#define AMF3_INTEGER_MAX 268435455
+#define AMF3_INTEGER_MIN -268435456
+
+int AMF3ReadInteger(const char *data, int32_t *val)
+{
+ //LogHex(data, 4);
+ int i=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];
+ }
+
+ //Log(LOGDEBUG, "%s, AMF3 integer: %d, size: %d", __FUNCTION__, *val, i>2 ? 4 : i+1);
+
+ return i>2 ? 4 : i+1;
+}
+
+int AMF3ReadString(const char *data, char **pStr)
+{
+ assert(pStr != 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);
+
+ //Log(LOGDEBUG, "AMF3 String, len: %d, data: |%s|", nSize, data);
+
+ (*pStr) = new char[nSize+1];
+ memcpy(*pStr, data, nSize);
+ (*pStr)[nSize]=0;
+
+ return len+nSize;
+ }
+ return len;
+}
+
+int RTMP_LIB::AMFObjectProperty::AMF3Decode(const char * pBuffer, int nSize, bool bDecodeName)
+{
+ int nOriginalSize = nSize;
+
+ if (nSize == 0 || !pBuffer) {
+ Log(LOGDEBUG,"empty buffer/no buffer pointer!");
+ return -1;
+ }
+
+ //Log(LOGDEBUG, "Decoding property:");
+ //LogHex(pBuffer, nSize);
+
+ // decode name
+ if(bDecodeName) {
+ char *name;
+ int nRes = AMF3ReadString(pBuffer, &name);
+
+ if(strlen(name) <= 0)
+ return nRes;
+
+ //Log(LOGDEBUG, "AMF3 Property name: |%s|, size: %d", name, strlen(name));
+ m_strName = name;
+ pBuffer += nRes;
+ nSize -= nRes;
+ }
+
+ // decode
+ uint8_t type = *pBuffer;
+
+ nSize--;
+ pBuffer++;
+
+ switch(type)
+ {
+ case 0x00: // AMF3_UNDEFINED
+ case 0x01: // AMF3_NULL
+ //Log(LOGDEBUG, "AMF3_UNDEFINED/NULL");
+ m_type = AMF_NULL;
+ break;
+ case 0x02: // AMF3_FALSE
+ //Log(LOGDEBUG, "AMF3_FALSE");
+ m_type = AMF_BOOLEAN;
+ m_dNumVal = 0.0;
+ break;
+ case 0x03: // AMF3_TRUE
+ //Log(LOGDEBUG, "AMF3_TRUE");
+ m_type = AMF_BOOLEAN;
+ m_dNumVal = 1.0;
+ break;
+ case 0x04: // AMF3_INTEGER
+ {
+ int32_t res = 0;
+ int len = AMF3ReadInteger(pBuffer, &res);
+ //Log(LOGDEBUG, "AMF3_INTEGER: %d", res);
+
+ m_type = AMF_NUMBER;
+
+ nSize -= len;
+ m_dNumVal = (double)res;
+ break;
+ }
+ case 0x0A: // AMF3_OBJECT
+ {
+ Log(LOGDEBUG, "AMF3_OBJECT");
+
+ int nRes = m_objVal.AMF3Decode(pBuffer, nSize, true);
+ if(nRes == -1)
+ return -1;
+ nSize -= nRes;
+ m_type = AMF_OBJECT;
+ break;
+ }
+ case 0x06: // AMF3_STRING
+ {
+ //Log(LOGDEBUG, "AMF3_STRING");
+ //LogHex(pBuffer, nSize);
+
+ char *str = 0;
+ int len = AMF3ReadString(pBuffer, &str);
+ //Log(LOGDEBUG, "AMF3_STRING: %s", str);
+ m_strVal = str;
+ delete [] str;
+ m_type = AMF_STRING;
+ nSize -= len;
+ break;
+ }
+ case 0x0B: // AMF3_XML_STRING, not tested
+ case 0x07: // AMF3_XML_DOC
+ {
+ Log(LOGDEBUG, "AMF3_XML_DOC");
+
+ char *str = 0;
+ int len = AMF3ReadString(pBuffer, &str);
+ m_strVal = str;
+ delete [] str;
+ m_type = AMF_STRING;
+ nSize -= len;
+ break;
+ }
+ case 0x05: // AMF3_NUMBER
+ Log(LOGDEBUG, "AMF3_NUMBER");
+ if (nSize < 8)
+ return -1;
+ m_dNumVal = ReadNumber(pBuffer);
+ nSize -= 8;
+ m_type = AMF_NUMBER;
+ break;
+ case 0x08: // AMF3_DATE, not tested
+ {
+ 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;
+
+ m_dNumVal = ReadNumber(pBuffer);
+ nSize -= 8;
+ m_type = AMF_NUMBER;
+ }
+ break;
+ }
+ case 0x09: // AMF3_ARRAY
+ case 0x0C: // 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 RTMP_LIB::AMFObjectProperty::Decode(const char * pBuffer, int nSize, bool bDecodeName)
+{
+ int nOriginalSize = nSize;
+
+ 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 = RTMP_LIB::CRTMP::ReadInt16(pBuffer);
+ if (nNameSize > nSize - 2) {
+ Log(LOGDEBUG,"%s: Name size out of range: namesize (%d) > len (%d) - 2", __FUNCTION__, nNameSize, nSize);
+ return -1;
+ }
+
+ m_strName = RTMP_LIB::CRTMP::ReadString(pBuffer);
+ nSize -= 2 + m_strName.size();
+ pBuffer += 2 + m_strName.size();
+
+ //Log(LOGDEBUG, "%s: Decoded name: %s", __FUNCTION__, m_strName.c_str());
+ }
+
+ if (nSize == 0) {
+ return -1;
+ }
+
+ nSize--;
+
+ switch (*pBuffer)
+ {
+ case 0x00: // AMF_NUMBER:
+ if (nSize < 8)
+ return -1;
+ m_dNumVal = ReadNumber(pBuffer+1);
+ nSize -= 8;
+ m_type = AMF_NUMBER;
+ break;
+ case 0x01: // AMF_BOOLEAN:
+ if (nSize < 1)
+ return -1;
+ m_dNumVal = (double)RTMP_LIB::CRTMP::ReadBool(pBuffer+1);
+ nSize--;
+ m_type = AMF_BOOLEAN;
+ break;
+ case 0x02: // AMF_STRING:
+ {
+ unsigned short nStringSize = RTMP_LIB::CRTMP::ReadInt16(pBuffer+1);
+ //Log(LOGDEBUG, "Read string, len: %d\n", nStringSize);
+ //LogHex(pBuffer, nSize);
+
+ if (nSize < (long)nStringSize + 2)
+ return -1;
+ m_strVal = RTMP_LIB::CRTMP::ReadString(pBuffer+1);
+ nSize -= (2 + nStringSize);
+ m_type = AMF_STRING;
+ break;
+ }
+ case 0x03: // AMF_OBJECT:
+ {
+ int nRes = m_objVal.Decode(pBuffer+1, nSize, true);
+ if (nRes == -1)
+ return -1;
+ nSize -= nRes;
+ m_type = AMF_OBJECT;
+ break;
+ }
+ case 0x04: // AMF_MOVIE_CLIP
+ {
+ Log(LOGERROR, "AMF_MOVIE_CLIP not supported!");
+ return -1;
+ break;
+ }
+ case 0x07: // AMF_REFERENCE
+ {
+ Log(LOGERROR, "AMF_REFERENCE not supported!");
+ return -1;
+ break;
+ }
+ case 0x0A: // AMF_ARRAY
+ {
+ unsigned int nArrayLen = RTMP_LIB::CRTMP::ReadInt32(pBuffer+1);
+ nSize -= 4;
+
+ int nRes = m_objVal.DecodeArray(pBuffer+5, nSize, nArrayLen, false);
+ if (nRes == -1)
+ return -1;
+ nSize -= nRes;
+ m_type = AMF_OBJECT;
+ break;
+ }
+ case 0x08: // AMF_MIXEDARRAY
+ {
+ //int nMaxIndex = RTMP_LIB::CRTMP::ReadInt32(pBuffer+1); // can be zero for unlimited
+ nSize -= 4;
+
+ // next comes the rest, mixed array has a final 0x000009 mark and names, so its an object
+ int nRes = m_objVal.Decode(pBuffer+5, nSize, true);
+ if (nRes == -1)
+ return -1;
+ nSize -= nRes;
+ m_type = AMF_OBJECT;
+ break;
+ }
+ case 0x05: /* AMF_NULL */
+ case 0x06: /* AMF_UNDEFINED */
+ case 0x0D: /* AMF_UNSUPPORTED */
+ m_type = AMF_NULL;
+ break;
+ case 0x0B: // AMF_DATE
+ {
+ Log(LOGDEBUG, "AMF_DATE");
+
+ if (nSize < 10)
+ return -1;
+
+ m_dNumVal = ReadNumber(pBuffer+1);
+ m_nUTCOffset = RTMP_LIB::CRTMP::ReadInt16(pBuffer+9);
+
+ m_type = AMF_DATE;
+ nSize -= 10;
+ break;
+ }
+ case 0x0C: // AMF_LONG_STRING
+ {
+ Log(LOGWARNING, "AMF_LONG_STRING not tested!");
+
+ unsigned int nStringSize = RTMP_LIB::CRTMP::ReadInt32(pBuffer+1);;
+ if (nSize < (long)nStringSize + 4)
+ return -1;
+ m_strVal = RTMP_LIB::CRTMP::ReadString(pBuffer+1);
+ nSize -= (4 + nStringSize);
+ m_type = AMF_STRING;
+ break;
+ }
+ case 0x0E: // AMF_RECORDSET
+ {
+ Log(LOGERROR, "AMF_RECORDSET not supported!");
+ return -1;
+ break;
+ }
+ case 0x0F: // AMF_XML
+ {
+ Log(LOGERROR, "AMF_XML not supported!");
+ return -1;
+ break;
+ }
+ case 0x10: //AMF_CLASS_OBJECT
+ {
+ Log(LOGERROR, "AMF_CLASS_OBJECT not supported!");
+ return -1;
+ break;
+ }
+ case 0x11: //AMF_AMF3_OBJECT
+ {
+ Log(LOGERROR, "AMF_AMF3_OBJECT to be tested!");
+ int nRes = m_objVal.AMF3Decode(pBuffer+1, nSize, true);
+ if (nRes == -1)
+ return -1;
+ nSize -= nRes;
+ m_type = AMF_OBJECT;
+ break;
+ }
+ default:
+ Log(LOGDEBUG,"%s - unknown datatype 0x%02x, @0x%08X", __FUNCTION__, (unsigned char)(*pBuffer), pBuffer);
+ return -1;
+ }
+
+ return nOriginalSize - nSize;
+}
+
+void RTMP_LIB::AMFObjectProperty::Dump() const
+{
+ if (m_type == AMF_INVALID)
+ {
+ Log(LOGDEBUG,"Property: INVALID");
+ return;
+ }
+
+ if (m_type == AMF_NULL)
+ {
+ Log(LOGDEBUG,"Property: NULL");
+ return;
+ }
+
+ if (m_type == AMF_OBJECT)
+ {
+ Log(LOGDEBUG,"Property: <Name: %25s, OBJECT>", m_strName.empty() ? "no-name." : m_strName.c_str());
+ m_objVal.Dump();
+ return;
+ }
+
+ char strRes[256]="";
+ snprintf(strRes, 255, "Name: %25s, ", m_strName.empty()? "no-name.":m_strName.c_str());
+
+ char str[256]="";
+ switch(m_type)
+ {
+ case AMF_NUMBER:
+ snprintf(str, 255, "NUMBER:\t%.2f", m_dNumVal);
+ break;
+ case AMF_BOOLEAN:
+ snprintf(str, 255, "BOOLEAN:\t%s", m_dNumVal == 1.?"TRUE":"FALSE");
+ break;
+ case AMF_STRING:
+ snprintf(str, 255, "STRING:\t%s", m_strVal.c_str());
+ break;
+ case AMF_DATE:
+ snprintf(str, 255, "DATE:\ttimestamp: %.2f, UTC offset: %d", m_dNumVal, m_nUTCOffset);
+ break;
+ default:
+ snprintf(str, 255, "INVALID TYPE 0x%02x", (unsigned char)m_type);
+ }
+
+ Log(LOGDEBUG,"Property: <%s%s>", strRes, str);
+}
+
+void RTMP_LIB::AMFObjectProperty::Reset()
+{
+ m_dNumVal = 0.;
+ m_strVal.clear();
+ m_objVal.Reset();
+ m_type = AMF_INVALID;
+}
+
+int RTMP_LIB::AMFObjectProperty::EncodeName(char *pBuffer) const
+{
+ short length = htons(m_strName.size());
+ memcpy(pBuffer, &length, sizeof(short));
+ pBuffer += sizeof(short);
+
+ memcpy(pBuffer, m_strName.c_str(), m_strName.size());
+ return m_strName.size() + sizeof(short);
+}
+
+
+// AMFObject
+
+RTMP_LIB::AMFObject::AMFObject()
+{
+ Reset();
+}
+
+RTMP_LIB::AMFObject::~ AMFObject()
+{
+ Reset();
+}
+
+int RTMP_LIB::AMFObject::Encode(char * pBuffer, int nSize) const
+{
+ if (nSize < 4)
+ return -1;
+
+ *pBuffer = 0x03; // object
+
+ int nOriginalSize = nSize;
+ for (size_t i=0; i<m_properties.size(); i++)
+ {
+ int nRes = m_properties[i].Encode(pBuffer, nSize);
+ if (nRes == -1)
+ {
+ Log(LOGERROR,"AMFObject::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
+
+ RTMP_LIB::CRTMP::EncodeInt24(pBuffer, 0x000009);
+ nSize -= 3;
+
+ return nOriginalSize - nSize;
+}
+
+int RTMP_LIB::AMFObject::DecodeArray(const char * pBuffer, int nSize, int nArrayLen, bool bDecodeName)
+{
+ int nOriginalSize = nSize;
+ bool bError = false;
+
+ while(nArrayLen > 0)
+ {
+ nArrayLen--;
+
+ RTMP_LIB::AMFObjectProperty prop;
+ int nRes = prop.Decode(pBuffer, nSize, bDecodeName);
+ if (nRes == -1)
+ bError = true;
+ else
+ {
+ nSize -= nRes;
+ pBuffer += nRes;
+ m_properties.push_back(prop);
+ }
+ }
+ if (bError)
+ return -1;
+
+ return nOriginalSize - nSize;
+}
+
+int RTMP_LIB::AMFObject::AMF3Decode(const char * pBuffer, int nSize, bool bAMFData)
+{
+ int nOriginalSize = nSize;
+
+ if(bAMFData) {
+ if(*pBuffer != 0x0A)
+ Log(LOGERROR, "AMF3 Object encapsulated in AMF stream does not start with 0x0A!");
+ pBuffer++;
+ nSize--;
+ }
+
+ int32_t ref = 0;
+ int 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);
+
+ AMF3ClassDefinition *classDef = 0;
+
+ if((classRef & 0x1) == 0) { // class reference
+ uint32_t classIndex = (classRef >> 1);
+ Log(LOGDEBUG, "Class reference: %d", classIndex);
+ } else {
+ int32_t classExtRef = (classRef >> 1);
+
+ bool bExternalizable = ( classExtRef & 0x1) == 1;
+ bool bDynamic = ((classExtRef >> 1) & 0x1) == 1;
+
+ uint32_t numMembers = classExtRef >> 2;
+
+ // class name
+ char *className = 0;
+
+ len = AMF3ReadString(pBuffer, &className);
+ nSize -= len;
+ pBuffer += len;
+
+ //std::string str = className;
+
+ Log(LOGDEBUG, "Class name: %s, externalizable: %d, dynamic: %d, classMembers: %d", className, bExternalizable, bDynamic, numMembers);
+ classDef = new AMF3ClassDefinition(className, bExternalizable, bDynamic);
+ delete [] className;
+
+ for(unsigned int i=0; i<numMembers; i++) {
+ char *memberName = 0;
+ len = AMF3ReadString(pBuffer, &memberName);
+ Log(LOGDEBUG, "Member: %s", memberName);
+ classDef->AddProperty(memberName);
+ delete [] memberName;
+ nSize -= len;
+ pBuffer += len;
+ }
+ }
+
+ // add as referencable object
+ // ...
+
+ if(classDef->isExternalizable()) {
+ Log(LOGDEBUG, "Externalizable, TODO check");
+
+ RTMP_LIB::AMFObjectProperty prop;
+ int nRes = prop.AMF3Decode(pBuffer, nSize, false);
+ if(nRes == -1)
+ Log(LOGDEBUG, "%s, failed to decode AMF3 property!", __FUNCTION__);
+ else {
+ nSize -= nRes;
+ pBuffer += nRes;
+ }
+
+ prop.SetPropName("DEFAULT_ATTRIBUTE");
+ m_properties.push_back(prop);
+ } else {
+ for(int i=0; i<classDef->GetMemberCount(); i++) // non-dynamic
+ {
+ RTMP_LIB::AMFObjectProperty prop;
+ int nRes = prop.AMF3Decode(pBuffer, nSize, false);
+ if(nRes == -1)
+ Log(LOGDEBUG, "%s, failed to decode AMF3 property!", __FUNCTION__);
+
+ prop.SetPropName(classDef->GetProperty(i));
+ //prop.Dump();
+ m_properties.push_back(prop);
+
+ pBuffer += nRes;
+ nSize -= nRes;
+ }
+ if(classDef->isDynamic()) {
+ int len = 0;
+
+ do {
+ RTMP_LIB::AMFObjectProperty prop;
+ int nRes = prop.AMF3Decode(pBuffer, nSize, true);
+
+ m_properties.push_back(prop);
+
+ pBuffer += nRes;
+ nSize -= nRes;
+
+ len = prop.GetPropName().length();
+ } while(len > 0);
+
+ // property name
+ /*
+ RTMP_LIB::AMFObjectProperty prop;
+ int nRes = prop.AMF3Decode(pBuffer, nSize);
+ if (nRes == -1)
+ Log(LOGDEBUG, "%s, failed to decode AMF3 property!", __FUNCTION__);
+ m_properties.push_back(prop);*/
+ }
+ }
+ Log(LOGDEBUG, "class object!");
+ }
+
+ /*while (nSize > 0) {
+ RTMP_LIB::AMFObjectProperty prop;
+ int nRes = prop.AMF3Decode(pBuffer, nSize, bDecodeName);
+ if(nRes == -1)
+ return -1;
+
+ nSize -= nRes;
+ pBuffer += nRes;
+ //if(prop.GetType() != AMF_NULL)
+ m_properties.push_back(prop);
+ }*/
+ return nOriginalSize - nSize;
+}
+
+int RTMP_LIB::AMFObject::Decode(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 0x000009
+
+ //Log(LOGDEBUG, "%s: size: %lu, %d", __FUNCTION__, nSize, bDecodeName);
+
+ while (nSize >= 3)
+ {
+ if (RTMP_LIB::CRTMP::ReadInt24(pBuffer) == 0x000009)
+ {
+ nSize -= 3;
+ bError = false;
+ break;
+ }
+
+ if (bError)
+ {
+ Log(LOGERROR,"DECODING ERROR, IGNORING BYTES UNTIL NEXT KNOWN PATTERN!");
+ nSize--;
+ pBuffer++;
+ continue;
+ }
+
+ RTMP_LIB::AMFObjectProperty prop;
+ int nRes = prop.Decode(pBuffer, nSize, bDecodeName);
+ if (nRes == -1)
+ bError = true;
+ else
+ {
+ nSize -= nRes;
+ pBuffer += nRes;
+ m_properties.push_back(prop);
+ }
+ }
+
+ if (bError)
+ return -1;
+
+ return nOriginalSize - nSize;
+}
+
+void RTMP_LIB::AMFObject::AddProperty(const AMFObjectProperty & prop)
+{
+ m_properties.push_back(prop);
+}
+
+int RTMP_LIB::AMFObject::GetPropertyCount() const
+{
+ return m_properties.size();
+}
+
+const RTMP_LIB::AMFObjectProperty & RTMP_LIB::AMFObject::GetProperty(const std::string & strName) const
+{
+ for (size_t n=0; n<m_properties.size(); n++)
+ {
+ if (m_properties[n].GetPropName() == strName)
+ return m_properties[n];
+ }
+
+ return m_invalidProp;
+}
+
+const RTMP_LIB::AMFObjectProperty & RTMP_LIB::AMFObject::GetProperty(size_t nIndex) const
+{
+ if (nIndex >= m_properties.size())
+ return m_invalidProp;
+
+ return m_properties[nIndex];
+}
+
+void RTMP_LIB::AMFObject::Dump() const
+{
+ //Log(LOGDEBUG,"START AMF Object Dump:");
+
+ for (size_t n=0; n<m_properties.size(); n++) {
+ m_properties[n].Dump();
+ }
+
+ //Log(LOGDEBUG,"END AMF Object Dump");
+}
+
+void RTMP_LIB::AMFObject::Reset()
+{
+ m_properties.clear();
+}
+
+// AMF3ClassDefinition
+std::string strEmpty = "";
+
+RTMP_LIB::AMF3ClassDefinition::AMF3ClassDefinition(const std::string &strClassName, bool bExternalizable, bool bDynamic)
+{
+ m_bExternalizable = bExternalizable;
+ m_bDynamic = bDynamic;
+ m_strClassName = strClassName;
+}
+
+RTMP_LIB::AMF3ClassDefinition::~AMF3ClassDefinition() {}
+
+void RTMP_LIB::AMF3ClassDefinition::AddProperty(const std::string &strPropertyName)
+{
+ m_properties.push_back(strPropertyName);
+}
+
+const std::string & RTMP_LIB::AMF3ClassDefinition::GetProperty(size_t nIndex) const
+{
+ if (nIndex >= m_properties.size())
+ return strEmpty;
+
+ return m_properties[nIndex];
+}
+
+int RTMP_LIB::AMF3ClassDefinition::GetMemberCount() const
+{
+ return m_properties.size();
+}
+
--- /dev/null
+#ifndef __AMF_OBJECT__H__
+#define __AMF_OBJECT__H__
+/*
+ * Copyright (C) 2005-2008 Team XBMC
+ * http://www.xbmc.org
+ * Copyright (C) 2008-2009 Andrej Stepanchuk
+ *
+ * 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>
+#include <vector>
+#include <stdint.h>
+
+namespace RTMP_LIB
+{
+ typedef enum {AMF_INVALID, AMF_NUMBER, AMF_BOOLEAN, AMF_STRING, AMF_OBJECT, AMF_NULL, AMF_MIXEDARRAY, AMF_ARRAY, AMF_DATE } AMFDataType;
+
+ class AMFObjectProperty;
+ class AMFObject
+ {
+ public:
+ AMFObject();
+ virtual ~AMFObject();
+
+ int Encode(char *pBuffer, int nSize) const;
+ int AMF3Decode(const char * pBuffer, int nSize, bool bDecodeName=false);
+ int Decode(const char *pBuffer, int nSize, bool bDecodeName=false);
+ int DecodeArray(const char * pBuffer, int nSize, int nArrayLen, bool bDecodeName=false);
+
+ void AddProperty(const AMFObjectProperty &prop);
+
+ int GetPropertyCount() const;
+ const AMFObjectProperty &GetProperty(const std::string &strName) const;
+ const AMFObjectProperty &GetProperty(size_t nIndex) const;
+
+ void Dump() const;
+ void Reset();
+ protected:
+ static AMFObjectProperty m_invalidProp; // returned when no prop matches
+ std::vector<AMFObjectProperty> m_properties;
+ };
+
+ class AMFObjectProperty
+ {
+ public:
+ AMFObjectProperty();
+ AMFObjectProperty(const std::string &strName, double dValue);
+ AMFObjectProperty(const std::string &strName, bool bValue);
+ AMFObjectProperty(const std::string &strName, const std::string &strValue);
+ AMFObjectProperty(const std::string &strName, const AMFObject &objValue);
+
+ virtual ~AMFObjectProperty();
+
+ const std::string &GetPropName() const;
+ void SetPropName(const std::string& strName);
+
+ AMFDataType GetType() const;
+
+ bool IsValid() const;
+
+ double GetNumber() const;
+ bool GetBoolean() const;
+ const std::string &GetString() const;
+ const AMFObject &GetObject() const;
+
+ int Encode(char *pBuffer, int nSize) const;
+ int AMF3Decode(const char * pBuffer, int nSize, bool bDecodeName=false);
+ int Decode(const char *pBuffer, int nSize, bool bDecodeName);
+
+ void Reset();
+ void Dump() const;
+ protected:
+ int EncodeName(char *pBuffer) const;
+
+ std::string m_strName;
+
+ AMFDataType m_type;
+ double m_dNumVal;
+ int16_t m_nUTCOffset;
+ AMFObject m_objVal;
+ std::string m_strVal;
+ };
+
+ class AMF3ClassDefinition
+ {
+ public:
+ AMF3ClassDefinition(const std::string &strClassName, bool bExternalizable, bool bDynamic);
+ virtual ~AMF3ClassDefinition();
+
+ void AddProperty(const std::string &strPropertyName);
+ const std::string &GetProperty(size_t nIndex) const;
+
+ int GetMemberCount() const;
+
+ bool isExternalizable() { return m_bExternalizable; }
+ bool isDynamic() { return m_bDynamic; }
+ protected:
+ std::string m_strClassName;
+ bool m_bExternalizable;
+ bool m_bDynamic;
+
+ std::vector<std::string> m_properties;
+ };
+};
+
+#endif
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 of the License, 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 this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null
+RTMPDump
+Copyright 2008-2009 Andrej Stepanchuk; Distributed under the GPL v2
+Copyright 2009 Howard Chu
+Copyright 2009 The Flvstreamer Team
+
+2 December 2009, v1.9a
+- fix auth string typo
+- handle FCUnsubscribe message
+- don't try retry on live streams
+- SIGPIPE portability fix
+- remove "not supported" comment for RTMPE
+
+13 November 2009, v1.9
+- Handle more signals to reduce risk of unresumable/corrupted partially streamed files
+- Fixed >2GB file handling
+- Added --hashes option for a hash progress bar instead of byte counter
+- Fix to allow win32 to use binary mode on stdout.
+- Added auto-unpause for buffer-limited streams
+
+1 November 2009, v1.7
+
+- added --subscribe option for subscribing to a stream
+- added --start / --stop options for specifying endpoints of a stream
+- added --debug / --quiet / --verbose options for controlling output
+
+- added SOCKS4 support (by Monsieur Video)
+
+- restructured to support auto-restart of timed-out streams
+
+- rewritten byteswapping, works on all platforms
+
+- fixed errors in command / result parsing
+
+- support functions rewritten in C to avoid g++ compiler bugs on ARM
+
+- support for 65600 channels instead of just 64
+
+- fixed signature buffer overruns
+
+17 May 2009, v1.6
+
+- big endian alignment fix, should fix sparc64 and others
+
+- moved timestamp handling into RTMP protocol innings, all packets have
+absolute timestamps now, when seeking the stream will start with timestamp 0
+even if seeked to a later position!
+
+- fixed a timestamp bug (should fix async audio/video problems)
+
+30 Apr 2009, v1.5a
+
+- fixed host name resolution bug (caused unexpected crashes if DNS resolution
+was not available)
+
+- also using the hostname in tcUrl instead of the IP turns out to give much
+better results
+
+27 Apr 2009, v1.5
+
+- RTMPE support (tested on Adobe 3.0.2,3.0.3,3.5.1, Wowza)
+
+- SWFVerification (tested on Adobe 3.0.2,3.0.3,3.5.1)
+
+- added AMF3 parsing support (experimental feauture, only some primitives, no references)
+
+- added -o - option which allows the stream to be dumped to stdout
+(debug/error messages go to stderr)
+
+- added --live option to enable download of live streams
+
+- added support for (Free)BSD and Mac (untested, so might need more fixing,
+especially for PPC/sparc64)
+
+- fixed a bug in url parsing
+
+- added a useful application: streams, it will start a streaming server and
+using a request like http://localhost/?r=rtmp://.... you can restream the
+content to your player over http
+
+11 Mar 2009, v1.4
+
+- fixed resume bug: when the server switches between audio/video packets and FLV
+chunk packets (why should a server want to do that? some actually do!) and rtmpdump
+was invoked with --resume the keyframe check prevented rtmpdump from continuing
+
+- fixed endianness
+
+- added win32 and arm support (you can cross-compile it onto your Windows box
+or even PDA)
+
+- removed libboost dependency, written a small parser for rtmp urls, but it is
+more of a heuristic one since the rtmp urls can be ambigous in some
+circumstances. The best way is to supply all prameters using the override
+options like --play, --app, etc.
+
+- fixed stream ids (from XBMC tree)
+
+19 Jan 2009, v1.3b
+
+- fixed segfault on Mac OS/BSDdue to times(0)
+
+- Makefile rewritten
+
+16 Jan 2009, v1.3a
+
+- fixed a bug introduced in v1.3 (wrong report bytes count), downloads won't
+hang anymore
+
+10 Jan 2009, v1.3
+
+- fixed audio only streams (rtmpdump now recognizes the stream and writes a
+correct tag, audio, video, audio+video)
+
+- improved resume function to wait till a the seek is executed by the server.
+The server might send playback data before seeking, so we ignore up to e.g. 50
+frames and keep waiting for a keyframe with a timestamp of zero.
+
+- nevertheless resuming does not always work since the server sometimes
+doesn't resend the keyframe, seeking in flash is unreliable
+
+02 Jan 2009, v1.2a
+
+- fixed non-standard rtmp urls (including characters + < > ; )
+
+- added small script get_hulu which can download hulu.com streams (US only)
+(many thanks to Richard Ablewhite for the help with hulu.com)
+
+01 Jan 2009, v1.2:
+
+- fixed FLV streams (support for resuming extended)
+
+- fixed hanging download at the end
+
+- several minor bugfixes
+
+- changed parameter behaviour: not supplied parameters are omitted from the
+connect packet, --auth is introduced (was automatically obtained from url
+before, but it is possible to have an auth in the tcurl/rtmp url only without
+an additional encoded string in the connect packet)
+
+28 Dec 2008, v1.1a:
+
+- fixed warnings, added -Wall to Makefile
+
+28 Dec 2008, v1.1:
+
+- fixed stucking downloads (the buffer time is set to the duration now,
+ so the server doesn't wait till the buffer is emptied
+
+ - added a --resume option to coninue incomplete downloads
+
+- added support for AMF_DATE (experimental, no stream to test so far)
+
+- fixed AMF parsing and several small bugs (works on 64bit platforms now)
+
+24 Dec 2008, v1.0:
+
+- First release
+
--- /dev/null
+CC=$(CROSS_COMPILE)gcc
+CXX=$(CROSS_COMPILE)g++
+LD=$(CROSS_COMPILE)ld
+
+OPT=-O2
+CFLAGS=-Wall $(XCFLAGS) $(INC) $(OPT)
+CXXFLAGS=-Wall $(XCFLAGS) $(INC) $(OPT)
+LDFLAGS=-Wall $(XLDFLAGS)
+LIBS=-lcrypto
+THREADLIB=-lpthread
+SLIBS=$(THREADLIB) $(LIBS)
+
+EXT=
+
+all:
+ @echo 'use "make linux" for a native Linux build, or'
+ @echo ' "make osx" for a native OSX build, or'
+ @echo ' "make mingw" for a MinGW32 build, or'
+ @echo ' "make cygwin" for a CygWin build, or'
+ @echo ' "make arm" for a cross-compiled Linux ARM build'
+
+progs: rtmpdump streams
+
+linux:
+ @$(MAKE) $(MAKEFLAGS) progs
+
+osx:
+ @$(MAKE) XCFLAGS="-arch ppc -arch i386" $(MAKEFLAGS) progs
+
+mingw:
+ @$(MAKE) CROSS_COMPILE=mingw32- LIBS="-lws2_32 -lwinmm -lcrypto -lgdi32" THREADLIB= EXT=.exe $(MAKEFLAGS) progs
+
+cygwin:
+ @$(MAKE) XCFLAGS=-static XLDFLAGS="-static-libgcc -static" EXT=.exe $(MAKEFLAGS) progs
+
+arm:
+ @$(MAKE) CROSS_COMPILE=armv7a-angstrom-linux-gnueabi- INC=-I/OE/tmp/staging/armv7a-angstrom-linux-gnueabi/usr/include $(MAKEFLAGS) progs
+
+clean:
+ rm -f *.o rtmpdump$(EXT) streams$(EXT)
+
+streams: bytes.o log.o rtmp.o AMFObject.o rtmppacket.o streams.o parseurl.o dh.o handshake.o
+ $(CXX) $(LDFLAGS) $^ -o $@$(EXT) $(SLIBS)
+
+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
+AMFObject.o: AMFObject.cpp AMFObject.h log.h rtmp.h Makefile
+rtmppacket.o: rtmppacket.cpp rtmppacket.h log.h Makefile
+rtmpdump.o: rtmpdump.cpp rtmp.h log.h AMFObject.h Makefile
+parseurl.o: parseurl.c parseurl.h log.h Makefile
+streams.o: streams.cpp rtmp.h log.h Makefile
+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
--- /dev/null
+RTMP Dump v1.9
+(C) 2009 Andrej Stepanchuk
+(C) 2009 Howard Chu
+License: GPLv2
+
+To compile type "make" with your platform name, e.g.
+
+ $ make linux
+
+or osx, mingw, or cygwin.
+
+You can cross-compile for ARM using
+
+ $ make arm
+
+but you may need to override the CROSS_COMPILE and INC variables, e.g.
+
+ $ make arm CROSS_COMPILE=arm-none-linux- INC=-I/my/cross/includes
+
+Please read the Makefile to see what other make variables are used.
+
+
+SWF Verification
+----------------
+
+Download the swf player you want to use for SWFVerification, unzip it using
+
+ $ flasm -x file.swf
+
+It will show the decompressed filesize, use it for --swfsize
+
+Now generate the hash
+
+ $ openssl sha -sha256 -hmac "Genuine Adobe Flash Player 001" file.swf
+
+and use the --swfhash "01234..." option to pass it.
+
+e.g. $ ./rtmpdump --swfhash "123456..." --swfsize 987...
+
+Building OpenSSL 0.9.8k
+-----------------------
+arm:
+./Configure -DL_ENDIAN --prefix=`pwd`/armlibs linux-generic32
+
+Then replace gcc, cc, ar, ranlib in Makefile and crypto/Makefile by arm-linux-* variants and use make && make install_sw
+
+win32:
+Try ./Configure mingw --prefix=`pwd`/win32libs -DL_ENDIAN -DOPENSSL_NO_HW
+Replace gcc, cc, ... by mingw32-* variants in Makefile and crypto/Makefile
+make && make install_sw
+
+OpenSSL cross-compiling can be a difficult beast.
+
+
+Credit goes to team boxee for the XBMC RTMP code used in RTMPDumper.
+
--- /dev/null
+/* RTMPDump
+ * 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 <string.h>
+#include <sys/types.h>
+#include "bytes.h"
+
+// write dVal as 64bit little endian double
+void WriteNumber(char *data, double dVal)
+{
+#if __FLOAT_WORD_ORDER == __BYTE_ORDER
+#if __BYTE_ORDER == __BIG_ENDIAN
+ memcpy(data, &dVal, 8);
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+ unsigned char *ci, *co;
+ ci = (unsigned char *)&dVal;
+ co = (unsigned char *)data;
+ co[0] = ci[7];
+ co[1] = ci[6];
+ co[2] = ci[5];
+ co[3] = ci[4];
+ co[4] = ci[3];
+ co[5] = ci[2];
+ co[6] = ci[1];
+ co[7] = ci[0];
+#endif
+#else
+#if __BYTE_ORDER == __LITTLE_ENDIAN // __FLOAT_WORD_ORER == __BIG_ENDIAN
+ unsigned char *ci, *co;
+ ci = (unsigned char *)&dVal;
+ co = (unsigned char *)data;
+ co[0] = ci[3];
+ co[1] = ci[2];
+ co[2] = ci[1];
+ co[3] = ci[0];
+ co[4] = ci[7];
+ co[5] = ci[6];
+ co[6] = ci[5];
+ co[7] = ci[4];
+#else // __BYTE_ORDER == __BIG_ENDIAN && __FLOAT_WORD_ORER == __LITTLE_ENDIAN
+ unsigned char *ci, *co;
+ ci = (unsigned char *)&dVal;
+ co = (unsigned char *)data;
+ co[0] = ci[4];
+ co[1] = ci[5];
+ co[2] = ci[6];
+ co[3] = ci[7];
+ co[4] = ci[0];
+ co[5] = ci[1];
+ co[6] = ci[2];
+ co[7] = ci[3];
+#endif
+#endif
+}
+
+// reads a little endian 64bit double from data
+double ReadNumber(const char *data)
+{
+#if __FLOAT_WORD_ORDER == __BYTE_ORDER
+#if __BYTE_ORDER == __BIG_ENDIAN
+ double dVal;
+ memcpy(&dVal, data, 8);
+ return dVal;
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+#if 1
+ double dVal;
+ unsigned char *ci, *co;
+ ci = (unsigned char *)data;
+ co = (unsigned char *)&dVal;
+ co[0] = ci[7];
+ co[1] = ci[6];
+ co[2] = ci[5];
+ co[3] = ci[4];
+ co[4] = ci[3];
+ co[5] = ci[2];
+ co[6] = ci[1];
+ co[7] = ci[0];
+ return dVal;
+#else
+ uint64_t in = *((uint64_t*)data);
+ uint64_t res = __bswap_64(in);
+ return *((double *)&res);
+#endif
+#endif
+#else
+#if __BYTE_ORDER == __LITTLE_ENDIAN // __FLOAT_WORD_ORER == __BIG_ENDIAN
+ uint32_t in1 = *((uint32_t*)data);
+ uint32_t in2 = *((uint32_t*)(data+4));
+
+ in1 = __bswap_32(in1);
+ in2 = __bswap_32(in2);
+
+ uint64_t res = ((uint64_t)in2<<32) | (uint64_t)in1;
+ return *((double *)&res);
+#else // __BYTE_ORDER == __BIG_ENDIAN && __FLOAT_WORD_ORER == __LITTLE_ENDIAN
+ uint32_t in1 = *((uint32_t*)data);
+ uint32_t in2 = *((uint32_t*)(data+4));
+
+ uint64_t res = ((uint64_t)in1<<32) | (uint64_t)in2;
+ return *((double *)&res);
+#endif
+#endif
+}
+
+// read little-endian 32bit integer
+int ReadInt32LE(const char *data)
+{
+ int val;
+ memcpy(&val, data, sizeof(int));
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+ val = __bswap_32(val);
+#endif
+
+ return val;
+}
+
+// write little-endian 32bit integer
+int EncodeInt32LE(char *output, int nVal)
+{
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+ nVal = __bswap_32(nVal);
+#endif
+
+ memcpy(output, &nVal, sizeof(int));
+ return sizeof(int);
+}
+
--- /dev/null
+#ifndef __BYTES_H__
+#define __BYTES_H__
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef WIN32
+// Windows is little endian only
+#define __LITTLE_ENDIAN 1234
+#define __BIG_ENDIAN 4321
+#define __BYTE_ORDER __LITTLE_ENDIAN
+#define __FLOAT_WORD_ORDER __BYTE_ORDER
+
+typedef unsigned char uint8_t;
+/*typedef signed char int8_t;
+typedef signed short int16_t;
+typedef signed long int int32_t;
+typedef signed long long int int64_t;
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned long int uint32_t;
+typedef unsigned long long int uint64_t;
+*/
+
+#elif (defined(__FreeBSD__) && __FreeBSD_version >= 470000) || defined(__OpenBSD__) || defined(__NetBSD__) // *BSD
+#include <sys/endian.h>
+#define __BIG_ENDIAN BIG_ENDIAN
+#define __LITTLE_ENDIAN LITTLE_ENDIAN
+#define __BYTE_ORDER BYTE_ORDER
+
+#elif (defined(BSD) && (BSD >= 199103)) || defined(__MacOSX__) // more BSD
+#include <machine/endian.h>
+#define __BIG_ENDIAN BIG_ENDIAN
+#define __LITTLE_ENDIAN LITTLE_ENDIAN
+#define __BYTE_ORDER BYTE_ORDER
+
+#elif defined(__linux__) //|| defined (__BEOS__) // Linux, BeOS
+#include <endian.h>
+#include <byteswap.h>
+
+//typedef __uint64_t uint64_t;
+//typedef __uint32_t uint32_t;
+#endif
+
+// define missing byte swap macros
+#ifndef __bswap_32
+#define __bswap_32(x) \
+ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
+ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
+#endif
+
+#ifndef __bswap_64
+#define __bswap_64(x) \
+ ((((x) & 0xff00000000000000ull) >> 56) \
+ | (((x) & 0x00ff000000000000ull) >> 40) \
+ | (((x) & 0x0000ff0000000000ull) >> 24) \
+ | (((x) & 0x000000ff00000000ull) >> 8) \
+ | (((x) & 0x00000000ff000000ull) << 8) \
+ | (((x) & 0x0000000000ff0000ull) << 24) \
+ | (((x) & 0x000000000000ff00ull) << 40) \
+ | (((x) & 0x00000000000000ffull) << 56))
+#endif
+
+// define default endianness
+#ifndef __LITTLE_ENDIAN
+#define __LITTLE_ENDIAN 1234
+#endif
+
+#ifndef __BIG_ENDIAN
+#define __BIG_ENDIAN 4321
+#endif
+
+#ifndef __BYTE_ORDER
+#warning "Byte order not defined on your system, assuming little endian!"
+#define __BYTE_ORDER __LITTLE_ENDIAN
+#endif
+
+// ok, we assume to have the same float word order and byte order if float word order is not defined
+#ifndef __FLOAT_WORD_ORDER
+#warning "Float word order not defined, assuming the same as byte order!"
+#define __FLOAT_WORD_ORDER __BYTE_ORDER
+#endif
+
+#if !defined(__BYTE_ORDER) || !defined(__FLOAT_WORD_ORDER)
+#error "Undefined byte or float word order!"
+#endif
+
+#if __FLOAT_WORD_ORDER != __BIG_ENDIAN && __FLOAT_WORD_ORDER != __LITTLE_ENDIAN
+#error "Unknown/unsupported float word order!"
+#endif
+
+#if __BYTE_ORDER != __BIG_ENDIAN && __BYTE_ORDER != __LITTLE_ENDIAN
+#error "Unknown/unsupported byte order!"
+#endif
+
+void WriteNumber(char *data, double dVal);
+double ReadNumber(const char *data);
+
+int ReadInt32LE(const char *data);
+int EncodeInt32LE(char *output, int nVal);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
--- /dev/null
+/* RTMPDump - Diffie-Hellmann Key Exchange
+ * 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
+ *
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <limits.h>
+
+#include "log.h"
+#include "dh.h"
+#include "dhgroups.h"
+
+/*
+BIGNUM *dh_shared_p = 0; // shared prime
+BIGNUM *dh_shared_g = 0; // shared base
+
+void dh_pg_init()
+{
+ if(dh_shared_p || dh_shared_g)
+ return;
+
+ dh_shared_p = BN_new();
+ dh_shared_g = BN_new();
+ assert(dh_shared_p && dh_shared_g);
+
+ int res = BN_hex2bn(&dh_shared_p, P1024); // prime P1024, see dhgroups.h
+ assert(res);
+
+ res = BN_set_word(dh_shared_g, 2); // base 2
+ assert(res);
+}
+*/
+
+// RFC 2631, Section 2.1.5, http://www.ietf.org/rfc/rfc2631.txt
+int isValidPublicKey(BIGNUM *y, BIGNUM *p , BIGNUM *q)
+{
+ assert(y);
+
+ BIGNUM *bn = BN_new();
+ assert(bn);
+
+ // y must lie in [2,p-1]
+ BN_set_word(bn,1);
+ if(BN_cmp(y,bn) < 0) {
+ Log(LOGWARNING, "DH public key must be at least 2");
+ goto failed;
+ }
+
+ // bn = p-2
+ BN_copy(bn, p);
+ BN_sub_word(bn, 1);
+ if(BN_cmp(y,bn) > 0) {
+ Log(LOGWARNING, "DH public key must be at most p-2");
+ goto failed;
+ }
+
+ // Verify with Sophie-Germain prime
+ //
+ // This is a nice test to make sure the public key position is calculated
+ // correctly. This test will fail in about 50% of the cases if applied to
+ // random data.
+ //
+ if(q) {
+ // y must fulfill y^q mod p = 1
+ BN_CTX *ctx = BN_CTX_new();
+ BN_mod_exp(bn, y, q, p, ctx);
+
+ //BIGNUM *one = BN_new();
+ //BN_one(one);
+
+ if(BN_cmp(bn, BN_value_one()) != 0) {
+ Log(LOGWARNING, "DH public key does not fulfill y^q mod p = 1");
+ BN_CTX_free(ctx);
+ //goto failed;
+ }
+ //BN_CTX_free(ctx);
+ } //*/
+
+ BN_free(bn);
+
+ return 1;
+failed:
+ //Log(LOGDEBUG, "Insecure DH public key: %s", BN_bn2hex(y));
+ BN_free(bn);
+ return 0;
+}
+
+DH* DHInit(int nKeyBits)
+{
+ int res;
+ DH* dh = DH_new();
+
+ if(!dh)
+ goto failed;
+
+ dh->p = BN_new();
+ dh->g = BN_new();
+
+ if(!dh->p || !dh->g)
+ goto failed;
+
+ res = BN_hex2bn(&dh->p, P1024); // prime P1024, see dhgroups.h
+ if(!res) { goto failed; }
+
+ res = BN_set_word(dh->g, 2); // base 2
+ if(!res) { goto failed; }
+
+ dh->length = nKeyBits;
+ return dh;
+
+failed:
+ if(dh)
+ DH_free(dh);
+
+ return 0;
+}
+
+int DHGenerateKey(DH *dh)
+{
+ if(!dh)
+ return 0;
+
+ int res = 0;
+ while(!res)
+ {
+ if(!DH_generate_key(dh))
+ return 0;
+
+ BIGNUM *q1 = BN_new();
+ assert(BN_hex2bn(&q1, Q1024));
+
+ res = isValidPublicKey(dh->pub_key, dh->p, q1);
+ if(!res) {
+ BN_free(dh->pub_key);
+ BN_free(dh->priv_key);
+ dh->pub_key = dh->priv_key = 0;
+ }
+
+ BN_free(q1);
+ }
+ return 1;
+}
+
+// fill pubkey with the public key in BIG ENDIAN order
+// 00 00 00 00 00 x1 x2 x3 .....
+
+int DHGetPublicKey(DH *dh, uint8_t *pubkey, size_t nPubkeyLen)
+{
+ if(!dh || !dh->pub_key)
+ return 0;
+
+ int len = BN_num_bytes(dh->pub_key);
+ if(len <= 0 || len > (int)nPubkeyLen)
+ return 0;
+
+ memset(pubkey, 0, nPubkeyLen);
+ BN_bn2bin(dh->pub_key, pubkey + (nPubkeyLen - len));
+ return 1;
+}
+
+int DHGetPrivateKey(DH *dh, uint8_t *privkey, size_t nPrivkeyLen)
+{
+ if(!dh || !dh->priv_key)
+ return 0;
+
+ int len = BN_num_bytes(dh->priv_key);
+ if(len <= 0 || len > (int)nPrivkeyLen)
+ return 0;
+
+ memset(privkey, 0, nPrivkeyLen);
+ BN_bn2bin(dh->priv_key, privkey + (nPrivkeyLen - len));
+ return 1;
+}
+
+// computes the shared secret key from the private DH value and the othe parties public key (pubkey)
+int DHComputeSharedSecretKey(DH *dh, uint8_t *pubkey, size_t nPubkeyLen, uint8_t *secret)
+{
+ if(!dh || !secret || nPubkeyLen >= INT_MAX)
+ return -1;
+
+ BIGNUM *pubkeyBn = BN_bin2bn(pubkey, nPubkeyLen, 0);
+ if(!pubkeyBn)
+ return -1;
+
+ BIGNUM *q1 = BN_new();
+ assert(BN_hex2bn(&q1, Q1024));
+
+ if(!isValidPublicKey(pubkeyBn, dh->p, q1)) {
+ BN_free(pubkeyBn);
+ BN_free(q1);
+ return -1;
+ }
+
+ BN_free(q1);
+
+ size_t len = DH_compute_key(secret, pubkeyBn, dh);
+ BN_free(pubkeyBn);
+
+ return len;
+}
+
+void DHFree(DH *dh)
+{
+ if(dh)
+ DH_free(dh);
+}
+
--- /dev/null
+/* RTMPDump - Diffie-Hellmann Key Exchange
+ * 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
+ *
+ */
+
+#include <openssl/bn.h>
+#include <openssl/dh.h>
+
+#include <openssl/sha.h>
+#include <openssl/hmac.h>
+#include <openssl/rc4.h>
+
+#include "bytes.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+int isValidPublicKey(BIGNUM *y, BIGNUM *p, BIGNUM *q);
+DH* DHInit(int nKeyBits);
+int DHGenerateKey(DH *dh);
+int DHGetPublicKey(DH *dh, uint8_t *pubkey, size_t nPubkeyLen);
+int DHGetPrivateKey(DH *dh, uint8_t *privkey, size_t nPrivkeyLen);
+int DHComputeSharedSecretKey(DH *dh, uint8_t *pubkey, size_t nPubkeyLen, uint8_t *secret);
+void DHFree(DH *dh);
+#ifdef __cplusplus
+}
+#endif
--- /dev/null
+/* RTMPDump - Diffie-Hellmann Key Exchange
+ * Copyright (C) 2009 Andrej Stepanchuk
+ *
+ * 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
+ *
+ */
+
+/* from RFC 3526, see http://www.ietf.org/rfc/rfc3526.txt */
+
+// 2^768 - 2 ^704 - 1 + 2^64 * { [2^638 pi] + 149686 }
+#define P768 \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
+ "E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF"
+
+// 2^1024 - 2^960 - 1 + 2^64 * { [2^894 pi] + 129093 }
+#define P1024 \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" \
+ "FFFFFFFFFFFFFFFF"
+
+// Group morder largest prime factor:
+#define Q1024 \
+ "7FFFFFFFFFFFFFFFE487ED5110B4611A62633145C06E0E68" \
+ "948127044533E63A0105DF531D89CD9128A5043CC71A026E" \
+ "F7CA8CD9E69D218D98158536F92F8A1BA7F09AB6B6A8E122" \
+ "F242DABB312F3F637A262174D31BF6B585FFAE5B7A035BF6" \
+ "F71C35FDAD44CFD2D74F9208BE258FF324943328F67329C0" \
+ "FFFFFFFFFFFFFFFF"
+
+// 2^1536 - 2^1472 - 1 + 2^64 * { [2^1406 pi] + 741804 }
+#define P1536 \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
+ "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF"
+
+// 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }
+#define P2048 \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
+ "15728E5A8AACAA68FFFFFFFFFFFFFFFF"
+
+// 2^3072 - 2^3008 - 1 + 2^64 * { [2^2942 pi] + 1690314 }
+#define P3072 \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \
+ "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF"
+
+// 2^4096 - 2^4032 - 1 + 2^64 * { [2^3966 pi] + 240904 }
+#define P4096 \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \
+ "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" \
+ "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" \
+ "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" \
+ "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" \
+ "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" \
+ "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" \
+ "FFFFFFFFFFFFFFFF"
+
+// 2^6144 - 2^6080 - 1 + 2^64 * { [2^6014 pi] + 929484 }
+#define P6144 \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \
+ "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" \
+ "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" \
+ "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" \
+ "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" \
+ "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" \
+ "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" \
+ "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" \
+ "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" \
+ "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" \
+ "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" \
+ "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" \
+ "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" \
+ "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" \
+ "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" \
+ "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" \
+ "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" \
+ "12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF"
+
+// 2^8192 - 2^8128 - 1 + 2^64 * { [2^8062 pi] + 4743158 }
+#define P8192 \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \
+ "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" \
+ "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" \
+ "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" \
+ "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" \
+ "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" \
+ "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" \
+ "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" \
+ "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" \
+ "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" \
+ "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" \
+ "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" \
+ "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" \
+ "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" \
+ "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" \
+ "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" \
+ "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" \
+ "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" \
+ "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" \
+ "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" \
+ "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" \
+ "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" \
+ "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" \
+ "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" \
+ "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" \
+ "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" \
+ "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" \
+ "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" \
+ "60C980DD98EDD3DFFFFFFFFFFFFFFFFF"
+
--- /dev/null
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+
+#include <assert.h>
+
+#ifdef WIN32
+#include <winsock.h>
+#define close(x) closesocket(x)
+#else
+#include <sys/times.h>
+#endif
+
+#include <openssl/sha.h>
+#include <openssl/hmac.h>
+#include <openssl/rc4.h>
+
+#include "rtmp.h"
+#include "AMFObject.h"
+#include "log.h"
+#include "bytes.h"
+
+#include "dh.h"
+
+#define RTMP_SIG_SIZE 1536
+#define RTMP_LARGE_HEADER_SIZE 12
+
+#define RTMP_BUFFER_CACHE_SIZE (16*1024) // needs to fit largest number of bytes recv() may return
+
+using namespace RTMP_LIB;
+using namespace std;
+
+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
+
+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
+
+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 = new RC4_KEY;
+ *rc4keyOut = new 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);
+}
+/*
+void RC4Encrypt(char *src, char *dst, size_t len)
+{
+ if(Link.rc4keyOut) {
+ RC4(Link.rc4keyOut, len, (uint8_t*)src, (uint8_t*)dst);
+ }
+}
+
+void RC4Decrypt(char *src, char *dst, size_t len)
+{
+ if(Link.rc4keyIn) {
+ RC4(Link.rc4keyIn, len, (uint8_t*)src, (uint8_t*)dst);
+ }
+}
+*/
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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);
+}
+
+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);
+}
+
+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
+ * ...
+ *
+ */
+
+bool CRTMP::HandShake(bool FP9HandShake)
+{
+ bool encrypted = Link.protocol == RTMP_PROTOCOL_RTMPE || Link.protocol == RTMP_PROTOCOL_RTMPTE;
+
+ if ( encrypted || Link.SWFHash )
+ FP9HandShake = true;
+ else
+ FP9HandShake = false;
+
+ char clientsig[RTMP_SIG_SIZE+1];
+ char serversig[RTMP_SIG_SIZE];
+
+ memset(clientsig, 0, RTMP_SIG_SIZE+1);
+
+ Link.rc4keyIn = 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) {
+ //* TODO RTMPE ;), its just RC4 with diffie-hellman
+ // set version to at least 9.0.115.0
+ clientsig[5] = 9;
+ clientsig[6] = 0;
+ clientsig[7] = 124;
+ clientsig[8] = 2;
+
+ //Log(LOGDEBUG, "Client type: %02X\n", clientsig[0]);
+ //clientsig[0] = 0x08;
+ 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(int i=9; i<=RTMP_SIG_SIZE; i++)
+ clientsig[i] = 0;//(char)(rand() % 256);//0xff;
+#else
+ for(int 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
+ Link.dh = DHInit(1024);
+ if(!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(Link.dh)) {
+ Log(LOGERROR, "%s: Couldn't generate Diffie-Hellmann public key!", __FUNCTION__);
+ return false;
+ }
+
+ if(!DHGetPublicKey(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(clientsig, RTMP_SIG_SIZE + 1))
+ return false;
+
+ char type;
+ if(ReadN(&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(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(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(Link.SWFHash) {
+ const char swfVerify[] = {0x01,0x01};
+
+ memcpy(Link.SWFVerificationResponse, swfVerify, 2);
+ EncodeInt32(&Link.SWFVerificationResponse[2], Link.SWFSize);
+ EncodeInt32(&Link.SWFVerificationResponse[6], Link.SWFSize);
+ HMACsha256(Link.SWFHash, SHA256_DIGEST_LENGTH, &serversig[RTMP_SIG_SIZE-SHA256_DIGEST_LENGTH], SHA256_DIGEST_LENGTH, &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(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(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(int i=0; i<RTMP_SIG_SIZE; i++)
+ clientResp[i] = 0;//(char)(rand() % 256);//0xff;
+#else
+ for(int 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(clientResp, RTMP_SIG_SIZE))
+ return false;
+ }
+
+ if(encrypted) {
+ // set keys for encryption from now on
+ Link.rc4keyIn = keyIn;
+ Link.rc4keyOut = keyOut;
+
+ char buff[RTMP_SIG_SIZE];
+
+ // update the keystreams
+ if(Link.rc4keyIn) {
+ RC4(Link.rc4keyIn, RTMP_SIG_SIZE, (uint8_t*)buff, (uint8_t*)buff);
+ }
+
+ if(Link.rc4keyOut) {
+ RC4(Link.rc4keyOut, RTMP_SIG_SIZE, (uint8_t*)buff, (uint8_t*)buff);
+ }
+ }
+
+ Log(LOGDEBUG, "%s: Handshaking finished....", __FUNCTION__);
+ return true;
+}
+
+
--- /dev/null
+/* RTMPDump
+ * 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 <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "log.h"
+
+#define MAX_PRINT_LEN 2048
+
+extern int debuglevel;
+
+static int neednl;
+
+FILE *fmsg;
+
+void LogSetOutput(FILE *file)
+{
+ fmsg = file;
+}
+
+void LogPrintf(const char *format, ...)
+{
+ char str[MAX_PRINT_LEN]="";
+ va_list args;
+ va_start(args, format);
+ vsnprintf(str, MAX_PRINT_LEN-1, format, args);
+ va_end(args);
+
+ if ( debuglevel==LOGCRIT )
+ return;
+
+ if ( !fmsg ) fmsg = stderr;
+
+ if (neednl) {
+ putc('\n', fmsg);
+ neednl = 0;
+ }
+
+ fprintf(fmsg, "%s", str);
+#ifdef _DEBUG
+ fflush(fmsg);
+#endif
+}
+
+void LogStatus(const char *format, ...)
+{
+ char str[MAX_PRINT_LEN]="";
+ va_list args;
+ va_start(args, format);
+ vsnprintf(str, MAX_PRINT_LEN-1, format, args);
+ va_end(args);
+
+ if ( debuglevel==LOGCRIT )
+ return;
+
+ if ( !fmsg ) fmsg = stderr;
+
+ fprintf(fmsg, "%s", str);
+#ifdef _DEBUG
+ fflush(fmsg);
+#endif
+ neednl = 1;
+}
+
+void Log(int level, const char *format, ...)
+{
+ char str[MAX_PRINT_LEN]="";
+ va_list args;
+ va_start(args, format);
+ vsnprintf(str, MAX_PRINT_LEN-1, format, args);
+ va_end(args);
+
+ // Filter out 'no-name'
+ if ( debuglevel<LOGALL && strstr(str, "no-name" ) != NULL )
+ return;
+
+ if ( !fmsg ) fmsg = stderr;
+
+ if ( level <= debuglevel ) {
+ if (neednl) {
+ putc('\n', fmsg);
+ neednl = 0;
+ }
+ fprintf(fmsg, "\r%s: %s\n", level==LOGDEBUG?"DEBUG":(level==LOGERROR?
+"ERROR":(level==LOGWARNING?"WARNING":(level==LOGCRIT?"CRIT":"INFO"))), str);
+
+#ifdef _DEBUG
+ fflush(fmsg);
+#endif
+ }
+}
+
+void LogHex(int level, const char *data, unsigned long len)
+{
+ unsigned long i;
+ if ( level > debuglevel )
+ return;
+ for(i=0; i<len; i++) {
+ LogPrintf("%02X ", (unsigned char)data[i]);
+ }
+ LogPrintf("\n");
+}
+
+void LogHexString(const char *data, unsigned long len)
+{
+ unsigned long i;
+ if ( debuglevel==LOGCRIT )
+ return;
+ for(i=0; i<len; i++) {
+ LogPrintf("%02X ", (unsigned char)data[i]);
+ }
+ LogPrintf("\n");
+
+ for(i=0; i<len; i++) {
+ LogPrintf("%c", (unsigned char)data[i]);
+ }
+ LogPrintf("\n");
+}
--- /dev/null
+/* RTMP Dump
+ * 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
+ *
+ */
+
+#ifndef __LOG_H__
+#define __LOG_H__
+
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+// Enable this to get full debugging output
+//#define _DEBUG
+#define CRYPTO
+
+#ifdef _DEBUG
+#undef NODEBUG
+#endif
+
+#define LOGCRIT 0
+#define LOGERROR 1
+#define LOGWARNING 2
+#define LOGINFO 3
+#define LOGDEBUG 4
+#define LOGALL 5
+
+void LogSetOutput(FILE *file);
+void LogPrintf(const char *format, ...);
+void LogStatus(const char *format, ...);
+void Log(int level, const char *format, ...);
+void LogHex(int level, const char *data, unsigned long len);
+void LogHexString(const char *data, unsigned long len);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
--- /dev/null
+/* 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
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <assert.h>
+#include <ctype.h>
+
+#include "log.h"
+#include "parseurl.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
+
+char *str2lower(char *str, int len)
+{
+ char *res = (char *)malloc(len+1);
+ char *p;
+
+ for(p=res; p<res+len; p++, str++) {
+ *p = tolower(*str);
+ }
+
+ *p = 0;
+
+ return res;
+}
+
+int chr2hex(char c)
+{
+ if(c <= 57 && c >= 48)
+ return c-48;
+ else if(c <= 102 && c >= 97)
+ return c-97+10;
+
+ return -1;
+}
+
+int hex2bin(char *str, char **hex)
+{
+ if(!str || !hex)
+ return 0;
+
+ int len = strlen(str);
+
+ if(len % 2 != 0)
+ return 0;
+
+ int ret = len/2;
+
+ *hex = (char *)malloc(ret);
+ if((*hex)==0)
+ return 0;
+
+ char *hexptr = *hex;
+ char *lwo = str2lower(str, len);
+ char *lw = lwo;
+
+ len /= 2;
+
+ while(len) {
+ int d1 = chr2hex(*lw); lw++;
+ int d2 = chr2hex(*lw); lw++;
+
+ if(d1<0 || d2<0) {
+ free(*hex);
+ free(lwo);
+ *hex=NULL;
+ return -1;
+ }
+
+ *hexptr = (unsigned char)(d1*16+d2);
+ hexptr++;
+ len--;
+ }
+
+ free(lwo);
+ return ret;
+}
+
+int ParseUrl(char *url, int *protocol, char **host, unsigned int *port, char **playpath, char **app)
+{
+ assert(url != 0 && protocol != 0 && host != 0 && port != 0 && playpath != 0 && app != 0);
+
+ Log(LOGDEBUG, "Parsing...");
+
+ *protocol = 0; // default: RTMP
+
+ // Old School Parsing
+ char *lw = str2lower(url, 6);
+ char *temp;
+
+ // look for usual :// pattern
+ char *p = strstr(url, "://");
+ int len = (int)(p-url);
+ if(p == 0) {
+ Log(LOGWARNING, "RTMP URL: No :// in url!");
+ free(lw);
+ return 0;
+ }
+
+ if(len == 4 && strncmp(lw, "rtmp", 4)==0)
+ *protocol = RTMP_PROTOCOL_RTMP;
+ else if(len == 5 && strncmp(lw, "rtmpt", 5)==0)
+ *protocol = RTMP_PROTOCOL_RTMPT;
+ else if(len == 5 && strncmp(lw, "rtmps", 5)==0)
+ *protocol = RTMP_PROTOCOL_RTMPS;
+ else if(len == 5 && strncmp(lw, "rtmpe", 5)==0)
+ *protocol = RTMP_PROTOCOL_RTMPE;
+ else if(len == 5 && strncmp(lw, "rtmfp", 5)==0)
+ *protocol = RTMP_PROTOCOL_RTMFP;
+ else if(len == 6 && strncmp(lw, "rtmpte", 6)==0)
+ *protocol = RTMP_PROTOCOL_RTMPTE;
+ else {
+ Log(LOGWARNING, "Unknown protocol!\n");
+ goto parsehost;
+ }
+
+ Log(LOGDEBUG, "Parsed protocol: %d", *protocol);
+
+parsehost:
+ free(lw);
+
+ // lets get the hostname
+ p+=3;
+
+ // check for sudden death
+ if(*p==0) {
+ Log(LOGWARNING, "No hostname in URL!");
+ return 0;
+ }
+
+ int iEnd = strlen(p);
+ int iCol = iEnd+1;
+ int iQues = iEnd+1;
+ int iSlash = iEnd+1;
+
+ if((temp=strstr(p, ":"))!=0)
+ iCol = temp-p;
+ if((temp=strstr(p, "?"))!=0)
+ iQues = temp-p;
+ if((temp=strstr(p, "/"))!=0)
+ iSlash = temp-p;
+
+ int min = iSlash < iEnd ? iSlash : iEnd+1;
+ min = iQues < min ? iQues : min;
+
+ int hostlen = iCol < min ? iCol : min;
+
+ if(min < 256) {
+ *host = (char *)malloc((hostlen+1)*sizeof(char));
+ strncpy(*host, p, hostlen);
+ (*host)[hostlen]=0;
+
+ Log(LOGDEBUG, "Parsed host : %s", *host);
+ } else {
+ Log(LOGWARNING, "Hostname exceeds 255 characters!");
+ }
+
+ p+=hostlen; iEnd-=hostlen;
+
+ // get the port number if available
+ if(*p == ':') {
+ p++; iEnd--;
+
+ int portlen = min-hostlen-1;
+ if(portlen < 6) {
+ char portstr[6];
+ strncpy(portstr,p,portlen);
+ portstr[portlen]=0;
+
+ *port = atoi(portstr);
+ if(*port == 0)
+ *port = 1935;
+
+ Log(LOGDEBUG, "Parsed port : %d", *port);
+ } else {
+ Log(LOGWARNING, "Port number is longer than 5 characters!");
+ }
+
+ p+=portlen; iEnd-=portlen;
+ }
+
+ if(*p != '/') {
+ Log(LOGWARNING, "No application or playpath in URL!");
+ return 1;
+ }
+ p++; iEnd--;
+
+ // parse application
+ //
+ // rtmp://host[:port]/app[/appinstance][/...]
+ // application = app[/appinstance]
+ int iSlash2 = iEnd+1; // 2nd slash
+ int iSlash3 = iEnd+1; // 3rd slash
+
+ if((temp=strstr(p, "/"))!=0)
+ iSlash2 = temp-p;
+
+ if((temp=strstr(p, "?"))!=0)
+ iQues = temp-p;
+
+ if(iSlash2 < iEnd)
+ if((temp=strstr(p+iSlash2+1, "/"))!=0)
+ iSlash3 = temp-p;
+
+ //Log(LOGDEBUG, "p:%s, iEnd: %d\niSlash : %d\niSlash2: %d\niSlash3: %d", p, iEnd, iSlash, iSlash2, iSlash3);
+
+ int applen = iEnd+1; // ondemand, pass all parameters as app
+ int appnamelen = 8; // ondemand length
+
+ if(iQues < iEnd && strstr(p, "slist=")) { // whatever it is, the '?' and slist= means we need to use everything as app and parse plapath from slist=
+ appnamelen = iQues;
+ applen = iEnd+1; // pass the parameters as well
+ }
+ else if(strncmp(p, "ondemand/", 9)==0) {
+ // app = ondemand/foobar, only pass app=ondemand
+ applen = 8;
+ }
+ else { // app!=ondemand, so app is app[/appinstance]
+ appnamelen = iSlash2 < iEnd ? iSlash2 : iEnd;
+ if(iSlash3 < iEnd)
+ appnamelen = iSlash3;
+
+ applen = appnamelen;
+ }
+
+ *app = (char *)malloc((applen+1)*sizeof(char));
+ strncpy(*app, p, applen);
+ (*app)[applen]=0;
+ Log(LOGDEBUG, "Parsed app : %s", *app);
+
+ p += appnamelen;
+ iEnd -= appnamelen;
+
+ if (*p == '/') {
+ p += 1;
+ iEnd -= 1;
+ }
+
+ *playpath = ParsePlaypath(p);
+
+ return 1;
+}
+
+/*
+ * Extracts playpath from RTMP URL. playpath is the file part of the
+ * URL, i.e. the part that comes after rtmp://host:port/app/
+ *
+ * Returns the stream name in a format understood by FMS. The name is
+ * the playpath part of the URL with formating depending on the stream
+ * type:
+ *
+ * mp4 streams: prepend "mp4:"
+ * mp3 streams: prepend "mp3:", remove extension
+ * flv streams: remove extension
+ */
+char *ParsePlaypath(const char *playpath) {
+ if (!playpath || !*playpath)
+ return NULL;
+
+ int addMP4 = 0;
+ int addMP3 = 0;
+ const char *temp;
+ const char *ppstart = playpath;
+ int pplen = strlen(playpath);
+
+ if ((*ppstart == '?') &&
+ (temp=strstr(ppstart, "slist=")) != 0) {
+ ppstart = temp+6;
+ pplen = strlen(ppstart);
+
+ temp = strchr(ppstart, '&');
+ if (temp) {
+ pplen = temp-ppstart;
+ }
+ }
+
+ if (pplen >= 4) {
+ const char *ext = &ppstart[pplen-4];
+ if ((strcmp(ext, ".f4v") == 0) ||
+ (strcmp(ext, ".mp4") == 0)) {
+ addMP4 = 1;
+ // Only remove .flv from rtmp URL, not slist params
+ } else if ((ppstart == playpath) &&
+ (strcmp(ext, ".flv") == 0)) {
+ pplen -= 4;
+ } else if (strcmp(ext, ".mp3") == 0) {
+ addMP3 = 1;
+ pplen -= 4;
+ }
+ }
+
+ char *streamname = (char *)malloc((pplen+4+1)*sizeof(char));
+ if (!streamname)
+ return NULL;
+
+ char *destptr = streamname;
+ if (addMP4 && (strncmp(ppstart, "mp4:", 4) != 0)) {
+ strcpy(destptr, "mp4:");
+ destptr += 4;
+ } else if (addMP3 && (strncmp(ppstart, "mp3:", 4) != 0)) {
+ strcpy(destptr, "mp3:");
+ destptr += 4;
+ }
+
+ strncpy(destptr, ppstart, pplen);
+ destptr[pplen] = '\0';
+
+ return streamname;
+}
--- /dev/null
+#ifndef _PARSEURL_H_
+#define _PARSEURL_H_
+/* 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
+ *
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+int hex2bin(char *str, char **hex);
+int ParseUrl(char *url, int *protocol, char **host, unsigned int *port, char **playpath, char **app);
+char *ParsePlaypath(const char *playpath);
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
--- /dev/null
+/*
+ * 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 "rtmp.h"
+#include "AMFObject.h"
+#include "log.h"
+#include "bytes.h"
+
+#define RTMP_SIG_SIZE 1536
+#define RTMP_LARGE_HEADER_SIZE 12
+
+#define RTMP_BUFFER_CACHE_SIZE (16*1024) // needs to fit largest number of bytes recv() may return
+
+using namespace RTMP_LIB;
+using namespace std;
+
+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;
+
+int32_t GetTime()
+{
+#ifdef _DEBUG
+ return 0;
+#elif defined(WIN32)
+ return timeGetTime();
+#else
+ struct tms t;
+ return times(&t)*1000/sysconf(_SC_CLK_TCK);
+#endif
+}
+
+char RTMPProtocolStrings[][7] =
+{
+ "RTMP",
+ "RTMPT",
+ "RTMPS",
+ "RTMPE",
+ "RTMPTE",
+ "RTMFP"
+};
+
+char RTMPProtocolStringsLower[][7] =
+{
+ "rtmp",
+ "rtmpt",
+ "rtmps",
+ "rtmpe",
+ "rtmpte",
+ "rtmpfp"
+};
+
+CRTMP::CRTMP() : m_socket(0)
+{
+ for (int i=0; i<65600; i++)
+ {
+ m_vecChannelsIn[i] = NULL;
+ m_vecChannelsOut[i] = NULL;
+ }
+ Close();
+ m_pBuffer = new char[RTMP_BUFFER_CACHE_SIZE];
+ m_nBufferMS = 300;
+ m_fDuration = 0;
+ m_stream_id = -1;
+ m_pBufferStart = NULL;
+ m_fAudioCodecs = 3191.0;
+ m_fVideoCodecs = 252.0;
+ m_bTimedout = false;
+ m_bPausing = 0;
+ m_mediaChannel = 0;
+}
+
+CRTMP::~CRTMP()
+{
+ Close();
+ delete [] m_pBuffer;
+}
+
+double CRTMP::GetDuration() { return m_fDuration; }
+bool CRTMP::IsConnected() { return m_socket != 0; }
+bool CRTMP::IsTimedout() { return m_bTimedout; }
+
+void CRTMP::SetBufferMS(int size)
+{
+ m_nBufferMS = size;
+}
+
+void CRTMP::UpdateBufferMS()
+{
+ SendCtrl(3, m_stream_id, m_nBufferMS);
+}
+
+void CRTMP::SetupStream(
+ int protocol,
+ const char *hostname,
+ unsigned int port,
+ const char *sockshost,
+ const char *playpath,
+ const char *tcUrl,
+ const char *swfUrl,
+ const char *pageUrl,
+ const char *app,
+ const char *auth,
+ const char *swfSHA256Hash,
+ uint32_t swfSize,
+ const char *flashVer,
+ const char *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);
+
+ if(tcUrl)
+ Log(LOGDEBUG, "tcUrl : %s", tcUrl);
+ if(swfUrl)
+ Log(LOGDEBUG, "swfUrl : %s", swfUrl);
+ if(pageUrl)
+ Log(LOGDEBUG, "pageUrl : %s", pageUrl);
+ if(app)
+ Log(LOGDEBUG, "app : %s", app);
+ if(auth)
+ Log(LOGDEBUG, "auth : %s", auth);
+ if(subscribepath)
+ Log(LOGDEBUG, "subscribepath : %s", subscribepath);
+ if(flashVer)
+ Log(LOGDEBUG, "flashVer : %s", flashVer);
+ 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) {
+ Link.SWFHash = swfSHA256Hash;
+ Link.SWFSize = swfSize;
+ Log(LOGDEBUG, "SWFSHA256:");
+ LogHex(LOGDEBUG, Link.SWFHash, 32);
+ Log(LOGDEBUG, "SWFSize : %lu", Link.SWFSize);
+ } else {
+ Link.SWFHash = NULL;
+ Link.SWFSize = 0;
+ }
+
+ if(sockshost)
+ {
+ const char *socksport = strchr(sockshost, ':');
+ char *hostname = strdup(sockshost);
+
+ if(socksport)
+ hostname[socksport - sockshost] = '\0';
+ Link.sockshost = hostname;
+
+ Link.socksport = socksport ? atoi(socksport + 1) : 1080;
+ Log(LOGDEBUG, "Connecting via SOCKS proxy: %s:%d", Link.sockshost, Link.socksport);
+ } else {
+ Link.sockshost = NULL;
+ Link.socksport = 0;
+ }
+
+
+ Link.tcUrl = tcUrl;
+ Link.swfUrl = swfUrl;
+ Link.pageUrl = pageUrl;
+ Link.app = app;
+ Link.auth = auth;
+ Link.flashVer = flashVer;
+ Link.subscribepath = subscribepath;
+ Link.seekTime = dTime;
+ Link.length = dLength;
+ Link.bLiveStream = bLiveStream;
+ Link.timeout = timeout;
+
+ Link.protocol = protocol;
+ Link.hostname = hostname;
+ Link.port = port;
+ Link.playpath = playpath;
+
+ if (Link.port == 0)
+ Link.port = 1935;
+}
+
+static bool add_addr_info(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 CRTMP::Connect() {
+ if (!Link.hostname)
+ return false;
+
+ // close any previous connection
+ Close();
+
+ m_bTimedout = false;
+ m_bPausing = 0;
+ m_fDuration = 0.0;
+
+ sockaddr_in service;
+ memset(&service, 0, sizeof(sockaddr_in));
+ service.sin_family = AF_INET;
+
+ if (Link.socksport)
+ {
+ // Connect via SOCKS
+ if(!add_addr_info(&service, Link.sockshost, Link.socksport)) return false;
+ } else {
+ // Connect directly
+ if(!add_addr_info(&service, Link.hostname, Link.port)) return false;
+ }
+
+ m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (m_socket != -1)
+ {
+ if (connect(m_socket, (sockaddr*) &service, sizeof(struct sockaddr)) < 0)
+ {
+ int err = GetSockError();
+ Log(LOGERROR, "%s, failed to connect socket. %d (%s)", __FUNCTION__,
+ err, strerror(err));
+ Close();
+ return false;
+ }
+
+ if(Link.socksport) {
+ Log(LOGDEBUG, "%s ... SOCKS negotiation", __FUNCTION__);
+ if (!SocksNegotiate())
+ {
+ Log(LOGERROR, "%s, SOCKS negotiation failed.", __FUNCTION__);
+ Close();
+ return false;
+ }
+ }
+
+ Log(LOGDEBUG, "%s, ... connected, handshaking", __FUNCTION__);
+ if (!HandShake())
+ {
+ Log(LOGERROR, "%s, handshake failed.", __FUNCTION__);
+ Close();
+ return false;
+ }
+
+ Log(LOGDEBUG, "%s, handshaked", __FUNCTION__);
+ if (!RTMPConnect())
+ {
+ Log(LOGERROR, "%s, RTMP connect failed.", __FUNCTION__);
+ Close();
+ return false;
+ }
+ // set timeout
+ struct timeval tv;
+ memset(&tv, 0, sizeof(tv));
+ tv.tv_sec = Link.timeout;
+ if (setsockopt(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(m_socket, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+ return true;
+}
+
+bool CRTMP::SocksNegotiate() {
+ sockaddr_in service;
+ memset(&service, 0, sizeof(sockaddr_in));
+
+ add_addr_info(&service, Link.hostname, Link.port);
+ unsigned long addr = htonl(service.sin_addr.s_addr);
+
+ char packet[] = {
+ 4, 1, // SOCKS 4, connect
+ (Link.port >> 8) & 0xFF,
+ (Link.port) & 0xFF,
+ (char) (addr >> 24) & 0xFF, (char) (addr >> 16) & 0xFF,
+ (char) (addr >> 8) & 0xFF, (char) addr & 0xFF,
+ 0}; // NULL terminate
+
+ WriteN(packet, sizeof packet);
+
+ if(ReadN(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 CRTMP::ConnectStream(double seekTime, uint32_t dLength) {
+ if (seekTime >= -2.0)
+ Link.seekTime = seekTime;
+
+ if (dLength >= 0)
+ Link.length = dLength;
+
+ m_mediaChannel = 0;
+ RTMPPacket packet;
+ while (!m_bPlaying && IsConnected() && ReadPacket(packet)) {
+ if (!packet.IsReady())
+ {
+ packet.FreePacket();
+ continue;
+ }
+
+ if ((packet.m_packetType == 0x8) || \
+ (packet.m_packetType == 0x9) || \
+ (packet.m_packetType == 0x16))
+ {
+ Log(LOGDEBUG, "%s, received FLV packet before play()!", __FUNCTION__);
+ break;
+ }
+
+ HandlePacket(packet);
+ }
+
+ return m_bPlaying;
+}
+
+bool CRTMP::ReconnectStream(int bufferTime, double seekTime, uint32_t dLength) {
+ DeleteStream();
+
+ SendCreateStream(2.0);
+
+ SetBufferMS(bufferTime);
+
+ return ConnectStream(seekTime, dLength);
+}
+
+bool CRTMP::ToggleStream()
+{
+ bool res;
+
+ res = SendPause(true, m_pauseStamp);
+ if (!res) return res;
+
+ m_bPausing = 1;
+ sleep(1);
+ res = SendPause(false, m_pauseStamp);
+ m_bPausing = 3;
+ return res;
+}
+
+void CRTMP::DeleteStream() {
+ if (m_stream_id < 0)
+ return;
+
+ m_bPlaying = false;
+
+ SendDeleteStream(m_stream_id);
+
+ // No response expected for deleteStream
+ if (m_methodCalls.back() == "deleteStream")
+ m_methodCalls.erase(m_methodCalls.end());
+}
+
+int CRTMP::GetNextMediaPacket(RTMPPacket &packet)
+{
+ int bHasMediaPacket = 0;
+ while (!bHasMediaPacket && IsConnected() && ReadPacket(packet))
+ {
+ if (!packet.IsReady())
+ {
+ packet.FreePacket();
+ //usleep(5000); // 5ms
+ continue;
+ }
+
+ bHasMediaPacket = HandlePacket(packet);
+
+ if (!bHasMediaPacket) {
+ packet.FreePacket();
+ } else if (m_bPausing == 3) {
+ if (packet.m_nTimeStamp <= 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, m_mediaStamp);
+#endif
+ continue;
+ }
+ m_bPausing = 0;
+ }
+ }
+
+ if (bHasMediaPacket)
+ m_bPlaying = true;
+ else if (m_bTimedout)
+ m_pauseStamp = m_channelTimestamp[m_mediaChannel];
+
+ return bHasMediaPacket;
+}
+
+int CRTMP::HandlePacket(RTMPPacket &packet) {
+ int bHasMediaPacket = 0;
+ switch (packet.m_packetType)
+ {
+ case 0x01:
+ // chunk size
+ HandleChangeChunkSize(packet);
+ break;
+
+ case 0x03:
+ // bytes read report
+ Log(LOGDEBUG, "%s, received: bytes read report", __FUNCTION__);
+ break;
+
+ case 0x04:
+ // ctrl
+ HandleCtrl(packet);
+ break;
+
+ case 0x05:
+ // server bw
+ HandleServerBW(packet);
+ break;
+
+ case 0x06:
+ // client bw
+ HandleClientBW(packet);
+ break;
+
+ case 0x08:
+ // audio data
+ //Log(LOGDEBUG, "%s, received: audio %lu bytes", __FUNCTION__, packet.m_nBodySize);
+ HandleAudio(packet);
+ bHasMediaPacket = 1;
+ if (!m_mediaChannel)
+ m_mediaChannel = packet.m_nChannel;
+ if (!m_bPausing)
+ m_mediaStamp = packet.m_nTimeStamp;
+ break;
+
+ case 0x09:
+ // video data
+ //Log(LOGDEBUG, "%s, received: video %lu bytes", __FUNCTION__, packet.m_nBodySize);
+ HandleVideo(packet);
+ bHasMediaPacket = 1;
+ if (!m_mediaChannel)
+ m_mediaChannel = packet.m_nChannel;
+ if (!m_bPausing)
+ 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(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(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(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;
+
+ while(pos+11 < packet.m_nBodySize) {
+ uint32_t dataSize = CRTMP::ReadInt24(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(packet.m_body+pos+11, dataSize);
+ } else if (packet.m_body[pos] == 8 || packet.m_body[pos] == 9) {
+ nTimeStamp = CRTMP::ReadInt24(packet.m_body+pos+4);
+ nTimeStamp |= (packet.m_body[pos+7]<<24);
+ }
+ pos += (11+dataSize+4);
+ }
+ if (!m_bPausing)
+ 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
+ }
+
+ return bHasMediaPacket;
+}
+
+#ifdef _DEBUG
+extern FILE *netstackdump;
+extern FILE *netstackdump_read;
+#endif
+
+int CRTMP::ReadN(char *buffer, int n)
+{
+ int nOriginalSize = n;
+
+ m_bTimedout = false;
+
+ #ifdef _DEBUG
+ memset(buffer, 0, n);
+ #endif
+
+ char *ptr = buffer;
+ while (n > 0)
+ {
+ int nBytes = 0;
+ if(m_nBufferSize == 0)
+ if (!FillBuffer()) {
+ if (!m_bTimedout)
+ Close();
+ return 0;
+ }
+ int nRead = ((n<m_nBufferSize)?n:m_nBufferSize);
+ if(nRead > 0) {
+ memcpy(ptr, m_pBufferStart, nRead);
+ m_pBufferStart += nRead;
+ m_nBufferSize -= nRead;
+ nBytes = nRead;
+ m_nBytesIn += nRead;
+ if(m_nBytesIn > m_nBytesInSent + m_nClientBW/2 )
+ SendBytesReceived();
+ }
+
+//again:
+// nBytes = recv(m_socket, ptr, n, 0);
+
+ //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;
+ Close();
+ break;
+ }
+
+ #ifdef CRYPTO
+ if(Link.rc4keyIn) {
+ RC4(Link.rc4keyIn, nBytes, (uint8_t*)ptr, (uint8_t*)ptr);
+ }
+ #endif
+
+ n -= nBytes;
+ ptr += nBytes;
+ }
+
+ return nOriginalSize - n;
+}
+
+bool CRTMP::WriteN(const char *buffer, int n)
+{
+ const char *ptr = buffer;
+
+ char *encrypted = 0;
+
+ #ifdef CRYPTO
+ if(Link.rc4keyOut) {
+ ptr = encrypted = (char *)malloc(n);
+ RC4(Link.rc4keyOut, n, (uint8_t*)buffer, (uint8_t*)ptr);
+ }
+ #endif
+
+ while (n > 0)
+ {
+#ifdef _DEBUG
+ fwrite(ptr, 1, n, netstackdump);
+#endif
+
+ int nBytes = send(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;
+
+ Close();
+
+ if(encrypted)
+ free(encrypted);
+ return false;
+ }
+
+ if (nBytes == 0)
+ break;
+
+ n -= nBytes;
+ ptr += nBytes;
+ }
+
+ if(encrypted)
+ free(encrypted);
+
+ return n == 0;
+}
+
+bool CRTMP::RTMPConnect()
+{
+ if (!SendConnectPacket())
+ {
+ Log(LOGERROR, "%s, failed to send connect RTMP packet", __FUNCTION__);
+ return false;
+ }
+
+ return true;
+}
+
+bool CRTMP::SendConnectPacket()
+{
+ RTMPPacket packet;
+ packet.m_nChannel = 0x03; // control channel (invoke)
+ packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
+ packet.m_packetType = 0x14; // INVOKE
+ packet.AllocPacket(4096);
+
+ char *enc = packet.m_body;
+ enc += EncodeString(enc, "connect");
+ enc += EncodeNumber(enc, 1.0);
+ *enc = 0x03; //Object Datatype
+ enc++;
+
+ if(Link.app)
+ enc += EncodeString(enc, "app", Link.app);
+ if(Link.flashVer)
+ enc += EncodeString(enc, "flashVer", Link.flashVer);
+ if(Link.swfUrl)
+ enc += EncodeString(enc, "swfUrl", Link.swfUrl);
+ if(Link.tcUrl)
+ enc += EncodeString(enc, "tcUrl", Link.tcUrl);
+
+ enc += EncodeBoolean(enc, "fpad", false);
+ enc += EncodeNumber(enc, "capabilities", 15.0);
+ enc += EncodeNumber(enc, "audioCodecs", m_fAudioCodecs);
+ enc += EncodeNumber(enc, "videoCodecs", m_fVideoCodecs);
+ enc += EncodeNumber(enc, "videoFunction", 1.0);
+ if(Link.pageUrl)
+ enc += EncodeString(enc, "pageUrl", Link.pageUrl);
+
+ enc += EncodeNumber(enc, "objectEncoding", 0.0); // AMF0, AMF3 not supported yet
+ enc += 2; // end of object - 0x00 0x00 0x09
+ *enc = 0x09;
+ enc++;
+
+ //enc += EncodeString(enc, "user"); // DEBUG, REMOVE!!!
+ //*enc = 0x05; enc++;
+ //enc += EncodeString(enc, "tvmanele1"); // DEBUG, REMOVE!!
+
+ // add auth string
+ if(Link.auth)
+ {
+ *enc = 0x01; enc++;
+ *enc = 0x01; enc++;
+
+ enc += EncodeString(enc, Link.auth);
+ }
+ packet.m_nBodySize = enc-packet.m_body;
+
+ return SendRTMP(packet);
+}
+
+bool CRTMP::SendBGHasStream(double dId, char *playpath)
+{
+ RTMPPacket packet;
+ packet.m_nChannel = 0x03; // control channel (invoke)
+ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+ packet.m_packetType = 0x14; // INVOKE
+
+ packet.AllocPacket(1024); // should be enough
+ char *enc = packet.m_body;
+ enc += EncodeString(enc, "bgHasStream");
+ enc += EncodeNumber(enc, dId);
+ *enc = 0x05; // NULL
+ enc++;
+
+ enc += EncodeString(enc, playpath);
+
+ packet.m_nBodySize = enc-packet.m_body;
+
+ return SendRTMP(packet);
+}
+
+bool CRTMP::SendCreateStream(double dStreamId)
+{
+ RTMPPacket packet;
+ packet.m_nChannel = 0x03; // control channel (invoke)
+ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+ packet.m_packetType = 0x14; // INVOKE
+
+ packet.AllocPacket(256); // should be enough
+ char *enc = packet.m_body;
+ enc += EncodeString(enc, "createStream");
+ enc += EncodeNumber(enc, dStreamId);
+ *enc = 0x05; // NULL
+ enc++;
+
+ packet.m_nBodySize = enc - packet.m_body;
+
+ return SendRTMP(packet);
+}
+
+bool CRTMP::SendFCSubscribe(const char *subscribepath)
+{
+ RTMPPacket packet;
+ packet.m_nChannel = 0x03; // control channel (invoke)
+ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+ packet.m_packetType = 0x14; // INVOKE
+
+ packet.AllocPacket(256); // should be enough
+ Log(LOGDEBUG, "FCSubscribe: %s", subscribepath);
+ char *enc = packet.m_body;
+ enc += EncodeString(enc, "FCSubscribe");
+ enc += EncodeNumber(enc, 4.0);
+ *enc = 0x05; // NULL
+ enc++;
+ enc += EncodeString(enc, subscribepath);
+
+ packet.m_nBodySize = enc - packet.m_body;
+
+ return SendRTMP(packet);
+}
+
+bool CRTMP::SendDeleteStream(double dStreamId)
+{
+ RTMPPacket packet;
+ packet.m_nChannel = 0x03; // control channel (invoke)
+ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+ packet.m_packetType = 0x14; // INVOKE
+
+ packet.AllocPacket(256); // should be enough
+ char *enc = packet.m_body;
+ enc += EncodeString(enc, "deleteStream");
+ enc += EncodeNumber(enc, 0.0);
+ *enc = 0x05; // NULL
+ enc++;
+ enc += EncodeNumber(enc, dStreamId);
+
+ packet.m_nBodySize = enc - packet.m_body;
+
+ return SendRTMP(packet);
+}
+
+bool CRTMP::SendPause(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.AllocPacket(256); // should be enough
+ char *enc = packet.m_body;
+ enc += EncodeString(enc, "pause");
+ enc += EncodeNumber(enc, 0);
+ *enc = 0x05; // NULL
+ enc++;
+ enc += EncodeBoolean(enc, DoPause);
+ enc += EncodeNumber(enc, (double)dTime);
+
+ packet.m_nBodySize = enc - packet.m_body;
+
+ return SendRTMP(packet);
+}
+
+bool CRTMP::SendSeek(double dTime)
+{
+ RTMPPacket packet;
+ packet.m_nChannel = 0x08; // video channel
+ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+ packet.m_packetType = 0x14; // invoke
+
+ packet.AllocPacket(256); // should be enough
+ char *enc = packet.m_body;
+ enc += EncodeString(enc, "seek");
+ enc += EncodeNumber(enc, 0);
+ *enc = 0x05; // NULL
+ enc++;
+ enc += EncodeNumber(enc, dTime);
+
+ packet.m_nBodySize = enc - packet.m_body;
+
+ return SendRTMP(packet);
+}
+
+bool CRTMP::SendServerBW()
+{
+ RTMPPacket packet;
+ packet.m_nChannel = 0x02; // control channel (invoke)
+ packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
+ packet.m_packetType = 0x05; // Server BW
+
+ packet.AllocPacket(4);
+ packet.m_nBodySize = 4;
+
+ EncodeInt32(packet.m_body, m_nServerBW);
+ return SendRTMP(packet);
+}
+
+bool CRTMP::SendBytesReceived()
+{
+ RTMPPacket packet;
+ packet.m_nChannel = 0x02; // control channel (invoke)
+ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+ packet.m_packetType = 0x03; // bytes in
+
+ packet.AllocPacket(4);
+ packet.m_nBodySize = 4;
+
+ EncodeInt32(packet.m_body, m_nBytesIn); // hard coded for now
+ m_nBytesInSent = m_nBytesIn;
+
+ //Log(LOGDEBUG, "Send bytes report. 0x%x (%d bytes)", (unsigned int)m_nBytesIn, m_nBytesIn);
+ return SendRTMP(packet);
+}
+
+bool CRTMP::SendCheckBW()
+{
+ RTMPPacket packet;
+
+ packet.m_nChannel = 0x03; // control channel (invoke)
+ packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
+ packet.m_packetType = 0x14; // INVOKE
+ packet.m_nInfoField1 = GetTime();
+
+ packet.AllocPacket(256); // should be enough
+ char *enc = packet.m_body;
+ enc += EncodeString(enc, "_checkbw");
+ enc += EncodeNumber(enc, 0);
+ *enc = 0x05; // NULL
+ enc++;
+
+ packet.m_nBodySize = enc - packet.m_body;
+
+ // triggers _onbwcheck and eventually results in _onbwdone
+ return SendRTMP(packet);
+}
+
+bool CRTMP::SendCheckBWResult(double txn)
+{
+ RTMPPacket packet;
+ bool res;
+
+ packet.m_nChannel = 0x03; // control channel (invoke)
+ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+ packet.m_packetType = 0x14; // INVOKE
+ packet.m_nInfoField1 = 0x16 * m_nBWCheckCounter; // temp inc value. till we figure it out.
+
+ packet.AllocPacket(256); // should be enough
+ char *enc = packet.m_body;
+ enc += EncodeString(enc, "_result");
+ enc += EncodeNumber(enc, txn);
+ *enc = 0x05; // NULL
+ enc++;
+ enc += EncodeNumber(enc, (double)m_nBWCheckCounter++);
+
+ packet.m_nBodySize = enc - packet.m_body;
+
+ res = SendRTMP(packet);
+ m_methodCalls.erase(m_methodCalls.end());
+ return res;
+}
+
+bool CRTMP::SendPlay()
+{
+ 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 = m_stream_id; //0x01000000;
+
+ packet.AllocPacket(1024); // should be enough
+ char *enc = packet.m_body;
+ enc += EncodeString(enc, "play");
+ enc += EncodeNumber(enc, 0.0); // stream id??
+ *enc = 0x05; // NULL
+ enc++;
+
+ Log(LOGDEBUG, "%s, seekTime=%.2f, dLength=%d, sending play: %s", __FUNCTION__, Link.seekTime, Link.length, Link.playpath);
+ enc += EncodeString(enc, 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(Link.bLiveStream)
+ enc += EncodeNumber(enc, -1000.0);
+ else {
+ if(Link.seekTime > 0.0)
+ enc += EncodeNumber(enc, Link.seekTime); // resume from here
+ else
+ enc += 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(Link.length)
+ enc += EncodeNumber(enc, Link.length); // len
+
+ packet.m_nBodySize = enc - packet.m_body;
+
+ return SendRTMP(packet);
+}
+/*
+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
+*/
+bool CRTMP::SendCtrl(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 = GetTime();
+
+ 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.AllocPacket(nSize);
+ packet.m_nBodySize = nSize;
+
+ char *buf = packet.m_body;
+ buf += EncodeInt16(buf, nType);
+
+ if(nType == 0x1B) {
+ #ifdef CRYPTO
+ memcpy(buf, Link.SWFVerificationResponse, 42);
+ Log(LOGDEBUG, "Sending SWFVerification response: ");
+ LogHex(LOGDEBUG, packet.m_body, packet.m_nBodySize);
+ #endif
+ } else {
+ if (nSize > 2)
+ buf += EncodeInt32(buf, nObject);
+
+ if (nSize > 6)
+ buf += EncodeInt32(buf, nTime);
+ }
+
+ return SendRTMP(packet);
+}
+
+// Returns 0 for OK/Failed/error, 1 for 'Stop or Complete'
+int CRTMP::HandleInvoke(const char *body, unsigned int nBodySize)
+{
+ 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;
+ }
+
+ RTMP_LIB::AMFObject obj;
+ int nRes = obj.Decode(body, nBodySize);
+ if (nRes < 0)
+ {
+ Log(LOGERROR, "%s, error decoding invoke packet", __FUNCTION__);
+ return 0;
+ }
+
+ obj.Dump();
+ std::string method = obj.GetProperty(0).GetString();
+ double txn = obj.GetProperty(1).GetNumber();
+ Log(LOGDEBUG, "%s, server invoking <%s>", __FUNCTION__, method.c_str());
+
+#define CSCMP(a,b) (a.size() == (sizeof(b)-1)) && !strcmp(a.c_str(),b)
+
+ if (CSCMP(method, "_result"))
+ {
+ std::string methodInvoked = m_methodCalls[0];
+ m_methodCalls.erase(m_methodCalls.begin());
+
+ Log(LOGDEBUG, "%s, received result for method call <%s>", __FUNCTION__, methodInvoked.c_str());
+
+ if (CSCMP(methodInvoked,"connect"))
+ {
+ SendServerBW();
+ SendCtrl(3, 0, 300);
+
+ SendCreateStream(2.0);
+
+ // Send the FCSubscribe if live stream or if subscribepath is set
+ if (Link.subscribepath)
+ SendFCSubscribe(Link.subscribepath);
+ else if (Link.bLiveStream)
+ SendFCSubscribe(Link.playpath);
+ }
+ else if (CSCMP(methodInvoked,"createStream"))
+ {
+ m_stream_id = (int)obj.GetProperty(3).GetNumber();
+
+ SendPlay();
+ /* not required since we send the seek parameter in the play packet now
+ if(Link.seekTime > 0) {
+ Log(LOGDEBUG, "%s, sending seek: %f ms", __FUNCTION__, Link.seekTime);
+ SendSeek(Link.seekTime);
+ }*/
+
+ SendCtrl(3, m_stream_id, m_nBufferMS);
+ }
+ else if (CSCMP(methodInvoked,"play"))
+ {
+ m_bPlaying = true;
+ SendPlay();
+ }
+ }
+ else if (CSCMP(method,"onBWDone"))
+ {
+ if(nBodySize < 25)
+ SendCheckBW();
+ else
+ {
+ /* Dunno why we get this bogus request */
+ ;
+ }
+ }
+ else if (CSCMP(method,"onFCSubscribe"))
+ {
+ // SendOnFCSubscribe();
+ }
+ else if (CSCMP(method,"onFCUnsubscribe"))
+ {
+ Close();
+ return 1;
+ }
+ else if (CSCMP(method,"_onbwcheck"))
+ {
+ SendCheckBWResult(txn);
+ }
+ else if (CSCMP(method,"_onbwdone"))
+ {
+ std::vector<std::string>::iterator i; //remote method calls queue
+ for (i=m_methodCalls.begin(); i<m_methodCalls.end(); i++)
+ if (i[0] == "_checkbw") {
+ m_methodCalls.erase(i);
+ break;
+ }
+ }
+ else if (CSCMP(method,"_error"))
+ {
+ Log(LOGERROR, "rtmp server sent error");
+ }
+ else if (CSCMP(method,"close"))
+ {
+ Log(LOGERROR, "rtmp server requested close");
+ Close();
+ }
+ else if (CSCMP(method,"onStatus"))
+ {
+ std::string code = obj.GetProperty(3).GetObject().GetProperty("code").GetString();
+ std::string level = obj.GetProperty(3).GetObject().GetProperty("level").GetString();
+
+ Log(LOGDEBUG, "%s, onStatus: %s", __FUNCTION__, code.c_str() );
+ if (code == "NetStream.Failed"
+ || code == "NetStream.Play.Failed"
+ || code == "NetStream.Play.StreamNotFound"
+ || code == "NetConnection.Connect.InvalidApp") {
+ m_stream_id = -1;
+ Close();
+ }
+
+ if (code == "NetStream.Play.Start") {
+ std::vector<std::string>::iterator i; //remote method calls queue
+ m_bPlaying = true;
+ for (i=m_methodCalls.begin(); i<m_methodCalls.end(); i++)
+ if (i[0] == "play") {
+ m_methodCalls.erase(i);
+ break;
+ }
+ }
+
+ // Return 1 if this is a Play.Complete or Play.Stop
+ if (code == "NetStream.Play.Complete"
+ || code == "NetStream.Play.Stop") {
+ Close();
+ return 1;
+ }
+
+ /*if(Link.seekTime > 0) {
+ if(code == "NetStream.Seek.Notify") { // seeked successfully, can play now!
+ bSeekedSuccessfully = true;
+ } else if(code == "NetStream.Play.Start" && !bSeekedSuccessfully) { // well, try to seek again
+ Log(LOGWARNING, "%s, server ignored seek!", __FUNCTION__);
+ }
+ }*/
+ }
+ else
+ {
+
+ }
+ return 0;
+}
+
+//int pnum=0;
+
+bool CRTMP::FindFirstMatchingProperty(AMFObject &obj, std::string name, AMFObjectProperty &p)
+{
+ // this is a small object search to locate the "duration" property
+ for (int n=0; n<obj.GetPropertyCount(); n++) {
+ AMFObjectProperty prop = obj.GetProperty(n);
+
+ if(prop.GetPropName() == name) {
+
+ p = obj.GetProperty(n);
+ return true;
+ }
+
+ if(prop.GetType() == AMF_OBJECT) {
+ AMFObject next = prop.GetObject();
+ return FindFirstMatchingProperty(next, name, p);
+ }
+ }
+ return false;
+}
+
+bool CRTMP::DumpMetaData(AMFObject &obj)
+{
+ std::string name;
+ AMFObjectProperty prop;
+ for (int n=0; n<obj.GetPropertyCount(); n++) {
+ AMFObjectProperty prop = obj.GetProperty(n);
+ if ( prop.GetType() != AMF_OBJECT ) {
+ char str[256]="";
+ switch( prop.GetType() )
+ {
+ case AMF_NUMBER:
+ if ( (double)int(prop.GetNumber()) == prop.GetNumber() )
+ snprintf(str, 255, "%.0f", prop.GetNumber() );
+ else
+ snprintf(str, 255, "%.2f", prop.GetNumber() );
+ break;
+ case AMF_BOOLEAN:
+ snprintf(str, 255, "%s", prop.GetNumber() == 1.?"TRUE":"FALSE");
+ break;
+ case AMF_STRING:
+ snprintf(str, 255, "%s", prop.GetString().c_str());
+ break;
+ case AMF_DATE:
+ snprintf(str, 255, "timestamp:%.2f", prop.GetNumber() );
+ break;
+ default:
+ snprintf(str, 255, "INVALID TYPE 0x%02x", (unsigned char)prop.GetType() );
+ }
+ if ( prop.GetPropName() != "" ) {
+ // chomp
+ if ( strlen(str) >= 1 && str[strlen(str)-1 ] == '\n')
+ str[strlen(str)-1] = '\0';
+ LogPrintf(" %-22s%s\n", prop.GetPropName().c_str(), str );
+ }
+ } else {
+ if ( prop.GetPropName() != "" )
+ LogPrintf("%s:\n", prop.GetPropName().c_str() );
+ AMFObject next = prop.GetObject();
+ DumpMetaData(next);
+ }
+ }
+ return false;
+}
+
+bool CRTMP::HandleMetadata(char *body, unsigned int len)
+{
+ /*Log(LOGDEBUG,"Parsing meta data: %d @0x%08X", packet.m_nBodySize, packet.m_body);
+ LogHex(packet.m_body, packet.m_nBodySize);
+
+ char str[256]={0};
+ sprintf(str, "packet%d", pnum);
+ pnum++;
+ FILE *f = fopen(str, "wb");
+ fwrite(packet.m_body, 1, packet.m_nBodySize, f);
+ fclose(f);//*/
+
+ // allright we get some info here, so parse it and print it
+ // also keep duration or filesize to make a nice progress bar
+
+ //int len = packet.m_nBodySize;
+ //char *p = packet.m_body;
+
+ RTMP_LIB::AMFObject obj;
+ int nRes = obj.Decode(body, len);
+ if(nRes < 0) {
+ Log(LOGERROR, "%s, error decoding meta data packet", __FUNCTION__);
+ return false;
+ }
+
+ obj.Dump();
+ std::string metastring = obj.GetProperty(0).GetString();
+
+ if(metastring == "onMetaData") {
+ AMFObjectProperty prop;
+ // Show metadata
+ LogPrintf("\r%s\n", "Metadata: " );
+ DumpMetaData(obj);
+ if(FindFirstMatchingProperty(obj, "duration", prop)) {
+ m_fDuration = prop.GetNumber();
+ //Log(LOGDEBUG, "Set duration: %.2f", m_fDuration);
+ }
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void CRTMP::HandleChangeChunkSize(const RTMPPacket &packet)
+{
+ if (packet.m_nBodySize >= 4)
+ {
+ m_chunkSize = ReadInt32(packet.m_body);
+ Log(LOGDEBUG, "%s, received: chunk size change to %d", __FUNCTION__, m_chunkSize);
+ }
+}
+
+void CRTMP::HandleAudio(const RTMPPacket &packet)
+{
+}
+
+void CRTMP::HandleVideo(const RTMPPacket &packet)
+{
+}
+
+void CRTMP::HandleCtrl(const RTMPPacket &packet)
+{
+ short nType = -1;
+ unsigned int tmp;
+ if (packet.m_body && packet.m_nBodySize >= 2)
+ nType = ReadInt16(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 = ReadInt32(packet.m_body + 2);
+ Log(LOGDEBUG, "%s, Stream Begin %d", __FUNCTION__, tmp);
+ break;
+
+ case 1:
+ tmp = ReadInt32(packet.m_body + 2);
+ Log(LOGDEBUG, "%s, Stream EOF %d", __FUNCTION__, tmp);
+ if (m_bPausing == 1)
+ m_bPausing = 2;
+ break;
+
+ case 2:
+ tmp = ReadInt32(packet.m_body + 2);
+ Log(LOGDEBUG, "%s, Stream Dry %d", __FUNCTION__, tmp);
+ break;
+
+ case 4:
+ tmp = ReadInt32(packet.m_body + 2);
+ Log(LOGDEBUG, "%s, Stream IsRecorded %d", __FUNCTION__, tmp);
+ break;
+
+ case 6: // server ping. reply with pong.
+ tmp = ReadInt32(packet.m_body + 2);
+ Log(LOGDEBUG, "%s, Ping %d", __FUNCTION__, tmp);
+ SendCtrl(0x07, tmp);
+ break;
+
+ case 31:
+ tmp = ReadInt32(packet.m_body + 2);
+ Log(LOGDEBUG, "%s, Stream BufferEmpty %d", __FUNCTION__, tmp);
+ if (!m_bPausing) {
+ m_pauseStamp = m_channelTimestamp[m_mediaChannel];
+ SendPause(true, m_pauseStamp);
+ m_bPausing = 1;
+ } else if (m_bPausing == 2) {
+ SendPause(false, m_pauseStamp);
+ m_bPausing = 3;
+ }
+ break;
+
+ case 32:
+ tmp = ReadInt32(packet.m_body + 2);
+ Log(LOGDEBUG, "%s, Stream BufferReady %d", __FUNCTION__, tmp);
+ break;
+
+ default:
+ tmp = ReadInt32(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(Link.SWFHash) {
+ SendCtrl(0x1B, 0, 0);
+ } else {
+ Log(LOGWARNING, "%s: Ignoring SWFVerification request, use --swfhash and --swfsize!", __FUNCTION__);
+ }
+ }
+}
+
+void CRTMP::HandleServerBW(const RTMPPacket &packet) {
+ m_nServerBW = ReadInt32(packet.m_body);
+ Log(LOGDEBUG, "%s: server BW = %d", __FUNCTION__, m_nServerBW);
+}
+
+void CRTMP::HandleClientBW(const RTMPPacket &packet) {
+ m_nClientBW = ReadInt32(packet.m_body);
+ if (packet.m_nBodySize > 4)
+ m_nClientBW2 = packet.m_body[4];
+ else
+ m_nClientBW2 = -1;
+ Log(LOGDEBUG, "%s: client BW = %d %d", __FUNCTION__, m_nClientBW, m_nClientBW2);
+}
+
+bool CRTMP::ReadPacket(RTMPPacket &packet)
+{
+ char type;
+ if (ReadN(&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(&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(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
+ packet.FreePacketHeader(); // test whether this avoids memory leak
+ if (m_vecChannelsIn[packet.m_nChannel])
+ packet = *m_vecChannelsIn[packet.m_nChannel];
+ }
+
+ nSize--;
+
+ char header[RTMP_LARGE_HEADER_SIZE] = {0};
+ if (nSize > 0 && ReadN(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 = ReadInt24(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 = ReadInt24(header + 3);
+ packet.m_nBytesRead = 0;
+ packet.FreePacketHeader(); // new packet body
+ }
+
+ if (nSize > 6)
+ packet.m_packetType = header[6];
+
+ if (nSize == 11)
+ packet.m_nInfoField2 = ReadInt32LE(header+7);
+
+ bool didAlloc = false;
+ if (packet.m_nBodySize > 0 && packet.m_body == NULL)
+ {
+ if (!packet.AllocPacket(packet.m_nBodySize)) {
+ Log(LOGDEBUG, "%s, failed to allocate packet", __FUNCTION__);
+ return false;
+ }
+ didAlloc = true;
+ }
+
+ int nToRead = packet.m_nBodySize - packet.m_nBytesRead;
+ int nChunk = m_chunkSize;
+ if (nToRead < nChunk)
+ nChunk = nToRead;
+
+ if (ReadN(packet.m_body + packet.m_nBytesRead, nChunk) != nChunk)
+ {
+ Log(LOGERROR, "%s, failed to read RTMP packet body. len: %lu", __FUNCTION__, packet.m_nBodySize);
+ if (!didAlloc) {
+ packet.m_body = NULL; // we dont want it deleted since its pointed to from the stored packets (m_vecChannelsIn)
+ packet.m_buffer = NULL;
+ }
+ return false;
+ }
+
+ packet.m_nBytesRead += nChunk;
+
+ // keep the packet as ref for other packets on this channel
+ if (!m_vecChannelsIn[packet.m_nChannel])
+ m_vecChannelsIn[packet.m_nChannel] = new RTMPPacket;
+ *m_vecChannelsIn[packet.m_nChannel] = packet;
+
+ if (packet.IsReady())
+ {
+ packet.m_nTimeStamp = packet.m_nInfoField1;
+
+ // make packet's timestamp absolute
+ if (!packet.m_hasAbsTimestamp)
+ packet.m_nTimeStamp += m_channelTimestamp[packet.m_nChannel]; // timestamps seem to be always relative!!
+
+ 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)
+ m_vecChannelsIn[packet.m_nChannel]->m_body = NULL;
+ m_vecChannelsIn[packet.m_nChannel]->m_buffer = NULL;
+ m_vecChannelsIn[packet.m_nChannel]->m_nBytesRead = 0;
+ m_vecChannelsIn[packet.m_nChannel]->m_hasAbsTimestamp = false; // can only be false if we reuse header
+ }
+ else {
+ packet.m_body = NULL; // so it wont be erased on "free"
+ packet.m_buffer = NULL; // so it wont be erased on "free"
+ }
+
+ return true;
+}
+
+unsigned short CRTMP::ReadInt16(const char *data)
+{
+ unsigned char *c = (unsigned char *)data;
+ unsigned short val;
+ val = (c[0] << 8) | c[1];
+ return val;
+}
+
+unsigned int CRTMP::ReadInt24(const char *data)
+{
+ unsigned char *c = (unsigned char *)data;
+ unsigned int val;
+ val = (c[0] << 16) | (c[1] << 8) | c[2];
+ return val;
+}
+
+// big-endian 32bit integer
+unsigned int CRTMP::ReadInt32(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;
+}
+
+std::string CRTMP::ReadString(const char *data)
+{
+ std::string strRes;
+ short len = ReadInt16(data);
+ if (len > 0)
+ {
+ char *pStr = new char[len+1];
+ memset(pStr, 0, len+1);
+ memcpy(pStr, data + sizeof(short), len);
+ strRes = pStr;
+ delete [] pStr;
+ }
+ return strRes;
+}
+
+bool CRTMP::ReadBool(const char *data)
+{
+ return *data == 0x01;
+}
+
+int CRTMP::EncodeString(char *output, const std::string &strName, const std::string &strValue)
+{
+ char *buf = output;
+ short length = htons(strName.size());
+ memcpy(buf, &length, 2);
+ buf += 2;
+
+ memcpy(buf, strName.c_str(), strName.size());
+ buf += strName.size();
+
+ buf += EncodeString(buf, strValue);
+ return buf - output;
+}
+
+int CRTMP::EncodeInt16(char *output, short nVal)
+{
+ output[1] = nVal & 0xff;
+ output[0] = nVal >> 8;
+ return sizeof(short);
+}
+
+int CRTMP::EncodeInt24(char *output, int nVal)
+{
+ output[2] = nVal & 0xff;
+ output[1] = nVal >> 8;
+ output[0] = nVal >> 16;
+ return 3;
+}
+
+// big-endian 32bit integer
+int CRTMP::EncodeInt32(char *output, int nVal)
+{
+ output[3] = nVal & 0xff;
+ output[2] = nVal >> 8;
+ output[1] = nVal >> 16;
+ output[0] = nVal >> 24;
+ return sizeof(int);
+}
+
+int CRTMP::EncodeNumber(char *output, const std::string &strName, double dVal)
+{
+ char *buf = output;
+
+ unsigned short length = htons(strName.size());
+ memcpy(buf, &length, 2);
+ buf += 2;
+
+ memcpy(buf, strName.c_str(), strName.size());
+ buf += strName.size();
+
+ buf += EncodeNumber(buf, dVal);
+ return buf - output;
+}
+
+int CRTMP::EncodeBoolean(char *output, const std::string &strName, bool bVal)
+{
+ char *buf = output;
+ unsigned short length = htons(strName.size());
+ memcpy(buf, &length, 2);
+ buf += 2;
+
+ memcpy(buf, strName.c_str(), strName.size());
+ buf += strName.size();
+
+ buf += EncodeBoolean(buf, bVal);
+
+ return buf - output;
+}
+
+int CRTMP::EncodeString(char *output, const std::string &strValue)
+{
+ char *buf = output;
+ *buf = 0x02; // Datatype: String
+ buf++;
+
+ short length = htons(strValue.size());
+ memcpy(buf, &length, 2);
+ buf += 2;
+
+ memcpy(buf, strValue.c_str(), strValue.size());
+ buf += strValue.size();
+
+ return buf - output;
+}
+
+int CRTMP::EncodeNumber(char *output, double dVal)
+{
+ char *buf = output;
+ *buf = 0x00; // type: Number
+ buf++;
+
+ WriteNumber(buf, dVal);
+ buf += 8;
+
+ return buf - output;
+}
+
+int CRTMP::EncodeBoolean(char *output, bool bVal)
+{
+ char *buf = output;
+
+ *buf = 0x01; // type: Boolean
+ buf++;
+
+ *buf = bVal?0x01:0x00;
+ buf++;
+
+ return buf - output;
+}
+
+#ifndef CRYPTO
+bool CRTMP::HandShake(bool FP9HandShake)
+{
+ char clientsig[RTMP_SIG_SIZE+1];
+ char serversig[RTMP_SIG_SIZE];
+
+ clientsig[0] = 0x03; // not encrypted
+
+ uint32_t uptime = htonl(GetTime());
+ memcpy(clientsig + 1, &uptime, 4);
+
+ memset(&clientsig[5], 0, 4);
+
+#ifdef _DEBUG
+ for (int i=9; i<RTMP_SIG_SIZE; i++)
+ clientsig[i] = 0xff;
+#else
+ for (int i=9; i<RTMP_SIG_SIZE; i++)
+ clientsig[i] = (char)(rand() % 256);
+#endif
+
+ if (!WriteN(clientsig, RTMP_SIG_SIZE + 1))
+ return false;
+
+ char type;
+ if (ReadN(&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(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
+ char resp[RTMP_SIG_SIZE];
+ if (ReadN(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 signiture does not match!",__FUNCTION__);
+ }
+
+ if (!WriteN(serversig, RTMP_SIG_SIZE))
+ return false;
+
+ return true;
+}
+#endif
+
+bool CRTMP::SendRTMP(RTMPPacket &packet)
+{
+ const RTMPPacket *prevPacket = 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!! tring 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)
+ EncodeInt24(header+1, packet.m_nInfoField1);
+
+ if (nSize > 4)
+ {
+ 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(header, nChunkSize+hSize);
+ header = NULL;
+ } else {
+ wrote=WriteN(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) { // we invoked a remote method, keep it in call queue till result arrives
+ m_methodCalls.push_back(ReadString(packet.m_body + 1));
+ Log(LOGDEBUG, "Invoking %s", ReadString(packet.m_body + 1).c_str());
+ }
+
+ if (!m_vecChannelsOut[packet.m_nChannel])
+ m_vecChannelsOut[packet.m_nChannel] = new RTMPPacket;
+ *m_vecChannelsOut[packet.m_nChannel] = packet;
+ m_vecChannelsOut[packet.m_nChannel]->m_body = NULL;
+ m_vecChannelsOut[packet.m_nChannel]->m_buffer = NULL;
+ return true;
+}
+
+void CRTMP::Close()
+{
+ if (IsConnected())
+ close(m_socket);
+
+ m_stream_id = -1;
+ m_socket = 0;
+ m_chunkSize = RTMP_DEFAULT_CHUNKSIZE;
+ m_nBWCheckCounter = 0;
+ m_nBytesIn = 0;
+ m_nBytesInSent = 0;
+ m_nClientBW = 2500000;
+ m_nClientBW2 = 2;
+ m_nServerBW = 2500000;
+
+ for (int i=0; i<65600; i++)
+ {
+ if (m_vecChannelsIn[i]) {
+ delete m_vecChannelsIn[i];
+ m_vecChannelsIn[i] = NULL;
+ }
+ if (m_vecChannelsOut[i]) {
+ delete m_vecChannelsOut[i];
+ m_vecChannelsOut[i] = NULL;
+ }
+ }
+ m_methodCalls.clear();
+
+ m_bPlaying = false;
+ m_nBufferSize = 0;
+}
+
+bool CRTMP::FillBuffer()
+{
+ assert(m_nBufferSize == 0); // only fill buffer when it's empty
+ int nBytes;
+
+again:
+ nBytes = recv(m_socket, m_pBuffer, RTMP_BUFFER_CACHE_SIZE, 0);
+ if(nBytes != -1) {
+ m_nBufferSize += nBytes;
+ m_pBufferStart = 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)
+ m_bTimedout = true;
+ else
+ Close();
+ return false;
+ }
+
+ return true;
+}
--- /dev/null
+#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
+ *
+ */
+
+//#include <string>
+//#include <vector>
+
+#ifdef WIN32
+#include <winsock.h>
+#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>
+#endif
+
+#include "log.h"
+
+#ifdef CRYPTO
+#include "dh.h"
+#endif
+
+#include "AMFObject.h"
+#include "rtmppacket.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
+
+extern char RTMPProtocolStringsLower[][7];
+
+int32_t GetTime();
+
+inline int GetSockError() {
+#ifdef WIN32
+ return WSAGetLastError();
+#else
+ return errno;
+#endif
+}
+
+namespace RTMP_LIB
+{
+
+typedef struct
+{
+ const char *hostname;
+ unsigned int port;
+ int protocol;
+ const char *playpath;
+
+ const char *tcUrl;
+ const char *swfUrl;
+ const char *pageUrl;
+ const char *app;
+ const char *auth;
+ const char *SWFHash;
+ uint32_t SWFSize;
+ const char *flashVer;
+ const char *subscribepath;
+
+ double seekTime;
+ uint32_t length;
+ bool bLiveStream;
+
+ long int timeout; // number of seconds before connection times out
+
+ #ifdef CRYPTO
+ DH *dh; // for encryption
+ RC4_KEY *rc4keyIn;
+ RC4_KEY *rc4keyOut;
+
+ //char SWFHashHMAC[32];
+ char SWFVerificationResponse[42];
+ #endif
+
+ const char *sockshost;
+ unsigned short socksport;
+} LNK;
+
+class CRTMP
+ {
+ public:
+
+ CRTMP();
+ virtual ~CRTMP();
+
+ void SetBufferMS(int size);
+ void UpdateBufferMS();
+
+ void SetupStream(
+ int protocol,
+ const char *hostname,
+ unsigned int port,
+ const char *sockshost,
+ const char *playpath,
+ const char *tcUrl,
+ const char *swfUrl,
+ const char *pageUrl,
+ const char *app,
+ const char *auth,
+ const char *swfSHA256Hash,
+ uint32_t swfSize,
+ const char *flashVer,
+ const char *subscribepath,
+ double dTime,
+ uint32_t dLength,
+ bool bLiveStream,
+ long int timeout=300);
+
+ bool Connect();
+
+ bool IsConnected();
+ bool IsTimedout();
+ double GetDuration();
+ bool ToggleStream();
+
+ bool ConnectStream(double seekTime=-10.0, uint32_t dLength=0);
+ bool ReconnectStream(int bufferTime, double seekTime=-10.0, uint32_t dLength=0);
+ void DeleteStream();
+ int GetNextMediaPacket(RTMPPacket &packet);
+
+ void Close();
+
+ static int EncodeString(char *output, const std::string &strValue);
+ static int EncodeNumber(char *output, double dVal);
+ static int EncodeInt16(char *output, short nVal);
+ static int EncodeInt24(char *output, int nVal);
+ static int EncodeInt32(char *output, int nVal);
+ static int EncodeBoolean(char *output,bool bVal);
+
+ static unsigned short ReadInt16(const char *data);
+ static unsigned int ReadInt24(const char *data);
+ static unsigned int ReadInt32(const char *data);
+ static std::string ReadString(const char *data);
+ static bool ReadBool(const char *data);
+ static double ReadNumber(const char *data);
+ bool SendPause(bool DoPause, double dTime);
+
+ static bool DumpMetaData(AMFObject &obj);
+ static bool FindFirstMatchingProperty(AMFObject &obj, std::string name, AMFObjectProperty &p);
+
+ protected:
+ bool HandShake(bool FP9HandShake=true);
+ bool RTMPConnect();
+ bool SocksNegotiate();
+
+ bool SendConnectPacket();
+ bool SendServerBW();
+ bool SendCheckBW();
+ bool SendCheckBWResult(double txn);
+ bool SendCtrl(short nType, unsigned int nObject, unsigned int nTime = 0);
+ bool SendBGHasStream(double dId, char *playpath);
+ bool SendCreateStream(double dStreamId);
+ bool SendDeleteStream(double dStreamId);
+ bool SendFCSubscribe(const char *subscribepath);
+ bool SendPlay();
+ bool SendSeek(double dTime);
+ bool SendBytesReceived();
+
+ int HandlePacket(RTMPPacket &packet);
+ int HandleInvoke(const char *body, unsigned int nBodySize);
+ bool HandleMetadata(char *body, unsigned int len);
+ void HandleChangeChunkSize(const RTMPPacket &packet);
+ void HandleAudio(const RTMPPacket &packet);
+ void HandleVideo(const RTMPPacket &packet);
+ void HandleCtrl(const RTMPPacket &packet);
+ void HandleServerBW(const RTMPPacket &packet);
+ void HandleClientBW(const RTMPPacket &packet);
+
+ int EncodeString(char *output, const std::string &strName, const std::string &strValue);
+ int EncodeNumber(char *output, const std::string &strName, double dVal);
+ int EncodeBoolean(char *output, const std::string &strName, bool bVal);
+
+ bool SendRTMP(RTMPPacket &packet);
+
+ bool ReadPacket(RTMPPacket &packet);
+ int ReadN(char *buffer, int n);
+ bool WriteN(const char *buffer, int n);
+
+ bool FillBuffer();
+ void FlushBuffer();
+
+ 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_bPausing;
+ int m_nServerBW;
+ int m_nClientBW;
+ uint8_t m_nClientBW2;
+ bool m_bPlaying;
+ bool m_bTimedout;
+
+ //std::string m_strPlayer;
+ //std::string m_strPageUrl;
+ //std::string m_strLink;
+ //std::string m_strPlayPath;
+
+ std::vector<std::string> m_methodCalls; //remote method calls queue
+
+ LNK Link;
+ char *m_pBuffer; // data read from socket
+ char *m_pBufferStart; // pointer into m_pBuffer of next byte to process
+ int m_nBufferSize; // number of unprocessed bytes in buffer
+ RTMPPacket *m_vecChannelsIn[65600];
+ RTMPPacket *m_vecChannelsOut[65600];
+ int m_channelTimestamp[65600]; // 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
+ };
+};
+
+#endif
--- /dev/null
+/* 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 <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 "rtmp.h"
+#include "log.h"
+#include "AMFObject.h"
+#include "parseurl.h"
+
+int debuglevel = 1;
+
+using namespace RTMP_LIB;
+
+#define RTMPDUMP_VERSION "v1.9"
+
+#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
+}
+
+//uint32_t nTimeStamp = 0;
+
+#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;
+}
+
+// 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(
+ CRTMP* 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;
+ RTMPPacket packet;
+
+ rtnGetNextMediaPacket = rtmp->GetNextMediaPacket(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) {
+
+ RTMP_LIB::AMFObject metaObj;
+ int nRes = metaObj.Decode(packetBody, nPacketLen);
+ if(nRes >= 0) {
+ std::string metastring = metaObj.GetProperty(0).GetString();
+
+ if(metastring == "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 = CRTMP::ReadInt24(packetBody+pos+1); // size without header (11) and prevTagSize (4)
+ ts = CRTMP::ReadInt24(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 += CRTMP::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 = CRTMP::ReadInt24(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 += CRTMP::EncodeInt24(ptr, nTimeStamp);
+ *ptr = (char)((nTimeStamp & 0xFF000000) >> 24);
+ ptr++;
+
+ // stream id
+ ptr += CRTMP::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 = CRTMP::ReadInt24(packetBody+pos+1); // size without header (11) and without prevTagSize (4)
+ nTimeStamp = CRTMP::ReadInt24(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;
+ CRTMP::EncodeInt32(ptr+pos+11+dataSize, prevTagSize);
+ size+=4; len+=4;
+ } else {
+ prevTagSize = CRTMP::ReadInt32(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;
+ CRTMP::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
+ CRTMP::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 = RTMP_LIB::CRTMP::ReadInt32(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 = RTMP_LIB::CRTMP::ReadInt32(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 = RTMP_LIB::CRTMP::ReadInt24(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;
+
+ RTMP_LIB::AMFObject metaObj;
+ int nRes = metaObj.Decode(buffer, dataSize);
+ if(nRes < 0) {
+ Log(LOGERROR, "%s, error decoding meta data packet", __FUNCTION__);
+ break;
+ }
+
+ std::string metastring = metaObj.GetProperty(0).GetString();
+
+ if(metastring == "onMetaData") {
+ metaObj.Dump();
+
+ *nMetaHeaderSize = dataSize;
+ if (*metaHeader) free(*metaHeader);
+ *metaHeader = (char *)malloc(*nMetaHeaderSize);
+ memcpy(*metaHeader, buffer, *nMetaHeaderSize);
+
+ // get duration
+ AMFObjectProperty prop;
+ if(RTMP_LIB::CRTMP::FindFirstMatchingProperty(metaObj, "duration", prop)) {
+ *duration = prop.GetNumber();
+ 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 = RTMP_LIB::CRTMP::ReadInt32(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 = RTMP_LIB::CRTMP::ReadInt24(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 = RTMP_LIB::CRTMP::ReadInt24(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::CRTMP::ReadInt32(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::CRTMP::ReadInt24(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(CRTMP *rtmp, // connected CRTMP 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 = round(*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 = GetTime();
+ lastUpdate = now-1000;
+ do
+ {
+ nRead = WriteStream(rtmp, &buffer, bufferSize, ×tamp, 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();
+
+ 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(bufferTime);
+ rtmp->UpdateBufferMS();
+ }
+ *percent = ((double)timestamp) / (duration*1000.0)*100.0;
+ *percent = round(*percent*10.0)/10.0;
+ if (bHashes) {
+ if ( lastPercent + 1 <= *percent ) {
+ LogStatus("#");
+ lastPercent = (unsigned long)*percent;
+ }
+ } else {
+ now = 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 = 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());
+ 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()) {
+ return RD_INCOMPLETE;
+ }
+
+ return RD_SUCCESS;
+}
+
+//#define _DEBUG_TEST_PLAYSTOP
+
+int main(int argc, char **argv)
+{
+ extern char *optarg;
+
+//#ifdef _DEBUG_TEST_PLAYSTOP
+// RTMPPacket packet;
+//#endif
+ 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;
+ char *playpath = 0;
+ char *subscribepath = 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;
+ char *swfUrl = 0;
+ char *tcUrl = 0;
+ char *pageUrl = 0;
+ char *app = 0;
+ char *auth = 0;
+ char *swfHash = 0;
+ uint32_t swfSize = 0;
+ char *flashVer = 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);
+ if(res!=32) {
+ swfHash = NULL;
+ Log(LOGWARNING, "Couldn't parse swf hash hex string, not heyxstring or not 32 bytes, ignoring!");
+ }
+ 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':
+ 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':
+ 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 == 0)
+ playpath = parsedPlaypath;
+ if(protocol == RTMP_PROTOCOL_UNDEFINED)
+ protocol = parsedProtocol;
+ if(app == 0)
+ app = parsedApp;
+ }
+ break;
+ }
+ case 's':
+ swfUrl = optarg;
+ break;
+ case 't':
+ tcUrl = optarg;
+ break;
+ case 'p':
+ pageUrl = optarg;
+ break;
+ case 'a':
+ app = optarg;
+ break;
+ case 'f':
+ flashVer = optarg;
+ break;
+ case 'o':
+ flvFile = optarg;
+ if(strcmp(flvFile, "-"))
+ bStdoutMode = false;
+
+ break;
+ case 'e':
+ bResume = true;
+ break;
+ case 'u':
+ 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 == 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 == 0 && swfSize > 0) {
+ Log(LOGWARNING, "Ignoring SWF size, supply also the hash with --swfhash");
+ swfSize=0;
+ }
+
+ if(swfHash != 0 && swfSize == 0) {
+ Log(LOGWARNING, "Ignoring SWF hash, supply also the swf size with --swfsize");
+ swfHash=NULL;
+ }
+
+ if(flashVer == 0)
+ flashVer = DEFAULT_FLASH_VER;
+
+ if(!InitSockets()) {
+ Log(LOGERROR, "Couldn't load sockets support on your platform, exiting!");
+ return RD_FAILED;
+ }
+
+ if(tcUrl == 0 && app != 0) {
+ char str[512]={0};
+
+ /*
+ struct hostent *h = gethostbyname(hostname);
+ if(h == NULL || h->h_addr == NULL) {
+ Log(LOGERROR, "DNS name resolution failed, exiting");
+ return RD_FAILED;
+ }
+ hostip = inet_ntoa(*(struct in_addr*)h->h_addr)
+ //*/
+ snprintf(str, 511, "%s://%s:%d/%s", RTMPProtocolStringsLower[protocol], hostname, port, app);
+ tcUrl = (char *)malloc(strlen(str)+1);
+ strcpy(tcUrl, 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;
+ }
+ }
+
+ CRTMP *rtmp = new CRTMP();
+ rtmp->SetupStream(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(bufferTime);
+
+ if (first) {
+ first = 0;
+ LogPrintf("Connecting ...\n");
+
+ if (!rtmp->Connect()) {
+ 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(dSeek, dLength)) {
+ nStatus = RD_FAILED;
+ break;
+ }
+ } else {
+ nInitialFrameSize = 0;
+
+ Log(LOGINFO, "Connection timed out, trying to resume.\n\n");
+ if (!rtmp->ToggleStream()) {
+ Log(LOGERROR, "Failed to resume the stream\n\n");
+ if (!rtmp->IsTimedout())
+ 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() || 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();
+ //LogPrintf("done!\n\n");
+
+ if(file != 0)
+ fclose(file);
+
+ CleanupSockets();
+
+#ifdef _DEBUG
+ if(netstackdump != 0)
+ fclose(netstackdump);
+ if(netstackdump_read != 0)
+ fclose(netstackdump_read);
+#endif
+ delete rtmp;
+ return nStatus;
+}
+
--- /dev/null
+/*
+ * 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 <string.h>
+#include <stdlib.h>
+
+#include "rtmppacket.h"
+#include "log.h"
+
+using namespace RTMP_LIB;
+
+RTMPPacket::RTMPPacket()
+{
+ Reset();
+}
+
+RTMPPacket::~RTMPPacket()
+{
+ FreePacket();
+}
+
+void RTMPPacket::Reset()
+{
+ m_headerType = 0;
+ m_packetType = 0;
+ m_nChannel = 0;
+ m_nInfoField1 = 0;
+ m_nInfoField2 = 0;
+ m_hasAbsTimestamp = false;
+ m_nBodySize = 0;
+ m_nBytesRead = 0;
+ m_body = NULL;
+ m_buffer = NULL;
+}
+
+bool RTMPPacket::AllocPacket(int nSize)
+{
+ m_buffer = (char *)calloc(1, nSize+RTMP_MAX_HEADER_SIZE);
+ if (!m_buffer)
+ return false;
+ m_body = m_buffer+RTMP_MAX_HEADER_SIZE;
+ m_nBytesRead = 0;
+ return true;
+}
+
+void RTMPPacket::FreePacket()
+{
+ FreePacketHeader();
+ Reset();
+}
+
+void RTMPPacket::FreePacketHeader()
+{
+ if (m_buffer)
+ free(m_buffer);
+ m_buffer = NULL;
+ m_body = NULL;
+}
+
+void RTMPPacket::Dump()
+{
+ Log(LOGDEBUG,"RTMP PACKET: packet type: 0x%02x. channel: 0x%02x. info 1: %d info 2: %d. Body size: %lu. body: 0x%02x", m_packetType, m_nChannel,
+ m_nInfoField1, m_nInfoField2, m_nBodySize, m_body?(unsigned char)m_body[0]:0);
+}
--- /dev/null
+#ifndef __RTMP_PACKET__
+#define __RTMP_PACKET__
+/*
+ * 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 <string>
+#include <stdint.h>
+
+#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
+{
+ BYTE Type; // 0x03 RTMP, 0x06 RTMPE
+
+} HANDSHAKE_SIGNATURE;
+
+namespace RTMP_LIB
+{
+ class RTMPPacket
+ {
+ public:
+ RTMPPacket();
+ virtual ~RTMPPacket();
+
+ void Reset();
+ bool AllocPacket(int nSize);
+ void FreePacket();
+ void FreePacketHeader();
+
+ inline bool IsReady() { return m_nBytesRead == m_nBodySize; }
+ void Dump();
+
+ 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_body;
+ char *m_buffer;
+ };
+};
+
+#endif
--- /dev/null
+/* HTTP-RTMP Stream Server
+ * 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
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include <signal.h> // to catch Ctrl-C
+#include <getopt.h>
+
+#include <assert.h>
+
+//#ifdef WIN32
+//#include <winsock.h>
+//#endif
+
+#include "rtmp.h"
+#include "log.h"
+#include "AMFObject.h"
+#include "parseurl.h"
+
+int debuglevel = 1;
+
+using namespace RTMP_LIB;
+
+#define RTMPDUMP_STREAMS_VERSION "v1.4"
+
+#define RD_SUCCESS 0
+#define RD_FAILED 1
+#define RD_INCOMPLETE 2
+
+#define PACKET_SIZE 1024*1024
+
+/*
+inline void InitSockets() {
+#ifdef WIN32
+ WORD version;
+ WSADATA wsaData;
+
+ version = MAKEWORD(1,1);
+ WSAStartup(version, &wsaData);
+#endif
+}
+
+inline void CleanupSockets() {
+#ifdef WIN32
+ WSACleanup();
+#endif
+}
+*/
+
+enum
+{
+ STREAMING_ACCEPTING,
+ STREAMING_IN_PROGRESS,
+ STREAMING_STOPPING,
+ STREAMING_STOPPED
+};
+
+typedef struct
+{
+ int socket;
+ int state;
+
+} STREAMING_SERVER;
+
+STREAMING_SERVER *httpServer = 0; // server structure pointer
+
+STREAMING_SERVER *startStreaming(int port);
+void stopStreaming(STREAMING_SERVER *server);
+
+bool bCtrlC = false;
+
+typedef struct
+{
+ uint32_t dSeek; // seek position in resume mode, 0 otherwise
+
+ char *hostname;
+ char *playpath;
+ int rtmpport;
+ int protocol;
+ bool bLiveStream; // is it a live stream? then we can't seek/resume
+
+ long int timeout; // timeout connection afte 300 seconds
+ uint32_t bufferTime;
+
+ char *rtmpurl;
+ char *swfUrl;
+ char *tcUrl;
+ char *pageUrl;
+ char *app;
+ char *auth;
+ char *swfHash;
+ uint32_t swfSize;
+ char *flashVer;
+ char *subscribepath;
+
+ uint32_t dStartOffset;
+ uint32_t dStopOffset;
+ uint32_t nTimeStamp;
+} RTMP_REQUEST;
+
+// this request is forrmed from the parameters and used to initialize a new request,
+// thus it is a default settings list. All settings can be overriden by specifying the
+// parameters in the GET request
+RTMP_REQUEST defaultRTMPRequest;
+
+bool ParseOption(char opt, char *arg, RTMP_REQUEST *req);
+
+char DEFAULT_FLASH_VER[] = "LNX 10,0,22,87";
+
+#ifdef _DEBUG
+uint32_t debugTS = 0;
+
+int pnum = 0;
+
+FILE *netstackdump = NULL;
+FILE *netstackdump_read = NULL;
+#endif
+/*
+uint32_t dSeek = 0; // seek position in resume mode, 0 otherwise
+
+char *hostname = 0;
+char *playpath = 0;
+int rtmpport = -1;
+int protocol = RTMP_PROTOCOL_UNDEFINED;
+bool bLiveStream = false; // is it a live stream? then we can't seek/resume
+
+long int timeout = 300; // timeout connection afte 300 seconds
+uint32_t bufferTime = 20*1000; // 10 seconds, 10*60*60*1000; // 10 hours as default
+
+char DEFAULT_FLASH_VER[] = "LNX 9,0,124,0";
+
+char *rtmpurl = 0;
+char *swfUrl = 0;
+char *tcUrl = 0;
+char *pageUrl = 0;
+char *app = 0;
+char *auth = 0;
+char *swfHash = 0;
+uint32_t swfSize = 0;
+char *flashVer = 0;
+
+uint32_t nTimeStamp = 0;
+
+#ifdef _DEBUG
+uint32_t debugTS = 0;
+int pnum=0;
+
+FILE *netstackdump = 0;
+FILE *netstackdump_read = 0;
+#endif
+*/
+
+/* inplace http unescape. This is possible .. strlen(unescaped_string) <= strlen(esacped_string) */
+void http_unescape(char* data)
+{
+ char hex[3];
+ char *stp;
+ int src_x=0;
+ int dst_x=0;
+
+ int length = (int)strlen(data);
+ hex[2]=0;
+
+ while(src_x<length)
+ {
+ if(strncmp(data+src_x,"%",1)==0 && src_x+2<length)
+ {
+ //
+ // Since we encountered a '%' we know this is an escaped character
+ //
+ hex[0] = data[src_x+1];
+ hex[1] = data[src_x+2];
+ data[dst_x] = (char)strtol(hex,&stp,16);
+ dst_x += 1;
+ src_x += 3;
+ }
+ else if(src_x!=dst_x)
+ {
+ //
+ // This doesn't need to be unescaped. If we didn't unescape anything previously
+ // there is no need to copy the string either
+ //
+ data[dst_x] = data[src_x];
+ src_x += 1;
+ dst_x += 1;
+ }
+ else
+ {
+ //
+ // This doesn't need to be unescaped, however we need to copy the string
+ //
+ src_x += 1;
+ dst_x += 1;
+ }
+ }
+ data[dst_x]='\0';
+}
+
+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;
+}
+
+int WriteStream(
+ CRTMP* rtmp,
+ char **buf, // target pointer, maybe preallocated
+ unsigned int len, // length of buffer if preallocated
+ uint32_t *nTimeStamp
+ )
+{
+ uint32_t prevTagSize = 0;
+ int rtnGetNextMediaPacket = 0;
+ RTMPPacket packet;
+
+ rtnGetNextMediaPacket = rtmp->GetNextMediaPacket(packet);
+ if(rtnGetNextMediaPacket)
+ {
+ char *packetBody = packet.m_body;
+ unsigned int nPacketLen = packet.m_nBodySize;
+
+ // 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", packet.m_packetType, nPacketLen, packet.m_nTimeStamp);
+ if(packet.m_packetType == 0x09)
+ Log(LOGDEBUG, "frametype: %02X", (*packetBody & 0xf0));
+#endif
+
+ // 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;
+
+ // 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) = packet.m_nTimeStamp;
+ prevTagSize = 11 + nPacketLen;
+
+ *ptr = packet.m_packetType; ptr++;
+ ptr += CRTMP::EncodeInt24(ptr, nPacketLen);
+ ptr += CRTMP::EncodeInt24(ptr, *nTimeStamp);
+ *ptr = (char)(((*nTimeStamp) & 0xFF000000) >> 24); ptr++;
+
+ // stream id
+ ptr += CRTMP::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 = CRTMP::ReadInt24(packetBody+pos+1); // size without header (11) and without prevTagSize (4)
+ *nTimeStamp = CRTMP::ReadInt24(packetBody+pos+4);
+ *nTimeStamp |= (packetBody[pos+7]<<24);
+
+ // 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;
+ CRTMP::EncodeInt32(ptr+pos+11+dataSize, prevTagSize);
+ size+=4; len+=4;
+ } else {
+ prevTagSize = CRTMP::ReadInt32(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;
+ CRTMP::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
+ CRTMP::EncodeInt32(ptr, prevTagSize);
+ //ptr += 4;
+ }
+
+ // Return 0 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 0;
+ }
+
+ return size;
+ }
+
+ return -1; // no more media packets
+}
+
+pthread_t ThreadCreate(void *(*routine)(void *), void *args)
+{
+ pthread_t id = 0;
+ pthread_attr_t attributes;
+ int ret;
+
+ pthread_attr_init(&attributes);
+ pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED);
+
+ ret = pthread_create(&id, &attributes, (void *(*)(void *))routine, (void *)args);
+ if(ret != 0)
+ LogPrintf("%s, pthread_create failed with %d\n", __FUNCTION__, ret);
+
+ return id;
+}
+
+void *controlServerThread(void *)
+{
+ char ich;
+ while(1)
+ {
+ ich = getchar();
+ switch(ich)
+ {
+ case 'q':
+ LogPrintf("Exiting\n");
+ stopStreaming(httpServer);
+ exit(0);
+ break;
+ default:
+ LogPrintf("Unknown command \'%c\', ignoring\n", ich);
+ }
+ }
+ return 0;
+}
+/*
+ssize_t readHTTPLine(int sockfd, char *buffer, size_t length)
+{
+ size_t i=0;
+
+ while(i < length-1) {
+ char c;
+ int n = read(sockfd, &c, 1);
+
+ if(n == 0)
+ break;
+
+ buffer[i] = c;
+ i++;
+
+ if(c == '\n')
+ break;
+ }
+ buffer[i]='\0';
+ i++;
+
+ return i;
+}
+
+bool isHTTPRequestEOF(char *line, size_t length)
+{
+ if(length < 2)
+ return true;
+
+ if(line[0]=='\r' && line[1]=='\n')
+ return true;
+
+ return false;
+}
+*/
+
+void processTCPrequest
+(
+ STREAMING_SERVER *server, // server socket and state (our listening socket)
+ int sockfd // client connection socket
+)
+{
+ char buf[512] = {0}; // answer buffer
+ char header[2048] = {0}; // request header
+ char *filename = NULL; // GET request: file name //512 not enuf
+ char *buffer = NULL; // stream buffer
+ char *ptr = NULL; // header pointer
+
+ size_t nRead = 0;
+
+ char srvhead[] = "\r\nServer:HTTP-RTMP Stream Server \r\nContent-Type: Video/MPEG \r\n\r\n";
+
+ server->state = STREAMING_IN_PROGRESS;
+
+ CRTMP *rtmp = new CRTMP();
+ uint32_t dSeek = 0; // can be used to start from a later point in the stream
+
+ // reset RTMP options to defaults specified upon invokation of streams
+ RTMP_REQUEST req;
+ memcpy(&req, &defaultRTMPRequest, sizeof(RTMP_REQUEST));
+
+ // timeout for http requests
+ fd_set fds;
+ struct timeval tv;
+
+ memset(&tv, 0, sizeof(struct timeval));
+ tv.tv_sec = 5;
+
+ // go through request lines
+ //do {
+ FD_ZERO(&fds);
+ FD_SET(sockfd, &fds);
+
+ if(select(sockfd + 1, &fds, NULL, NULL, &tv) <= 0) {
+ Log(LOGERROR, "Request timeout/select failed, ignoring request");
+ goto quit;
+ } else {
+ nRead = read(sockfd, header, 2047);
+ header[2047]='\0';
+
+ Log(LOGDEBUG, "%s: header: %s", __FUNCTION__, header);
+
+ if(strstr(header,"Range: bytes=") != 0)
+ {
+ // TODO check range starts from 0 and asking till the end.
+ LogPrintf("%s, Range request not supported\n", __FUNCTION__);
+ sprintf(buf,"HTTP/1.0 416 Requested Range Not Satisfiable%s", srvhead);
+ send(sockfd, buf, (int) strlen(buf), 0);
+ goto quit;
+ }
+
+ if(strncmp(header,"GET", 3) == 0 && nRead > 4) {
+ filename = header+4;
+
+ // filter " HTTP/..." from end of request
+ char *p = filename;
+ while(*p != '\0') {
+ if(*p == ' ') {
+ *p='\0';
+ break;
+ }
+ p++;
+ }
+ }
+ }
+ //} while(!isHTTPRequestEOF(header, nRead));
+
+ // if we got a filename from the GET method
+ if(filename != NULL)
+ {
+ Log(LOGDEBUG, "%s: Request header: %s", __FUNCTION__, filename);
+ if(filename[0] == '/') { // if its not empty, is it /?
+ ptr = filename+1;
+
+ // parse parameters
+ if(*ptr == '?')
+ {
+ ptr++;
+ int len = strlen(ptr);
+
+ while(len >= 2) {
+ char ich = *ptr; ptr++;
+ if(*ptr != '=')
+ goto filenotfound; // long parameters not (yet) supported
+
+ ptr++; len-=2;
+
+ // get position of the next '&'
+ char *temp;
+
+ unsigned int nArgLen = len;
+ if((temp=strstr(ptr, "&"))!=0) {
+ nArgLen = temp-ptr;
+ }
+
+ char *arg = (char *)malloc((nArgLen+1)*sizeof(char));
+ memcpy(arg, ptr, nArgLen*sizeof(char));
+ arg[nArgLen]='\0';
+
+ //Log(LOGDEBUG, "%s: unescaping parameter: %s", __FUNCTION__, arg);
+ http_unescape(arg);
+
+ Log(LOGDEBUG, "%s: parameter: %c, arg: %s", __FUNCTION__, ich, arg);
+
+ ptr += nArgLen+1;
+ len -= nArgLen+1;
+
+ ParseOption(ich, arg, &req);
+ }
+ }
+ }
+ else {
+ goto filenotfound;
+ }
+ } else {
+ LogPrintf("%s: No request header received/unsupported method\n", __FUNCTION__);
+ }
+
+ // do necessary checks right here to make sure the combined request of default values and GET parameters is correct
+ if(req.hostname == 0) {
+ Log(LOGERROR, "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
+ goto filenotfound;
+ }
+ if(req.playpath == 0) {
+ Log(LOGERROR, "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
+ goto filenotfound;;
+ }
+
+ if(req.rtmpport == -1) {
+ Log(LOGWARNING, "You haven't specified a port (--port) or rtmp url (-r), using default port 1935");
+ req.rtmpport = 1935;
+ }
+ if(req.protocol == RTMP_PROTOCOL_UNDEFINED) {
+ Log(LOGWARNING, "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
+ req.protocol = RTMP_PROTOCOL_RTMP;
+ }
+
+ if(req.flashVer == 0)
+ req.flashVer = DEFAULT_FLASH_VER;
+
+ if(req.tcUrl == 0 && req.app != 0) {
+ char str[512]={0};
+ snprintf(str, 511, "%s://%s/%s", RTMPProtocolStringsLower[req.protocol], req.hostname, req.app);
+ req.tcUrl = (char *)malloc(strlen(str)+1);
+ strcpy(req.tcUrl, str);
+ }
+
+ if(req.rtmpport == 0)
+ req.rtmpport = 1935;
+
+ // after validation of the http request send response header
+ sprintf(buf,"HTTP/1.0 200 OK%s", srvhead);
+ send(sockfd, buf, (int)strlen(buf), 0);
+
+ // send the packets
+ buffer = (char *)calloc(PACKET_SIZE,1);
+
+ // User defined seek offset
+ if (req.dStartOffset > 0) {
+ if (req.bLiveStream)
+ Log(LOGWARNING, "Can't seek in a live stream, ignoring --seek option");
+ else
+ dSeek += req.dStartOffset;
+ }
+
+ if(dSeek != 0) {
+ LogPrintf("Starting at TS: %d ms\n", req.nTimeStamp);
+ }
+
+ Log(LOGDEBUG, "Setting buffer time to: %dms", req.bufferTime);
+ rtmp->SetBufferMS(req.bufferTime);
+ rtmp->SetupStream(
+ req.protocol,
+ req.hostname,
+ req.rtmpport,
+ NULL, // sockshost
+ req.playpath,
+ req.tcUrl,
+ req.swfUrl,
+ req.pageUrl,
+ req.app,
+ req.auth,
+ req.swfHash,
+ req.swfSize,
+ req.flashVer,
+ req.subscribepath,
+ dSeek,
+ -1, // length
+ req.bLiveStream,
+ req.timeout);
+
+ LogPrintf("Connecting ... port: %d, app: %s\n", req.rtmpport, req.app);
+ if (!rtmp->Connect()) {
+ LogPrintf("%s, failed to connect!\n", __FUNCTION__);
+ }
+ else
+ {
+ unsigned long size = 0;
+ double percent = 0;
+ double duration = 0.0;
+
+ int nWritten = 0;
+ int nRead = 0;
+
+ // write FLV header first
+ nRead = WriteHeader(&buffer, PACKET_SIZE);
+ if(nRead > 0) {
+ nWritten = send(sockfd, buffer, nRead, 0);
+ if(nWritten < 0) {
+ Log(LOGERROR, "%s, sending failed, error: %d", __FUNCTION__, GetSockError());
+ goto cleanup; // we are in STREAMING_IN_PROGRESS, so we'll go to STREAMING_ACCEPTING
+ }
+
+ size += nRead;
+ } else {
+ Log(LOGERROR, "%s: Couldn't obtain FLV header, exiting!", __FUNCTION__);
+ goto cleanup;
+ }
+
+ // get the rest of the stream
+ do
+ {
+ nRead = WriteStream(rtmp, &buffer, PACKET_SIZE, &req.nTimeStamp);
+
+ if(nRead > 0)
+ {
+ nWritten = send(sockfd, buffer, nRead, 0);
+ //Log(LOGDEBUG, "written: %d", nWritten);
+ if(nWritten < 0) {
+ Log(LOGERROR, "%s, sending failed, error: %d", __FUNCTION__, GetSockError());
+ goto cleanup; // we are in STREAMING_IN_PROGRESS, so we'll go to STREAMING_ACCEPTING
+ }
+
+ 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();
+
+ if(duration > 0) {
+ percent = ((double)(dSeek+req.nTimeStamp)) / (duration*1000.0)*100.0;
+ percent = round(percent*10.0)/10.0;
+ LogPrintf("\r%.3f KB / %.2f sec (%.1f%%)", (double)size/1024.0, (double)(req.nTimeStamp)/1000.0, percent);
+ } else {
+ LogPrintf("\r%.3f KB / %.2f sec", (double)size/1024.0, (double)(req.nTimeStamp)/1000.0);
+ }
+ }
+ #ifdef _DEBUG
+ else { Log(LOGDEBUG, "zero read!"); }
+ #endif
+
+ // Force clean close if a specified stop offset is reached
+ if (req.dStopOffset && req.nTimeStamp >= req.dStopOffset) {
+ LogPrintf("\nStop offset has been reached at %.2f seconds\n", (double)req.dStopOffset/1000.0);
+ nRead = 0;
+ rtmp->Close();
+ }
+
+ } while(server->state == STREAMING_IN_PROGRESS && nRead > -1 && rtmp->IsConnected() && nWritten >= 0);
+ }
+cleanup:
+ LogPrintf("Closing connection... ");
+ rtmp->Close();
+ LogPrintf("done!\n\n");
+
+quit:
+ if(buffer) {
+ free(buffer);
+ buffer = NULL;
+ }
+
+ if(sockfd)
+ close(sockfd);
+
+ if(server->state == STREAMING_IN_PROGRESS)
+ server->state = STREAMING_ACCEPTING;
+
+ return;
+
+filenotfound:
+ LogPrintf("%s, File not found, %s\n", __FUNCTION__, filename);
+ sprintf(buf,"HTTP/1.0 404 File Not Found%s", srvhead);
+ send(sockfd, buf, (int) strlen(buf), 0);
+ goto quit;
+}
+
+void *serverThread(STREAMING_SERVER *server)
+{
+ server->state = STREAMING_ACCEPTING;
+
+ while(server->state == STREAMING_ACCEPTING)
+ {
+ struct sockaddr_in addr;
+ socklen_t addrlen = sizeof(struct sockaddr_in);
+ int sockfd = accept(server->socket, (struct sockaddr *)&addr, &addrlen);
+
+ if(sockfd > 0)
+ {
+ // Create a new process and transfer the control to that
+ Log(LOGDEBUG, "%s: accepted connection from %s\n", __FUNCTION__, inet_ntoa(addr.sin_addr));
+ processTCPrequest(server, sockfd);
+ Log(LOGDEBUG, "%s: processed request\n", __FUNCTION__);
+ }
+ else {
+ Log(LOGERROR, "%s: accept failed", __FUNCTION__);
+ }
+ }
+ server->state = STREAMING_STOPPED;
+ return 0;
+}
+
+STREAMING_SERVER *startStreaming(const char *address, int port)
+{
+ struct sockaddr_in addr;
+ int sockfd;
+ STREAMING_SERVER *server;
+
+ sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if(sockfd == -1) {
+ Log(LOGERROR, "%s, couldn't create socket", __FUNCTION__);
+ return 0;
+ }
+
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = inet_addr(address);//htonl(INADDR_ANY);
+ addr.sin_port = htons(port);
+
+ if(bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == -1) {
+ Log(LOGERROR, "%s, TCP bind failed for port number: %d", __FUNCTION__, port);
+ return 0;
+ }
+
+ if(listen(sockfd, 10) == -1)
+ {
+ Log(LOGERROR, "%s, listen failed", __FUNCTION__);
+ close(sockfd);
+ return 0;
+ }
+
+ server = (STREAMING_SERVER *)calloc(1, sizeof(STREAMING_SERVER));
+ server->socket = sockfd;
+
+ ThreadCreate((void* (*)(void*))serverThread, server);
+
+ return server;
+}
+
+void stopStreaming(STREAMING_SERVER *server)
+{
+ assert(server);
+
+ if(server->state != STREAMING_STOPPED)
+ {
+ if(server->state == STREAMING_IN_PROGRESS)
+ {
+ server->state = STREAMING_STOPPING;
+
+ // wait for streaming threads to exit
+ while(server->state != STREAMING_STOPPED)
+ usleep(1*1000);
+ }
+
+ if(close(server->socket))
+ Log(LOGERROR, "%s: Failed to close listening socket, error %d", GetSockError());
+
+ server->state = STREAMING_STOPPED;
+ }
+}
+
+
+void sigIntHandler(int sig) {
+ bCtrlC = true;
+ LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig);
+ if(httpServer)
+ stopStreaming(httpServer);
+ signal(SIGINT, SIG_DFL);
+}
+
+// this will parse RTMP related options as needed
+// excludes the following options: h, d, g
+
+// Return values: true (option parsing ok)
+// false (option not parsed/invalid)
+bool ParseOption(char opt, char *arg, RTMP_REQUEST *req)
+{
+ switch(opt)
+ {
+ case 'w':
+ {
+ int res = hex2bin(arg, &req->swfHash);
+ if(!res || res!=32) {
+ req->swfHash = NULL;
+ Log(LOGWARNING, "Couldn't parse swf hash hex string, not heyxstring or not 32 bytes, ignoring!");
+ }
+ break;
+ }
+ case 'x':
+ {
+ int size = atoi(arg);
+ if(size <= 0) {
+ Log(LOGERROR, "SWF Size must be at least 1, ignoring\n");
+ } else {
+ req->swfSize = size;
+ }
+ break;
+ }
+ case 'b':
+ {
+ int32_t bt = atol(arg);
+ if(bt < 0) {
+ Log(LOGERROR, "Buffer time must be greater than zero, ignoring the specified value %d!", bt);
+ } else {
+ req->bufferTime = bt;
+ }
+ break;
+ }
+ case 'v':
+ req->bLiveStream = true; // no seeking or resuming possible!
+ break;
+ case 'd':
+ req->subscribepath = optarg;
+ break;
+ case 'n':
+ req->hostname = arg;
+ break;
+ case 'c':
+ req->rtmpport = atoi(arg);
+ break;
+ case 'l':
+ {
+ int protocol = atoi(arg);
+ if(protocol != RTMP_PROTOCOL_RTMP && protocol != RTMP_PROTOCOL_RTMPE) {
+ Log(LOGERROR, "Unknown protocol specified: %d, using default", protocol);
+ return false;
+ } else {
+ req->protocol = protocol;
+ }
+ break;
+ }
+ case 'y':
+ req->playpath = arg;
+ break;
+ case 'r':
+ {
+ req->rtmpurl = arg;
+
+ char *parsedHost = 0;
+ unsigned int parsedPort = 0;
+ char *parsedPlaypath = 0;
+ char *parsedApp = 0;
+ int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
+
+ if(!ParseUrl(req->rtmpurl, &parsedProtocol, &parsedHost, &parsedPort, &parsedPlaypath, &parsedApp)) {
+ Log(LOGWARNING, "Couldn't parse the specified url (%s)!", arg);
+ } else {
+ if(req->hostname == 0)
+ req->hostname = parsedHost;
+ if(req->rtmpport == -1)
+ req->rtmpport = parsedPort;
+ if(req->playpath == 0)
+ req->playpath = parsedPlaypath;
+ if(req->protocol == RTMP_PROTOCOL_UNDEFINED)
+ req->protocol = parsedProtocol;
+ if(req->app == 0)
+ req->app = parsedApp;
+ }
+ break;
+ }
+ case 's':
+ req->swfUrl = arg;
+ break;
+ case 't':
+ req->tcUrl = arg;
+ break;
+ case 'p':
+ req->pageUrl = arg;
+ break;
+ case 'a':
+ req->app = arg;
+ break;
+ case 'f':
+ req->flashVer = arg;
+ break;
+ case 'u':
+ req->auth = arg;
+ break;
+ case 'm':
+ req->timeout = atoi(arg);
+ break;
+ case 'A':
+ req->dStartOffset = atoi(arg)*1000;
+ //printf("dStartOffset = %d\n", dStartOffset);
+ break;
+ case 'B':
+ req->dStopOffset = atoi(arg)*1000;
+ //printf("dStartOffset = %d\n", dStartOffset);
+ break;
+ case 'q':
+ debuglevel = LOGCRIT;
+ break;
+ case 'V':
+ debuglevel = LOGDEBUG;
+ break;
+ case 'z':
+ debuglevel = LOGALL;
+ break;
+ default:
+ LogPrintf("unknown option: %c, arg: %s\n", opt, arg);
+ break;
+ }
+ return true;
+}
+
+int main(int argc, char **argv)
+{
+ int nStatus = RD_SUCCESS;
+
+ // http streaming server
+ char DEFAULT_HTTP_STREAMING_DEVICE[] = "0.0.0.0"; // 0.0.0.0 is any device
+
+ char *httpStreamingDevice = DEFAULT_HTTP_STREAMING_DEVICE; // streaming device, default 0.0.0.0
+ int nHttpStreamingPort = 80; // port
+
+ LogPrintf("HTTP-RTMP Stream Server %s\n", RTMPDUMP_STREAMS_VERSION);
+ LogPrintf("(c) 2009 Andrej Stepanchuk, license: GPL\n\n");
+
+ // init request
+ memset(&defaultRTMPRequest, 0, sizeof(RTMP_REQUEST));
+
+ defaultRTMPRequest.rtmpport = -1;
+ defaultRTMPRequest.protocol = RTMP_PROTOCOL_UNDEFINED;
+ defaultRTMPRequest.bLiveStream = false; // is it a live stream? then we can't seek/resume
+
+ defaultRTMPRequest.timeout = 300; // timeout connection afte 300 seconds
+ defaultRTMPRequest.bufferTime = 20*1000;
+
+
+ int opt;
+ struct option longopts[] = {
+ {"help", 0, NULL, 'h'},
+ {"host", 1, NULL, 'n'},
+ {"port", 1, NULL, 'c'},
+ {"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'},
+ {"swfhash", 1, NULL, 'w'},
+ {"swfsize", 1, NULL, 'x'},
+ {"auth", 1, NULL, 'u'},
+ {"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'},
+ {"device", 1, NULL, 'D'},
+ {"sport", 1, NULL, 'g'},
+ {"subscribe",1,NULL, 'd'},
+ {"start", 1, NULL, 'A'},
+ {"stop", 1, NULL, 'B'},
+ {"debug", 0, NULL, 'z'},
+ {"quiet", 0, NULL, 'q'},
+ {"verbose", 0, NULL, 'V'},
+ {0,0,0,0}
+ };
+
+ signal(SIGINT, sigIntHandler);
+ signal(SIGPIPE, SIG_IGN);
+
+ while((opt = getopt_long(argc, argv, "hvqVzr:s:t:p:a:f:u:n:c:l:y:m:d:D:A:B:g:w:x:", 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("--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");
+ 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");
+ 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 Get a live stream, no --resume (seeking) of live strems possible\n");
+ LogPrintf("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specifed)\n");
+ LogPrintf("--timeout|-m num Timeout connection num seconds (default: %lu)\n", defaultRTMPRequest.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("--buffer|-b Buffer time in milliseconds (default: %lu)\n\n",
+ defaultRTMPRequest.bufferTime);
+
+ LogPrintf("--device|-D Streaming device ip address (default: %s)\n", DEFAULT_HTTP_STREAMING_DEVICE);
+ LogPrintf("--sport|-g Streaming port (default: %d)\n\n", nHttpStreamingPort);
+ LogPrintf("--quiet|-q Supresses all command output.\n");
+ LogPrintf("--verbose|-x 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;
+ break;
+ // streaming server specific options
+ case 'D':
+ if(inet_addr(optarg) == INADDR_NONE) {
+ Log(LOGERROR, "Invalid binding address (requested address %s), ignoring", optarg);
+ } else {
+ httpStreamingDevice = optarg;
+ }
+ break;
+ case 'g':
+ {
+ int port = atoi(optarg);
+ if(port < 0 || port > 65535) {
+ Log(LOGERROR, "Streaming port out of range (requested port %d), ignoring\n", port);
+ } else {
+ nHttpStreamingPort = port;
+ }
+ break;
+ }
+ default:
+ //LogPrintf("unknown option: %c\n", opt);
+ ParseOption(opt, optarg, &defaultRTMPRequest);
+ break;
+ }
+ }
+
+ #ifdef _DEBUG
+ netstackdump = fopen("netstackdump", "wb");
+ netstackdump_read = fopen("netstackdump_read", "wb");
+ #endif
+
+ //InitSockets();
+
+ // start text UI
+ ThreadCreate(controlServerThread, 0);
+
+ // start http streaming
+ if((httpServer=startStreaming(httpStreamingDevice, nHttpStreamingPort))==0) {
+ Log(LOGERROR, "Failed to start HTTP server, exiting!");
+ return RD_FAILED;
+ }
+ LogPrintf("Streaming on http://%s:%d\n", httpStreamingDevice, nHttpStreamingPort);
+
+ while(httpServer->state != STREAMING_STOPPED) {sleep(1);}
+ Log(LOGDEBUG, "Done, exiting...");
+
+ //CleanupSockets();
+
+#ifdef _DEBUG
+ if(netstackdump != 0)
+ fclose(netstackdump);
+ if(netstackdump_read != 0)
+ fclose(netstackdump_read);
+#endif
+ return nStatus;
+}
+