Even more documentation, RTMP Connector compiles again, but still extremely buggy. Will create RTMP debugging tool soon. Something wrong with RTMP Connector reading FLV::Tags... needs more investigation.

This commit is contained in:
Thulinma 2011-04-11 17:05:36 +02:00
parent b592442d6b
commit 6db6829545
29 changed files with 1451 additions and 1437 deletions

View file

@ -1,3 +1,8 @@
/// \file ABST_Parser/main.cpp
/// Debugging tool for ABST boxes.
/// Expects ABST data through stdin, outputs human-readable information to stderr.
/// \todo Erik, update, delete or properly document this file.
#include <stdint.h> #include <stdint.h>
#include <iostream> #include <iostream>
#include <string> #include <string>

View file

@ -1,3 +1,7 @@
/// \file AMF_Tester/main.cpp
/// Debugging tool for AMF data.
/// Expects AMF data through stdin, outputs human-readable information to stderr.
#define DEBUG 10 //maximum debugging level #define DEBUG 10 //maximum debugging level
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
@ -5,6 +9,8 @@
#include <string> #include <string>
#include "../util/amf.h" #include "../util/amf.h"
/// Debugging tool for AMF data.
/// Expects AMF data through stdin, outputs human-readable information to stderr.
int main() { int main() {
std::string temp; std::string temp;
while (std::cin.good()){temp += std::cin.get();}//read all of std::cin to temp while (std::cin.good()){temp += std::cin.get();}//read all of std::cin to temp

View file

@ -1,3 +1,8 @@
/// \file Admin/main.cpp
/// Attempted administration tool for DDVTECH Clients.
/// Never finished - perhaps now obsolete...?
/// \todo This could serve as a basis for a new, more robust, control method for gearbox / the API.
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>

View file

@ -1,3 +1,6 @@
/// \file Buffer/main.cpp
/// Contains the main code for the Buffer.
#include <fcntl.h> #include <fcntl.h>
#include <iostream> #include <iostream>
#include <string> #include <string>
@ -12,24 +15,26 @@
#include <sys/epoll.h> #include <sys/epoll.h>
/// Holds all code unique to the Buffer.
namespace Buffer{ namespace Buffer{
void termination_handler (int signum){ ///A simple signal handler that ignores all signals.
void termination_handler (int signum){
switch (signum){ switch (signum){
case SIGPIPE: return; break; case SIGPIPE: return; break;
default: return; break; default: return; break;
} }
} }
///holds FLV::Tag objects and their numbers ///holds FLV::Tag objects and their numbers
struct buffer{ struct buffer{
int number; int number;
FLV::Tag FLV; FLV::Tag FLV;
};//buffer };//buffer
/// Holds connected users. /// Holds connected users.
/// Keeps track of what buffer users are using and the connection status. /// Keeps track of what buffer users are using and the connection status.
class user{ class user{
public: public:
int MyBuffer; ///< Index of currently used buffer. int MyBuffer; ///< Index of currently used buffer.
int MyBuffer_num; ///< Number of currently used buffer. int MyBuffer_num; ///< Number of currently used buffer.
@ -72,7 +77,7 @@ class user{
/// \param ringbuf Array of buffers (FLV:Tag with ID attached) /// \param ringbuf Array of buffers (FLV:Tag with ID attached)
/// \param buffers Count of elements in ringbuf /// \param buffers Count of elements in ringbuf
void Send(buffer ** ringbuf, int buffers){ void Send(buffer ** ringbuf, int buffers){
//TODO: Bij MP3: gotproperaudio - if false, stuur alleen als eerste byte is 0xFF en set op true /// \todo For MP3: gotproperaudio - if false, only send if first byte is 0xFF and set to true
if (!S.connected()){return;}//cancel if not connected if (!S.connected()){return;}//cancel if not connected
//still waiting for next buffer? check it //still waiting for next buffer? check it
@ -118,11 +123,11 @@ class user{
currsend = 0; currsend = 0;
}//completed a send }//completed a send
}//send }//send
}; };
int user::UserCount = 0; int user::UserCount = 0;
/// Starts a loop, waiting for connections to send video data to. /// Starts a loop, waiting for connections to send video data to.
int Start(int argc, char ** argv) { int Start(int argc, char ** argv) {
//first make sure no segpipe signals will kill us //first make sure no segpipe signals will kill us
struct sigaction new_action; struct sigaction new_action;
new_action.sa_handler = termination_handler; new_action.sa_handler = termination_handler;
@ -224,7 +229,7 @@ int Start(int argc, char ** argv) {
users.back().currsend = 0; users.back().currsend = 0;
users.back().MyBuffer = lastproper; users.back().MyBuffer = lastproper;
users.back().MyBuffer_num = -1; users.back().MyBuffer_num = -1;
//TODO: Do this more nicely? /// \todo Do this more nicely?
if (!incoming.write(FLV::Header, 13)){ if (!incoming.write(FLV::Header, 13)){
users.back().Disconnect("failed to receive the header!"); users.back().Disconnect("failed to receive the header!");
}else{ }else{
@ -266,10 +271,11 @@ int Start(int argc, char ** argv) {
} }
} }
return 0; return 0;
} }
};//Buffer namespace };//Buffer namespace
/// Entry point for Buffer, simply calls Buffer::Start().
int main(int argc, char ** argv){ int main(int argc, char ** argv){
Buffer::Start(argc, argv); Buffer::Start(argc, argv);
}//main }//main

View file

@ -1,9 +1,12 @@
/// \file Connector_HTTP/main.cpp
/// Contains the main code for the HTTP Connector
/// Sets the global debugging level. /// Sets the global debugging level.
/// debugging level 0 = nothing // debugging level 0 = nothing
/// debugging level 1 = critical errors // debugging level 1 = critical errors
/// debugging level 2 = errors // debugging level 2 = errors
/// debugging level 3 = status information // debugging level 3 = status information
/// debugging level 4 = extremely verbose status information // debugging level 4 = extremely verbose status information
#define DEBUG 4 #define DEBUG 4
#include <iostream> #include <iostream>

View file

@ -1,6 +1,12 @@
/// \file Connector_RAW/main.cpp
/// Contains the main code for the RAW connector.
#include <iostream> #include <iostream>
#include "../util/ddv_socket.h" #include "../util/ddv_socket.h"
/// Contains the main code for the RAW connector.
/// Expects a single commandline argument telling it which stream to connect to,
/// then outputs the raw stream to stdout.
int main(int argc, char ** argv) { int main(int argc, char ** argv) {
if (argc < 2){ if (argc < 2){
std::cout << "Usage: " << argv[0] << " stream_name" << std::endl; std::cout << "Usage: " << argv[0] << " stream_name" << std::endl;
@ -8,11 +14,14 @@ int main(int argc, char ** argv) {
} }
std::string input = "/tmp/shared_socket_"; std::string input = "/tmp/shared_socket_";
input += argv[1]; input += argv[1];
//connect to the proper stream
DDV::Socket S(input); DDV::Socket S(input);
if (!S.connected()){ if (!S.connected()){
std::cout << "Could not open stream " << argv[1] << std::endl; std::cout << "Could not open stream " << argv[1] << std::endl;
return 1; return 1;
} }
//transport ~50kb at a time
//this is a nice tradeoff between CPU usage and speed
char buffer[50000]; char buffer[50000];
while(std::cout.good() && S.read(buffer,50000)){std::cout.write(buffer,50000);} while(std::cout.good() && S.read(buffer,50000)){std::cout.write(buffer,50000);}
S.close(); S.close();

View file

@ -1,4 +1,4 @@
SRC = main.cpp ../util/ddv_socket.cpp ../util/flv_tag.cpp ../util/amf.cpp SRC = main.cpp ../util/ddv_socket.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/rtmpchunks.cpp ../util/crypto.cpp
OBJ = $(SRC:.cpp=.o) OBJ = $(SRC:.cpp=.o)
OUT = DDV_Conn_RTMP OUT = DDV_Conn_RTMP
INCLUDES = INCLUDES =
@ -13,7 +13,7 @@ LIBS = -lssl -lcrypto
default: $(OUT) default: $(OUT)
.cpp.o: .cpp.o:
$(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@ $(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@
$(OUT): $(OBJ) chunkstream.cpp parsechunks.cpp handshake.cpp crypto.cpp $(OUT): $(OBJ)
$(CC) -o $(OUT) $(OBJ) $(STATIC) $(LIBS) $(CC) -o $(OUT) $(OBJ) $(STATIC) $(LIBS)
clean: clean:
rm -rf $(OBJ) $(OUT) Makefile.bak *~ rm -rf $(OBJ) $(OUT) Makefile.bak *~

View file

@ -1,38 +0,0 @@
#!/bin/sh
#
# description: DDVTech RTMP Connector
# processname: Connector_RTMP
prog="Connector_RTMP"
fullprog="/usr/bin/Connector_RTMP"
RETVAL=0
start() {
echo "Starting $prog"
$fullprog
return $?
}
stop() {
echo "Stopping $prog"
killall $prog
return $?
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
*)
echo "Usage: $0 {start|stop|restart}"
RETVAL=1
esac
exit $RETVAL

View file

@ -1,501 +0,0 @@
#include <map>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <arpa/inet.h>
unsigned int getNowMS(){
timeval t;
gettimeofday(&t, 0);
return t.tv_sec + t.tv_usec/1000;
}
unsigned int chunk_rec_max = 128;
unsigned int chunk_snd_max = 128;
unsigned int rec_window_size = 0xFA00;
unsigned int snd_window_size = 1024*500;
unsigned int rec_window_at = 0;
unsigned int snd_window_at = 0;
unsigned int rec_cnt = 0;
unsigned int snd_cnt = 0;
unsigned int firsttime;
struct chunkinfo {
unsigned int cs_id;
unsigned int timestamp;
unsigned int len;
unsigned int real_len;
unsigned int len_left;
unsigned char msg_type_id;
unsigned int msg_stream_id;
};//chunkinfo
struct chunkpack {
unsigned char chunktype;
unsigned int cs_id;
unsigned int timestamp;
unsigned int len;
unsigned int real_len;
unsigned int len_left;
unsigned char msg_type_id;
unsigned int msg_stream_id;
unsigned char * data;
};//chunkpack
//clean a chunk so that it may be re-used without memory leaks
void scrubChunk(struct chunkpack c){
if (c.data){free(c.data);}
c.data = 0;
c.real_len = 0;
}//scrubChunk
//ugly global, but who cares...
std::map<unsigned int, chunkinfo> prevmap;
//return previous packet of this cs_id
chunkinfo GetPrev(unsigned int cs_id){
return prevmap[cs_id];
}//GetPrev
//store packet information of last packet of this cs_id
void PutPrev(chunkpack prev){
prevmap[prev.cs_id].timestamp = prev.timestamp;
prevmap[prev.cs_id].len = prev.len;
prevmap[prev.cs_id].real_len = prev.real_len;
prevmap[prev.cs_id].len_left = prev.len_left;
prevmap[prev.cs_id].msg_type_id = prev.msg_type_id;
prevmap[prev.cs_id].msg_stream_id = prev.msg_stream_id;
}//PutPrev
//ugly global, but who cares...
std::map<unsigned int, chunkinfo> sndprevmap;
//return previous packet of this cs_id
chunkinfo GetSndPrev(unsigned int cs_id){
return sndprevmap[cs_id];
}//GetPrev
//store packet information of last packet of this cs_id
void PutSndPrev(chunkpack prev){
sndprevmap[prev.cs_id].cs_id = prev.cs_id;
sndprevmap[prev.cs_id].timestamp = prev.timestamp;
sndprevmap[prev.cs_id].len = prev.len;
sndprevmap[prev.cs_id].real_len = prev.real_len;
sndprevmap[prev.cs_id].len_left = prev.len_left;
sndprevmap[prev.cs_id].msg_type_id = prev.msg_type_id;
sndprevmap[prev.cs_id].msg_stream_id = prev.msg_stream_id;
}//PutPrev
//sends the chunk over the network
void SendChunk(chunkpack ch){
unsigned char tmp;
unsigned int tmpi;
unsigned char chtype = 0x00;
chunkinfo prev = GetSndPrev(ch.cs_id);
ch.timestamp -= firsttime;
if (prev.cs_id == ch.cs_id){
if (ch.msg_stream_id == prev.msg_stream_id){
chtype = 0x40;//do not send msg_stream_id
if (ch.len == prev.len){
if (ch.msg_type_id == prev.msg_type_id){
chtype = 0x80;//do not send len and msg_type_id
if (ch.timestamp == prev.timestamp){
chtype = 0xC0;//do not send timestamp
}
}
}
}
}
if (ch.cs_id <= 63){
tmp = chtype | ch.cs_id; DDV_write(&tmp, 1, 1, CONN_fd);
snd_cnt+=1;
}else{
if (ch.cs_id <= 255+64){
tmp = chtype | 0; DDV_write(&tmp, 1, 1, CONN_fd);
tmp = ch.cs_id - 64; DDV_write(&tmp, 1, 1, CONN_fd);
snd_cnt+=2;
}else{
tmp = chtype | 1; DDV_write(&tmp, 1, 1, CONN_fd);
tmpi = ch.cs_id - 64;
tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd);
tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd);
snd_cnt+=3;
}
}
unsigned int ntime = 0;
if (chtype != 0xC0){
//timestamp or timestamp diff
if (chtype == 0x00){
tmpi = ch.timestamp;
if (tmpi >= 0x00ffffff){ntime = tmpi; tmpi = 0x00ffffff;}
tmp = tmpi / (256*256); DDV_write(&tmp, 1, 1, CONN_fd);
tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd);
tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd);
snd_cnt+=3;
}else{
tmpi = ch.timestamp - prev.timestamp;
if (tmpi >= 0x00ffffff){ntime = tmpi; tmpi = 0x00ffffff;}
tmp = tmpi / (256*256); DDV_write(&tmp, 1, 1, CONN_fd);
tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd);
tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd);
snd_cnt+=3;
}
if (chtype != 0x80){
//len
tmpi = ch.len;
tmp = tmpi / (256*256); DDV_write(&tmp, 1, 1, CONN_fd);
tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd);
tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd);
snd_cnt+=3;
//msg type id
tmp = ch.msg_type_id; DDV_write(&tmp, 1, 1, CONN_fd);
snd_cnt+=1;
if (chtype != 0x40){
//msg stream id
tmp = ch.msg_stream_id % 256; DDV_write(&tmp, 1, 1, CONN_fd);
tmp = ch.msg_stream_id / 256; DDV_write(&tmp, 1, 1, CONN_fd);
tmp = ch.msg_stream_id / (256*256); DDV_write(&tmp, 1, 1, CONN_fd);
tmp = ch.msg_stream_id / (256*256*256); DDV_write(&tmp, 1, 1, CONN_fd);
snd_cnt+=4;
}
}
}
//support for 0x00ffffff timestamps
if (ntime){
tmp = ntime / (256*256*256); DDV_write(&tmp, 1, 1, CONN_fd);
tmp = ntime / (256*256); DDV_write(&tmp, 1, 1, CONN_fd);
tmp = ntime / 256; DDV_write(&tmp, 1, 1, CONN_fd);
tmp = ntime % 256; DDV_write(&tmp, 1, 1, CONN_fd);
snd_cnt+=4;
}
ch.len_left = 0;
while (ch.len_left < ch.len){
tmpi = ch.len - ch.len_left;
if (tmpi > chunk_snd_max){tmpi = chunk_snd_max;}
DDV_write((ch.data + ch.len_left), 1, tmpi, CONN_fd);
snd_cnt+=tmpi;
ch.len_left += tmpi;
if (ch.len_left < ch.len){
if (ch.cs_id <= 63){
tmp = 0xC0 + ch.cs_id; DDV_write(&tmp, 1, 1, CONN_fd);
snd_cnt+=1;
}else{
if (ch.cs_id <= 255+64){
tmp = 0xC0; DDV_write(&tmp, 1, 1, CONN_fd);
tmp = ch.cs_id - 64; DDV_write(&tmp, 1, 1, CONN_fd);
snd_cnt+=2;
}else{
tmp = 0xC1; DDV_write(&tmp, 1, 1, CONN_fd);
tmpi = ch.cs_id - 64;
tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd);
tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd);
snd_cnt+=4;
}
}
}
}
PutSndPrev(ch);
}//SendChunk
//sends a chunk
void SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data){
chunkpack ch;
ch.cs_id = cs_id;
ch.timestamp = getNowMS();
ch.len = data.size();
ch.real_len = data.size();
ch.len_left = 0;
ch.msg_type_id = msg_type_id;
ch.msg_stream_id = msg_stream_id;
ch.data = (unsigned char*)malloc(data.size());
memcpy(ch.data, data.c_str(), data.size());
SendChunk(ch);
free(ch.data);
}//SendChunk
//sends a media chunk
void SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts){
chunkpack ch;
ch.cs_id = msg_type_id;
ch.timestamp = ts;
ch.len = len;
ch.real_len = len;
ch.len_left = 0;
ch.msg_type_id = msg_type_id;
ch.msg_stream_id = 1;
ch.data = (unsigned char*)malloc(len);
memcpy(ch.data, data, len);
SendChunk(ch);
free(ch.data);
}//SendMedia
//sends a control message
void SendCTL(unsigned char type, unsigned int data){
chunkpack ch;
ch.cs_id = 2;
ch.timestamp = getNowMS();
ch.len = 4;
ch.real_len = 4;
ch.len_left = 0;
ch.msg_type_id = type;
ch.msg_stream_id = 0;
ch.data = (unsigned char*)malloc(4);
data = htonl(data);
memcpy(ch.data, &data, 4);
SendChunk(ch);
free(ch.data);
}//SendCTL
//sends a control message
void SendCTL(unsigned char type, unsigned int data, unsigned char data2){
chunkpack ch;
ch.cs_id = 2;
ch.timestamp = getNowMS();
ch.len = 5;
ch.real_len = 5;
ch.len_left = 0;
ch.msg_type_id = type;
ch.msg_stream_id = 0;
ch.data = (unsigned char*)malloc(5);
data = htonl(data);
memcpy(ch.data, &data, 4);
ch.data[4] = data2;
SendChunk(ch);
free(ch.data);
}//SendCTL
//sends a usr control message
void SendUSR(unsigned char type, unsigned int data){
chunkpack ch;
ch.cs_id = 2;
ch.timestamp = getNowMS();
ch.len = 6;
ch.real_len = 6;
ch.len_left = 0;
ch.msg_type_id = 4;
ch.msg_stream_id = 0;
ch.data = (unsigned char*)malloc(6);
data = htonl(data);
memcpy(ch.data+2, &data, 4);
ch.data[0] = 0;
ch.data[1] = type;
SendChunk(ch);
free(ch.data);
}//SendUSR
//sends a usr control message
void SendUSR(unsigned char type, unsigned int data, unsigned int data2){
chunkpack ch;
ch.cs_id = 2;
ch.timestamp = getNowMS();
ch.len = 10;
ch.real_len = 10;
ch.len_left = 0;
ch.msg_type_id = 4;
ch.msg_stream_id = 0;
ch.data = (unsigned char*)malloc(10);
data = htonl(data);
data2 = htonl(data2);
memcpy(ch.data+2, &data, 4);
memcpy(ch.data+6, &data2, 4);
ch.data[0] = 0;
ch.data[1] = type;
SendChunk(ch);
free(ch.data);
}//SendUSR
//get a chunk from standard input
struct chunkpack getChunk(){
gettimeofday(&lastrec, 0);
struct chunkpack ret;
unsigned char temp;
DDV_read(&(ret.chunktype), 1, 1, CONN_fd);
rec_cnt++;
//read the chunkstream ID properly
switch (ret.chunktype & 0x3F){
case 0:
DDV_read(&temp, 1, 1, CONN_fd);
rec_cnt++;
ret.cs_id = temp + 64;
break;
case 1:
DDV_read(&temp, 1, 1, CONN_fd);
ret.cs_id = temp + 64;
DDV_read(&temp, 1, 1, CONN_fd);
ret.cs_id += temp * 256;
rec_cnt+=2;
break;
default:
ret.cs_id = ret.chunktype & 0x3F;
break;
}
chunkinfo prev = GetPrev(ret.cs_id);
//process the rest of the header, for each chunk type
switch (ret.chunktype & 0xC0){
case 0x00:
DDV_read(&temp, 1, 1, CONN_fd);
ret.timestamp = temp*256*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.timestamp += temp*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.timestamp += temp;
DDV_read(&temp, 1, 1, CONN_fd);
ret.len = temp*256*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.len += temp*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.len += temp;
ret.len_left = 0;
DDV_read(&temp, 1, 1, CONN_fd);
ret.msg_type_id = temp;
DDV_read(&temp, 1, 1, CONN_fd);
ret.msg_stream_id = temp;
DDV_read(&temp, 1, 1, CONN_fd);
ret.msg_stream_id += temp*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.msg_stream_id += temp*256*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.msg_stream_id += temp*256*256*256;
rec_cnt+=11;
break;
case 0x40:
DDV_read(&temp, 1, 1, CONN_fd);
ret.timestamp = temp*256*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.timestamp += temp*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.timestamp += temp;
ret.timestamp += prev.timestamp;
DDV_read(&temp, 1, 1, CONN_fd);
ret.len = temp*256*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.len += temp*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.len += temp;
ret.len_left = 0;
DDV_read(&temp, 1, 1, CONN_fd);
ret.msg_type_id = temp;
ret.msg_stream_id = prev.msg_stream_id;
rec_cnt+=7;
break;
case 0x80:
DDV_read(&temp, 1, 1, CONN_fd);
ret.timestamp = temp*256*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.timestamp += temp*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.timestamp += temp;
ret.timestamp += prev.timestamp;
ret.len = prev.len;
ret.len_left = prev.len_left;
ret.msg_type_id = prev.msg_type_id;
ret.msg_stream_id = prev.msg_stream_id;
rec_cnt+=3;
break;
case 0xC0:
ret.timestamp = prev.timestamp;
ret.len = prev.len;
ret.len_left = prev.len_left;
ret.msg_type_id = prev.msg_type_id;
ret.msg_stream_id = prev.msg_stream_id;
break;
}
//calculate chunk length, real length, and length left till complete
if (ret.len_left > 0){
ret.real_len = ret.len_left;
ret.len_left -= ret.real_len;
}else{
ret.real_len = ret.len;
}
if (ret.real_len > chunk_rec_max){
ret.len_left += ret.real_len - chunk_rec_max;
ret.real_len = chunk_rec_max;
}
//read extended timestamp, if neccesary
if (ret.timestamp == 0x00ffffff){
DDV_read(&temp, 1, 1, CONN_fd);
ret.timestamp = temp*256*256*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.timestamp += temp*256*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.timestamp += temp*256;
DDV_read(&temp, 1, 1, CONN_fd);
ret.timestamp += temp;
rec_cnt+=4;
}
//read data if length > 0, and allocate it
if (ret.real_len > 0){
ret.data = (unsigned char*)malloc(ret.real_len);
DDV_read(ret.data, 1, ret.real_len, CONN_fd);
rec_cnt+=ret.real_len;
}else{
ret.data = 0;
}
PutPrev(ret);
return ret;
}//getChunk
//adds newchunk to global list of unfinished chunks, re-assembling them complete
//returns pointer to chunk when a chunk is finished, 0 otherwise
//removes pointed to chunk from internal list if returned, without cleanup
// (cleanup performed in getWholeChunk function)
chunkpack * AddChunkPart(chunkpack newchunk){
chunkpack * p;
unsigned char * tmpdata = 0;
static std::map<unsigned int, chunkpack *> ch_lst;
std::map<unsigned int, chunkpack *>::iterator it;
it = ch_lst.find(newchunk.cs_id);
if (it == ch_lst.end()){
p = (chunkpack*)malloc(sizeof(chunkpack));
*p = newchunk;
p->data = (unsigned char*)malloc(p->real_len);
memcpy(p->data, newchunk.data, p->real_len);
if (p->len_left == 0){return p;}
ch_lst[newchunk.cs_id] = p;
}else{
p = it->second;
tmpdata = (unsigned char*)realloc(p->data, p->real_len + newchunk.real_len);
if (tmpdata == 0){
#if DEBUG >= 1
fprintf(stderr, "Error allocating memory!\n");
#endif
return 0;
}
p->data = tmpdata;
memcpy(p->data+p->real_len, newchunk.data, newchunk.real_len);
p->real_len += newchunk.real_len;
p->len_left -= newchunk.real_len;
if (p->len_left <= 0){
ch_lst.erase(it);
return p;
}else{
ch_lst[newchunk.cs_id] = p;//pointer may have changed
}
}
return 0;
}//AddChunkPart
//grabs chunks until a whole one comes in, then returns that
chunkpack getWholeChunk(){
static chunkpack gwc_next, gwc_complete;
static bool clean = false;
int counter = 0;
if (!clean){gwc_complete.data = 0; clean = true;}//prevent brain damage
chunkpack * ret = 0;
scrubChunk(gwc_complete);
while (counter < 1000){
gwc_next = getChunk();
ret = AddChunkPart(gwc_next);
scrubChunk(gwc_next);
if (ret){
gwc_complete = *ret;
free(ret);//cleanup returned chunk
return gwc_complete;
}
if (socketError || socketBlocking){break;}
counter++;
}
gwc_complete.msg_type_id = 0;
return gwc_complete;
}//getWholeChunk

View file

@ -1,45 +0,0 @@
#ifndef _CRYPTO_H
#define _CRYPTO_H
#define DLLEXP
#include <openssl/bn.h>
#include <openssl/dh.h>
#include <openssl/rc4.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/hmac.h>
class DLLEXP DHWrapper {
private:
int32_t _bitsCount;
DH *_pDH;
uint8_t *_pSharedKey;
int32_t _sharedKeyLength;
BIGNUM *_peerPublickey;
public:
DHWrapper(int32_t bitsCount);
virtual ~DHWrapper();
bool Initialize();
bool CopyPublicKey(uint8_t *pDst, int32_t dstLength);
bool CopyPrivateKey(uint8_t *pDst, int32_t dstLength);
bool CreateSharedKey(uint8_t *pPeerPublicKey, int32_t length);
bool CopySharedKey(uint8_t *pDst, int32_t dstLength);
private:
void Cleanup();
bool CopyKey(BIGNUM *pNum, uint8_t *pDst, int32_t dstLength);
};
DLLEXP void InitRC4Encryption(uint8_t *secretKey, uint8_t *pubKeyIn, uint8_t *pubKeyOut,
RC4_KEY *rc4keyIn, RC4_KEY *rc4keyOut);
DLLEXP std::string md5(std::string source, bool textResult);
DLLEXP std::string b64(std::string source);
DLLEXP std::string b64(uint8_t *pBuffer, uint32_t length);
DLLEXP std::string unb64(std::string source);
DLLEXP std::string unb64(uint8_t *pBuffer, uint32_t length);
#endif /* _CRYPTO_H */

View file

@ -1,137 +0,0 @@
#undef OLDHANDSHAKE //change to #define for old handshake method
char versionstring[] = "WWW.DDVTECH.COM ";
#ifdef OLDHANDSHAKE
struct Handshake {
char Time[4];
char Zero[4];
char Random[1528];
};//Handshake
bool doHandshake(){
char Version;
Handshake Client;
Handshake Server;
/** Read C0 **/
DDV_read(&(Version), 1, 1, CONN_fd);
/** Read C1 **/
DDV_read(Client.Time, 1, 4, CONN_fd);
DDV_read(Client.Zero, 1, 4, CONN_fd);
DDV_read(Client.Random, 1, 1528, CONN_fd);
rec_cnt+=1537;
/** Build S1 Packet **/
Server.Time[0] = 0; Server.Time[1] = 0; Server.Time[2] = 0; Server.Time[3] = 0;
Server.Zero[0] = 0; Server.Zero[1] = 0; Server.Zero[2] = 0; Server.Zero[3] = 0;
for (int i = 0; i < 1528; i++){
Server.Random[i] = versionstring[i%sizeof(versionstring)];
}
/** Send S0 **/
DDV_write(&(Version), 1, 1, CONN_fd);
/** Send S1 **/
DDV_write(Server.Time, 1, 4, CONN_fd);
DDV_write(Server.Zero, 1, 4, CONN_fd);
DDV_write(Server.Random, 1, 1528, CONN_fd);
/** Flush output, just for certainty **/
//fflush(CONN_fd);
snd_cnt+=1537;
/** Send S2 **/
DDV_write(Client.Time, 1, 4, CONN_fd);
DDV_write(Client.Time, 1, 4, CONN_fd);
DDV_write(Client.Random, 1, 1528, CONN_fd);
snd_cnt+=1536;
/** Flush, necessary in order to work **/
//fflush(CONN_fd);
/** Read and discard C2 **/
DDV_read(Client.Time, 1, 4, CONN_fd);
DDV_read(Client.Zero, 1, 4, CONN_fd);
DDV_read(Client.Random, 1, 1528, CONN_fd);
rec_cnt+=1536;
return true;
}//doHandshake
#else
#include "crypto.cpp" //cryptography for handshaking
bool doHandshake(){
char Version;
/** Read C0 **/
DDV_read(&Version, 1, 1, CONN_fd);
uint8_t Client[1536];
uint8_t Server[3072];
DDV_read(&Client, 1, 1536, CONN_fd);
rec_cnt+=1537;
/** Build S1 Packet **/
*((uint32_t*)Server) = 0;//time zero
*(((uint32_t*)(Server+4))) = htonl(0x01020304);//version 1 2 3 4
for (int i = 8; i < 3072; ++i){Server[i] = versionstring[i%13];}//"random" data
bool encrypted = (Version == 6);
#if DEBUG >= 4
fprintf(stderr, "Handshake version is %hhi\n", Version);
#endif
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;
RC4_KEY _pKeyIn;
RC4_KEY _pKeyOut;
InitRC4Encryption(secretKey, (uint8_t*) & Client[clientDHOffset], (uint8_t*) & Server[serverDHOffset], &_pKeyIn, &_pKeyOut);
uint8_t data[1536];
RC4(&_pKeyIn, 1536, data, data);
RC4(&_pKeyOut, 1536, data, data);
}
//generate the digest
uint32_t serverDigestOffset = GetDigestOffset(Server, _validationScheme);
uint8_t *pTempBuffer = new uint8_t[1536 - 32];
memcpy(pTempBuffer, Server, serverDigestOffset);
memcpy(pTempBuffer + serverDigestOffset, Server + serverDigestOffset + 32, 1536 - serverDigestOffset - 32);
uint8_t *pTempHash = new uint8_t[512];
HMACsha256(pTempBuffer, 1536 - 32, genuineFMSKey, 36, pTempHash);
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];
HMACsha256(Client + keyChallengeIndex, 32, genuineFMSKey, 68, pTempHash);
uint8_t *pLastHash = new uint8_t[512];
HMACsha256(Server + 1536, 1536 - 32, pTempHash, 32, pLastHash);
memcpy(Server + 1536 * 2 - 32, pLastHash, 32);
delete[] pTempHash;
delete[] pLastHash;
//***** DONE BUILDING THE RESPONSE ***//
/** Send response **/
DDV_write(&Version, 1, 1, CONN_fd);
DDV_write(&Server, 1, 3072, CONN_fd);
snd_cnt+=3073;
/** Flush, necessary in order to work **/
//fflush(CONN_fd);
/** Read and discard C2 **/
DDV_read(Client, 1, 1536, CONN_fd);
rec_cnt+=1536;
return true;
}
#endif

View file

@ -1,9 +1,11 @@
/// \file Connector_RTMP/main.cpp
/// Contains the main code for the RTMP Connector
//debugging level 0 = nothing //debugging level 0 = nothing
//debugging level 1 = critical errors //debugging level 1 = critical errors
//debugging level 2 = errors //debugging level 2 = errors
//debugging level 3 = status information //debugging level 3 = status information
//debugging level 4 = extremely verbose status information //debugging level 4 = extremely verbose status information
//debugging level 5 = save all streams to FLV files
#define DEBUG 4 #define DEBUG 4
#include <iostream> #include <iostream>
@ -18,9 +20,8 @@
#include <getopt.h> #include <getopt.h>
#include "../util/ddv_socket.h" #include "../util/ddv_socket.h"
#include "../util/flv_tag.h" #include "../util/flv_tag.h"
#include "../util/amf.h"
#include "parsechunks.cpp" //chunkstream parsing #include "../util/rtmpchunks.h"
#include "handshake.cpp" //handshaking
/// Holds all functions and data unique to the RTMP Connector /// Holds all functions and data unique to the RTMP Connector
namespace Connector_RTMP{ namespace Connector_RTMP{
@ -29,23 +30,33 @@ namespace Connector_RTMP{
bool ready4data = false; ///< Set to true when streaming starts. bool ready4data = false; ///< Set to true when streaming starts.
bool inited = false; ///< Set to true when ready to connect to Buffer. bool inited = false; ///< Set to true when ready to connect to Buffer.
bool stopparsing = false; ///< Set to true when all parsing needs to be cancelled. bool stopparsing = false; ///< Set to true when all parsing needs to be cancelled.
timeval lastrec; ///< Timestamp of last received data.
DDV::Socket Socket; ///< Socket connected to user DDV::Socket Socket; ///< Socket connected to user
std::string streamname = "/tmp/shared_socket"; ///< Stream that will be opened
void parseChunk();
int Connector_RTMP(DDV::Socket conn);
};//Connector_RTMP namespace;
/// Main Connector_RTMP function
int Connector_RTMP(DDV::Socket conn){ /// Main Connector_RTMP function
int Connector_RTMP::Connector_RTMP(DDV::Socket conn){
Socket = conn; Socket = conn;
unsigned int ts; unsigned int ts;
unsigned int fts = 0; unsigned int fts = 0;
unsigned int ftst; unsigned int ftst;
DDV::Socket SS; DDV::Socket SS;
FLV::Tag tag = 0; FLV::Tag tag;
//first timestamp set //first timestamp set
firsttime = getNowMS(); RTMPStream::firsttime = RTMPStream::getNowMS();
if (doHandshake()){ while (RTMPStream::handshake_in.size() < 1537){
Socket.read(RTMPStream::handshake_in);
}
if (RTMPStream::doHandshake()){
Socket.write(RTMPStream::handshake_out);
Socket.read((char*)RTMPStream::handshake_in.c_str(), 1536);
RTMPStream::rec_cnt += 1536;
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Handshake succcess!\n"); fprintf(stderr, "Handshake succcess!\n");
#endif #endif
@ -61,14 +72,9 @@ namespace Connector_RTMP{
int sspoller = epoll_create(1); int sspoller = epoll_create(1);
struct epoll_event ev; struct epoll_event ev;
ev.events = EPOLLIN; ev.events = EPOLLIN;
ev.data.fd = CONN_fd; ev.data.fd = Socket.getSocket();
epoll_ctl(poller, EPOLL_CTL_ADD, CONN_fd, &ev); epoll_ctl(poller, EPOLL_CTL_ADD, Socket.getSocket(), &ev);
struct epoll_event events[1]; struct epoll_event events[1];
#if DEBUG >= 5
//for writing whole stream to a file
FILE * tmpfile = 0;
char tmpstr[200];
#endif
while (Socket.connected() && !FLV::Parse_Error){ while (Socket.connected() && !FLV::Parse_Error){
//only parse input if available or not yet init'ed //only parse input if available or not yet init'ed
@ -100,7 +106,6 @@ namespace Connector_RTMP{
#endif #endif
inited = true; inited = true;
} }
retval = epoll_wait(sspoller, events, 1, 1); retval = epoll_wait(sspoller, events, 1, 1);
switch (SS.ready()){ switch (SS.ready()){
case -1: case -1:
@ -114,44 +119,27 @@ namespace Connector_RTMP{
if (tag.SockLoader(SS)){//able to read a full packet? if (tag.SockLoader(SS)){//able to read a full packet?
ts = tag.tagTime(); ts = tag.tagTime();
if (ts != 0){ if (ts != 0){
if (fts == 0){fts = ts;ftst = getNowMS();} if (fts == 0){fts = ts;ftst = RTMPStream::getNowMS();}
ts -= fts; ts -= fts;
tag.tagTime(ts); tag.tagTime(ts);
ts += ftst; ts += ftst;
}else{ }else{
ftst = getNowMS(); ftst = RTMPStream::getNowMS();
tag.tagTime(ftst); tag.tagTime(ftst);
} }
SendMedia((unsigned char)tag.data[0], (unsigned char *)tag.data+11, tag.len-15, ts); Socket.write(RTMPStream::SendMedia((unsigned char)tag.data[0], (unsigned char *)tag.data+11, tag.len-15, ts));
#if DEBUG >= 5
//write whole stream to a file
if (tmpfile == 0){
sprintf(tmpstr, "./tmpfile_socket_%i.flv", CONN_fd);
tmpfile = fopen(tmpstr, "w");
fwrite(FLVHeader, 13, 1, tmpfile);
}
fwrite(tag->data, tag->len, 1, tmpfile);
#endif
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Sent a tag to %i\n", CONN_fd); fprintf(stderr, "Sent a tag to %i\n", Socket.getSocket());
#endif #endif
} }
break; break;
} }
} }
//send ACK if we received a whole window
if ((rec_cnt - rec_window_at > rec_window_size)){
rec_window_at = rec_cnt;
SendCTL(3, rec_cnt);//send ack (msg 3)
}
} }
SS.close(); SS.close();
Socket.close(); Socket.close();
#if DEBUG >= 5
fclose(tmpfile);
#endif
#if DEBUG >= 1 #if DEBUG >= 1
if (FLV::Parse_Error){fprintf(stderr, "FLV Parse Error\n");} if (FLV::Parse_Error){fprintf(stderr, "FLV Parse Error: %s\n", FLV::Error_Str.c_str());}
fprintf(stderr, "User %i disconnected.\n", conn.getSocket()); fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
if (inited){ if (inited){
fprintf(stderr, "Status was: inited\n"); fprintf(stderr, "Status was: inited\n");
@ -164,9 +152,270 @@ namespace Connector_RTMP{
} }
#endif #endif
return 0; return 0;
}//Connector_RTMP }//Connector_RTMP
/// Tries to get and parse one RTMP chunk at a time.
void Connector_RTMP::parseChunk(){
static RTMPStream::Chunk next;
static std::string inbuffer;
static AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER);
static AMF::Object amfelem("empty", AMF::AMF0_DDV_CONTAINER);
if (!Connector_RTMP::Socket.read(inbuffer)){return;} //try to get more data
while (next.Parse(inbuffer)){
//send ACK if we received a whole window
if ((RTMPStream::rec_cnt - RTMPStream::rec_window_at > RTMPStream::rec_window_size)){
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
Socket.write(RTMPStream::SendCTL(3, RTMPStream::rec_cnt));//send ack (msg 3)
}
switch (next.msg_type_id){
case 0://does not exist
break;//happens when connection breaks unexpectedly
case 1://set chunk size
RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str());
#if DEBUG >= 4
fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max);
#endif
break;
case 2://abort message - we ignore this one
#if DEBUG >= 4
fprintf(stderr, "CTRL: Abort message\n");
#endif
//4 bytes of stream id to drop
break;
case 3://ack
#if DEBUG >= 4
fprintf(stderr, "CTRL: Acknowledgement\n");
#endif
RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str());
RTMPStream::snd_window_at = RTMPStream::snd_cnt;
break;
case 4:{
#if DEBUG >= 4
short int ucmtype = ntohs(*(short int*)next.data.c_str());
fprintf(stderr, "CTRL: User control message %hi\n", ucmtype);
#endif
//2 bytes event type, rest = event data
//types:
//0 = stream begin, 4 bytes ID
//1 = stream EOF, 4 bytes ID
//2 = stream dry, 4 bytes ID
//3 = setbufferlen, 4 bytes ID, 4 bytes length
//4 = streamisrecorded, 4 bytes ID
//6 = pingrequest, 4 bytes data
//7 = pingresponse, 4 bytes data
//we don't need to process this
} break;
case 5://window size of other end
#if DEBUG >= 4
fprintf(stderr, "CTRL: Window size\n");
#endif
RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str());
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
Socket.write(RTMPStream::SendCTL(3, RTMPStream::rec_cnt));//send ack (msg 3)
break;
case 6:
#if DEBUG >= 4
fprintf(stderr, "CTRL: Set peer bandwidth\n");
#endif
//4 bytes window size, 1 byte limit type (ignored)
RTMPStream::snd_window_size = ntohl(*(int*)next.data.c_str());
Socket.write(RTMPStream::SendCTL(5, RTMPStream::snd_window_size));//send window acknowledgement size (msg 5)
break;
case 8:
#if DEBUG >= 4
fprintf(stderr, "Received audio data\n");
#endif
break;
case 9:
#if DEBUG >= 4
fprintf(stderr, "Received video data\n");
#endif
break;
case 15:
#if DEBUG >= 4
fprintf(stderr, "Received AFM3 data message\n");
#endif
break;
case 16:
#if DEBUG >= 4
fprintf(stderr, "Received AFM3 shared object\n");
#endif
break;
case 17:
#if DEBUG >= 4
fprintf(stderr, "Received AFM3 command message\n");
#endif
break;
case 18:
#if DEBUG >= 4
fprintf(stderr, "Received AFM0 data message\n");
#endif
break;
case 19:
#if DEBUG >= 4
fprintf(stderr, "Received AFM0 shared object\n");
#endif
break;
case 20:{//AMF0 command message
bool parsed = false;
amfdata = AMF::parse(next.data);
#if DEBUG >= 4
amfdata.Print();
#endif
if (amfdata.getContentP(0)->StrValue() == "connect"){
#if DEBUG >= 4
int tmpint;
tmpint = amfdata.getContentP(2)->getContentP("videoCodecs")->NumValue();
if (tmpint & 0x04){fprintf(stderr, "Sorensen video support detected\n");}
if (tmpint & 0x80){fprintf(stderr, "H264 video support detected\n");}
tmpint = amfdata.getContentP(2)->getContentP("audioCodecs")->NumValue();
if (tmpint & 0x04){fprintf(stderr, "MP3 audio support detected\n");}
if (tmpint & 0x400){fprintf(stderr, "AAC video support detected\n");}
#endif
Socket.write(RTMPStream::SendCTL(6, RTMPStream::rec_window_size, 0));//send peer bandwidth (msg 6)
Socket.write(RTMPStream::SendCTL(5, RTMPStream::snd_window_size));//send window acknowledgement size (msg 5)
Socket.write(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1
//send a _result reply
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "_result"));//result success
amfreply.addContent(amfdata.getContent(1));//same transaction ID
// amfreply.addContent(AMFType("", (double)0, 0x05));//null - command info
amfreply.addContent(AMF::Object(""));//server properties
amfreply.getContentP(2)->addContent(AMF::Object("fmsVer", "FMS/3,5,2,654"));//stolen from examples
amfreply.getContentP(2)->addContent(AMF::Object("capabilities", (double)31));//stolen from examples
amfreply.getContentP(2)->addContent(AMF::Object("mode", (double)1));//stolen from examples
amfreply.getContentP(2)->addContent(AMF::Object("objectEncoding", (double)0));
amfreply.addContent(AMF::Object(""));//info
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetConnection.Connect.Success"));
amfreply.getContentP(3)->addContent(AMF::Object("description", "Connection succeeded."));
#if DEBUG >= 4
amfreply.Print();
#endif
Socket.write(RTMPStream::SendChunk(3, 20, next.msg_stream_id, amfreply.Pack()));
//send onBWDone packet
//amfreply = AMFType("container", (unsigned char)0xFF);
//amfreply.addContent(AMFType("", "onBWDone"));//result success
//amfreply.addContent(AMFType("", (double)0));//zero
//amfreply.addContent(AMFType("", (double)0, 0x05));//null
//SendChunk(3, 20, next.msg_stream_id, amfreply.Pack());
parsed = true;
}//connect
if (amfdata.getContentP(0)->StrValue() == "createStream"){
//send a _result reply
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "_result"));//result success
amfreply.addContent(amfdata.getContent(1));//same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
amfreply.addContent(AMF::Object("", (double)1));//stream ID - we use 1
#if DEBUG >= 4
amfreply.Print();
#endif
Socket.write(RTMPStream::SendChunk(3, 20, next.msg_stream_id, amfreply.Pack()));
Socket.write(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1
parsed = true;
}//createStream
if ((amfdata.getContentP(0)->StrValue() == "getStreamLength") || (amfdata.getContentP(0)->StrValue() == "getMovLen")){
//send a _result reply
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "_result"));//result success
amfreply.addContent(amfdata.getContent(1));//same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
amfreply.addContent(AMF::Object("", (double)0));//zero length
#if DEBUG >= 4
amfreply.Print();
#endif
Socket.write(RTMPStream::SendChunk(3, 20, next.msg_stream_id, amfreply.Pack()));
parsed = true;
}//getStreamLength
if (amfdata.getContentP(0)->StrValue() == "checkBandwidth"){
//send a _result reply
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "_result"));//result success
amfreply.addContent(amfdata.getContent(1));//same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
#if DEBUG >= 4
amfreply.Print();
#endif
Socket.write(RTMPStream::SendChunk(3, 20, 1, amfreply.Pack()));
parsed = true;
}//checkBandwidth
if ((amfdata.getContentP(0)->StrValue() == "play") || (amfdata.getContentP(0)->StrValue() == "play2")){
//send streambegin
streamname = amfdata.getContentP(3)->StrValue();
for (std::string::iterator i=streamname.end()-1; i>=streamname.begin(); --i){
if (!isalpha(*i) && !isdigit(*i)){streamname.erase(i);}else{*i=tolower(*i);}
}
streamname = "/tmp/shared_socket_" + streamname;
Socket.write(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1
//send a status reply
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "onStatus"));//status reply
amfreply.addContent(amfdata.getContent(1));//same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
amfreply.addContent(AMF::Object(""));//info
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
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));
#if DEBUG >= 4
amfreply.Print();
#endif
Socket.write(RTMPStream::SendChunk(4, 20, next.msg_stream_id, amfreply.Pack()));
amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "onStatus"));//status reply
amfreply.addContent(amfdata.getContent(1));//same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
amfreply.addContent(AMF::Object(""));//info
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
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));
#if DEBUG >= 4
amfreply.Print();
#endif
Socket.write(RTMPStream::SendChunk(4, 20, 1, amfreply.Pack()));
//No clue what this does. Most real servers send it, though...
// amfreply = AMFType("container", (unsigned char)0xFF);
// amfreply.addContent(AMFType("", "|RtmpSampleAccess"));//status reply
// amfreply.addContent(AMFType("", (double)1, 0x01));//bool true - audioaccess
// amfreply.addContent(AMFType("", (double)1, 0x01));//bool true - videoaccess
// SendChunk(4, 20, next.msg_stream_id, amfreply.Pack());
RTMPStream::chunk_snd_max = 1024*1024;
Socket.write(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max));//send chunk size max (msg 1)
Connector_RTMP::ready4data = true;//start sending video data!
parsed = true;
}//createStream
#if DEBUG >= 3
fprintf(stderr, "AMF0 command: %s\n", amfdata.getContentP(0)->StrValue().c_str());
#endif
if (!parsed){
#if DEBUG >= 2
fprintf(stderr, "AMF0 command not processed! :(\n");
#endif
}
} break;
case 22:
#if DEBUG >= 4
fprintf(stderr, "Received aggregate message\n");
#endif
break;
default:
#if DEBUG >= 1
fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n");
#endif
Connector_RTMP::stopparsing = true;
break;
}
}
}//parseChunk
};//Connector_RTMP namespace
// Load main server setup file, default port 1935, handler is Connector_RTMP::Connector_RTMP // Load main server setup file, default port 1935, handler is Connector_RTMP::Connector_RTMP
#define DEFAULT_PORT 1935 #define DEFAULT_PORT 1935

View file

@ -1,254 +0,0 @@
#include "chunkstream.cpp" //chunkstream decoding
#include "amf.cpp" //simple AMF0 parsing
std::string streamname = "/tmp/shared_socket";
//gets and parses one chunk
void parseChunk(){
static chunkpack next;
static AMFType amfdata("empty", (unsigned char)AMF0_DDV_CONTAINER);
static AMFType amfelem("empty", (unsigned char)AMF0_DDV_CONTAINER);
next = getWholeChunk();
switch (next.msg_type_id){
case 0://does not exist
break;//happens when connection breaks unexpectedly
case 1://set chunk size
chunk_rec_max = ntohl(*(int*)next.data);
#if DEBUG >= 4
fprintf(stderr, "CTRL: Set chunk size: %i\n", chunk_rec_max);
#endif
break;
case 2://abort message - we ignore this one
#if DEBUG >= 4
fprintf(stderr, "CTRL: Abort message\n");
#endif
//4 bytes of stream id to drop
break;
case 3://ack
#if DEBUG >= 4
fprintf(stderr, "CTRL: Acknowledgement\n");
#endif
snd_window_at = ntohl(*(int*)next.data);
snd_window_at = snd_cnt;
break;
case 4:{
#if DEBUG >= 4
short int ucmtype = ntohs(*(short int*)next.data);
fprintf(stderr, "CTRL: User control message %hi\n", ucmtype);
#endif
//2 bytes event type, rest = event data
//types:
//0 = stream begin, 4 bytes ID
//1 = stream EOF, 4 bytes ID
//2 = stream dry, 4 bytes ID
//3 = setbufferlen, 4 bytes ID, 4 bytes length
//4 = streamisrecorded, 4 bytes ID
//6 = pingrequest, 4 bytes data
//7 = pingresponse, 4 bytes data
//we don't need to process this
} break;
case 5://window size of other end
#if DEBUG >= 4
fprintf(stderr, "CTRL: Window size\n");
#endif
rec_window_size = ntohl(*(int*)next.data);
rec_window_at = rec_cnt;
SendCTL(3, rec_cnt);//send ack (msg 3)
break;
case 6:
#if DEBUG >= 4
fprintf(stderr, "CTRL: Set peer bandwidth\n");
#endif
//4 bytes window size, 1 byte limit type (ignored)
snd_window_size = ntohl(*(int*)next.data);
SendCTL(5, snd_window_size);//send window acknowledgement size (msg 5)
break;
case 8:
#if DEBUG >= 4
fprintf(stderr, "Received audio data\n");
#endif
break;
case 9:
#if DEBUG >= 4
fprintf(stderr, "Received video data\n");
#endif
break;
case 15:
#if DEBUG >= 4
fprintf(stderr, "Received AFM3 data message\n");
#endif
break;
case 16:
#if DEBUG >= 4
fprintf(stderr, "Received AFM3 shared object\n");
#endif
break;
case 17:
#if DEBUG >= 4
fprintf(stderr, "Received AFM3 command message\n");
#endif
break;
case 18:
#if DEBUG >= 4
fprintf(stderr, "Received AFM0 data message\n");
#endif
break;
case 19:
#if DEBUG >= 4
fprintf(stderr, "Received AFM0 shared object\n");
#endif
break;
case 20:{//AMF0 command message
bool parsed = false;
amfdata = parseAMF(next.data, next.real_len);
#if DEBUG >= 4
amfdata.Print();
#endif
if (amfdata.getContentP(0)->StrValue() == "connect"){
#if DEBUG >= 4
int tmpint;
tmpint = amfdata.getContentP(2)->getContentP("videoCodecs")->NumValue();
if (tmpint & 0x04){fprintf(stderr, "Sorensen video support detected\n");}
if (tmpint & 0x80){fprintf(stderr, "H264 video support detected\n");}
tmpint = amfdata.getContentP(2)->getContentP("audioCodecs")->NumValue();
if (tmpint & 0x04){fprintf(stderr, "MP3 audio support detected\n");}
if (tmpint & 0x400){fprintf(stderr, "AAC video support detected\n");}
#endif
SendCTL(6, rec_window_size, 0);//send peer bandwidth (msg 6)
SendCTL(5, snd_window_size);//send window acknowledgement size (msg 5)
SendUSR(0, 1);//send UCM StreamBegin (0), stream 1
//send a _result reply
AMFType amfreply("container", (unsigned char)AMF0_DDV_CONTAINER);
amfreply.addContent(AMFType("", "_result"));//result success
amfreply.addContent(amfdata.getContent(1));//same transaction ID
// amfreply.addContent(AMFType("", (double)0, 0x05));//null - command info
amfreply.addContent(AMFType(""));//server properties
amfreply.getContentP(2)->addContent(AMFType("fmsVer", "FMS/3,5,2,654"));//stolen from examples
amfreply.getContentP(2)->addContent(AMFType("capabilities", (double)31));//stolen from examples
amfreply.getContentP(2)->addContent(AMFType("mode", (double)1));//stolen from examples
amfreply.getContentP(2)->addContent(AMFType("objectEncoding", (double)0));
amfreply.addContent(AMFType(""));//info
amfreply.getContentP(3)->addContent(AMFType("level", "status"));
amfreply.getContentP(3)->addContent(AMFType("code", "NetConnection.Connect.Success"));
amfreply.getContentP(3)->addContent(AMFType("description", "Connection succeeded."));
#if DEBUG >= 4
amfreply.Print();
#endif
SendChunk(3, 20, next.msg_stream_id, amfreply.Pack());
//send onBWDone packet
//amfreply = AMFType("container", (unsigned char)0xFF);
//amfreply.addContent(AMFType("", "onBWDone"));//result success
//amfreply.addContent(AMFType("", (double)0));//zero
//amfreply.addContent(AMFType("", (double)0, 0x05));//null
//SendChunk(3, 20, next.msg_stream_id, amfreply.Pack());
parsed = true;
}//connect
if (amfdata.getContentP(0)->StrValue() == "createStream"){
//send a _result reply
AMFType amfreply("container", (unsigned char)AMF0_DDV_CONTAINER);
amfreply.addContent(AMFType("", "_result"));//result success
amfreply.addContent(amfdata.getContent(1));//same transaction ID
amfreply.addContent(AMFType("", (double)0, AMF0_NULL));//null - command info
amfreply.addContent(AMFType("", (double)1));//stream ID - we use 1
#if DEBUG >= 4
amfreply.Print();
#endif
SendChunk(3, 20, next.msg_stream_id, amfreply.Pack());
SendUSR(0, 1);//send UCM StreamBegin (0), stream 1
parsed = true;
}//createStream
if ((amfdata.getContentP(0)->StrValue() == "getStreamLength") || (amfdata.getContentP(0)->StrValue() == "getMovLen")){
//send a _result reply
AMFType amfreply("container", (unsigned char)AMF0_DDV_CONTAINER);
amfreply.addContent(AMFType("", "_result"));//result success
amfreply.addContent(amfdata.getContent(1));//same transaction ID
amfreply.addContent(AMFType("", (double)0, AMF0_NULL));//null - command info
amfreply.addContent(AMFType("", (double)0));//zero length
#if DEBUG >= 4
amfreply.Print();
#endif
SendChunk(3, 20, next.msg_stream_id, amfreply.Pack());
parsed = true;
}//getStreamLength
if (amfdata.getContentP(0)->StrValue() == "checkBandwidth"){
//send a _result reply
AMFType amfreply("container", (unsigned char)AMF0_DDV_CONTAINER);
amfreply.addContent(AMFType("", "_result"));//result success
amfreply.addContent(amfdata.getContent(1));//same transaction ID
amfreply.addContent(AMFType("", (double)0, AMF0_NULL));//null - command info
amfreply.addContent(AMFType("", (double)0, AMF0_NULL));//null - command info
#if DEBUG >= 4
amfreply.Print();
#endif
SendChunk(3, 20, 1, amfreply.Pack());
parsed = true;
}//checkBandwidth
if ((amfdata.getContentP(0)->StrValue() == "play") || (amfdata.getContentP(0)->StrValue() == "play2")){
//send streambegin
streamname = amfdata.getContentP(3)->StrValue();
for (std::string::iterator i=streamname.end()-1; i>=streamname.begin(); --i){
if (!isalpha(*i) && !isdigit(*i)){streamname.erase(i);}else{*i=tolower(*i);}
}
streamname = "/tmp/shared_socket_" + streamname;
SendUSR(0, 1);//send UCM StreamBegin (0), stream 1
//send a status reply
AMFType amfreply("container", (unsigned char)AMF0_DDV_CONTAINER);
amfreply.addContent(AMFType("", "onStatus"));//status reply
amfreply.addContent(amfdata.getContent(1));//same transaction ID
amfreply.addContent(AMFType("", (double)0, AMF0_NULL));//null - command info
amfreply.addContent(AMFType(""));//info
amfreply.getContentP(3)->addContent(AMFType("level", "status"));
amfreply.getContentP(3)->addContent(AMFType("code", "NetStream.Play.Reset"));
amfreply.getContentP(3)->addContent(AMFType("description", "Playing and resetting..."));
amfreply.getContentP(3)->addContent(AMFType("details", "PLS"));
amfreply.getContentP(3)->addContent(AMFType("clientid", (double)1));
#if DEBUG >= 4
amfreply.Print();
#endif
SendChunk(4, 20, next.msg_stream_id, amfreply.Pack());
amfreply = AMFType("container", (unsigned char)AMF0_DDV_CONTAINER);
amfreply.addContent(AMFType("", "onStatus"));//status reply
amfreply.addContent(amfdata.getContent(1));//same transaction ID
amfreply.addContent(AMFType("", (double)0, AMF0_NULL));//null - command info
amfreply.addContent(AMFType(""));//info
amfreply.getContentP(3)->addContent(AMFType("level", "status"));
amfreply.getContentP(3)->addContent(AMFType("code", "NetStream.Play.Start"));
amfreply.getContentP(3)->addContent(AMFType("description", "Playing!"));
amfreply.getContentP(3)->addContent(AMFType("details", "PLS"));
amfreply.getContentP(3)->addContent(AMFType("clientid", (double)1));
#if DEBUG >= 4
amfreply.Print();
#endif
SendChunk(4, 20, 1, amfreply.Pack());
//No clue what this does. Most real servers send it, though...
// amfreply = AMFType("container", (unsigned char)0xFF);
// amfreply.addContent(AMFType("", "|RtmpSampleAccess"));//status reply
// amfreply.addContent(AMFType("", (double)1, 0x01));//bool true - audioaccess
// amfreply.addContent(AMFType("", (double)1, 0x01));//bool true - videoaccess
// SendChunk(4, 20, next.msg_stream_id, amfreply.Pack());
chunk_snd_max = 1024*1024;
SendCTL(1, chunk_snd_max);//send chunk size max (msg 1)
ready4data = true;//start sending video data!
parsed = true;
}//createStream
#if DEBUG >= 3
fprintf(stderr, "AMF0 command: %s\n", amfdata.getContentP(0)->StrValue().c_str());
#endif
if (!parsed){
#if DEBUG >= 2
fprintf(stderr, "AMF0 command not processed! :(\n");
#endif
}
} break;
case 22:
#if DEBUG >= 4
fprintf(stderr, "Received aggregate message\n");
#endif
break;
default:
#if DEBUG >= 1
fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n");
#endif
stopparsing = true;
break;
}
}//parseChunk

View file

@ -43,7 +43,7 @@ SYMBOL_CACHE_SIZE = 0
# Build related configuration options # Build related configuration options
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
EXTRACT_ALL = YES EXTRACT_ALL = YES
EXTRACT_PRIVATE = NO EXTRACT_PRIVATE = YES
EXTRACT_STATIC = YES EXTRACT_STATIC = YES
EXTRACT_LOCAL_CLASSES = YES EXTRACT_LOCAL_CLASSES = YES
EXTRACT_LOCAL_METHODS = NO EXTRACT_LOCAL_METHODS = NO

View file

@ -1,3 +1,11 @@
/// \file HTTP_Box_Parser/main.cpp
/// Debugging tool for F4M HTTP streaming data.
/// Expects raw TCP data through stdin, outputs human-readable information to stderr.
/// This will attempt to read either HTTP requests or responses from stdin, and if the body is more than
/// 10,000 bytes long will attempt to parse the data as a MP4 box. (Other cases show a message about the fragment being too small)
/// Then it will take the payload of this box, print the first four bytes, and attempt to parse the whole payload as FLV data.
/// The parsed FLV data is then pretty-printed, containing information about the codec parameters and types of tags it encounters.
#include <stdint.h> #include <stdint.h>
#include <iostream> #include <iostream>
#include <string> #include <string>
@ -6,6 +14,12 @@
#include "../util/MP4/box_includes.h" #include "../util/MP4/box_includes.h"
#include "../util/flv_tag.h" #include "../util/flv_tag.h"
/// Debugging tool for F4M HTTP streaming data.
/// Expects raw TCP data through stdin, outputs human-readable information to stderr.
/// This will attempt to read either HTTP requests or responses from stdin, and if the body is more than
/// 10,000 bytes long will attempt to parse the data as a MP4 box. (Other cases show a message about the fragment being too small)
/// Then it will take the payload of this box, print the first four bytes, and attempt to parse the whole payload as FLV data.
/// The parsed FLV data is then pretty-printed, containing information about the codec parameters and types of tags it encounters.
int main(){ int main(){
HTTPReader H; HTTPReader H;
FLV::Tag F; FLV::Tag F;
@ -28,4 +42,4 @@ int main(){
std::cout << "Skipped too small fragment of size " << H.body.size() << std::endl; std::cout << "Skipped too small fragment of size " << H.body.size() << std::endl;
} }
} }
} }//main

View file

@ -18,5 +18,5 @@ client-install: client-clean client
cd Connector_RAW; $(MAKE) install cd Connector_RAW; $(MAKE) install
cd Buffer; $(MAKE) install cd Buffer; $(MAKE) install
docs: docs:
doxygen ./Doxyfile doxygen ./Doxyfile > /dev/null

View file

@ -1,3 +1,6 @@
/// \file amf.cpp
/// Holds all code for the AMF namespace.
#include "amf.h" #include "amf.h"
/// Returns the std::string Indice for the current object, if available. /// Returns the std::string Indice for the current object, if available.

View file

@ -1,3 +1,6 @@
/// \file amf.h
/// Holds all headers for the AMF namespace.
#pragma once #pragma once
#include <vector> #include <vector>
#include <iostream> #include <iostream>

View file

@ -1,3 +1,6 @@
/// \file crypto.cpp
/// Holds all code needed for RTMP cryptography.
#define STR(x) (((std::string)(x)).c_str()) #define STR(x) (((std::string)(x)).c_str())
#include "crypto.h" #include "crypto.h"

56
util/crypto.h Normal file
View file

@ -0,0 +1,56 @@
/// \file crypto.h
/// Holds all headers needed for RTMP cryptography functions.
#pragma once
#include <stdint.h>
#include <string>
#include <openssl/bn.h>
#include <openssl/dh.h>
#include <openssl/rc4.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/hmac.h>
class DHWrapper {
private:
int32_t _bitsCount;
DH *_pDH;
uint8_t *_pSharedKey;
int32_t _sharedKeyLength;
BIGNUM *_peerPublickey;
public:
DHWrapper(int32_t bitsCount);
virtual ~DHWrapper();
bool Initialize();
bool CopyPublicKey(uint8_t *pDst, int32_t dstLength);
bool CopyPrivateKey(uint8_t *pDst, int32_t dstLength);
bool CreateSharedKey(uint8_t *pPeerPublicKey, int32_t length);
bool CopySharedKey(uint8_t *pDst, int32_t dstLength);
private:
void Cleanup();
bool CopyKey(BIGNUM *pNum, uint8_t *pDst, int32_t dstLength);
};
void InitRC4Encryption(uint8_t *secretKey, uint8_t *pubKeyIn, uint8_t *pubKeyOut, RC4_KEY *rc4keyIn, RC4_KEY *rc4keyOut);
std::string md5(std::string source, bool textResult);
std::string b64(std::string source);
std::string b64(uint8_t *pBuffer, uint32_t length);
std::string unb64(std::string source);
std::string unb64(uint8_t *pBuffer, uint32_t length);
void HMACsha256(const void *pData, uint32_t dataLength, const void *pKey, uint32_t keyLength, void *pResult);
uint32_t GetDigestOffset0(uint8_t *pBuffer);
uint32_t GetDigestOffset1(uint8_t *pBuffer);
uint32_t GetDigestOffset(uint8_t *pBuffer, uint8_t scheme);
uint32_t GetDHOffset0(uint8_t *pBuffer);
uint32_t GetDHOffset1(uint8_t *pBuffer);
uint32_t GetDHOffset(uint8_t *pBuffer, uint8_t scheme);
extern uint8_t genuineFMSKey[];
bool ValidateClientScheme(uint8_t * pBuffer, uint8_t scheme);

View file

@ -1,3 +1,6 @@
/// \file ddv_socket.cpp
/// Holds all code for the DDV namespace.
#include "ddv_socket.h" #include "ddv_socket.h"
/// Create a new base socket. This is a basic constructor for converting any valid socket to a DDV::Socket. /// Create a new base socket. This is a basic constructor for converting any valid socket to a DDV::Socket.
@ -207,6 +210,21 @@ int DDV::Socket::iread(void * buffer, int len){
return r; return r;
}//DDV::Socket::iread }//DDV::Socket::iread
/// Read call that is compatible with std::string.
/// Data is read using iread (which is nonblocking if the DDV::Socket itself is),
/// then appended to end of buffer.
/// \param buffer std::string to append data to.
/// \return True if new data arrived, false otherwise.
bool DDV::Socket::read(std::string & buffer){
char cbuffer[5000];
int num = iread(cbuffer, 5000);
if (num > 0){
buffer.append(cbuffer, num);
return true;
}
return false;
}//read
/// Create a new base ServerSocket. The socket is never connected, and a placeholder for later connections. /// Create a new base ServerSocket. The socket is never connected, and a placeholder for later connections.
DDV::ServerSocket::ServerSocket(){ DDV::ServerSocket::ServerSocket(){
sock = -1; sock = -1;

View file

@ -1,3 +1,6 @@
/// \file ddv_socket.h
/// Holds all headers for the DDV namespace.
#pragma once #pragma once
#include <string> #include <string>
#include <sys/types.h> #include <sys/types.h>
@ -33,6 +36,7 @@ namespace DDV{
bool write(const std::string data); ///< Write call that is compatible with std::string. bool write(const std::string data); ///< Write call that is compatible with std::string.
int iwrite(void * buffer, int len); ///< Incremental write call. int iwrite(void * buffer, int len); ///< Incremental write call.
int iread(void * buffer, int len); ///< Incremental read call. int iread(void * buffer, int len); ///< Incremental read call.
bool read(std::string & buffer); ///< Read call that is compatible with std::string.
void close(); ///< Close connection. void close(); ///< Close connection.
int getSocket(); ///< Returns internal socket number. int getSocket(); ///< Returns internal socket number.
}; };

View file

@ -1,3 +1,6 @@
/// \file flv_tag.cpp
/// Holds all code for the FLV namespace.
#include "flv_tag.h" #include "flv_tag.h"
#include <stdio.h> //for Tag::FileLoader #include <stdio.h> //for Tag::FileLoader
#include <unistd.h> //for Tag::FileLoader #include <unistd.h> //for Tag::FileLoader
@ -8,6 +11,7 @@
char FLV::Header[13]; ///< Holds the last FLV header parsed. char FLV::Header[13]; ///< Holds the last FLV header parsed.
bool FLV::Parse_Error = false; ///< This variable is set to true if a problem is encountered while parsing the FLV. 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 = "";
/// Checks a FLV Header for validness. Returns true if the header is valid, false /// Checks a FLV Header for validness. Returns true if the header is valid, false
/// if the header is not. Not valid can mean: /// if the header is not. Not valid can mean:
@ -219,7 +223,7 @@ bool FLV::Tag::MemLoader(char * D, unsigned int S, unsigned int & P){
if (FLV::check_header(data)){ if (FLV::check_header(data)){
sofar = 0; sofar = 0;
memcpy(FLV::Header, data, 13); memcpy(FLV::Header, data, 13);
}else{FLV::Parse_Error = true; return false;} }else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;}
} }
}else{ }else{
//if a tag header, calculate length and read tag body //if a tag header, calculate length and read tag body
@ -227,7 +231,7 @@ bool FLV::Tag::MemLoader(char * D, unsigned int S, unsigned int & P){
len += (data[2] << 8); len += (data[2] << 8);
len += (data[1] << 16); len += (data[1] << 16);
if (buf < len){data = (char*)realloc(data, len); buf = len;} if (buf < len){data = (char*)realloc(data, len); buf = len;}
if (data[0] > 0x12){FLV::Parse_Error = true; return false;} if (data[0] > 0x12){FLV::Parse_Error = true; Error_Str = "Invalid Tag received."; return false;}
done = false; done = false;
} }
} }
@ -259,7 +263,7 @@ bool FLV::Tag::SockReadUntil(char * buffer, unsigned int count, unsigned int & s
if (r < 0){ if (r < 0){
if (errno != EWOULDBLOCK){ if (errno != EWOULDBLOCK){
FLV::Parse_Error = true; FLV::Parse_Error = true;
fprintf(stderr, "ReadUntil fail: %s. All Hell Broke Loose!\n", strerror(errno)); Error_Str = "Error reading from socket.";
} }
return false; return false;
} }
@ -267,7 +271,7 @@ bool FLV::Tag::SockReadUntil(char * buffer, unsigned int count, unsigned int & s
if (sofar == count){return true;} if (sofar == count){return true;}
if (sofar > count){ if (sofar > count){
FLV::Parse_Error = true; FLV::Parse_Error = true;
fprintf(stderr, "ReadUntil fail: %s. Read too much. All Hell Broke Loose!\n", strerror(errno)); Error_Str = "Socket buffer overflow.";
} }
return false; return false;
}//Tag::SockReadUntil }//Tag::SockReadUntil
@ -289,7 +293,7 @@ bool FLV::Tag::SockLoader(DDV::Socket sock){
if (FLV::check_header(data)){ if (FLV::check_header(data)){
sofar = 0; sofar = 0;
memcpy(FLV::Header, data, 13); memcpy(FLV::Header, data, 13);
}else{FLV::Parse_Error = true; return false;} }else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;}
} }
}else{ }else{
//if a tag header, calculate length and read tag body //if a tag header, calculate length and read tag body
@ -297,7 +301,7 @@ bool FLV::Tag::SockLoader(DDV::Socket sock){
len += (data[2] << 8); len += (data[2] << 8);
len += (data[1] << 16); len += (data[1] << 16);
if (buf < len){data = (char*)realloc(data, len); buf = len;} if (buf < len){data = (char*)realloc(data, len); buf = len;}
if (data[0] > 0x12){FLV::Parse_Error = true; return false;} if (data[0] > 0x12){FLV::Parse_Error = true; Error_Str = "Invalid Tag received."; return false;}
done = false; done = false;
} }
} }
@ -335,7 +339,7 @@ bool FLV::Tag::FileReadUntil(char * buffer, unsigned int count, unsigned int & s
if (sofar >= count){return true;} if (sofar >= count){return true;}
int r = 0; int r = 0;
r = fread(buffer + sofar,1,count-sofar,f); r = fread(buffer + sofar,1,count-sofar,f);
if (r < 0){FLV::Parse_Error = true; return false;} if (r < 0){FLV::Parse_Error = true; Error_Str = "File reading error."; return false;}
sofar += r; sofar += r;
if (sofar >= count){return true;} if (sofar >= count){return true;}
return false; return false;
@ -363,7 +367,7 @@ bool FLV::Tag::FileLoader(FILE * f){
if (FLV::check_header(data)){ if (FLV::check_header(data)){
sofar = 0; sofar = 0;
memcpy(FLV::Header, data, 13); memcpy(FLV::Header, data, 13);
}else{FLV::Parse_Error = true;} }else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;}
} }
}else{ }else{
//if a tag header, calculate length and read tag body //if a tag header, calculate length and read tag body
@ -371,7 +375,7 @@ bool FLV::Tag::FileLoader(FILE * f){
len += (data[2] << 8); len += (data[2] << 8);
len += (data[1] << 16); len += (data[1] << 16);
if (buf < len){data = (char*)realloc(data, len); buf = len;} if (buf < len){data = (char*)realloc(data, len); buf = len;}
if (data[0] > 0x12){FLV::Parse_Error = true;} if (data[0] > 0x12){FLV::Parse_Error = true; Error_Str = "Invalid Tag received."; return false;}
done = false; done = false;
} }
} }

View file

@ -1,3 +1,6 @@
/// \file flv_tag.h
/// Holds all headers for the FLV namespace.
#pragma once #pragma once
#include "ddv_socket.h" #include "ddv_socket.h"
#include <string> #include <string>
@ -7,6 +10,7 @@ namespace FLV {
//variables //variables
extern char Header[13]; ///< Holds the last FLV header parsed. extern char Header[13]; ///< Holds the last FLV header parsed.
extern bool Parse_Error; ///< This variable is set to true if a problem is encountered while parsing the FLV. extern bool Parse_Error; ///< This variable is set to true if a problem is encountered while parsing the FLV.
extern std::string Error_Str; ///< This variable is set if a problem is encountered while parsing the FLV.
//functions //functions
bool check_header(char * header); ///< Checks a FLV Header for validness. bool check_header(char * header); ///< Checks a FLV Header for validness.

View file

@ -1,8 +1,14 @@
#include "http_parser.h" /// \file http_parser.cpp
#include "ddv_socket.h" /// Holds all code for the HTTP namespace.
HTTPReader::HTTPReader(){Clean();} #include "http_parser.h"
void HTTPReader::Clean(){
/// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing.
/// All this constructor does is call HTTP::Parser::Clean().
HTTP::Parser::Parser(){Clean();}
/// Completely re-initializes the HTTP::Parser, leaving it ready for either reading or writing usage.
void HTTP::Parser::Clean(){
seenHeaders = false; seenHeaders = false;
seenReq = false; seenReq = false;
method = "GET"; method = "GET";
@ -11,11 +17,14 @@ void HTTPReader::Clean(){
body = ""; body = "";
length = 0; length = 0;
HTTPbuffer = ""; HTTPbuffer = "";
headers.erase(headers.begin(), headers.end()); headers.clear();
vars.erase(vars.begin(), vars.end()); vars.clear();
} }
bool HTTPReader::CleanForNext(){ /// Re-initializes the HTTP::Parser, leaving the internal data buffer alone, then tries to parse a new request or response.
/// First does the same as HTTP::Parser::Clean(), but does not clear the internal data buffer.
/// This function then calls the HTTP::Parser::parse() function, and returns that functions return value.
bool HTTP::Parser::CleanForNext(){
seenHeaders = false; seenHeaders = false;
seenReq = false; seenReq = false;
method = "GET"; method = "GET";
@ -23,12 +32,19 @@ bool HTTPReader::CleanForNext(){
protocol = "HTTP/1.1"; protocol = "HTTP/1.1";
body = ""; body = "";
length = 0; length = 0;
headers.erase(headers.begin(), headers.end()); headers.clear();
vars.erase(vars.begin(), vars.end()); vars.clear();
return parse(); return parse();
} }
std::string HTTPReader::BuildRequest(){ /// Returns a string containing a valid HTTP 1.0 or 1.1 request, ready for sending.
/// The request is build from internal variables set before this call is made.
/// To be precise, method, url, protocol, headers and the internal data buffer are used,
/// where the internal data buffer is used as the body of the request.
/// This means you cannot mix receiving and sending, because the body would get corrupted.
/// \return A string containing a valid HTTP 1.0 or 1.1 request, ready for sending.
std::string HTTP::Parser::BuildRequest(){
/// \todo Include GET/POST variable parsing?
std::map<std::string, std::string>::iterator it; std::map<std::string, std::string>::iterator it;
std::string tmp = method+" "+url+" "+protocol+"\n"; std::string tmp = method+" "+url+" "+protocol+"\n";
for (it=headers.begin(); it != headers.end(); it++){ for (it=headers.begin(); it != headers.end(); it++){
@ -39,7 +55,16 @@ std::string HTTPReader::BuildRequest(){
return tmp; return tmp;
} }
std::string HTTPReader::BuildResponse(std::string code, std::string message){ /// Returns a string containing a valid HTTP 1.0 or 1.1 response, ready for sending.
/// The response is partly build from internal variables set before this call is made.
/// To be precise, protocol, headers and the internal data buffer are used,
/// where the internal data buffer is used as the body of the response.
/// This means you cannot mix receiving and sending, because the body would get corrupted.
/// \param code The HTTP response code. Usually you want 200.
/// \param message The HTTP response message. Usually you want "OK".
/// \return A string containing a valid HTTP 1.0 or 1.1 response, ready for sending.
std::string HTTP::Parser::BuildResponse(std::string code, std::string message){
/// \todo Include GET/POST variable parsing?
std::map<std::string, std::string>::iterator it; std::map<std::string, std::string>::iterator it;
std::string tmp = protocol+" "+code+" "+message+"\n"; std::string tmp = protocol+" "+code+" "+message+"\n";
for (it=headers.begin(); it != headers.end(); it++){ for (it=headers.begin(); it != headers.end(); it++){
@ -50,47 +75,61 @@ std::string HTTPReader::BuildResponse(std::string code, std::string message){
return tmp; return tmp;
} }
void HTTPReader::Trim(std::string & s){ /// Trims any whitespace at the front or back of the string.
/// Used when getting/setting headers.
/// \param s The string to trim. The string itself will be changed, not returned.
void HTTP::Parser::Trim(std::string & s){
size_t startpos = s.find_first_not_of(" \t"); size_t startpos = s.find_first_not_of(" \t");
size_t endpos = s.find_last_not_of(" \t"); size_t endpos = s.find_last_not_of(" \t");
if ((std::string::npos == startpos) || (std::string::npos == endpos)){s = "";}else{s = s.substr(startpos, endpos-startpos+1);} if ((std::string::npos == startpos) || (std::string::npos == endpos)){s = "";}else{s = s.substr(startpos, endpos-startpos+1);}
} }
void HTTPReader::SetBody(std::string s){ /// Function that sets the body of a response or request, along with the correct Content-Length header.
/// \param s The string to set the body to.
void HTTP::Parser::SetBody(std::string s){
HTTPbuffer = s; HTTPbuffer = s;
SetHeader("Content-Length", s.length()); SetHeader("Content-Length", s.length());
} }
void HTTPReader::SetBody(char * buffer, int len){ /// Function that sets the body of a response or request, along with the correct Content-Length header.
/// \param buffer The buffer data to set the body to.
/// \param len Length of the buffer data.
void HTTP::Parser::SetBody(char * buffer, int len){
HTTPbuffer = ""; HTTPbuffer = "";
HTTPbuffer.append(buffer, len); HTTPbuffer.append(buffer, len);
SetHeader("Content-Length", len); SetHeader("Content-Length", len);
} }
/// Returns header i, if set.
std::string HTTP::Parser::GetHeader(std::string i){return headers[i];}
/// Returns POST variable i, if set.
std::string HTTP::Parser::GetVar(std::string i){return vars[i];}
std::string HTTPReader::GetHeader(std::string i){return headers[i];} /// Sets header i to string value v.
std::string HTTPReader::GetVar(std::string i){return vars[i];} void HTTP::Parser::SetHeader(std::string i, std::string v){
void HTTPReader::SetHeader(std::string i, std::string v){
Trim(i); Trim(i);
Trim(v); Trim(v);
headers[i] = v; headers[i] = v;
} }
void HTTPReader::SetHeader(std::string i, int v){ /// Sets header i to integer value v.
void HTTP::Parser::SetHeader(std::string i, int v){
Trim(i); Trim(i);
char val[128]; char val[128];
sprintf(val, "%i", v); sprintf(val, "%i", v);
headers[i] = val; headers[i] = val;
} }
void HTTPReader::SetVar(std::string i, std::string v){ /// Sets POST variable i to string value v.
void HTTP::Parser::SetVar(std::string i, std::string v){
Trim(i); Trim(i);
Trim(v); Trim(v);
vars[i] = v; vars[i] = v;
} }
bool HTTPReader::Read(DDV::Socket & sock){ /// Attempt to read a whole HTTP request or response from DDV::Socket sock.
/// \return True of a whole request or response was read, false otherwise.
bool HTTP::Parser::Read(DDV::Socket & sock){
//returned true als hele http packet gelezen is //returned true als hele http packet gelezen is
int r = 0; int r = 0;
int b = 0; int b = 0;
@ -111,7 +150,9 @@ bool HTTPReader::Read(DDV::Socket & sock){
return false; return false;
}//HTTPReader::ReadSocket }//HTTPReader::ReadSocket
bool HTTPReader::Read(FILE * F){ /// Reads a full set of HTTP responses/requests from file F.
/// \return Always false. Use HTTP::Parser::CleanForNext() to parse the contents of the file.
bool HTTP::Parser::Read(FILE * F){
//returned true als hele http packet gelezen is //returned true als hele http packet gelezen is
int b = 1; int b = 1;
char buffer[500]; char buffer[500];
@ -122,7 +163,11 @@ bool HTTPReader::Read(FILE * F){
return false; return false;
}//HTTPReader::ReadSocket }//HTTPReader::ReadSocket
bool HTTPReader::parse(){ /// Attempt to read a whole HTTP response or request from the internal data buffer.
/// If succesful, fills its own fields with the proper data and removes the response/request
/// from the internal data buffer.
/// \return True on success, false otherwise.
bool HTTP::Parser::parse(){
size_t f; size_t f;
std::string tmpA, tmpB, tmpC; std::string tmpA, tmpB, tmpC;
while (HTTPbuffer != ""){ while (HTTPbuffer != ""){
@ -140,7 +185,7 @@ bool HTTPReader::parse(){
if (f != std::string::npos){url = tmpA.substr(0, f); tmpA.erase(0, f+1);} if (f != std::string::npos){url = tmpA.substr(0, f); tmpA.erase(0, f+1);}
f = tmpA.find(' '); f = tmpA.find(' ');
if (f != std::string::npos){protocol = tmpA.substr(0, f); tmpA.erase(0, f+1);} if (f != std::string::npos){protocol = tmpA.substr(0, f); tmpA.erase(0, f+1);}
//TODO: GET variable parsing? /// \todo Include GET variable parsing?
}else{ }else{
if (tmpA.size() == 0){ if (tmpA.size() == 0){
seenHeaders = true; seenHeaders = true;
@ -156,7 +201,7 @@ bool HTTPReader::parse(){
} }
if (seenHeaders){ if (seenHeaders){
if (length > 0){ if (length > 0){
//TODO: POST variable parsing? /// \todo Include POST variable parsing?
if (HTTPbuffer.length() >= length){ if (HTTPbuffer.length() >= length){
body = HTTPbuffer.substr(0, length); body = HTTPbuffer.substr(0, length);
HTTPbuffer.erase(0, length); HTTPbuffer.erase(0, length);
@ -172,23 +217,40 @@ bool HTTPReader::parse(){
return false; //we should never get here... return false; //we should never get here...
}//HTTPReader::parse }//HTTPReader::parse
void HTTPReader::SendResponse(DDV::Socket & conn, std::string code, std::string message){ /// Sends data as response to conn.
/// The response is automatically first build using HTTP::Parser::BuildResponse().
/// \param conn The DDV::Socket to send the response over.
/// \param code The HTTP response code. Usually you want 200.
/// \param message The HTTP response message. Usually you want "OK".
void HTTP::Parser::SendResponse(DDV::Socket & conn, std::string code, std::string message){
std::string tmp = BuildResponse(code, message); std::string tmp = BuildResponse(code, message);
conn.write(tmp); conn.write(tmp);
} }
void HTTPReader::SendBodyPart(DDV::Socket & conn, char * buffer, int len){ /// Sends data as HTTP/1.1 bodypart to conn.
/// HTTP/1.1 chunked encoding is automatically applied if needed.
/// \param conn The DDV::Socket to send the part over.
/// \param buffer The buffer to send.
/// \param len The length of the buffer.
void HTTP::Parser::SendBodyPart(DDV::Socket & conn, char * buffer, int len){
std::string tmp; std::string tmp;
tmp.append(buffer, len); tmp.append(buffer, len);
SendBodyPart(conn, tmp); SendBodyPart(conn, tmp);
} }
void HTTPReader::SendBodyPart(DDV::Socket & conn, std::string bodypart){ /// Sends data as HTTP/1.1 bodypart to conn.
/// HTTP/1.1 chunked encoding is automatically applied if needed.
/// \param conn The DDV::Socket to send the part over.
/// \param bodypart The data to send.
void HTTP::Parser::SendBodyPart(DDV::Socket & conn, std::string bodypart){
if (protocol == "HTTP/1.1"){
static char len[10]; static char len[10];
int sizelen; int sizelen;
sizelen = snprintf(len, 10, "%x\r\n", (unsigned int)bodypart.size()); sizelen = snprintf(len, 10, "%x\r\n", (unsigned int)bodypart.size());
conn.write(len, sizelen); conn.write(len, sizelen);
conn.write(bodypart); conn.write(bodypart);
conn.write(len+sizelen-2, 2); conn.write(len+sizelen-2, 2);
}else{
conn.write(bodypart);
}
} }

View file

@ -1,3 +1,6 @@
/// \file http_parser.h
/// Holds all headers for the HTTP namespace.
#pragma once #pragma once
#include <map> #include <map>
#include <string> #include <string>
@ -5,9 +8,12 @@
#include <stdio.h> #include <stdio.h>
#include "ddv_socket.h" #include "ddv_socket.h"
class HTTPReader{ /// Holds all HTTP processing related code.
namespace HTTP{
/// Simple class for reading and writing HTTP 1.0 and 1.1.
class Parser{
public: public:
HTTPReader(); Parser();
bool Read(DDV::Socket & sock); bool Read(DDV::Socket & sock);
bool Read(FILE * F); bool Read(FILE * F);
std::string GetHeader(std::string i); std::string GetHeader(std::string i);
@ -37,5 +43,5 @@ class HTTPReader{
std::map<std::string, std::string> headers; std::map<std::string, std::string> headers;
std::map<std::string, std::string> vars; std::map<std::string, std::string> vars;
void Trim(std::string & s); void Trim(std::string & s);
};//HTTPReader };//HTTP::Parser class
};//HTTP namespace

442
util/rtmpchunks.cpp Normal file
View file

@ -0,0 +1,442 @@
/// \file rtmpchunks.cpp
/// Holds all code for the RTMPStream namespace.
#include "rtmpchunks.h"
#include "crypto.h"
char versionstring[] = "WWW.DDVTECH.COM "; ///< String that is repeated in the RTMP handshake
std::string RTMPStream::handshake_in; ///< Input for the handshake.
std::string RTMPStream::handshake_out;///< Output for the handshake.
/// Gets the current system time in milliseconds.
unsigned int RTMPStream::getNowMS(){
timeval t;
gettimeofday(&t, 0);
return t.tv_sec + t.tv_usec/1000;
}//RTMPStream::getNowMS
unsigned int RTMPStream::chunk_rec_max = 128;
unsigned int RTMPStream::chunk_snd_max = 128;
unsigned int RTMPStream::rec_window_size = 0xFA00;
unsigned int RTMPStream::snd_window_size = 1024*500;
unsigned int RTMPStream::rec_window_at = 0;
unsigned int RTMPStream::snd_window_at = 0;
unsigned int RTMPStream::rec_cnt = 0;
unsigned int RTMPStream::snd_cnt = 0;
timeval RTMPStream::lastrec;
unsigned int RTMPStream::firsttime;
/// Holds the last sent chunk for every msg_id.
std::map<unsigned int, RTMPStream::Chunk> RTMPStream::Chunk::lastsend;
/// Holds the last received chunk for every msg_id.
std::map<unsigned int, RTMPStream::Chunk> RTMPStream::Chunk::lastrecv;
/// Packs up the chunk for sending over the network.
/// \warning Do not call if you are not actually sending the resulting data!
/// \returns A std::string ready to be sent.
std::string RTMPStream::Chunk::Pack(){
std::string output = "";
RTMPStream::Chunk prev = lastsend[cs_id];
unsigned int tmpi;
unsigned char chtype = 0x00;
timestamp -= firsttime;
if (prev.cs_id == cs_id){
if (msg_stream_id == prev.msg_stream_id){
chtype = 0x40;//do not send msg_stream_id
if (len == prev.len){
if (msg_type_id == prev.msg_type_id){
chtype = 0x80;//do not send len and msg_type_id
if (timestamp == prev.timestamp){
chtype = 0xC0;//do not send timestamp
}
}
}
}
}
if (cs_id <= 63){
output += (unsigned char)(chtype | cs_id);
}else{
if (cs_id <= 255+64){
output += (unsigned char)(chtype | 0);
output += (unsigned char)(cs_id - 64);
}else{
output += (unsigned char)(chtype | 1);
output += (unsigned char)((cs_id - 64) % 256);
output += (unsigned char)((cs_id - 64) / 256);
}
}
unsigned int ntime = 0;
if (chtype != 0xC0){
//timestamp or timestamp diff
if (chtype == 0x00){
tmpi = timestamp;
}else{
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);
if (chtype != 0x80){
//len
tmpi = len;
output += (unsigned char)(tmpi / (256*256));
output += (unsigned char)(tmpi / 256);
output += (unsigned char)(tmpi % 256);
//msg type id
output += (unsigned char)msg_type_id;
if (chtype != 0x40){
//msg stream id
output += (unsigned char)(msg_stream_id % 256);
output += (unsigned char)(msg_stream_id / 256);
output += (unsigned char)(msg_stream_id / (256*256));
output += (unsigned char)(msg_stream_id / (256*256*256));
}
}
}
//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));
}
len_left = 0;
while (len_left < len){
tmpi = len - len_left;
if (tmpi > RTMPStream::chunk_snd_max){tmpi = RTMPStream::chunk_snd_max;}
output.append(data, len_left, tmpi);
len_left += tmpi;
if (len_left < len){
if (cs_id <= 63){
output += (unsigned char)(0xC0 + cs_id);
}else{
if (cs_id <= 255+64){
output += (unsigned char)(0xC0);
output += (unsigned char)(cs_id - 64);
}else{
output += (unsigned char)(0xC1);
output += (unsigned char)((cs_id - 64) % 256);
output += (unsigned char)((cs_id - 64) / 256);
}
}
}
}
lastsend[cs_id] = *this;
RTMPStream::snd_cnt += output.size();
return output;
}//SendChunk
/// Default contructor, creates an empty chunk with all values initialized to zero.
RTMPStream::Chunk::Chunk(){
cs_id = 0;
timestamp = 0;
len = 0;
real_len = 0;
len_left = 0;
msg_type_id = 0;
msg_stream_id = 0;
data = "";
}//constructor
/// Packs up a chunk with the given arguments as properties.
std::string RTMPStream::SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data){
RTMPStream::Chunk ch;
ch.cs_id = cs_id;
ch.timestamp = RTMPStream::getNowMS();
ch.len = data.size();
ch.real_len = data.size();
ch.len_left = 0;
ch.msg_type_id = msg_type_id;
ch.msg_stream_id = msg_stream_id;
ch.data = data;
return ch.Pack();
}//constructor
/// Packs up a chunk with media contents.
/// \param msg_type_id Type number of the media, as per FLV standard.
/// \param data Contents of the media data.
/// \param len Length of the media data, in bytes.
/// \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.timestamp = ts;
ch.len = len;
ch.real_len = len;
ch.len_left = 0;
ch.msg_type_id = msg_type_id;
ch.msg_stream_id = 1;
ch.data.append((char*)data, (size_t)len);
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;
ch.cs_id = 2;
ch.timestamp = RTMPStream::getNowMS();
ch.len = 4;
ch.real_len = 4;
ch.len_left = 0;
ch.msg_type_id = type;
ch.msg_stream_id = 0;
ch.data.resize(4);
*(int*)((char*)ch.data.c_str()) = htonl(data);
return ch.Pack();
}//SendCTL
/// Packs up a chunk for a control message with 2 arguments.
std::string RTMPStream::SendCTL(unsigned char type, unsigned int data, unsigned char data2){
RTMPStream::Chunk ch;
ch.cs_id = 2;
ch.timestamp = RTMPStream::getNowMS();
ch.len = 5;
ch.real_len = 5;
ch.len_left = 0;
ch.msg_type_id = type;
ch.msg_stream_id = 0;
ch.data.resize(5);
*(int*)((char*)ch.data.c_str()) = htonl(data);
ch.data[4] = data2;
return ch.Pack();
}//SendCTL
/// Packs up a chunk for a user control message with 1 argument.
std::string RTMPStream::SendUSR(unsigned char type, unsigned int data){
RTMPStream::Chunk ch;
ch.cs_id = 2;
ch.timestamp = RTMPStream::getNowMS();
ch.len = 6;
ch.real_len = 6;
ch.len_left = 0;
ch.msg_type_id = 4;
ch.msg_stream_id = 0;
ch.data.resize(6);
*(int*)((char*)ch.data.c_str()+2) = htonl(data);
ch.data[0] = 0;
ch.data[1] = type;
return ch.Pack();
}//SendUSR
/// Packs up a chunk for a user control message with 2 arguments.
std::string RTMPStream::SendUSR(unsigned char type, unsigned int data, unsigned int data2){
RTMPStream::Chunk ch;
ch.cs_id = 2;
ch.timestamp = RTMPStream::getNowMS();
ch.len = 10;
ch.real_len = 10;
ch.len_left = 0;
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);
ch.data[0] = 0;
ch.data[1] = type;
return ch.Pack();
}//SendUSR
/// Parses the argument string into the current chunk.
/// Tries to read a whole chunk, if successful it will remove
/// the corresponding data from the input string.
/// \param indata The input string to parse and update.
/// \warning This function will destroy the current data in this chunk!
/// \returns True if a whole chunk could be read, false otherwise.
bool RTMPStream::Chunk::Parse(std::string & indata){
gettimeofday(&RTMPStream::lastrec, 0);
unsigned int i = 0;
if (indata.size() < 3) return false;//need at least 3 bytes to continue
unsigned char chunktype = indata[i++];
//read the chunkstream ID properly
switch (chunktype & 0x3F){
case 0:
cs_id = indata[i++] + 64;
break;
case 1:
cs_id = indata[i++] + 64;
cs_id += indata[i++] * 256;
break;
default:
cs_id = chunktype & 0x3F;
break;
}
RTMPStream::Chunk prev = lastrecv[cs_id];
//process the rest of the header, for each chunk type
switch (chunktype & 0xC0){
case 0x00:
if (indata.size() < i+11) return false; //can't read whole header
timestamp = indata[i++]*256*256;
timestamp += indata[i++]*256;
timestamp += indata[i++];
len = indata[i++]*256*256;
len += indata[i++]*256;
len += indata[i++];
len_left = 0;
msg_type_id = indata[i++];
msg_stream_id = indata[i++];
msg_stream_id += indata[i++]*256;
msg_stream_id += indata[i++]*256*256;
msg_stream_id += indata[i++]*256*256*256;
break;
case 0x40:
if (indata.size() < i+7) return false; //can't read whole header
timestamp = indata[i++]*256*256;
timestamp += indata[i++]*256;
timestamp += indata[i++];
timestamp += prev.timestamp;
len = indata[i++]*256*256;
len += indata[i++]*256;
len += indata[i++];
len_left = 0;
msg_type_id = indata[i++];
msg_stream_id = prev.msg_stream_id;
break;
case 0x80:
if (indata.size() < i+3) return false; //can't read whole header
timestamp = indata[i++]*256*256;
timestamp += indata[i++]*256;
timestamp += indata[i++];
timestamp += prev.timestamp;
len = prev.len;
len_left = prev.len_left;
msg_type_id = prev.msg_type_id;
msg_stream_id = prev.msg_stream_id;
break;
case 0xC0:
timestamp = prev.timestamp;
len = prev.len;
len_left = prev.len_left;
msg_type_id = prev.msg_type_id;
msg_stream_id = prev.msg_stream_id;
break;
}
//calculate chunk length, real length, and length left till complete
if (len_left > 0){
real_len = len_left;
len_left -= real_len;
}else{
real_len = len;
}
if (real_len > RTMPStream::chunk_rec_max){
len_left += real_len - RTMPStream::chunk_rec_max;
real_len = RTMPStream::chunk_rec_max;
}
//read extended timestamp, if neccesary
if (timestamp == 0x00ffffff){
if (indata.size() < i+4) return false; //can't read whole header
timestamp = indata[i++]*256*256*256;
timestamp += indata[i++]*256*256;
timestamp += indata[i++]*256;
timestamp += indata[i++];
}
//read data if length > 0, and allocate it
if (real_len > 0){
if (prev.len_left > 0){
data = prev.data;
}else{
data = "";
}
if (indata.size() < i+real_len) return false;//can't read all data (yet)
data.append(indata, i, real_len);
indata = indata.substr(i+real_len);
lastrecv[cs_id] = *this;
RTMPStream::rec_cnt += i+real_len;
if (len_left == 0){
return true;
}else{
return false;
}
}else{
data = "";
indata = indata.substr(i+real_len);
lastrecv[cs_id] = *this;
RTMPStream::rec_cnt += i+real_len;
return true;
}
}//Parse
/// Does the handshake. Expects handshake_in to be filled, and fills handshake_out.
/// After calling this function, don't forget to read and ignore 1536 extra bytes,
/// this is the handshake response and not interesting for us because we don't do client
/// verification.
bool RTMPStream::doHandshake(){
char Version;
//Read C0
Version = RTMPStream::handshake_in[0];
uint8_t * Client = (uint8_t *)RTMPStream::handshake_in.c_str() + 1;
RTMPStream::handshake_out.resize(3073);
uint8_t * Server = (uint8_t *)RTMPStream::handshake_out.c_str() + 1;
RTMPStream::rec_cnt += 1537;
//Build S1 Packet
*((uint32_t*)Server) = 0;//time zero
*(((uint32_t*)(Server+4))) = htonl(0x01020304);//version 1 2 3 4
for (int i = 8; i < 3072; ++i){Server[i] = versionstring[i%16];}//"random" data
bool encrypted = (Version == 6);
#if DEBUG >= 4
fprintf(stderr, "Handshake version is %hhi\n", Version);
#endif
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;
RC4_KEY _pKeyIn;
RC4_KEY _pKeyOut;
InitRC4Encryption(secretKey, (uint8_t*) & Client[clientDHOffset], (uint8_t*) & Server[serverDHOffset], &_pKeyIn, &_pKeyOut);
uint8_t data[1536];
RC4(&_pKeyIn, 1536, data, data);
RC4(&_pKeyOut, 1536, data, data);
}
//generate the digest
uint32_t serverDigestOffset = GetDigestOffset(Server, _validationScheme);
uint8_t *pTempBuffer = new uint8_t[1536 - 32];
memcpy(pTempBuffer, Server, serverDigestOffset);
memcpy(pTempBuffer + serverDigestOffset, Server + serverDigestOffset + 32, 1536 - serverDigestOffset - 32);
uint8_t *pTempHash = new uint8_t[512];
HMACsha256(pTempBuffer, 1536 - 32, genuineFMSKey, 36, pTempHash);
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];
HMACsha256(Client + keyChallengeIndex, 32, genuineFMSKey, 68, pTempHash);
uint8_t *pLastHash = new uint8_t[512];
HMACsha256(Server + 1536, 1536 - 32, pTempHash, 32, pLastHash);
memcpy(Server + 1536 * 2 - 32, pLastHash, 32);
delete[] pTempHash;
delete[] pLastHash;
//DONE BUILDING THE RESPONSE ***//
Server[-1] = Version;
RTMPStream::snd_cnt += 3073;
return true;
}

65
util/rtmpchunks.h Normal file
View file

@ -0,0 +1,65 @@
/// \file rtmpchunks.h
/// Holds all headers for the RTMPStream namespace.
#pragma once
#include <map>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <string>
#include <arpa/inet.h>
#define DEBUG 4
/// Contains all functions and classes needed for RTMP connections.
namespace RTMPStream{
/// Gets the current system time in milliseconds.
unsigned int getNowMS();
extern unsigned int chunk_rec_max; ///< Maximum size for a received chunk.
extern unsigned int chunk_snd_max; ///< Maximum size for a sent chunk.
extern unsigned int rec_window_size; ///< Window size for receiving.
extern unsigned int snd_window_size; ///< Window size for sending.
extern unsigned int rec_window_at; ///< Current position of the receiving window.
extern unsigned int snd_window_at; ///< Current position of the sending window.
extern unsigned int rec_cnt; ///< Counter for total data received, in bytes.
extern unsigned int snd_cnt; ///< Counter for total data sent, in bytes.
extern timeval lastrec; ///< Timestamp of last time data was received.
extern unsigned int firsttime; ///< Timestamp of first time a chunk was sent.
/// Holds a single RTMP chunk, either send or receive direction.
class Chunk{
public:
unsigned int cs_id; ///< ContentStream ID
unsigned int timestamp; ///< Timestamp of this chunk.
unsigned int len; ///< Length of the complete chunk.
unsigned int real_len; ///< Length of this particular part of it.
unsigned int len_left; ///< Length not yet received, out of complete chunk.
unsigned char msg_type_id; ///< Message Type ID
unsigned int msg_stream_id; ///< Message Stream ID
std::string data; ///< Payload of chunk.
Chunk();
bool Parse(std::string & data);
std::string Pack();
private:
static std::map<unsigned int, Chunk> lastsend;
static std::map<unsigned int, Chunk> lastrecv;
};//RTMPStream::Chunk
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 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);
std::string SendUSR(unsigned char type, unsigned int data, unsigned int data2);
/// This value should be set to the first 1537 bytes received.
extern std::string handshake_in;
/// This value is the handshake response that is to be sent out.
extern std::string handshake_out;
/// Does the handshake. Expects handshake_in to be filled, and fills handshake_out.
bool doHandshake();
};//RTMPStream namespace

View file

@ -1,12 +1,34 @@
#include <signal.h> /// \file server_setup.cpp
/// Contains generic functions for setting up a DDVTECH Connector.
#ifndef MAINHANDLER
/// Handler that is called for accepted incoming connections.
#define MAINHANDLER NoHandler
#error "No handler was set!"
#endif
#ifndef DEFAULT_PORT
/// Default port for this server.
#define DEFAULT_PORT 0
#error "No default port was set!"
#endif
#ifndef CONFIGSECT
/// Configuration file section for this server.
#define CONFIGSECT None
#error "No configuration file section was set!"
#endif
#include "ddv_socket.h" //DDVTech Socket wrapper #include "ddv_socket.h" //DDVTech Socket wrapper
#include "flv_tag.h" //FLV parsing with DDVTech Socket wrapper #include <signal.h>
#include <sys/types.h> #include <sys/types.h>
#include <pwd.h> #include <pwd.h>
#include <fstream> #include <fstream>
#define defstr(x) #x //converts a define name to string #define defstr(x) #x ///< converts a define name to string
#define defstrh(x) "[" defstr(x) "]" //converts define name to [string] #define defstrh(x) "[" defstr(x) "]" ///< converts define name to [string]
DDV::ServerSocket server_socket(-1); DDV::ServerSocket server_socket(-1); ///< Placeholder for the server socket
/// Basic signal handler. Disconnects the server_socket if it receives /// Basic signal handler. Disconnects the server_socket if it receives
/// a SIGINT, SIGHUP or SIGTERM signal, but does nothing for SIGPIPE. /// a SIGINT, SIGHUP or SIGTERM signal, but does nothing for SIGPIPE.
@ -26,10 +48,10 @@ void signal_handler (int signum){
/// Generic main entry point and loop for DDV Connectors. /// Generic main entry point and loop for DDV Connectors.
/// This sets up the proper termination handler, checks commandline options, /// This sets up the proper termination handler, checks commandline options,
/// parses config files and opens a listening socket on the requested port. /// parses config files and opens a listening socket on the requested port.
/// Any incoming connections will be accepted and start up the function MAINHANDLER, /// Any incoming connections will be accepted and start up the function #MAINHANDLER,
/// which should be #defined before including server_setup.cpp. /// which should be defined before including server_setup.cpp.
/// The default port is set by #define DEFAULT_PORT. /// The default port is set by define #DEFAULT_PORT.
/// The configuration file section is set by #define CONFIGSECT. /// The configuration file section is set by define #CONFIGSECT.
int main(int argc, char ** argv){ int main(int argc, char ** argv){
DDV::Socket S;//placeholder for incoming connections DDV::Socket S;//placeholder for incoming connections