diff --git a/ABST_Parser/main.cpp b/ABST_Parser/main.cpp index 18391e95..bcc649b5 100644 --- a/ABST_Parser/main.cpp +++ b/ABST_Parser/main.cpp @@ -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 #include #include diff --git a/AMF_Tester/main.cpp b/AMF_Tester/main.cpp index 5f6fe992..bf5ae674 100644 --- a/AMF_Tester/main.cpp +++ b/AMF_Tester/main.cpp @@ -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 #include #include @@ -5,6 +9,8 @@ #include #include "../util/amf.h" +/// Debugging tool for AMF data. +/// Expects AMF data through stdin, outputs human-readable information to stderr. int main() { std::string temp; while (std::cin.good()){temp += std::cin.get();}//read all of std::cin to temp diff --git a/Admin/main.cpp b/Admin/main.cpp index 227aa710..7286af79 100644 --- a/Admin/main.cpp +++ b/Admin/main.cpp @@ -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 #include #include diff --git a/Buffer/main.cpp b/Buffer/main.cpp index 10eee8b1..6d806d9c 100644 --- a/Buffer/main.cpp +++ b/Buffer/main.cpp @@ -1,3 +1,6 @@ +/// \file Buffer/main.cpp +/// Contains the main code for the Buffer. + #include #include #include @@ -12,264 +15,267 @@ #include +/// Holds all code unique to the Buffer. namespace Buffer{ -void termination_handler (int signum){ - switch (signum){ - case SIGPIPE: return; break; - default: return; break; + ///A simple signal handler that ignores all signals. + void termination_handler (int signum){ + switch (signum){ + case SIGPIPE: return; break; + default: return; break; + } } -} -///holds FLV::Tag objects and their numbers -struct buffer{ - int number; - FLV::Tag FLV; -};//buffer + ///holds FLV::Tag objects and their numbers + struct buffer{ + int number; + FLV::Tag FLV; + };//buffer -/// Holds connected users. -/// Keeps track of what buffer users are using and the connection status. -class user{ - public: - int MyBuffer; ///< Index of currently used buffer. - int MyBuffer_num; ///< Number of currently used buffer. - int MyBuffer_len; ///< Length in bytes of currently used buffer. - int MyNum; ///< User ID of this user. - int currsend; ///< Current amount of bytes sent. - bool gotproperaudio; ///< Whether the user received proper audio yet. - void * lastpointer; ///< Pointer to data part of current buffer. - static int UserCount; ///< Global user counter. - DDV::Socket S; ///< Connection to user - /// Creates a new user from a newly connected socket. - /// Also prints "User connected" text to stdout. - user(DDV::Socket fd){ - S = fd; - MyNum = UserCount++; - gotproperaudio = false; - std::cout << "User " << MyNum << " connected" << std::endl; - }//constructor - /// Disconnects the current user. Doesn't do anything if already disconnected. - /// Prints "Disconnected user" to stdout if disconnect took place. - void Disconnect(std::string reason) { - if (S.connected()) { - S.close(); - std::cout << "Disconnected user " << MyNum << ": " << reason << std::endl; - } - }//Disconnect - /// Tries to send the current buffer, returns true if success, false otherwise. - /// Has a side effect of dropping the connection if send will never complete. - bool doSend(){ - int r = S.iwrite((char*)lastpointer+currsend, MyBuffer_len-currsend); - if (r <= 0){ - if ((r < 0) && (errno == EWOULDBLOCK)){return false;} - Disconnect("Connection closed"); - return false; - } - currsend += r; - return (currsend == MyBuffer_len); - }//doSend - /// Try to send data to this user. Disconnects if any problems occur. - /// \param ringbuf Array of buffers (FLV:Tag with ID attached) - /// \param buffers Count of elements in ringbuf - void Send(buffer ** ringbuf, int buffers){ - //TODO: Bij MP3: gotproperaudio - if false, stuur alleen als eerste byte is 0xFF en set op true - if (!S.connected()){return;}//cancel if not connected - - //still waiting for next buffer? check it - if (MyBuffer_num < 0){ - MyBuffer_num = ringbuf[MyBuffer]->number; + /// Holds connected users. + /// Keeps track of what buffer users are using and the connection status. + class user{ + public: + int MyBuffer; ///< Index of currently used buffer. + int MyBuffer_num; ///< Number of currently used buffer. + int MyBuffer_len; ///< Length in bytes of currently used buffer. + int MyNum; ///< User ID of this user. + int currsend; ///< Current amount of bytes sent. + bool gotproperaudio; ///< Whether the user received proper audio yet. + void * lastpointer; ///< Pointer to data part of current buffer. + static int UserCount; ///< Global user counter. + DDV::Socket S; ///< Connection to user + /// Creates a new user from a newly connected socket. + /// Also prints "User connected" text to stdout. + user(DDV::Socket fd){ + S = fd; + MyNum = UserCount++; + gotproperaudio = false; + std::cout << "User " << MyNum << " connected" << std::endl; + }//constructor + /// Disconnects the current user. Doesn't do anything if already disconnected. + /// Prints "Disconnected user" to stdout if disconnect took place. + void Disconnect(std::string reason) { + if (S.connected()) { + S.close(); + std::cout << "Disconnected user " << MyNum << ": " << reason << std::endl; + } + }//Disconnect + /// Tries to send the current buffer, returns true if success, false otherwise. + /// Has a side effect of dropping the connection if send will never complete. + bool doSend(){ + int r = S.iwrite((char*)lastpointer+currsend, MyBuffer_len-currsend); + if (r <= 0){ + if ((r < 0) && (errno == EWOULDBLOCK)){return false;} + Disconnect("Connection closed"); + return false; + } + currsend += r; + return (currsend == MyBuffer_len); + }//doSend + /// Try to send data to this user. Disconnects if any problems occur. + /// \param ringbuf Array of buffers (FLV:Tag with ID attached) + /// \param buffers Count of elements in ringbuf + void Send(buffer ** ringbuf, int buffers){ + /// \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 + + //still waiting for next buffer? check it if (MyBuffer_num < 0){ - return; //still waiting? don't crash - wait longer. - }else{ - MyBuffer_len = ringbuf[MyBuffer]->FLV.len; - lastpointer = ringbuf[MyBuffer]->FLV.data; + MyBuffer_num = ringbuf[MyBuffer]->number; + if (MyBuffer_num < 0){ + return; //still waiting? don't crash - wait longer. + }else{ + MyBuffer_len = ringbuf[MyBuffer]->FLV.len; + lastpointer = ringbuf[MyBuffer]->FLV.data; + } } - } - - //do check for buffer resizes - if (lastpointer != ringbuf[MyBuffer]->FLV.data){ - Disconnect("Buffer resize at wrong time... had to disconnect"); - return; - } - - //try to complete a send - if (doSend()){ - //switch to next buffer - if ((ringbuf[MyBuffer]->number != MyBuffer_num)){ - //if corrupt data, warn and find keyframe - std::cout << "Warning: User " << MyNum << " was send corrupt video data and send to the next keyframe!" << std::endl; - int nocrashcount = 0; - do{ + + //do check for buffer resizes + if (lastpointer != ringbuf[MyBuffer]->FLV.data){ + Disconnect("Buffer resize at wrong time... had to disconnect"); + return; + } + + //try to complete a send + if (doSend()){ + //switch to next buffer + if ((ringbuf[MyBuffer]->number != MyBuffer_num)){ + //if corrupt data, warn and find keyframe + std::cout << "Warning: User " << MyNum << " was send corrupt video data and send to the next keyframe!" << std::endl; + int nocrashcount = 0; + do{ + MyBuffer++; + nocrashcount++; + MyBuffer %= buffers; + }while(!ringbuf[MyBuffer]->FLV.isKeyframe && (nocrashcount < buffers)); + //if keyframe not available, try again later + if (nocrashcount >= buffers){ + std::cout << "Warning: No keyframe found in buffers! Skipping search for now..." << std::endl; + return; + } + }else{ MyBuffer++; - nocrashcount++; MyBuffer %= buffers; - }while(!ringbuf[MyBuffer]->FLV.isKeyframe && (nocrashcount < buffers)); - //if keyframe not available, try again later - if (nocrashcount >= buffers){ - std::cout << "Warning: No keyframe found in buffers! Skipping search for now..." << std::endl; - return; } - }else{ - MyBuffer++; - MyBuffer %= buffers; - } - MyBuffer_num = -1; - lastpointer = 0; - currsend = 0; - }//completed a send - }//send -}; -int user::UserCount = 0; + MyBuffer_num = -1; + lastpointer = 0; + currsend = 0; + }//completed a send + }//send + }; + int user::UserCount = 0; -/// Starts a loop, waiting for connections to send video data to. -int Start(int argc, char ** argv) { - //first make sure no segpipe signals will kill us - struct sigaction new_action; - new_action.sa_handler = termination_handler; - sigemptyset (&new_action.sa_mask); - new_action.sa_flags = 0; - sigaction (SIGPIPE, &new_action, NULL); + /// Starts a loop, waiting for connections to send video data to. + int Start(int argc, char ** argv) { + //first make sure no segpipe signals will kill us + struct sigaction new_action; + new_action.sa_handler = termination_handler; + sigemptyset (&new_action.sa_mask); + new_action.sa_flags = 0; + sigaction (SIGPIPE, &new_action, NULL); - //then check and parse the commandline - if (argc < 3) { - std::cout << "usage: " << argv[0] << " buffers_count streamname" << std::endl; - return 1; - } - std::string shared_socket = "/tmp/shared_socket_"; - shared_socket += argv[2]; + //then check and parse the commandline + if (argc < 3) { + std::cout << "usage: " << argv[0] << " buffers_count streamname" << std::endl; + return 1; + } + std::string shared_socket = "/tmp/shared_socket_"; + shared_socket += argv[2]; - DDV::ServerSocket SS(shared_socket, true); - FLV::Tag metadata; - FLV::Tag video_init; - FLV::Tag audio_init; - int buffers = atoi(argv[1]); - buffer ** ringbuf = (buffer**) calloc (buffers,sizeof(buffer*)); - std::vector users; - std::vector::iterator usersIt; - for (int i = 0; i < buffers; ++i) ringbuf[i] = new buffer; - int current_buffer = 0; - int lastproper = 0;//last properly finished buffer number - unsigned int loopcount = 0; - DDV::Socket incoming; - - unsigned char packtype; - bool gotVideoInfo = false; - bool gotAudioInfo = false; - - int infile = fileno(stdin);//get file number for stdin - - //add stdin to an epoll - int poller = epoll_create(1); - struct epoll_event ev; - ev.events = EPOLLIN; - ev.data.fd = infile; - epoll_ctl(poller, EPOLL_CTL_ADD, infile, &ev); - struct epoll_event events[1]; + DDV::ServerSocket SS(shared_socket, true); + FLV::Tag metadata; + FLV::Tag video_init; + FLV::Tag audio_init; + int buffers = atoi(argv[1]); + buffer ** ringbuf = (buffer**) calloc (buffers,sizeof(buffer*)); + std::vector users; + std::vector::iterator usersIt; + for (int i = 0; i < buffers; ++i) ringbuf[i] = new buffer; + int current_buffer = 0; + int lastproper = 0;//last properly finished buffer number + unsigned int loopcount = 0; + DDV::Socket incoming; + + unsigned char packtype; + bool gotVideoInfo = false; + bool gotAudioInfo = false; + + int infile = fileno(stdin);//get file number for stdin + + //add stdin to an epoll + int poller = epoll_create(1); + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.fd = infile; + epoll_ctl(poller, EPOLL_CTL_ADD, infile, &ev); + struct epoll_event events[1]; - while(!feof(stdin) && !FLV::Parse_Error){ - //invalidate the current buffer - ringbuf[current_buffer]->number = -1; - if ((epoll_wait(poller, events, 1, 10) > 0) && ringbuf[current_buffer]->FLV.FileLoader(stdin)){ - loopcount++; - packtype = ringbuf[current_buffer]->FLV.data[0]; - //store metadata, if available - if (packtype == 0x12){ - metadata = ringbuf[current_buffer]->FLV; - std::cout << "Received metadata!" << std::endl; - if (gotVideoInfo && gotAudioInfo){ - FLV::Parse_Error = true; - std::cout << "... after proper video and audio? Cancelling broadcast!" << std::endl; + while(!feof(stdin) && !FLV::Parse_Error){ + //invalidate the current buffer + ringbuf[current_buffer]->number = -1; + if ((epoll_wait(poller, events, 1, 10) > 0) && ringbuf[current_buffer]->FLV.FileLoader(stdin)){ + loopcount++; + packtype = ringbuf[current_buffer]->FLV.data[0]; + //store metadata, if available + if (packtype == 0x12){ + metadata = ringbuf[current_buffer]->FLV; + std::cout << "Received metadata!" << std::endl; + if (gotVideoInfo && gotAudioInfo){ + FLV::Parse_Error = true; + std::cout << "... after proper video and audio? Cancelling broadcast!" << std::endl; + } + gotVideoInfo = false; + gotAudioInfo = false; } - gotVideoInfo = false; - gotAudioInfo = false; - } - //store video init data, if available - if (!gotVideoInfo && ringbuf[current_buffer]->FLV.isKeyframe){ - if ((ringbuf[current_buffer]->FLV.data[11] & 0x0f) == 7){//avc packet - if (ringbuf[current_buffer]->FLV.data[12] == 0){ + //store video init data, if available + if (!gotVideoInfo && ringbuf[current_buffer]->FLV.isKeyframe){ + if ((ringbuf[current_buffer]->FLV.data[11] & 0x0f) == 7){//avc packet + if (ringbuf[current_buffer]->FLV.data[12] == 0){ + ringbuf[current_buffer]->FLV.tagTime(0);//timestamp to zero + video_init = ringbuf[current_buffer]->FLV; + gotVideoInfo = true; + std::cout << "Received video configuration!" << std::endl; + } + }else{gotVideoInfo = true;}//non-avc = no config... + } + //store audio init data, if available + if (!gotAudioInfo && (packtype == 0x08)){ + if (((ringbuf[current_buffer]->FLV.data[11] & 0xf0) >> 4) == 10){//aac packet ringbuf[current_buffer]->FLV.tagTime(0);//timestamp to zero - video_init = ringbuf[current_buffer]->FLV; - gotVideoInfo = true; - std::cout << "Received video configuration!" << std::endl; + audio_init = ringbuf[current_buffer]->FLV; + gotAudioInfo = true; + std::cout << "Received audio configuration!" << std::endl; + }else{gotAudioInfo = true;}//no aac = no config... + } + //on keyframe set possible start point + if (packtype == 0x09){ + if (((ringbuf[current_buffer]->FLV.data[11] & 0xf0) >> 4) == 1){ + lastproper = current_buffer; } - }else{gotVideoInfo = true;}//non-avc = no config... - } - //store audio init data, if available - if (!gotAudioInfo && (packtype == 0x08)){ - if (((ringbuf[current_buffer]->FLV.data[11] & 0xf0) >> 4) == 10){//aac packet - ringbuf[current_buffer]->FLV.tagTime(0);//timestamp to zero - audio_init = ringbuf[current_buffer]->FLV; - gotAudioInfo = true; - std::cout << "Received audio configuration!" << std::endl; - }else{gotAudioInfo = true;}//no aac = no config... - } - //on keyframe set possible start point - if (packtype == 0x09){ - if (((ringbuf[current_buffer]->FLV.data[11] & 0xf0) >> 4) == 1){ - lastproper = current_buffer; } + //keep track of buffers + ringbuf[current_buffer]->number = loopcount; + current_buffer++; + current_buffer %= buffers; } - //keep track of buffers - ringbuf[current_buffer]->number = loopcount; - current_buffer++; - current_buffer %= buffers; - } - //check for new connections, accept them if there are any - incoming = SS.accept(true); - if (incoming.connected()){ - users.push_back(incoming); - //send the FLV header - users.back().currsend = 0; - users.back().MyBuffer = lastproper; - users.back().MyBuffer_num = -1; - //TODO: Do this more nicely? - if (!incoming.write(FLV::Header, 13)){ - users.back().Disconnect("failed to receive the header!"); - }else{ - if (!incoming.write(metadata.data, metadata.len)){ - users.back().Disconnect("failed to receive metadata!"); - } - if (!incoming.write(video_init.data, video_init.len)){ - users.back().Disconnect("failed to receive video init!"); - } - if (!incoming.write(audio_init.data, audio_init.len)){ - users.back().Disconnect("failed to receive audio init!"); - } - } - } - - //send all connections what they need, if and when they need it - if (users.size() > 0){ - for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ - if (!(*usersIt).S.connected()){ - users.erase(usersIt); break; + //check for new connections, accept them if there are any + incoming = SS.accept(true); + if (incoming.connected()){ + users.push_back(incoming); + //send the FLV header + users.back().currsend = 0; + users.back().MyBuffer = lastproper; + users.back().MyBuffer_num = -1; + /// \todo Do this more nicely? + if (!incoming.write(FLV::Header, 13)){ + users.back().Disconnect("failed to receive the header!"); }else{ - (*usersIt).Send(ringbuf, buffers); + if (!incoming.write(metadata.data, metadata.len)){ + users.back().Disconnect("failed to receive metadata!"); + } + if (!incoming.write(video_init.data, video_init.len)){ + users.back().Disconnect("failed to receive video init!"); + } + if (!incoming.write(audio_init.data, audio_init.len)){ + users.back().Disconnect("failed to receive audio init!"); + } } } - } - }//main loop - // disconnect listener - if (FLV::Parse_Error){ - std::cout << "FLV parse error" << std::endl; - }else{ - std::cout << "Reached EOF of input" << std::endl; - } - SS.close(); - while (users.size() > 0){ - for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ - (*usersIt).Disconnect("Shutting down..."); - if (!(*usersIt).S.connected()){users.erase(usersIt);break;} + //send all connections what they need, if and when they need it + if (users.size() > 0){ + for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ + if (!(*usersIt).S.connected()){ + users.erase(usersIt); break; + }else{ + (*usersIt).Send(ringbuf, buffers); + } + } + } + }//main loop + + // disconnect listener + if (FLV::Parse_Error){ + std::cout << "FLV parse error" << std::endl; + }else{ + std::cout << "Reached EOF of input" << std::endl; } + SS.close(); + while (users.size() > 0){ + for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ + (*usersIt).Disconnect("Shutting down..."); + if (!(*usersIt).S.connected()){users.erase(usersIt);break;} + } + } + return 0; } - return 0; -} };//Buffer namespace +/// Entry point for Buffer, simply calls Buffer::Start(). int main(int argc, char ** argv){ Buffer::Start(argc, argv); }//main diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index ee517cf2..a9495465 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -1,9 +1,12 @@ +/// \file Connector_HTTP/main.cpp +/// Contains the main code for the HTTP Connector + /// Sets the global debugging level. -/// debugging level 0 = nothing -/// debugging level 1 = critical errors -/// debugging level 2 = errors -/// debugging level 3 = status information -/// debugging level 4 = extremely verbose status information +// debugging level 0 = nothing +// debugging level 1 = critical errors +// debugging level 2 = errors +// debugging level 3 = status information +// debugging level 4 = extremely verbose status information #define DEBUG 4 #include diff --git a/Connector_RAW/main.cpp b/Connector_RAW/main.cpp index 51898ec0..c90d6498 100644 --- a/Connector_RAW/main.cpp +++ b/Connector_RAW/main.cpp @@ -1,6 +1,12 @@ +/// \file Connector_RAW/main.cpp +/// Contains the main code for the RAW connector. + #include #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) { if (argc < 2){ 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_"; input += argv[1]; + //connect to the proper stream DDV::Socket S(input); if (!S.connected()){ std::cout << "Could not open stream " << argv[1] << std::endl; return 1; } + //transport ~50kb at a time + //this is a nice tradeoff between CPU usage and speed char buffer[50000]; while(std::cout.good() && S.read(buffer,50000)){std::cout.write(buffer,50000);} S.close(); diff --git a/Connector_RTMP/Makefile b/Connector_RTMP/Makefile index e8cc8757..dfb274a8 100644 --- a/Connector_RTMP/Makefile +++ b/Connector_RTMP/Makefile @@ -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) OUT = DDV_Conn_RTMP INCLUDES = @@ -13,7 +13,7 @@ LIBS = -lssl -lcrypto default: $(OUT) .cpp.o: $(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@ -$(OUT): $(OBJ) chunkstream.cpp parsechunks.cpp handshake.cpp crypto.cpp +$(OUT): $(OBJ) $(CC) -o $(OUT) $(OBJ) $(STATIC) $(LIBS) clean: rm -rf $(OBJ) $(OUT) Makefile.bak *~ diff --git a/Connector_RTMP/RTMP_Conn b/Connector_RTMP/RTMP_Conn deleted file mode 100755 index 1f38e92b..00000000 --- a/Connector_RTMP/RTMP_Conn +++ /dev/null @@ -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 diff --git a/Connector_RTMP/chunkstream.cpp b/Connector_RTMP/chunkstream.cpp deleted file mode 100644 index d27cbd45..00000000 --- a/Connector_RTMP/chunkstream.cpp +++ /dev/null @@ -1,501 +0,0 @@ -#include -#include -#include -#include -#include - -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 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 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 ch_lst; - std::map::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 diff --git a/Connector_RTMP/crypto.h b/Connector_RTMP/crypto.h deleted file mode 100644 index 8d36188b..00000000 --- a/Connector_RTMP/crypto.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef _CRYPTO_H -#define _CRYPTO_H -#define DLLEXP - -#include -#include -#include -#include -#include -#include -#include -#include - -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 */ - diff --git a/Connector_RTMP/handshake.cpp b/Connector_RTMP/handshake.cpp deleted file mode 100644 index 24095c3b..00000000 --- a/Connector_RTMP/handshake.cpp +++ /dev/null @@ -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 diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index 3cc33d29..6fe57bfe 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -1,9 +1,11 @@ +/// \file Connector_RTMP/main.cpp +/// Contains the main code for the RTMP Connector + //debugging level 0 = nothing //debugging level 1 = critical errors //debugging level 2 = errors //debugging level 3 = status information //debugging level 4 = extremely verbose status information -//debugging level 5 = save all streams to FLV files #define DEBUG 4 #include @@ -18,155 +20,402 @@ #include #include "../util/ddv_socket.h" #include "../util/flv_tag.h" - -#include "parsechunks.cpp" //chunkstream parsing -#include "handshake.cpp" //handshaking +#include "../util/amf.h" +#include "../util/rtmpchunks.h" /// Holds all functions and data unique to the RTMP Connector namespace Connector_RTMP{ - + //for connection to server bool ready4data = false; ///< Set to true when streaming starts. 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. - timeval lastrec; ///< Timestamp of last received data. - - DDV::Socket Socket; ///< Socket connected to user - - /// Main Connector_RTMP function - int Connector_RTMP(DDV::Socket conn){ - Socket = conn; - unsigned int ts; - unsigned int fts = 0; - unsigned int ftst; - DDV::Socket SS; - FLV::Tag tag = 0; - - //first timestamp set - firsttime = getNowMS(); - - if (doHandshake()){ - #if DEBUG >= 4 - fprintf(stderr, "Handshake succcess!\n"); - #endif - }else{ - #if DEBUG >= 1 - fprintf(stderr, "Handshake fail!\n"); - #endif - return 0; - } - - int retval; - int poller = epoll_create(1); - int sspoller = epoll_create(1); - struct epoll_event ev; - ev.events = EPOLLIN; - ev.data.fd = CONN_fd; - epoll_ctl(poller, EPOLL_CTL_ADD, CONN_fd, &ev); - 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){ - //only parse input if available or not yet init'ed - //rightnow = getNowMS(); - retval = epoll_wait(poller, events, 1, 1); - if ((retval > 0) || !ready4data){// || (snd_cnt - snd_window_at >= snd_window_size) - switch (Socket.ready()){ - case -1: break; //disconnected - case 0: break; //not ready yet - default: parseChunk(); break; //new data is waiting - } - } - if (ready4data){ - if (!inited){ - //we are ready, connect the socket! - SS = DDV::Socket(streamname); - if (!SS.connected()){ - #if DEBUG >= 1 - fprintf(stderr, "Could not connect to server!\n"); - #endif - Socket.close();//disconnect user - break; - } - ev.events = EPOLLIN; - ev.data.fd = SS.getSocket(); - epoll_ctl(sspoller, EPOLL_CTL_ADD, SS.getSocket(), &ev); - #if DEBUG >= 3 - fprintf(stderr, "Everything connected, starting to send video data...\n"); - #endif - inited = true; - } + 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; - retval = epoll_wait(sspoller, events, 1, 1); - switch (SS.ready()){ - case -1: - #if DEBUG >= 1 - fprintf(stderr, "Source socket is disconnected.\n"); - #endif - Socket.close();//disconnect user - break; - case 0: break;//not ready yet - default: - if (tag.SockLoader(SS)){//able to read a full packet? - ts = tag.tagTime(); - if (ts != 0){ - if (fts == 0){fts = ts;ftst = getNowMS();} - ts -= fts; - tag.tagTime(ts); - ts += ftst; - }else{ - ftst = getNowMS(); - tag.tagTime(ftst); - } - 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 - fprintf(stderr, "Sent a tag to %i\n", CONN_fd); - #endif - } - 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(); - Socket.close(); - #if DEBUG >= 5 - fclose(tmpfile); + +/// Main Connector_RTMP function +int Connector_RTMP::Connector_RTMP(DDV::Socket conn){ + Socket = conn; + unsigned int ts; + unsigned int fts = 0; + unsigned int ftst; + DDV::Socket SS; + FLV::Tag tag; + + //first timestamp set + RTMPStream::firsttime = RTMPStream::getNowMS(); + + 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 + fprintf(stderr, "Handshake succcess!\n"); #endif + }else{ #if DEBUG >= 1 - if (FLV::Parse_Error){fprintf(stderr, "FLV Parse Error\n");} - fprintf(stderr, "User %i disconnected.\n", conn.getSocket()); - if (inited){ - fprintf(stderr, "Status was: inited\n"); - }else{ - if (ready4data){ - fprintf(stderr, "Status was: ready4data\n"); - }else{ - fprintf(stderr, "Status was: connected\n"); - } - } + fprintf(stderr, "Handshake fail!\n"); #endif return 0; - }//Connector_RTMP + } + + int retval; + int poller = epoll_create(1); + int sspoller = epoll_create(1); + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.fd = Socket.getSocket(); + epoll_ctl(poller, EPOLL_CTL_ADD, Socket.getSocket(), &ev); + struct epoll_event events[1]; + + while (Socket.connected() && !FLV::Parse_Error){ + //only parse input if available or not yet init'ed + //rightnow = getNowMS(); + retval = epoll_wait(poller, events, 1, 1); + if ((retval > 0) || !ready4data){// || (snd_cnt - snd_window_at >= snd_window_size) + switch (Socket.ready()){ + case -1: break; //disconnected + case 0: break; //not ready yet + default: parseChunk(); break; //new data is waiting + } + } + if (ready4data){ + if (!inited){ + //we are ready, connect the socket! + SS = DDV::Socket(streamname); + if (!SS.connected()){ + #if DEBUG >= 1 + fprintf(stderr, "Could not connect to server!\n"); + #endif + Socket.close();//disconnect user + break; + } + ev.events = EPOLLIN; + ev.data.fd = SS.getSocket(); + epoll_ctl(sspoller, EPOLL_CTL_ADD, SS.getSocket(), &ev); + #if DEBUG >= 3 + fprintf(stderr, "Everything connected, starting to send video data...\n"); + #endif + inited = true; + } + retval = epoll_wait(sspoller, events, 1, 1); + switch (SS.ready()){ + case -1: + #if DEBUG >= 1 + fprintf(stderr, "Source socket is disconnected.\n"); + #endif + Socket.close();//disconnect user + break; + case 0: break;//not ready yet + default: + if (tag.SockLoader(SS)){//able to read a full packet? + ts = tag.tagTime(); + if (ts != 0){ + if (fts == 0){fts = ts;ftst = RTMPStream::getNowMS();} + ts -= fts; + tag.tagTime(ts); + ts += ftst; + }else{ + ftst = RTMPStream::getNowMS(); + tag.tagTime(ftst); + } + Socket.write(RTMPStream::SendMedia((unsigned char)tag.data[0], (unsigned char *)tag.data+11, tag.len-15, ts)); + #if DEBUG >= 4 + fprintf(stderr, "Sent a tag to %i\n", Socket.getSocket()); + #endif + } + break; + } + } + } + SS.close(); + Socket.close(); + #if DEBUG >= 1 + if (FLV::Parse_Error){fprintf(stderr, "FLV Parse Error: %s\n", FLV::Error_Str.c_str());} + fprintf(stderr, "User %i disconnected.\n", conn.getSocket()); + if (inited){ + fprintf(stderr, "Status was: inited\n"); + }else{ + if (ready4data){ + fprintf(stderr, "Status was: ready4data\n"); + }else{ + fprintf(stderr, "Status was: connected\n"); + } + } + #endif + return 0; +}//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 #define DEFAULT_PORT 1935 diff --git a/Connector_RTMP/parsechunks.cpp b/Connector_RTMP/parsechunks.cpp deleted file mode 100644 index 6b5fae0f..00000000 --- a/Connector_RTMP/parsechunks.cpp +++ /dev/null @@ -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 diff --git a/Doxyfile b/Doxyfile index dd8ba205..b6edff54 100644 --- a/Doxyfile +++ b/Doxyfile @@ -43,7 +43,7 @@ SYMBOL_CACHE_SIZE = 0 # Build related configuration options #--------------------------------------------------------------------------- EXTRACT_ALL = YES -EXTRACT_PRIVATE = NO +EXTRACT_PRIVATE = YES EXTRACT_STATIC = YES EXTRACT_LOCAL_CLASSES = YES EXTRACT_LOCAL_METHODS = NO diff --git a/HTTP_Box_Parser/main.cpp b/HTTP_Box_Parser/main.cpp index 6cc55223..432bbd35 100644 --- a/HTTP_Box_Parser/main.cpp +++ b/HTTP_Box_Parser/main.cpp @@ -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 #include #include @@ -6,6 +14,12 @@ #include "../util/MP4/box_includes.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(){ HTTPReader H; FLV::Tag F; @@ -28,4 +42,4 @@ int main(){ std::cout << "Skipped too small fragment of size " << H.body.size() << std::endl; } } -} +}//main diff --git a/Makefile b/Makefile index 71a53364..0389a1ad 100644 --- a/Makefile +++ b/Makefile @@ -18,5 +18,5 @@ client-install: client-clean client cd Connector_RAW; $(MAKE) install cd Buffer; $(MAKE) install docs: - doxygen ./Doxyfile + doxygen ./Doxyfile > /dev/null diff --git a/util/amf.cpp b/util/amf.cpp index fdff660f..97d97ef5 100644 --- a/util/amf.cpp +++ b/util/amf.cpp @@ -1,3 +1,6 @@ +/// \file amf.cpp +/// Holds all code for the AMF namespace. + #include "amf.h" /// Returns the std::string Indice for the current object, if available. diff --git a/util/amf.h b/util/amf.h index 81ab9881..8ae95cd4 100644 --- a/util/amf.h +++ b/util/amf.h @@ -1,3 +1,6 @@ +/// \file amf.h +/// Holds all headers for the AMF namespace. + #pragma once #include #include diff --git a/Connector_RTMP/crypto.cpp b/util/crypto.cpp similarity index 99% rename from Connector_RTMP/crypto.cpp rename to util/crypto.cpp index bc1e616c..c523c680 100644 --- a/Connector_RTMP/crypto.cpp +++ b/util/crypto.cpp @@ -1,3 +1,6 @@ +/// \file crypto.cpp +/// Holds all code needed for RTMP cryptography. + #define STR(x) (((std::string)(x)).c_str()) #include "crypto.h" diff --git a/util/crypto.h b/util/crypto.h new file mode 100644 index 00000000..f4daa4bb --- /dev/null +++ b/util/crypto.h @@ -0,0 +1,56 @@ +/// \file crypto.h +/// Holds all headers needed for RTMP cryptography functions. + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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); diff --git a/util/ddv_socket.cpp b/util/ddv_socket.cpp index 76c2ee8a..e82ea8ae 100644 --- a/util/ddv_socket.cpp +++ b/util/ddv_socket.cpp @@ -1,3 +1,6 @@ +/// \file ddv_socket.cpp +/// Holds all code for the DDV namespace. + #include "ddv_socket.h" /// 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; }//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. DDV::ServerSocket::ServerSocket(){ sock = -1; diff --git a/util/ddv_socket.h b/util/ddv_socket.h index 298b5954..5d58e0bd 100644 --- a/util/ddv_socket.h +++ b/util/ddv_socket.h @@ -1,3 +1,6 @@ +/// \file ddv_socket.h +/// Holds all headers for the DDV namespace. + #pragma once #include #include @@ -33,6 +36,7 @@ namespace DDV{ bool write(const std::string data); ///< Write call that is compatible with std::string. int iwrite(void * buffer, int len); ///< Incremental write 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. int getSocket(); ///< Returns internal socket number. }; diff --git a/util/flv_tag.cpp b/util/flv_tag.cpp index 70321b17..08fed05b 100644 --- a/util/flv_tag.cpp +++ b/util/flv_tag.cpp @@ -1,3 +1,6 @@ +/// \file flv_tag.cpp +/// Holds all code for the FLV namespace. + #include "flv_tag.h" #include //for Tag::FileLoader #include //for Tag::FileLoader @@ -8,6 +11,7 @@ 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. +std::string FLV::Error_Str = ""; /// Checks a FLV Header for validness. Returns true if the header is valid, false /// 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)){ sofar = 0; 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{ //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[1] << 16); 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; } } @@ -259,7 +263,7 @@ bool FLV::Tag::SockReadUntil(char * buffer, unsigned int count, unsigned int & s if (r < 0){ if (errno != EWOULDBLOCK){ FLV::Parse_Error = true; - fprintf(stderr, "ReadUntil fail: %s. All Hell Broke Loose!\n", strerror(errno)); + Error_Str = "Error reading from socket."; } 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){ 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; }//Tag::SockReadUntil @@ -289,7 +293,7 @@ bool FLV::Tag::SockLoader(DDV::Socket sock){ if (FLV::check_header(data)){ sofar = 0; 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{ //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[1] << 16); 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; } } @@ -335,7 +339,7 @@ bool FLV::Tag::FileReadUntil(char * buffer, unsigned int count, unsigned int & s if (sofar >= count){return true;} int r = 0; 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; if (sofar >= count){return true;} return false; @@ -363,7 +367,7 @@ bool FLV::Tag::FileLoader(FILE * f){ if (FLV::check_header(data)){ sofar = 0; memcpy(FLV::Header, data, 13); - }else{FLV::Parse_Error = true;} + }else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;} } }else{ //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[1] << 16); 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; } } diff --git a/util/flv_tag.h b/util/flv_tag.h index d91cabb5..e824228c 100644 --- a/util/flv_tag.h +++ b/util/flv_tag.h @@ -1,3 +1,6 @@ +/// \file flv_tag.h +/// Holds all headers for the FLV namespace. + #pragma once #include "ddv_socket.h" #include @@ -7,7 +10,8 @@ namespace FLV { //variables 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 std::string Error_Str; ///< This variable is set if a problem is encountered while parsing the FLV. + //functions bool check_header(char * header); ///< Checks a FLV Header for validness. bool is_header(char * header); ///< Checks the first 3 bytes for the string "FLV". diff --git a/util/http_parser.cpp b/util/http_parser.cpp index dd01e9b2..60d370e7 100644 --- a/util/http_parser.cpp +++ b/util/http_parser.cpp @@ -1,8 +1,14 @@ -#include "http_parser.h" -#include "ddv_socket.h" +/// \file http_parser.cpp +/// Holds all code for the HTTP namespace. -HTTPReader::HTTPReader(){Clean();} -void HTTPReader::Clean(){ +#include "http_parser.h" + +/// 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; seenReq = false; method = "GET"; @@ -11,11 +17,14 @@ void HTTPReader::Clean(){ body = ""; length = 0; HTTPbuffer = ""; - headers.erase(headers.begin(), headers.end()); - vars.erase(vars.begin(), vars.end()); + headers.clear(); + 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; seenReq = false; method = "GET"; @@ -23,12 +32,19 @@ bool HTTPReader::CleanForNext(){ protocol = "HTTP/1.1"; body = ""; length = 0; - headers.erase(headers.begin(), headers.end()); - vars.erase(vars.begin(), vars.end()); + headers.clear(); + vars.clear(); 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::iterator it; std::string tmp = method+" "+url+" "+protocol+"\n"; for (it=headers.begin(); it != headers.end(); it++){ @@ -39,7 +55,16 @@ std::string HTTPReader::BuildRequest(){ 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::iterator it; std::string tmp = protocol+" "+code+" "+message+"\n"; for (it=headers.begin(); it != headers.end(); it++){ @@ -50,47 +75,61 @@ std::string HTTPReader::BuildResponse(std::string code, std::string message){ 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 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);} } -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; 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.append(buffer, 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];} -std::string HTTPReader::GetVar(std::string i){return vars[i];} - -void HTTPReader::SetHeader(std::string i, std::string v){ +/// Sets header i to string value v. +void HTTP::Parser::SetHeader(std::string i, std::string v){ Trim(i); Trim(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); char val[128]; sprintf(val, "%i", v); 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(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 int r = 0; int b = 0; @@ -111,7 +150,9 @@ bool HTTPReader::Read(DDV::Socket & sock){ return false; }//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 int b = 1; char buffer[500]; @@ -122,7 +163,11 @@ bool HTTPReader::Read(FILE * F){ return false; }//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; std::string tmpA, tmpB, tmpC; while (HTTPbuffer != ""){ @@ -140,7 +185,7 @@ bool HTTPReader::parse(){ if (f != std::string::npos){url = tmpA.substr(0, f); tmpA.erase(0, f+1);} f = tmpA.find(' '); 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{ if (tmpA.size() == 0){ seenHeaders = true; @@ -156,7 +201,7 @@ bool HTTPReader::parse(){ } if (seenHeaders){ if (length > 0){ - //TODO: POST variable parsing? + /// \todo Include POST variable parsing? if (HTTPbuffer.length() >= length){ body = HTTPbuffer.substr(0, length); HTTPbuffer.erase(0, length); @@ -172,23 +217,40 @@ bool HTTPReader::parse(){ return false; //we should never get here... }//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); 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; tmp.append(buffer, len); SendBodyPart(conn, tmp); } -void HTTPReader::SendBodyPart(DDV::Socket & conn, std::string bodypart){ - static char len[10]; - int sizelen; - sizelen = snprintf(len, 10, "%x\r\n", (unsigned int)bodypart.size()); - conn.write(len, sizelen); - conn.write(bodypart); - conn.write(len+sizelen-2, 2); +/// 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]; + int sizelen; + sizelen = snprintf(len, 10, "%x\r\n", (unsigned int)bodypart.size()); + conn.write(len, sizelen); + conn.write(bodypart); + conn.write(len+sizelen-2, 2); + }else{ + conn.write(bodypart); + } } - diff --git a/util/http_parser.h b/util/http_parser.h index 2741f0c4..d08cadef 100644 --- a/util/http_parser.h +++ b/util/http_parser.h @@ -1,3 +1,6 @@ +/// \file http_parser.h +/// Holds all headers for the HTTP namespace. + #pragma once #include #include @@ -5,37 +8,40 @@ #include #include "ddv_socket.h" -class HTTPReader{ - public: - HTTPReader(); - bool Read(DDV::Socket & sock); - bool Read(FILE * F); - std::string GetHeader(std::string i); - std::string GetVar(std::string i); - void SetHeader(std::string i, std::string v); - void SetHeader(std::string i, int v); - void SetVar(std::string i, std::string v); - void SetBody(std::string s); - void SetBody(char * buffer, int len); - std::string BuildRequest(); - std::string BuildResponse(std::string code, std::string message); - void SendResponse(DDV::Socket & conn, std::string code, std::string message); - void SendBodyPart(DDV::Socket & conn, char * buffer, int len); - void SendBodyPart(DDV::Socket & conn, std::string bodypart); - void Clean(); - bool CleanForNext(); - std::string body; - std::string method; - std::string url; - std::string protocol; - unsigned int length; - private: - bool seenHeaders; - bool seenReq; - bool parse(); - std::string HTTPbuffer; - std::map headers; - std::map vars; - void Trim(std::string & s); -};//HTTPReader - +/// Holds all HTTP processing related code. +namespace HTTP{ + /// Simple class for reading and writing HTTP 1.0 and 1.1. + class Parser{ + public: + Parser(); + bool Read(DDV::Socket & sock); + bool Read(FILE * F); + std::string GetHeader(std::string i); + std::string GetVar(std::string i); + void SetHeader(std::string i, std::string v); + void SetHeader(std::string i, int v); + void SetVar(std::string i, std::string v); + void SetBody(std::string s); + void SetBody(char * buffer, int len); + std::string BuildRequest(); + std::string BuildResponse(std::string code, std::string message); + void SendResponse(DDV::Socket & conn, std::string code, std::string message); + void SendBodyPart(DDV::Socket & conn, char * buffer, int len); + void SendBodyPart(DDV::Socket & conn, std::string bodypart); + void Clean(); + bool CleanForNext(); + std::string body; + std::string method; + std::string url; + std::string protocol; + unsigned int length; + private: + bool seenHeaders; + bool seenReq; + bool parse(); + std::string HTTPbuffer; + std::map headers; + std::map vars; + void Trim(std::string & s); + };//HTTP::Parser class +};//HTTP namespace diff --git a/util/rtmpchunks.cpp b/util/rtmpchunks.cpp new file mode 100644 index 00000000..d297b5e5 --- /dev/null +++ b/util/rtmpchunks.cpp @@ -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 RTMPStream::Chunk::lastsend; +/// Holds the last received chunk for every msg_id. +std::map 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; +} diff --git a/util/rtmpchunks.h b/util/rtmpchunks.h new file mode 100644 index 00000000..b11142c8 --- /dev/null +++ b/util/rtmpchunks.h @@ -0,0 +1,65 @@ +/// \file rtmpchunks.h +/// Holds all headers for the RTMPStream namespace. + +#pragma once +#include +#include +#include +#include +#include +#include +#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 lastsend; + static std::map 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 diff --git a/util/server_setup.cpp b/util/server_setup.cpp index daa9e6c6..36e09c47 100644 --- a/util/server_setup.cpp +++ b/util/server_setup.cpp @@ -1,12 +1,34 @@ -#include +/// \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 "flv_tag.h" //FLV parsing with DDVTech Socket wrapper +#include #include #include #include -#define defstr(x) #x //converts a define name to string -#define defstrh(x) "[" defstr(x) "]" //converts define name to [string] -DDV::ServerSocket server_socket(-1); +#define defstr(x) #x ///< converts a define name to string +#define defstrh(x) "[" defstr(x) "]" ///< converts define name to [string] +DDV::ServerSocket server_socket(-1); ///< Placeholder for the server socket /// Basic signal handler. Disconnects the server_socket if it receives /// 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. /// This sets up the proper termination handler, checks commandline options, /// parses config files and opens a listening socket on the requested port. -/// Any incoming connections will be accepted and start up the function MAINHANDLER, -/// which should be #defined before including server_setup.cpp. -/// The default port is set by #define DEFAULT_PORT. -/// The configuration file section is set by #define CONFIGSECT. +/// Any incoming connections will be accepted and start up the function #MAINHANDLER, +/// which should be defined before including server_setup.cpp. +/// The default port is set by define #DEFAULT_PORT. +/// The configuration file section is set by define #CONFIGSECT. int main(int argc, char ** argv){ DDV::Socket S;//placeholder for incoming connections