Merge branch 'master' of octo.ddvtech.com:pls

This commit is contained in:
Erik Zandvliet 2011-08-24 09:11:34 +02:00
commit b35ef497ea
7 changed files with 172 additions and 46 deletions

View file

@ -121,13 +121,13 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
}
if (viddone && auddone && justdone){
if (viddata.len != 0){
Socket.write(RTMPStream::SendMedia((unsigned char)viddata.data[0], (unsigned char *)viddata.data+11, viddata.len-15, 0));
Socket.write(RTMPStream::SendMedia(viddata));
#if DEBUG >= 8
fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), viddata.tagTime(), viddata.tagType().c_str());
#endif
}
if (auddata.len != 0){
Socket.write(RTMPStream::SendMedia((unsigned char)auddata.data[0], (unsigned char *)auddata.data+11, auddata.len-15, 0));
Socket.write(RTMPStream::SendMedia(auddata));
#if DEBUG >= 8
fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), auddata.tagTime(), auddata.tagType().c_str());
#endif
@ -137,7 +137,7 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
//not gotten init yet? cancel this tag
if (viddata.len == 0 || auddata.len == 0){break;}
//send tag normally
Socket.write(RTMPStream::SendMedia((unsigned char)tag.data[0], (unsigned char *)tag.data+11, tag.len-15, tag.tagTime()));
Socket.write(RTMPStream::SendMedia(tag));
#if DEBUG >= 8
fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), tag.tagTime(), tag.tagType().c_str());
#endif
@ -383,7 +383,7 @@ void Connector_RTMP::parseChunk(){
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Play.Reset"));
amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing and resetting..."));
amfreply.getContentP(3)->addContent(AMF::Object("details", "PLS"));
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1));
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
#if DEBUG >= 4
amfreply.Print();
#endif
@ -397,7 +397,7 @@ void Connector_RTMP::parseChunk(){
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Play.Start"));
amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing!"));
amfreply.getContentP(3)->addContent(AMF::Object("details", "PLS"));
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1));
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
#if DEBUG >= 4
amfreply.Print();
#endif

View file

@ -1,4 +1,4 @@
SRC = main.cpp ../util/amf.cpp ../util/rtmpchunks.cpp ../util/crypto.cpp
SRC = main.cpp ../util/amf.cpp ../util/rtmpchunks.cpp ../util/crypto.cpp ../util/flv_tag.cpp ../util/socket.cpp
OBJ = $(SRC:.cpp=.o)
OUT = RTMP_Parser
INCLUDES =
@ -8,7 +8,7 @@ CC = $(CROSS)g++
LD = $(CROSS)ld
AR = $(CROSS)ar
LIBS = -lssl -lcrypto
.SUFFIXES: .cpp
.SUFFIXES: .cpp
.PHONY: clean default
default: $(OUT)
.cpp.o:

View file

@ -2,30 +2,61 @@
/// Debugging tool for RTMP data.
/// Expects RTMP data of one side of the conversion through stdin, outputs human-readable information to stderr.
/// Automatically skips 3073 bytes of handshake data.
/// Optionally reconstructs an FLV file
/// Singular argument is a bitmask indicating the following (defaulting to 0):
/// - 0 = Info: Output chunk meanings and fulltext commands to stderr.
/// - 1 = Reconstruct: Output valid .flv file to stdout.
/// - 2 = Explicit: Audio/video data details.
/// - 4 = Verbose: details about every whole chunk.
#define DEBUG 10 //maximum debugging level
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <string>
#include "../util/flv_tag.h"
#include "../util/amf.h"
#include "../util/rtmpchunks.h"
int Detail = 0;
#define DETAIL_RECONSTRUCT 1
#define DETAIL_EXPLICIT 2
#define DETAIL_VERBOSE 4
/// Debugging tool for RTMP data.
/// Expects RTMP data of one side of the conversion through stdin, outputs human-readable information to stderr.
/// Will output FLV file to stdout, if available
/// Automatically skips 3073 bytes of handshake data.
int main(){
int main(int argc, char ** argv){
if (argc > 1){
Detail = atoi(argv[1]);
fprintf(stderr, "Detail level set:\n");
if ((Detail & DETAIL_RECONSTRUCT) == DETAIL_RECONSTRUCT){
fprintf(stderr, " - Will reconstuct FLV file to stdout\n");
std::cout.write(FLV::Header, 13);
}
if ((Detail & DETAIL_EXPLICIT) == DETAIL_EXPLICIT){
fprintf(stderr, " - Will list explicit video/audio data information\n");
}
if ((Detail & DETAIL_VERBOSE) == DETAIL_VERBOSE){
fprintf(stderr, " - Will list verbose chunk information\n");
}
}
std::string inbuffer;
while (std::cin.good()){inbuffer += std::cin.get();}//read all of std::cin to temp
inbuffer.erase(0, 3073);//strip the handshake part
RTMPStream::Chunk next;
FLV::Tag F;//FLV holder
AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER);
AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER);
while (next.Parse(inbuffer)){
if ((Detail & DETAIL_VERBOSE) == DETAIL_VERBOSE){
fprintf(stderr, "Chunk info: [%#2X] CS ID %u, timestamp %u, len %u, type ID %u, Stream ID %u\n", next.headertype, next.cs_id, next.timestamp, next.len, next.msg_type_id, next.msg_stream_id);
}
switch (next.msg_type_id){
case 0://does not exist
fprintf(stderr, "Error chunk - %i, %i, %i, %i, %i\n", next.cs_id, next.timestamp, next.real_len, next.len_left, next.msg_stream_id);
@ -47,28 +78,28 @@ int main(){
short int ucmtype = ntohs(*(short int*)next.data.c_str());
switch (ucmtype){
case 0:
fprintf(stderr, "CTRL: User control message: stream begin %i\n", ntohl(*(int*)next.data.c_str()+2));
fprintf(stderr, "CTRL: User control message: stream begin %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 1:
fprintf(stderr, "CTRL: User control message: stream EOF %i\n", ntohl(*(int*)next.data.c_str()+2));
fprintf(stderr, "CTRL: User control message: stream EOF %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 2:
fprintf(stderr, "CTRL: User control message: stream dry %i\n", ntohl(*(int*)next.data.c_str()+2));
fprintf(stderr, "CTRL: User control message: stream dry %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 3:
fprintf(stderr, "CTRL: User control message: setbufferlen %i\n", ntohl(*(int*)next.data.c_str()+2));
fprintf(stderr, "CTRL: User control message: setbufferlen %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 4:
fprintf(stderr, "CTRL: User control message: streamisrecorded %i\n", ntohl(*(int*)next.data.c_str()+2));
fprintf(stderr, "CTRL: User control message: streamisrecorded %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 6:
fprintf(stderr, "CTRL: User control message: pingrequest %i\n", ntohl(*(int*)next.data.c_str()+2));
fprintf(stderr, "CTRL: User control message: pingrequest %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 7:
fprintf(stderr, "CTRL: User control message: pingresponse %i\n", ntohl(*(int*)next.data.c_str()+2));
fprintf(stderr, "CTRL: User control message: pingresponse %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
default:
fprintf(stderr, "CTRL: User control message: UNKNOWN %hi - %i\n", ucmtype, ntohl(*(int*)next.data.c_str()+2));
fprintf(stderr, "CTRL: User control message: UNKNOWN %hu - %u\n", ucmtype, ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
}
} break;
@ -83,10 +114,28 @@ int main(){
fprintf(stderr, "CTRL: Set peer bandwidth: %i\n", RTMPStream::snd_window_size);
break;
case 8:
fprintf(stderr, "Received %i bytes audio data\n", next.len);
if (Detail & (DETAIL_EXPLICIT | DETAIL_RECONSTRUCT)){
F.ChunkLoader(next);
if ((Detail & DETAIL_EXPLICIT) == DETAIL_EXPLICIT){
fprintf(stderr, "Received %i bytes audio data\n", next.len);
std::cerr << "Got a " << F.len << " bytes " << F.tagType() << " FLV tag of time " << F.tagTime() << "." << std::endl;
}
if ((Detail & DETAIL_RECONSTRUCT) == DETAIL_RECONSTRUCT){
std::cout.write(F.data, F.len);
}
}
break;
case 9:
fprintf(stderr, "Received %i bytes video data\n", next.len);
if (Detail & (DETAIL_EXPLICIT | DETAIL_RECONSTRUCT)){
F.ChunkLoader(next);
if ((Detail & DETAIL_EXPLICIT) == DETAIL_EXPLICIT){
fprintf(stderr, "Received %i bytes video data\n", next.len);
std::cerr << "Got a " << F.len << " bytes " << F.tagType() << " FLV tag of time " << F.tagTime() << "." << std::endl;
}
if ((Detail & DETAIL_RECONSTRUCT) == DETAIL_RECONSTRUCT){
std::cout.write(F.data, F.len);
}
}
break;
case 15:
fprintf(stderr, "Received AFM3 data message\n");
@ -110,6 +159,10 @@ int main(){
fprintf(stderr, "Received AFM0 data message (metadata):\n");
amfdata = AMF::parse(next.data);
amfdata.Print();
if ((Detail & DETAIL_RECONSTRUCT) == DETAIL_RECONSTRUCT){
F.ChunkLoader(next);
std::cout.write(F.data, F.len);
}
} break;
case 19:
fprintf(stderr, "Received AFM0 shared object\n");

View file

@ -2,13 +2,17 @@
/// Holds all code for the FLV namespace.
#include "flv_tag.h"
#include "rtmpchunks.h"
#include <stdio.h> //for Tag::FileLoader
#include <unistd.h> //for Tag::FileLoader
#include <fcntl.h> //for Tag::FileLoader
#include <stdlib.h> //malloc
#include <string.h> //memcpy
char FLV::Header[13]; ///< Holds the last FLV header parsed.
/// Holds the last FLV header parsed.
/// Defaults to a audio+video header on FLV version 0x01 if no header received yet.
char FLV::Header[13] = {'F', 'L', 'V', 0x01, 0x05, 0, 0, 0, 0x09, 0, 0, 0, 0};
bool FLV::Parse_Error = false; ///< This variable is set to true if a problem is encountered while parsing the FLV.
std::string FLV::Error_Str = "";
@ -209,6 +213,16 @@ FLV::Tag::Tag(const Tag& O){
isKeyframe = O.isKeyframe;
}//copy constructor
/// Copy constructor from a RTMP chunk.
/// Copies the contents of a RTMP chunk into a valid FLV tag.
/// Exactly the same as making a chunk by through the default (empty) constructor
/// and then calling FLV::Tag::ChunkLoader with the chunk as argument.
FLV::Tag::Tag(const RTMPStream::Chunk& O){
len = 0; buf = 0; data = 0; isKeyframe = false; done = true; sofar = 0;
ChunkLoader(O);
}
/// Assignment operator - works exactly like the copy constructor.
/// This operator checks for self-assignment.
FLV::Tag & FLV::Tag::operator= (const FLV::Tag& O){
@ -231,6 +245,32 @@ FLV::Tag & FLV::Tag::operator= (const FLV::Tag& O){
return *this;
}//assignment operator
/// FLV loader function from chunk.
/// Copies the contents and wraps it in a FLV header.
bool FLV::Tag::ChunkLoader(const RTMPStream::Chunk& O){
len = O.len + 15;
if (len > 0){
if (!data){
data = (char*)malloc(len);
buf = len;
}else{
if (buf < len){
data = (char*)realloc(data, len);
buf = len;
}
}
memcpy(data+11, &(O.data[0]), O.len);
}
((unsigned int *)(data+len-4))[0] = O.len;
data[0] = O.msg_type_id;
data[3] = O.len & 0xFF;
data[2] = (O.len >> 8) & 0xFF;
data[1] = (O.len >> 16) & 0xFF;
tagTime(O.timestamp);
return true;
}
/// Helper function for FLV::MemLoader.
/// This function will try to read count bytes from data buffer D into buffer.
/// This function should be called repeatedly until true.

View file

@ -5,6 +5,11 @@
#include "socket.h"
#include <string>
//forward declaration of RTMPStream::Chunk to avoid circular dependencies.
namespace RTMPStream{
class Chunk;
};
/// This namespace holds all FLV-parsing related functionality.
namespace FLV {
//variables
@ -30,7 +35,9 @@ namespace FLV {
Tag(); ///< Constructor for a new, empty, tag.
Tag(const Tag& O); ///< Copy constructor, copies the contents of an existing tag.
Tag & operator= (const Tag& O); ///< Assignment operator - works exactly like the copy constructor.
Tag(const RTMPStream::Chunk& O); ///<Copy constructor from a RTMP chunk.
//loader functions
bool ChunkLoader(const RTMPStream::Chunk& O);
bool MemLoader(char * D, unsigned int S, unsigned int & P);
bool SockLoader(int sock);
bool SockLoader(Socket::Connection sock);

View file

@ -2,6 +2,7 @@
/// Holds all code for the RTMPStream namespace.
#include "rtmpchunks.h"
#include "flv_tag.h"
#include "crypto.h"
char versionstring[] = "WWW.DDVTECH.COM "; ///< String that is repeated in the RTMP handshake
@ -41,7 +42,7 @@ std::string RTMPStream::Chunk::Pack(){
RTMPStream::Chunk prev = lastsend[cs_id];
unsigned int tmpi;
unsigned char chtype = 0x00;
timestamp -= firsttime;
//timestamp -= firsttime;
if ((prev.msg_type_id > 0) && (prev.cs_id == cs_id)){
if (msg_stream_id == prev.msg_stream_id){
chtype = 0x40;//do not send msg_stream_id
@ -54,6 +55,8 @@ std::string RTMPStream::Chunk::Pack(){
}
}
}
//override - we always sent type 0x00 if the timestamp has decreased since last chunk in this channel
if (timestamp < prev.timestamp){chtype = 0x00;}
}
if (cs_id <= 63){
output += (unsigned char)(chtype | cs_id);
@ -76,15 +79,15 @@ std::string RTMPStream::Chunk::Pack(){
tmpi = timestamp - prev.timestamp;
}
if (tmpi >= 0x00ffffff){ntime = tmpi; tmpi = 0x00ffffff;}
output += (unsigned char)(tmpi / (256*256));
output += (unsigned char)(tmpi / 256);
output += (unsigned char)(tmpi % 256);
output += (unsigned char)((tmpi >> 16) & 0xff);
output += (unsigned char)((tmpi >> 8) & 0xff);
output += (unsigned char)(tmpi & 0xff);
if (chtype != 0x80){
//len
tmpi = len;
output += (unsigned char)(tmpi / (256*256));
output += (unsigned char)(tmpi / 256);
output += (unsigned char)(tmpi % 256);
output += (unsigned char)((tmpi >> 16) & 0xff);
output += (unsigned char)((tmpi >> 8) & 0xff);
output += (unsigned char)(tmpi & 0xff);
//msg type id
output += (unsigned char)msg_type_id;
if (chtype != 0x40){
@ -98,10 +101,10 @@ std::string RTMPStream::Chunk::Pack(){
}
//support for 0x00ffffff timestamps
if (ntime){
output += (unsigned char)(ntime % 256);
output += (unsigned char)(ntime / 256);
output += (unsigned char)(ntime / (256*256));
output += (unsigned char)(ntime / (256*256*256));
output += (unsigned char)(ntime & 0xff);
output += (unsigned char)((ntime >> 8) & 0xff);
output += (unsigned char)((ntime >> 16) & 0xff);
output += (unsigned char)((ntime >> 24) & 0xff);
}
len_left = 0;
while (len_left < len){
@ -162,7 +165,7 @@ std::string RTMPStream::SendChunk(unsigned int cs_id, unsigned char msg_type_id,
/// \param ts Timestamp of the media data, relative to current system time.
std::string RTMPStream::SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts){
RTMPStream::Chunk ch;
ch.cs_id = msg_type_id;
ch.cs_id = msg_type_id+42;
ch.timestamp = ts;
ch.len = len;
ch.real_len = len;
@ -173,6 +176,21 @@ std::string RTMPStream::SendMedia(unsigned char msg_type_id, unsigned char * dat
return ch.Pack();
}//SendMedia
/// Packs up a chunk with media contents.
/// \param tag FLV::Tag with media to send.
std::string RTMPStream::SendMedia(FLV::Tag & tag){
RTMPStream::Chunk ch;
ch.cs_id = ((unsigned char)tag.data[0]);
ch.timestamp = tag.tagTime();
ch.len = tag.len-15;
ch.real_len = tag.len-15;
ch.len_left = 0;
ch.msg_type_id = (unsigned char)tag.data[0];
ch.msg_stream_id = 1;
ch.data.append(tag.data+11, (size_t)(tag.len-15));
return ch.Pack();
}//SendMedia
/// Packs up a chunk for a control message with 1 argument.
std::string RTMPStream::SendCTL(unsigned char type, unsigned int data){
RTMPStream::Chunk ch;
@ -199,7 +217,7 @@ std::string RTMPStream::SendCTL(unsigned char type, unsigned int data, unsigned
ch.msg_type_id = type;
ch.msg_stream_id = 0;
ch.data.resize(5);
*(int*)((char*)ch.data.c_str()) = htonl(data);
*(unsigned int*)((char*)ch.data.c_str()) = htonl(data);
ch.data[4] = data2;
return ch.Pack();
}//SendCTL
@ -215,7 +233,7 @@ std::string RTMPStream::SendUSR(unsigned char type, unsigned int data){
ch.msg_type_id = 4;
ch.msg_stream_id = 0;
ch.data.resize(6);
*(int*)((char*)ch.data.c_str()+2) = htonl(data);
*(unsigned int*)(((char*)ch.data.c_str())+2) = htonl(data);
ch.data[0] = 0;
ch.data[1] = type;
return ch.Pack();
@ -232,8 +250,8 @@ std::string RTMPStream::SendUSR(unsigned char type, unsigned int data, unsigned
ch.msg_type_id = 4;
ch.msg_stream_id = 0;
ch.data.resize(10);
*(int*)((char*)ch.data.c_str()+2) = htonl(data);
*(int*)((char*)ch.data.c_str()+6) = htonl(data2);
*(unsigned int*)(((char*)ch.data.c_str())+2) = htonl(data);
*(unsigned int*)(((char*)ch.data.c_str())+6) = htonl(data2);
ch.data[0] = 0;
ch.data[1] = type;
return ch.Pack();
@ -270,11 +288,12 @@ bool RTMPStream::Chunk::Parse(std::string & indata){
cs_id = chunktype & 0x3F;
break;
}
RTMPStream::Chunk prev = lastrecv[cs_id];
//process the rest of the header, for each chunk type
switch (chunktype & 0xC0){
headertype = chunktype & 0xC0;
switch (headertype){
case 0x00:
if (indata.size() < i+11) return false; //can't read whole header
timestamp = indata[i++]*256*256;
@ -296,7 +315,7 @@ bool RTMPStream::Chunk::Parse(std::string & indata){
timestamp = indata[i++]*256*256;
timestamp += indata[i++]*256;
timestamp += indata[i++];
timestamp += prev.timestamp;
if (timestamp != 0x00ffffff){timestamp += prev.timestamp;}
len = indata[i++]*256*256;
len += indata[i++]*256;
len += indata[i++];
@ -310,7 +329,7 @@ bool RTMPStream::Chunk::Parse(std::string & indata){
timestamp = indata[i++]*256*256;
timestamp += indata[i++]*256;
timestamp += indata[i++];
timestamp += prev.timestamp;
if (timestamp != 0x00ffffff){timestamp += prev.timestamp;}
len = prev.len;
len_left = prev.len_left;
msg_type_id = prev.msg_type_id;
@ -344,7 +363,7 @@ bool RTMPStream::Chunk::Parse(std::string & indata){
timestamp += indata[i++]*256;
timestamp += indata[i++];
}
//read data if length > 0, and allocate it
if (real_len > 0){
if (prev.len_left > 0){
@ -397,22 +416,22 @@ bool RTMPStream::doHandshake(){
uint8_t _validationScheme = 5;
if (ValidateClientScheme(Client, 0)) _validationScheme = 0;
if (ValidateClientScheme(Client, 1)) _validationScheme = 1;
#if DEBUG >= 4
fprintf(stderr, "Handshake type is %hhi, encryption is %s\n", _validationScheme, encrypted?"on":"off");
#endif
//FIRST 1536 bytes from server response
//compute DH key position
uint32_t serverDHOffset = GetDHOffset(Server, _validationScheme);
uint32_t clientDHOffset = GetDHOffset(Client, _validationScheme);
//generate DH key
DHWrapper dhWrapper(1024);
if (!dhWrapper.Initialize()) return false;
if (!dhWrapper.CreateSharedKey(Client + clientDHOffset, 128)) return false;
if (!dhWrapper.CopyPublicKey(Server + serverDHOffset, 128)) return false;
if (encrypted) {
uint8_t secretKey[128];
if (!dhWrapper.CopySharedKey(secretKey, sizeof (secretKey))) return false;
@ -433,7 +452,7 @@ bool RTMPStream::doHandshake(){
memcpy(Server + serverDigestOffset, pTempHash, 32);
delete[] pTempBuffer;
delete[] pTempHash;
//SECOND 1536 bytes from server response
uint32_t keyChallengeIndex = GetDigestOffset(Client, _validationScheme);
pTempHash = new uint8_t[512];

View file

@ -9,6 +9,11 @@
#include <string>
#include <arpa/inet.h>
//forward declaration of FLV::Tag to avoid circular dependencies.
namespace FLV{
class Tag;
};
/// Contains all functions and classes needed for RTMP connections.
namespace RTMPStream{
@ -30,6 +35,7 @@ namespace RTMPStream{
/// Holds a single RTMP chunk, either send or receive direction.
class Chunk{
public:
unsigned char headertype; ///< For input chunks, the type of header. This is calculated automatically for output chunks.
unsigned int cs_id; ///< ContentStream ID
unsigned int timestamp; ///< Timestamp of this chunk.
unsigned int len; ///< Length of the complete chunk.
@ -50,6 +56,7 @@ namespace RTMPStream{
std::string SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data);
std::string SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts);
std::string SendMedia(FLV::Tag & tag);
std::string SendCTL(unsigned char type, unsigned int data);
std::string SendCTL(unsigned char type, unsigned int data, unsigned char data2);
std::string SendUSR(unsigned char type, unsigned int data);